Skip to content

Update Filesystem_Migration - #4286

Merged
LKuemmel merged 3 commits into
openWB:masterfrom
AlexanderHa98:feature_update_Dateisystem_Migrationen
Nov 11, 2026
Merged

LKuemmel merged 3 commits into
openWB:masterfrom
AlexanderHa98:feature_update_Dateisystem_Migrationen

Conversation

@AlexanderHa98

@AlexanderHa98 AlexanderHa98 commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

filesystem_migrations.py
-> in eigene Datei verschoben, wegen Circular Import-Error

Summary by Sourcery

Run filesystem migrations as a serialized, resumable background process separate from broker configuration updates.

New Features:

  • Run filesystem migrations in a dedicated background worker with persisted progress and automatic retry after failures.

Bug Fixes:

  • Prevent concurrent filesystem and totals migrations by preserving lock files and failing clearly when locks are contended or replaced.
  • Allow totals generation to continue processing remaining logs after an individual log fails.

Enhancements:

  • Move filesystem migration ownership out of UpdateConfig and use the dedicated migration version as the validation source.

Tests:

  • Add coverage for migration ordering, resumability, publish failures, worker topic loading, lock handling, and continued totals processing.

@sourcery-ai

sourcery-ai Bot commented Nov 9, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

API

UpdateConfig now starts a dedicated filesystem-migration worker, moving filesystem migration state handling out of its update flow.

  • packages/helpermodules/update_config.py L936-950 (+4 small edits)

Setdata validates the filesystem-operation version against the version owned by the new migration module.

  • packages/helpermodules/setdata.py L15, L1123

Implementation

Implement the serialized filesystem-migration worker, including broker state loading, ordered resumable operations, completion tracking, and error reporting.

  • packages/helpermodules/filesystem_migrations.py L1-124

Make totals generation use a persistent lock, propagate contention and lock-file replacement failures, and publish readiness on the system topic.

  • packages/helpermodules/measurement_logging/generate_totals_subprocess.py L16 (removed lines), L75, L82-106

Tests

Add coverage for the worker’s migration sequencing, progress persistence, recovery behavior, and broker state loading.

  • packages/helpermodules/filesystem_migrations_test.py L1-196

Test totals lock retention, contention handling, and continuation across individual log failures.

  • packages/helpermodules/measurement_logging/generate_totals_subprocess_test.py L1-84

Remove filesystem-migration tests from UpdateConfig and verify that it starts the new worker with the expected process options.

  • packages/helpermodules/update_config_test.py L622 (removed lines), L652-669

Lock files and vendored code

Ignore the added generated or runtime artifact path.

  • .gitignore L22-23

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 9 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="packages/helpermodules/filesystem_migrations.py" line_range="70" />
<code_context>
+        log.debug(f"current file operation version: {file_operation_version}")
+        log.debug(f"target file operation version: {self.FILE_OPERATION_VERSION}")
+        for version in list(range(self.FILE_OPERATION_VERSION+1)):
+            operation_required = version not in file_operation_version
+            if version == 0:
+                # Alte Installationen haben Version 0 schon beim Prozessstart eingetragen.
</code_context>
<issue_to_address>
**Legacy progress blocks migrations**

When the retained `openWB/system/file_operation_version` topic contains the legacy integer format, `version not in file_operation_version` raises `TypeError`, so the worker exits without running or recording any filesystem migration.

Normalize the legacy integer progress value to the iterable format before checking migration versions.
</issue_to_address>

### Comment 2
<location path="packages/helpermodules/filesystem_migrations.py" line_range="102-114" />
<code_context>
+    # Die Lock-Datei erst nach dem gesamten Migrationslauf entfernen, auch bei Fehlern.
+    with LOCK_FILE.open("a") as lock_file:
+        try:
+            fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
+        except BlockingIOError:
+            log.info("Dateisystem-Migrationen laufen bereits. Kein zweiter Lauf gestartet.")
+            return
+
+        try:
+            FilesystemMigrations().update_filesystem()
+        except Exception:
+            log.exception("Dateisystem-Migrationen abgebrochen.")
+            pub_system_message({}, "Fehler bei der Aktualisierung der Dateisystem-Daten.", MessageType.ERROR)
+            raise
+        finally:
+            LOCK_FILE.unlink(missing_ok=True)
+
+
</code_context>
<issue_to_address>
**Workers run migrations concurrently**

When the lock path is deleted or replaced while a worker holds its original file descriptor, `run_migrations` unlinks `LOCK_FILE` without verifying that it still names the locked inode, allowing workers to lock different inodes and run migrations concurrently.

Stop unlinking the lock file during cleanup so workers continue contending on the same inode.

Also at `packages/helpermodules/measurement_logging/generate_totals_subprocess.py:106`.
</issue_to_address>

### Comment 3
<location path="packages/helpermodules/filesystem_migrations.py" line_range="54" />
<code_context>
+        """Liest den aktuellen Broker-Zustand im Migrationsprozess ein."""
+        broker = BrokerClient("filesystem-migrations", self.on_connect, self.on_message)
+        try:
+            broker.start_finite_loop()
+        finally:
+            broker.disconnect()
</code_context>
<issue_to_address>
**Delayed topics erase migration progress**

When the broker takes longer than one second to deliver retained migration topics, `start_finite_loop` returns with an incomplete topic snapshot, so the worker treats the missing version topic as a new installation, publishes an empty version list, and can rerun completed migrations.

Wait for the required retained topics before processing them, and do not initialize or publish missing progress when the wait ends without those topics.

Also at `packages/helpermodules/filesystem_migrations.py:65`.
</issue_to_address>

### Comment 4
<location path="packages/helpermodules/filesystem_migrations.py" line_range="86" />
<code_context>
+        """Generiere die Totals-Summen für Tage und Monate."""
+
+        self._update_topic("openWB/system/log_data_ready", False)
+        generate_totals()
+        self.all_received_topics["openWB/system/log_data_ready"] = True
+
</code_context>
<issue_to_address>
**Failed totals are marked ready**

When a daily or monthly totals save fails, including from a transient filesystem error, `generate_totals()` catches per-day and per-month exceptions and returns normally, so `upgrade_file_operation_0()` marks logs ready and the worker records version 0 even when a totals write failed. The missing totals are then not retried by this migration.

Propagate a failure or persist failed periods for retry, and only mark readiness/version complete once those failures are resolved.
</issue_to_address>

### Comment 5
<location path="packages/helpermodules/update_config.py" line_range="839" />
<code_context>
             self._remove_invalid_topics()
             self.__pub_missing_defaults()
             self.__update_version()
+            self.__start_filesystem_migrations()
         except Exception:
             log.exception("Fehler bei der Aktualisierung des Brokers.")
</code_context>
<issue_to_address>
**Stale totals remain available**

When a scheduled five-minute handler runs after the worker starts but before it publishes `log_data_ready=False`, `UpdateConfig` signals completion while the old readiness value is still `True`, so `update_pv_monthly_yearly_yields` publishes totals from before the migration and users see stale PV yields.

Publish `log_data_ready=False` before launching the worker or signaling configuration completion.

Also at `packages/helpermodules/filesystem_migrations.py:85`, `packages/helpermodules/update_config.py:941`.
</issue_to_address>

### Comment 6
<location path="packages/helpermodules/setdata.py" line_range="15" />
<code_context>
+import pytest
+
+from helpermodules import filesystem_migrations
+from helpermodules.filesystem_migrations import FilesystemMigrations
+
+
</code_context>
<issue_to_address>
**Main-process logs are redirected**

When the main application imports `setdata` during startup, importing `setdata` imports `FilesystemMigrations`, which imports `generate_totals_subprocess`; that module runs `setup_generate_totals_logging(include_process_log=True)` at import time. It sets the main process's `process_log` logger to stop propagating to the root logger and sends its messages to `generate_totals.log` instead of `main.log`.

Avoid importing the totals subprocess module from the main process, or move its process-log setup out of module import and invoke it only in the migration subprocess.

Also at `packages/helpermodules/filesystem_migrations.py:21`.
</issue_to_address>

### Comment 7
<location path="packages/helpermodules/filesystem_migrations_test.py" line_range="149" />
<code_context>
+def test_file_operation_0_start_behavior(monkeypatch, file_operation_version, finished, expected_calls):
+    monkeypatch.setattr(FilesystemMigrations, "FILE_OPERATION_VERSION", 0)
+    updater = FilesystemMigrations()
+    updater.all_received_topics = {
+        "openWB/system/file_operation_version": file_operation_version,
+        "openWB/system/log_data_ready": finished,
</code_context>
<issue_to_address>
**Broker message handling can stay broken**

When the broker loop stops delivering messages through `updater.on_message`, in `test_filesystem_worker_reads_current_topics_before_migrating` (line 149), `read_topics` writes directly to `all_received_topics`, bypassing `on_message`; the no-migration assertion still passes if broker messages are never collected.

Have the broker mock deliver an MQTT message through the callback and assert that its topic is collected before migration decisions are made.
</issue_to_address>

### Comment 8
<location path="packages/helpermodules/filesystem_migrations_test.py" line_range="197" />
<code_context>
+    with pytest.raises(RuntimeError, match="migration failed"):
+        filesystem_migrations.run_migrations()
+
+    message.assert_called_once()
+    assert not lock_path.exists()
+    update.side_effect = None
</code_context>
<issue_to_address>
**Failure report contents go unchecked**

When the worker calls `pub_system_message` with incorrect report arguments, in `test_worker_reports_failure_and_releases_lock` (line 197), `message.assert_called_once()` passes even if the worker reports success or sends the wrong message and type, so the test can miss a broken failure report.

Assert the expected message text and `MessageType.ERROR` in the mock call.
</issue_to_address>

### Comment 9
<location path="packages/helpermodules/filesystem_migrations_test.py" line_range="138" />
<code_context>
+    with pytest.raises(RuntimeError, match="generator failed"):
+        updater._FilesystemMigrations__solve_breaking_changes_filesystem()
+
+    assert updater.all_received_topics["openWB/system/log_data_ready"] is False
+    assert updater.all_received_topics["openWB/system/file_operation_version"] == completed
+    next_migration.assert_not_called()
</code_context>
<issue_to_address>
**Unready state can go unpublished**

When the migration updates local readiness without publishing the retained system topic, in `test_file_operation_0_failure_leaves_logs_unready_and_blocks_next` (line 138), the assertion checks only the worker’s in-memory topic map, so the test passes if `log_data_ready` is set to `False` locally but the retained broker topic stays `True`.

Assert that the publisher was called with `openWB/system/log_data_ready` and the serialized `false` payload.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨

"""Liest den aktuellen Broker-Zustand im Migrationsprozess ein."""
broker = BrokerClient("filesystem-migrations", self.on_connect, self.on_message)
try:
broker.start_finite_loop()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium · Delayed topics erase migration progress

When the broker takes longer than one second to deliver retained migration topics, start_finite_loop returns with an incomplete topic snapshot, so the worker treats the missing version topic as a new installation, publishes an empty version list, and can rerun completed migrations.

Wait for the required retained topics before processing them, and do not initialize or publish missing progress when the wait ends without those topics.

Also at packages/helpermodules/filesystem_migrations.py:65.

Prompt for AI agents
In `packages/helpermodules/filesystem_migrations.py` at line 54:

**Delayed topics erase migration progress**

When the broker takes longer than one second to deliver retained migration topics, `start_finite_loop` returns with an incomplete topic snapshot, so the worker treats the missing version topic as a new installation, publishes an empty version list, and can rerun completed migrations.

Wait for the required retained topics before processing them, and do not initialize or publish missing progress when the wait ends without those topics.

Also at `packages/helpermodules/filesystem_migrations.py:65`.

"""Generiere die Totals-Summen für Tage und Monate."""

self._update_topic("openWB/system/log_data_ready", False)
generate_totals()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium · Failed totals are marked ready

When a daily or monthly totals save fails, including from a transient filesystem error, generate_totals() catches per-day and per-month exceptions and returns normally, so upgrade_file_operation_0() marks logs ready and the worker records version 0 even when a totals write failed. The missing totals are then not retried by this migration.

Propagate a failure or persist failed periods for retry, and only mark readiness/version complete once those failures are resolved.

Prompt for AI agents
In `packages/helpermodules/filesystem_migrations.py` at line 86:

**Failed totals are marked ready**

When a daily or monthly totals save fails, including from a transient filesystem error, `generate_totals()` catches per-day and per-month exceptions and returns normally, so `upgrade_file_operation_0()` marks logs ready and the worker records version 0 even when a totals write failed. The missing totals are then not retried by this migration.

Propagate a failure or persist failed periods for retry, and only mark readiness/version complete once those failures are resolved.

self._remove_invalid_topics()
self.__pub_missing_defaults()
self.__update_version()
self.__start_filesystem_migrations()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium · Stale totals remain available

When a scheduled five-minute handler runs after the worker starts but before it publishes log_data_ready=False, UpdateConfig signals completion while the old readiness value is still True, so update_pv_monthly_yearly_yields publishes totals from before the migration and users see stale PV yields.

Publish log_data_ready=False before launching the worker or signaling configuration completion.

Also at packages/helpermodules/filesystem_migrations.py:85, packages/helpermodules/update_config.py:941.

Prompt for AI agents
In `packages/helpermodules/update_config.py` at line 839:

**Stale totals remain available**

When a scheduled five-minute handler runs after the worker starts but before it publishes `log_data_ready=False`, `UpdateConfig` signals completion while the old readiness value is still `True`, so `update_pv_monthly_yearly_yields` publishes totals from before the migration and users see stale PV yields.

Publish `log_data_ready=False` before launching the worker or signaling configuration completion.

Also at `packages/helpermodules/filesystem_migrations.py:85`, `packages/helpermodules/update_config.py:941`.

Comment thread packages/helpermodules/setdata.py Resolved

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Disconnecting before retained state is confirmed can cause delayed delivery to be treated as a fresh installation, overwriting migration history and rerunning completed migrations.

2 open issues
What changed in this PR

Moves filesystem migrations out of broker configuration updates into a dedicated background worker, separating responsibilities and supporting ordered, resumable execution.

Changes:

  • Runs migrations sequentially and persists completed versions through MQTT.
  • Uses persistent file locks to prevent concurrent migration runs.
  • Updates tests for worker startup, migration recovery, legacy progress conversion, and totals processing.
File Description
packages/​helpermodules/​update_config.py Replaces inline filesystem migrations with worker startup.
packages/​helpermodules/​update_config_test.py Verifies the worker launch arguments.
packages/​helpermodules/​setdata.py Uses the migration worker’s version constant for validation.
packages/​helpermodules/​measurement_logging/​generate_totals_subprocess.py Retains lock files, propagates lock failures, and publishes readiness directly.
packages/​helpermodules/​measurement_logging/​generate_totals_subprocess_test.py Replaces wrapper tests with per-log failure-handling tests.
packages/​helpermodules/​filesystem_migrations.py Adds the serialized worker with retained progress and retry handling.
packages/​helpermodules/​filesystem_migrations_test.py Covers ordering, recovery, legacy progress, and broker-state loading.
.gitignore Ignores persistent migration lock files.

Review effort: Balanced

Comment on lines +54 to +56
broker.start_finite_loop()
finally:
broker.disconnect()
Comment on lines +88 to +91
except BlockingIOError:
log.exception("generate_totals_subprocess läuft bereits. "
"Der zweite Prozess wird beendet.")
return
raise
@AlexanderHa98
AlexanderHa98 force-pushed the feature_update_Dateisystem_Migrationen branch from 2e2a3b7 to 7758c0e Compare November 9, 2026 14:02
@LKuemmel
LKuemmel merged commit 08fa8a3 into openWB:master Nov 11, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants