From 3d2689863286ad1f929c12d31ac6d8de49e03bd7 Mon Sep 17 00:00:00 2001 From: "cmeans-claude-dev[bot]" <3223881+cmeans-claude-dev[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 20:01:22 -0500 Subject: [PATCH 1/6] Add --yes flag to vdsm_setup.py for non-interactive CI use Enables CI workflows to run the golden image builder end-to-end without stopping at the 'Overwrite existing golden image?' prompt. Co-Authored-By: Claude Opus 4.6 (1M context) --- scripts/vdsm_setup.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/scripts/vdsm_setup.py b/scripts/vdsm_setup.py index dbece20..217e406 100755 --- a/scripts/vdsm_setup.py +++ b/scripts/vdsm_setup.py @@ -72,7 +72,15 @@ def _check_prerequisites() -> None: confirmation_prompt=True, help="Admin password for DSM setup wizard.", ) -def setup(dsm_version: str, admin_user: str, admin_password: str) -> None: +@click.option( + "--yes", + "-y", + "assume_yes", + is_flag=True, + default=False, + help="Skip confirmation prompts (e.g. overwrite existing golden image). Required for CI use.", +) +def setup(dsm_version: str, admin_user: str, admin_password: str, assume_yes: bool) -> None: """Create a golden image for virtual-dsm testing. This command starts a virtual-dsm container, waits for you to complete @@ -86,8 +94,9 @@ def setup(dsm_version: str, admin_user: str, admin_password: str) -> None: click.echo(" Docker: OK") # 2. Check if golden image already exists - if has_golden_image(dsm_version) and not click.confirm( - f"\nGolden image for DSM {dsm_version} already exists. Overwrite?" + if has_golden_image(dsm_version) and not ( + assume_yes + or click.confirm(f"\nGolden image for DSM {dsm_version} already exists. Overwrite?") ): click.echo("Aborted.") sys.exit(0) From 05ff689744ebb1b55741e436a1116324f2159147 Mon Sep 17 00:00:00 2001 From: "cmeans-claude-dev[bot]" <3223881+cmeans-claude-dev[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 20:01:48 -0500 Subject: [PATCH 2/6] Add vdsm CI workflow with golden image caching MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New .github/workflows/vdsm.yml runs the 47 vdsm integration tests on ubuntu-24.04 with /dev/kvm access. Golden image is cached via actions/cache, keyed on DSM version + hash of setup scripts. On cache miss, scripts/vdsm_setup.py builds a fresh image. Job is marked continue-on-error until proven stable in CI — promotion to a required check is a follow-up. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/vdsm.yml | 74 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 .github/workflows/vdsm.yml diff --git a/.github/workflows/vdsm.yml b/.github/workflows/vdsm.yml new file mode 100644 index 0000000..83a6cac --- /dev/null +++ b/.github/workflows/vdsm.yml @@ -0,0 +1,74 @@ +name: vdsm + +on: + push: + branches: [main] + pull_request: + branches: [main] + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + DSM_VERSION: "7.2.2" + # Default admin credentials used inside vdsm setup. NOT secrets — the + # VM is ephemeral and exposed only to the test runner. + VDSM_ADMIN_USER: mcpadmin + VDSM_ADMIN_PASSWORD: "McpTest123!" + +jobs: + vdsm: + name: vdsm integration tests + runs-on: ubuntu-24.04 + # Non-blocking while the job stabilizes. Promote to required check + # after the job is green on main for several consecutive runs. + continue-on-error: true + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + + - name: Verify /dev/kvm is available + run: | + ls -la /dev/kvm + sudo chmod 666 /dev/kvm + echo "KVM available" + + - uses: astral-sh/setup-uv@v5 + with: + python-version: "3.12" + + - name: Install project with vdsm extras + run: uv sync --extra dev --extra vdsm + + - name: Install Playwright Chromium + run: uv run playwright install --with-deps chromium + + - name: Restore golden image cache + id: cache-golden + uses: actions/cache@v4 + with: + path: | + .vdsm/golden/dsm-${{ env.DSM_VERSION }}.tar.gz + .vdsm/golden/dsm-${{ env.DSM_VERSION }}.meta.json + key: vdsm-golden-${{ env.DSM_VERSION }}-${{ hashFiles('scripts/vdsm_setup.py', 'tests/vdsm/setup_dsm.py', 'tests/vdsm/config.py', 'tests/vdsm/golden_image.py', 'tests/vdsm/container.py') }} + + - name: Build golden image (cache miss) + if: steps.cache-golden.outputs.cache-hit != 'true' + run: | + uv run python scripts/vdsm_setup.py \ + --version "${DSM_VERSION}" \ + --admin-user "${VDSM_ADMIN_USER}" \ + --admin-password "${VDSM_ADMIN_PASSWORD}" \ + --yes + + - name: Run vdsm integration tests + run: uv run pytest -m vdsm -v --no-cov --log-cli-level=INFO + + - name: Upload logs on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: vdsm-failure-logs + path: | + .vdsm/storage/**/log/ + /tmp/vdsm-*.log + if-no-files-found: ignore + retention-days: 7 From e588a2a7b062d56c87e36eacaa46020e3480b6bc Mon Sep 17 00:00:00 2001 From: "cmeans-claude-dev[bot]" <3223881+cmeans-claude-dev[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 20:02:04 -0500 Subject: [PATCH 3/6] CHANGELOG entry for vdsm CI workflow Co-Authored-By: Claude Opus 4.6 (1M context) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd048c6..7e455e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Added +- **vdsm CI workflow** (#NN) — adds `.github/workflows/vdsm.yml` that runs the 47 vdsm integration tests on every PR using GitHub Actions' `ubuntu-24.04` runner with `/dev/kvm` access. Golden image is cached via `actions/cache@v4`, keyed on DSM version + hash of the setup scripts (`scripts/vdsm_setup.py`, `tests/vdsm/setup_dsm.py`, `tests/vdsm/config.py`, `tests/vdsm/golden_image.py`, `tests/vdsm/container.py`). Cache miss path invokes `scripts/vdsm_setup.py --yes` to build a fresh golden image. The new workflow is independent from `ci.yml` so a vdsm flake never blocks unit-test merges, and starts with `continue-on-error: true` until it has a track record of stability. Also adds a `--yes/-y` flag to `vdsm_setup.py` for non-interactive CI use. - **vdsm 47/47: fix all 5 remaining virtual-dsm test failures** (#23) — fixes the 5 vdsm-specific test failures identified in the #22 handoff. Production code improvements: `get_dir_size` now handles DSM error 599 (task completed before status poll) gracefully instead of crashing, returning a best-effort result; `list_recycle_bin` catches error 408 on the `#recycle` path and returns a friendly "recycle bin not enabled" message instead of raising. Test fixes: `test_get_system_info` makes Temperature assertion conditional on non-virtual hardware; `test_search_keyword_finds_directory` creates a "Bambu Studio" directory via the API (DSM search matches names, not content) and searches from the share root with retries; `test_utilization_before_and_during_load` tolerates DirSize failure since it's only a load generator. Setup fix: `setup_dsm.py` enables recycle bin via `synoshare --setopt` after share creation. New unit test for the error 599 path. - **vdsm full automation: SSH + synoshare for shared folders** (#22) — completes the vdsm golden image setup by SSH-ing into the DSM guest VM (not the QEMU host container) to run `/usr/syno/sbin/synoshare --add` for proper DSM shared folder registration. Exposes SSH port 22, enables SSH via `SYNO.Core.Terminal` API, creates test data without sudo. Fixes FileStation API error 119 by adding `session` parameter to login. 42/47 vdsm tests pass; 5 remaining failures are virtual-dsm behavioral differences (no temp sensor, background task timing, search indexing, recycle bin config). From adf6a8588ef85f0ec47042d58b4f991aecb12006 Mon Sep 17 00:00:00 2001 From: "cmeans-claude-dev[bot]" <3223881+cmeans-claude-dev[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 20:02:42 -0500 Subject: [PATCH 4/6] Update CHANGELOG with real PR number Co-Authored-By: Claude Opus 4.6 (1M context) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e455e8..76d6b51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Added -- **vdsm CI workflow** (#NN) — adds `.github/workflows/vdsm.yml` that runs the 47 vdsm integration tests on every PR using GitHub Actions' `ubuntu-24.04` runner with `/dev/kvm` access. Golden image is cached via `actions/cache@v4`, keyed on DSM version + hash of the setup scripts (`scripts/vdsm_setup.py`, `tests/vdsm/setup_dsm.py`, `tests/vdsm/config.py`, `tests/vdsm/golden_image.py`, `tests/vdsm/container.py`). Cache miss path invokes `scripts/vdsm_setup.py --yes` to build a fresh golden image. The new workflow is independent from `ci.yml` so a vdsm flake never blocks unit-test merges, and starts with `continue-on-error: true` until it has a track record of stability. Also adds a `--yes/-y` flag to `vdsm_setup.py` for non-interactive CI use. +- **vdsm CI workflow** (#24) — adds `.github/workflows/vdsm.yml` that runs the 47 vdsm integration tests on every PR using GitHub Actions' `ubuntu-24.04` runner with `/dev/kvm` access. Golden image is cached via `actions/cache@v4`, keyed on DSM version + hash of the setup scripts (`scripts/vdsm_setup.py`, `tests/vdsm/setup_dsm.py`, `tests/vdsm/config.py`, `tests/vdsm/golden_image.py`, `tests/vdsm/container.py`). Cache miss path invokes `scripts/vdsm_setup.py --yes` to build a fresh golden image. The new workflow is independent from `ci.yml` so a vdsm flake never blocks unit-test merges, and starts with `continue-on-error: true` until it has a track record of stability. Also adds a `--yes/-y` flag to `vdsm_setup.py` for non-interactive CI use. - **vdsm 47/47: fix all 5 remaining virtual-dsm test failures** (#23) — fixes the 5 vdsm-specific test failures identified in the #22 handoff. Production code improvements: `get_dir_size` now handles DSM error 599 (task completed before status poll) gracefully instead of crashing, returning a best-effort result; `list_recycle_bin` catches error 408 on the `#recycle` path and returns a friendly "recycle bin not enabled" message instead of raising. Test fixes: `test_get_system_info` makes Temperature assertion conditional on non-virtual hardware; `test_search_keyword_finds_directory` creates a "Bambu Studio" directory via the API (DSM search matches names, not content) and searches from the share root with retries; `test_utilization_before_and_during_load` tolerates DirSize failure since it's only a load generator. Setup fix: `setup_dsm.py` enables recycle bin via `synoshare --setopt` after share creation. New unit test for the error 599 path. - **vdsm full automation: SSH + synoshare for shared folders** (#22) — completes the vdsm golden image setup by SSH-ing into the DSM guest VM (not the QEMU host container) to run `/usr/syno/sbin/synoshare --add` for proper DSM shared folder registration. Exposes SSH port 22, enables SSH via `SYNO.Core.Terminal` API, creates test data without sudo. Fixes FileStation API error 119 by adding `session` parameter to login. 42/47 vdsm tests pass; 5 remaining failures are virtual-dsm behavioral differences (no temp sensor, background task timing, search indexing, recycle bin config). From ae65df4790c12e5d7df55c577472e2bd931a7ade Mon Sep 17 00:00:00 2001 From: "cmeans-claude-dev[bot]" <3223881+cmeans-claude-dev[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 20:22:13 -0500 Subject: [PATCH 5/6] Remove synoshare --setopt recycle bin call (command doesn't exist) DSM 7.2.2's synoshare CLI has no --setopt subcommand. The call I added in PR #23 for QA F2 was based on an incorrect assumption and wasn't verified against a fresh golden image build. It only surfaced in CI because my local run used a pre-existing golden image. The production code in list_recycle_bin already handles the disabled case gracefully (returns "Recycle bin is not enabled" instead of raising), and test_02_list_recycle_bin's permissive assertion accepts either response. Removing the enablement step actually exercises the more valuable 408 fallback path. Co-Authored-By: Claude Opus 4.6 (1M context) --- tests/vdsm/setup_dsm.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/tests/vdsm/setup_dsm.py b/tests/vdsm/setup_dsm.py index f90c5f5..38f4d69 100644 --- a/tests/vdsm/setup_dsm.py +++ b/tests/vdsm/setup_dsm.py @@ -485,15 +485,12 @@ def ssh(cmd: str, *, sudo: bool = True) -> tuple[int, str]: msg = f"synoshare --add {name} failed (rc={rc}): {out}" raise RuntimeError(msg) - # Enable recycle bin on shares (synoshare --add creates them disabled). - # This is a prerequisite for test_02_list_recycle_bin — failure is fatal. - for name in ["testshare", "writable"]: - rc, out = ssh(f"{_SYNOSHARE} --setopt {name} enable_recycle_bin=yes") - if rc == 0: - print(f" Recycle bin enabled on {name}") - else: - msg = f"synoshare --setopt {name} enable_recycle_bin=yes failed (rc={rc}): {out}" - raise RuntimeError(msg) + # Note: DSM 7.2.2's `synoshare` CLI has no supported command to toggle the + # per-share recycle bin. Shares created via `synoshare --add` have recycle + # bin disabled. This is fine — `list_recycle_bin` in production code + # handles the disabled case gracefully (returns a friendly + # "Recycle bin is not enabled" message), and `test_02_list_recycle_bin` + # exercises exactly that path. # Verify shares registered rc, out = ssh(f"{_SYNOSHARE} --enum ALL") From c74b3556300acbd07c59d15d6ae9b9dde2e5e950 Mon Sep 17 00:00:00 2001 From: "cmeans-claude-dev[bot]" <3223881+cmeans-claude-dev[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 20:58:04 -0500 Subject: [PATCH 6/6] Address QA observations F2, F3, F4 + disclose revert in CHANGELOG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit F1: CHANGELOG now has a dedicated ### Fixed entry for the synoshare --setopt revert, and the PR body summary documents the revert as in-scope. F2: Add comment explaining FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 env var. F3: Update TestRecycleBin docstring — no longer claims recycle bin must be enabled; test is tolerant of both states. F4: Add comment documenting the cache key design choice (why pyproject.toml/uv.lock are intentionally excluded). Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/vdsm.yml | 11 +++++++++++ CHANGELOG.md | 4 ++++ tests/test_integration.py | 8 ++++++-- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/.github/workflows/vdsm.yml b/.github/workflows/vdsm.yml index 83a6cac..0457036 100644 --- a/.github/workflows/vdsm.yml +++ b/.github/workflows/vdsm.yml @@ -7,6 +7,10 @@ on: branches: [main] env: + # Pins JavaScript-action Node runtime to Node 24 across all JS-based + # GitHub Actions in this workflow. Matches the pattern used by the + # other workflows in .github/workflows/ so CI behavior is consistent + # across runners during GitHub's staged Node runtime rollouts. FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true DSM_VERSION: "7.2.2" # Default admin credentials used inside vdsm setup. NOT secrets — the @@ -41,6 +45,13 @@ jobs: - name: Install Playwright Chromium run: uv run playwright install --with-deps chromium + # Cache key deliberately hashes only the DSM version and the Python + # files that drive image construction. `pyproject.toml` and `uv.lock` + # are NOT in the key because the image content is DSM-determined — a + # testcontainers or playwright dependency upgrade doesn't change the + # bits inside the saved tarball. If that assumption ever changes + # (e.g., a dep upgrade affects bind-mount format or timing), bump the + # cache key manually or add the manifest to hashFiles. - name: Restore golden image cache id: cache-golden uses: actions/cache@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 76d6b51..096d1c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ ### Added - **vdsm CI workflow** (#24) — adds `.github/workflows/vdsm.yml` that runs the 47 vdsm integration tests on every PR using GitHub Actions' `ubuntu-24.04` runner with `/dev/kvm` access. Golden image is cached via `actions/cache@v4`, keyed on DSM version + hash of the setup scripts (`scripts/vdsm_setup.py`, `tests/vdsm/setup_dsm.py`, `tests/vdsm/config.py`, `tests/vdsm/golden_image.py`, `tests/vdsm/container.py`). Cache miss path invokes `scripts/vdsm_setup.py --yes` to build a fresh golden image. The new workflow is independent from `ci.yml` so a vdsm flake never blocks unit-test merges, and starts with `continue-on-error: true` until it has a track record of stability. Also adds a `--yes/-y` flag to `vdsm_setup.py` for non-interactive CI use. + +### Fixed + +- **Remove `synoshare --setopt` recycle bin enablement** (#24) — reverts the recycle bin enablement added in #23. DSM 7.2.2's `synoshare` CLI has no `--setopt` subcommand, so the call always fails (only hidden locally because the pre-existing golden image was built before the change). The revert is safe because `list_recycle_bin` in production code already handles a disabled recycle bin gracefully (returns a friendly "not enabled" message), which is exactly the path `test_02_list_recycle_bin` exercises. `TestRecycleBin` docstring updated to reflect that the test is tolerant of both states. - **vdsm 47/47: fix all 5 remaining virtual-dsm test failures** (#23) — fixes the 5 vdsm-specific test failures identified in the #22 handoff. Production code improvements: `get_dir_size` now handles DSM error 599 (task completed before status poll) gracefully instead of crashing, returning a best-effort result; `list_recycle_bin` catches error 408 on the `#recycle` path and returns a friendly "recycle bin not enabled" message instead of raising. Test fixes: `test_get_system_info` makes Temperature assertion conditional on non-virtual hardware; `test_search_keyword_finds_directory` creates a "Bambu Studio" directory via the API (DSM search matches names, not content) and searches from the share root with retries; `test_utilization_before_and_during_load` tolerates DirSize failure since it's only a load generator. Setup fix: `setup_dsm.py` enables recycle bin via `synoshare --setopt` after share creation. New unit test for the error 599 path. - **vdsm full automation: SSH + synoshare for shared folders** (#22) — completes the vdsm golden image setup by SSH-ing into the DSM guest VM (not the QEMU host container) to run `/usr/syno/sbin/synoshare --add` for proper DSM shared folder registration. Exposes SSH port 22, enables SSH via `SYNO.Core.Terminal` API, creates test data without sudo. Fixes FileStation API error 119 by adding `session` parameter to login. 42/47 vdsm tests pass; 5 remaining failures are virtual-dsm behavioral differences (no temp sensor, background task timing, search indexing, recycle bin config). diff --git a/tests/test_integration.py b/tests/test_integration.py index 47ed952..135311d 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -628,8 +628,12 @@ async def test_10_verify_deleted(self, nas_client: Any) -> None: class TestRecycleBin: """Test recycle bin listing after delete. - Requires the writable_folder's share to have recycle bin enabled. - Creates a folder, deletes it, checks recycle bin, then cleans up. + Works whether or not the writable_folder's share has recycle bin enabled: + when enabled, the deleted folder shows up in `#recycle`; when disabled, + `list_recycle_bin` returns a friendly "not enabled" message. Both paths + are valid and the test verifies `list_recycle_bin` returns a string in + either case. Creates a folder, deletes it, checks recycle bin, then + cleans up. """ _RECYCLE_TEST = "_recycle_bin_test"