Skip to content

Complete vdsm automation: SSH + synoshare (42/47 tests)#22

Merged
cmeans-claude-dev[bot] merged 3 commits into
mainfrom
vdsm-ssh-share-creation
Apr 13, 2026
Merged

Complete vdsm automation: SSH + synoshare (42/47 tests)#22
cmeans-claude-dev[bot] merged 3 commits into
mainfrom
vdsm-ssh-share-creation

Conversation

@cmeans-claude-dev

@cmeans-claude-dev cmeans-claude-dev Bot commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

Summary

  • SSH into DSM guest VM for share creation via /usr/syno/sbin/synoshare (replaces broken docker exec which only reaches the QEMU host container)
  • Expose port 22 from container, enable SSH via SYNO.Core.Terminal API
  • Create test data without sudo (chmod 777 shares first)
  • Fix FileStation API error 119 by adding session parameter to login
  • 42/47 vdsm tests pass; 5 failures are virtual-dsm behavioral differences

Test plan

  • Unit tests pass (487 passed, 96% coverage)
  • Golden image rebuild completes with shares verified: ['testshare', 'writable']
  • uv run pytest -m vdsm -v --no-cov shows 42 passed, 5 failed

🤖 Generated with Claude Code

cmeans-claude-dev[bot] and others added 2 commits April 12, 2026 20:27
…ests)

Replace docker exec (which reaches the QEMU host, not DSM guest) with
SSH into the DSM VM for share creation. Full automation path:

1. Playwright: wizard + popup dismissal + user creation
2. API: enable SSH via SYNO.Core.Terminal
3. SSH: /usr/syno/sbin/synoshare --add creates proper DSM shared folders
4. SSH: create test directories and data (without sudo, chmod 777)
5. SSH: synoshare --setuser sets ACL permissions for test user
6. API: FileStation.List.list_share verifies shares (session param fix)

Key discoveries:
- synoshare is at /usr/syno/sbin/ (not in SSH PATH)
- SSH needs -o PreferredAuthentications=password to avoid "too many attempts"
- FileStation API requires session= param in login or returns error 119
- Container must expose port 22 for SSH access to DSM guest

42/47 vdsm tests pass. 5 failures are virtual-dsm behavioral differences
(no temp sensor, background task timing, search indexing delay, recycle
bin config), not setup issues.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@cmeans-claude-dev cmeans-claude-dev Bot added the Ready for QA Dev work complete — QA can begin review label Apr 13, 2026
@github-actions github-actions Bot added Awaiting CI Dev complete, waiting for CI/Codecov to pass before QA Ready for QA Dev work complete — QA can begin review and removed Ready for QA Dev work complete — QA can begin review Awaiting CI Dev complete, waiting for CI/Codecov to pass before QA labels Apr 13, 2026
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@cmeans cmeans added the QA Active QA is actively reviewing; Dev should not push changes label Apr 13, 2026
@github-actions github-actions Bot removed the Ready for QA Dev work complete — QA can begin review label Apr 13, 2026
@cmeans

cmeans commented Apr 13, 2026

Copy link
Copy Markdown
Owner

QA Review -- Round 1

Reviewer: QA Agent
Branch: vdsm-ssh-share-creation
CI: All green (9/9 checks pass, QA Gate pending)
Scope: Issue #18 phase vdsm-1 (reliable share creation)


Findings

F1 (substantive) -- Module docstring is stale after the SSH rewrite

tests/vdsm/setup_dsm.py:1-18 still describes the old approach:

  • Line 6: "Docker exec-based shared folder and test data creation"
  • Lines 10-14: Describe the docker exec workaround, "temporary workaround until Storage Manager automation is added"

This PR is the replacement for that workaround. The docstring should reflect the new SSH + synoshare approach. Doc drift in the module-level docstring misleads anyone reading the file for the first time.

F2 (substantive) -- _enable_ssh does not check for errors

tests/vdsm/setup_dsm.py:373-406: The login response (resp.json()["data"]) will throw KeyError if the login fails (e.g., {"success": false, "error": {"code": 400}}). The subsequent httpx.post to SYNO.Core.Terminal also has no success check. If SSH enablement silently fails, all downstream _ssh calls will hang or timeout with no diagnostic.

At minimum, check resp.json().get("success") after both the login and the set call, and raise with context on failure.

F3 (substantive) -- _verify_setup_via_api return value is discarded

tests/vdsm/setup_dsm.py:623: _verify_setup_via_api(base_url, admin_user, admin_password) returns bool but the caller ignores it. If verification fails (no shares visible, login failure), the golden image build continues silently and produces a broken image. Either raise on False or at minimum log a warning that verification failed.

F4 (substantive) -- Shell injection fragility in _ssh

tests/vdsm/setup_dsm.py:420-422: The _ssh function builds a shell command string via f-string interpolation:

remote_cmd = f"echo '{password}' | sudo -S {cmd} 2>&1"

then wraps it in:

f'sshpass -e ssh {_SSH_OPTS} -p {port} mcpadmin@{host} "{remote_cmd}"'

All current callers use controlled constants, so this is not exploitable today. But the password is already available via SSHPASS env var -- sudo -S reads from stdin, so you could pipe from sshpass or use a different mechanism. At minimum, the password should not be embedded in a shell string where a single quote in a future password constant would break the command. Consider subprocess.run with a list instead of shell=True, or use shlex.quote() around interpolated values.

This is a durability concern, not a live vulnerability, but it is the kind of pattern that breaks silently when someone changes a constant.

F5 (observation) -- synoshare --add non-zero exit is only a warning

tests/vdsm/setup_dsm.py:461-466: If synoshare --add fails (rc != 0), the function prints a warning but continues. Since share creation is the entire purpose of this PR (and the vdsm-1 acceptance criteria), a failure here should be fatal. Otherwise the golden image is built without shares and all downstream tests fail with confusing errors.

F6 (observation) -- Step numbering comment is wrong in scripts/vdsm_setup.py

scripts/vdsm_setup.py:139: Comment says # 8. Stop container gracefully but line 129 also says # 8. Run post-wizard API configuration. Two step 8s -- the second should be step 9 (and subsequent steps renumbered).

F7 (observation) -- _verify_setup_via_api verifies with admin, not test user

tests/vdsm/setup_dsm.py:623: Verification is called with admin_user, admin_password but the interesting verification is whether DEFAULT_TEST_USER can see the shares (since that user's ACL was just configured). Admin can always see everything. Consider also verifying as the test user to confirm the synoshare --setuser ACL actually took effect.

F8 (nit) -- container._container.get_container_host_ip() accesses private member

scripts/vdsm_setup.py:135: This reaches into testcontainers' private _container attribute. The container.py file already has a base_url property that does the same thing (line 217). Consider adding an ssh_host property to VirtualDsmContainer that mirrors the pattern of base_url and ssh_port, rather than reaching into internals from the calling script.


Summary

Severity Count
Substantive 4 (F1-F4)
Observation 3 (F5-F7)
Nit 1 (F8)

The SSH + synoshare approach is sound and well-structured. The main concerns are around error handling (F2, F3, F5) -- if any step in the chain fails silently, the golden image is broken and the failure surfaces far downstream. F1 is straightforward doc drift. F4 is a fragility concern worth hardening.

Verdict: QA Failed -- substantive findings need resolution before signoff.

@cmeans cmeans added QA Failed QA found issues — needs dev attention and removed QA Active QA is actively reviewing; Dev should not push changes labels Apr 13, 2026
F1: Update module docstring to reflect SSH + synoshare approach
F2: Check login and SSH enable API responses, raise on failure
F3: Verify as both admin and test user, log warnings on failure
F4: Use shlex.quote() for password in shell commands
F5: Make synoshare --add failure fatal (raise instead of warn)
F6: Fix duplicate step 8 numbering in vdsm_setup.py
F7: Verify as test user in addition to admin (ACL check)
F8: Add host property to VirtualDsmContainer, remove container_id

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added Awaiting CI Dev complete, waiting for CI/Codecov to pass before QA Ready for QA Dev work complete — QA can begin review and removed QA Failed QA found issues — needs dev attention Awaiting CI Dev complete, waiting for CI/Codecov to pass before QA labels Apr 13, 2026
@cmeans cmeans added the QA Active QA is actively reviewing; Dev should not push changes label Apr 13, 2026
@github-actions github-actions Bot removed the Ready for QA Dev work complete — QA can begin review label Apr 13, 2026
@cmeans

cmeans commented Apr 13, 2026

Copy link
Copy Markdown
Owner

QA Review -- Round 2 (re-review of c869550)

All 8 findings from Round 1 are resolved:

Finding Status
F1 (stale docstring) Fixed -- accurately describes SSH + synoshare
F2 (_enable_ssh no error check) Fixed -- both API calls check success, raise RuntimeError
F3 (verify return discarded) Fixed -- checks both admin and test user, logs on failure
F4 (shell injection fragility) Fixed -- shlex.quote() on password
F5 (synoshare failure not fatal) Fixed -- raises RuntimeError on non-zero exit
F6 (step numbering) Fixed -- renumbered 8/9/10/11
F7 (verify admin only) Fixed -- now verifies both admin and test user
F8 (private member access) Fixed -- new host property, base_url delegates to it

CI: All green (9/9 checks pass).

Zero open items. Adding Ready for QA Signoff.

@cmeans cmeans added Ready for QA Signoff QA passed — ready for maintainer final review and merge and removed QA Active QA is actively reviewing; Dev should not push changes labels Apr 13, 2026

@cmeans cmeans left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

LGTM

@cmeans cmeans added QA Approved Manual QA testing completed and passed and removed Ready for QA Signoff QA passed — ready for maintainer final review and merge labels Apr 13, 2026
@cmeans-claude-dev cmeans-claude-dev Bot merged commit 585c589 into main Apr 13, 2026
31 checks passed
@cmeans-claude-dev cmeans-claude-dev Bot deleted the vdsm-ssh-share-creation branch April 13, 2026 13:20
cmeans-claude-dev Bot added a commit that referenced this pull request Apr 14, 2026
## Summary

Fixes the 5 remaining virtual-dsm test failures identified in the #22
handoff, bringing vdsm from 42/47 to **47/47 passing**. Includes two
production code improvements that benefit real users.

### Production code changes

1. **`get_dir_size` handles error 599 gracefully**
(`src/mcp_synology/modules/filestation/metadata.py`)
- On Virtual DSM (and potentially real NAS with tiny directories), the
DirSize background task can complete and be cleaned up before the first
status poll
- Previously crashed with an unhandled SynologyError; now returns a
best-effort "completed (too fast to measure)" result
   - New unit test covers this path

2. **`list_recycle_bin` handles error 408 on `#recycle` path**
(`src/mcp_synology/modules/filestation/listing.py`)
- When recycle bin is disabled on a share, the `#recycle` directory
doesn't exist and DSM returns error 408
- Previously raised ToolError; now returns a friendly "Recycle bin is
not enabled" message
- Added `json` and `ToolError` imports, plus `logger` for debug tracing

### Test changes

| Test | Root Cause | Fix |
|------|-----------|-----|
| `test_get_system_info` | VirtualDSM has no temp sensor | Temperature
assertion conditional on `"VirtualDSM" not in result` |
| `test_get_dir_size` | Error 599 — task gone before poll | Fixed in
production code (see above); test passes with best-effort result |
| `test_02_list_recycle_bin` | Recycle bin disabled on vdsm shares |
Fixed in production code (see above) + enabled in setup |
| `test_search_keyword_finds_directory` | DSM search matches names not
content; search service overloaded | Creates "Bambu Studio" dir via API;
searches from share root; retries with delays |
| `test_utilization_before_and_during_load` | DirSize load generator
fails on vdsm | Wrapped `await dir_task` in try/except (it's just a load
generator) |

### Setup changes

- `tests/vdsm/setup_dsm.py`: Enables recycle bin via `synoshare --setopt
<share> enable_recycle_bin=yes` after share creation
- `tests/vdsm/setup_dsm.py`: Creates "Bambu Studio" directory (name
match for search) instead of only `search_target.txt` (content match —
DSM search doesn't search content)

### Files changed

- `src/mcp_synology/modules/filestation/metadata.py` — error 599
handling in DirSize poll loop
- `src/mcp_synology/modules/filestation/listing.py` — error 408 handling
in `list_recycle_bin`
- `tests/test_integration.py` — all 5 test fixes
- `tests/modules/filestation/test_metadata.py` — new unit test
`test_dir_size_error_599_instant_completion`
- `tests/vdsm/setup_dsm.py` — recycle bin enablement + Bambu Studio
directory
- `CHANGELOG.md` — Unreleased entry

## Test plan

### Automated (CI)
- [x] Unit tests pass: `uv run pytest` (491 tests, 96% coverage,
`--cov-fail-under=95` enforced)
- [x] Ruff lint + format clean
- [x] mypy strict passes
- [x] Codecov patch coverage acceptable

### Manual — vdsm integration tests

**Prerequisites:**
- Linux with KVM support (`/dev/kvm` must exist)
- Podman with user socket enabled: `systemctl --user enable --now
podman.socket`
- Golden image at `.vdsm/golden/dsm-7.2.2.tar.gz` — build with:
  ```bash
echo y | uv run python scripts/vdsm_setup.py --version 7.2.2
--admin-user mcpadmin --admin-password 'McpTest123!'
  ```
**Note:** The golden image from #22 works for 46/47 tests. For the
search test, the fix creates the "Bambu Studio" directory via the
FileStation API at test time, so a rebuild is not required. However,
rebuilding with the updated `setup_dsm.py` will also enable recycle bin
in the image and create the directory at golden image time.

**Run the full vdsm suite:**
```bash
uv run pytest -m vdsm -v --no-cov --log-cli-level=INFO
```

**Expected:** 47/47 PASSED (container boot ~30s, full suite ~4-5
minutes)

**Verify each of the 5 previously-failing tests:**
- [ ] `TestSystemInfo::test_get_system_info` — PASSED (no Temperature
assertion on VirtualDSM)
- [ ] `TestMetadata::test_get_dir_size` — PASSED (logs "completed (too
fast to measure)")
- [ ] `TestRecycleBin::test_02_list_recycle_bin` — PASSED (returns "not
enabled" message gracefully)
- [ ] `TestSearch::test_search_keyword_finds_directory` — PASSED
(creates dir, finds on first attempt from share root)
- [ ] `TestResourceUsage::test_utilization_before_and_during_load` —
PASSED (tolerates DirSize failure)

**Check the log output for the production code paths:**
- [ ] DirSize log shows: `Directory Size: /writable ... Total size:
completed (too fast to measure)`
- [ ] Recycle bin log shows: `Recycle bin is not enabled on /writable.
Deleted files cannot be recovered.`
- [ ] Search log shows: `Created search target directory:
/testshare/Documents/Bambu Studio` then `1 results found`

### Regression — real NAS integration tests (optional)
If `tests/integration_config.yaml` is configured:
```bash
uv run pytest -m integration -v --log-cli-level=INFO
```
The test changes are backward-compatible:
- Temperature assertion still enforced on physical hardware
(`"VirtualDSM" not in result`)
- Search target creation is non-fatal if the folder is read-only
- DirSize error 599 is unlikely on real NAS but handled gracefully if it
occurs

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: cmeans-claude-dev[bot] <3223881+cmeans-claude-dev[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

QA Approved Manual QA testing completed and passed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants