Skip to content

Clean up backup E2E workspaces after failed provisioning - #5171

Closed
Jay W (JayDoubleu) wants to merge 1 commit into
mainfrom
fix/backup-e2e-cleanup-5135
Closed

Jay W (JayDoubleu) wants to merge 1 commit into
mainfrom
fix/backup-e2e-cleanup-5135

Conversation

@JayDoubleu

@JayDoubleu Jay W (JayDoubleu) commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator

Superseded by #5195. See #5119 for the current release plan. Historical evidence follows.

When a backup workspace fails provisioning, post_resource() raises before the test receives its path. The existing finally block then skips cleanup and leaves the workspace behind. Use the existing temporary_resource() helper to capture ownership before polling and attempt teardown after deployment or assertion failures. Preserve the original failure if teardown also fails.

Related to #5170. Refs #5135. Draft follow-up outside v0.30.0; non-blocking for that release. This fixes the test cleanup defect, not the unresolved Azure Backup CloudInternalError during storage-account registration.

Backup-enabled/disabled assertions and delete_backups_on_uninstall=true for the disposable backup-enabled workspace remain unchanged. No Terraform, network, identity, provider, dependency or workflow definitions change. E2E tests have no independently versioned component; the change is recorded in Unreleased.

Validation:

  • Reproduced the missing cleanup on the original candidate using the actual backup test and HTTP/polling helpers: only POST and GET occurred after failed provisioning.
  • All 38 E2E helper tests passed with Python 3.12 and the CI dependency/PYTHONPATH settings, including six new tests. Coverage includes both backup settings, failed deployment/disable, missing output, preservation of the original polling error, failed teardown and rejected creation.
  • All applicable pre-commit hooks passed for the three changed files, including Ruff, Bandit, spelling and Markdown lint. git diff --check passed.
  • Tested and published commit: 2b48a91f3. GitHub CI results are tracked on this draft.
  • No live Azure validation of this fix, retry of the failed backup registration or cleanup of the existing failed workspace has run. Those remain separate follow-up work in Investigate Azure Backup registration failure and failed-workspace cleanup #5135.

Keep #5135 open for the Azure registration investigation and later backup validation. The current v0.30.0 candidate is unchanged.

@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown

Unit Test Results

0 tests   0 ✅  0s ⏱️
0 suites  0 💤
0 files    0 ❌

Results for commit 2b48a91.

@JayDoubleu
Jay W (JayDoubleu) marked this pull request as ready for review October 9, 2026 14:59
@JayDoubleu
Jay W (JayDoubleu) requested a review from a team as a code owner October 9, 2026 14:59
Copilot AI balanced review requested due to automatic review settings October 9, 2026 14:59

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.

🟢 Approval recommended

The focused lifecycle change satisfies the stated acceptance criteria and is covered by passing regression tests.

0 open findings

What changed in this PR

Updates backup E2E tests to reliably clean up workspaces after provisioning or assertion failures while preserving the original error.

Changes:

  • Uses temporary_resource() for workspace lifecycle management.
  • Adds six cleanup regression tests.
  • Records the fix in the Unreleased changelog.
File Description
CHANGELOG.md Records the cleanup fix.
e2e_tests/​test_backups.py Adopts failure-safe temporary resource handling.
e2e_tests/​unit_tests/​test_backup_workspace_cleanup.py Tests success, failure, cleanup, and rejected-creation paths.

🧠 Review effort: Balanced


💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

🟢 Approval recommended

The implementation meets the acceptance criteria with focused passing regression coverage.

0 open findings

🧠 Review effort: Balanced

@JayDoubleu

Copy link
Copy Markdown
Collaborator Author

Replacement draft PR: #5195 (backup-test cleanup).

Release stack and validation plan: #5119.

@JayDoubleu

Copy link
Copy Markdown
Collaborator Author

Replaced by #5195 in the v0.30.0 stack. This PR is closed without merging.

Historical discussion and test evidence remain here and in #5119. Further implementation and validation continue in the replacement PRs.

Open defects and validation gaps remain open.

@JayDoubleu Jay W (JayDoubleu) added the v0.30.0-prep-v1 Original v0.30.0 preparation: superseded PRs and completed validation issues. label Oct 10, 2026
Jay W (JayDoubleu) added a commit that referenced this pull request Oct 29, 2026
)

* Clean up backup test workspaces after failure (#5135)

Source PRs: #5171

* Make one Azure Backup registration attempt before Terraform registers (#5135)

Every backup-enabled workspace failed to deploy. Azure Backup returned
CloudInternalError when Terraform registered the workspace storage
account with the new vault.

A scratch reproduction in switzerlandnorth showed that the first
registration in a new vault usually fails, whatever the storage
settings: 4 of 5 new vaults failed it, as did 3 of 3 TRE deployments.
Every later registration in the same vault succeeded (9 of 9). A failed
attempt leaves no container behind.

A new step runs before the Terraform registration:
- If the storage account is already registered, it does nothing.
- Otherwise it makes one registration attempt and waits for the result.
- If that attempt succeeds, it removes the registration again, so that
  Terraform creates and owns it.

A scratch Terraform apply covered a new vault and a re-run on a
registered account. Unit tests run the step against a fake Azure CLI.

* Try the backup registration warm-up up to five times (#5135)

In two live runs, the warm-up attempt failed with CloudInternalError,
and the Terraform registration right after it failed with the same
error. In two earlier runs, the Terraform registration passed after
a failed warm-up attempt.

The warm-up now tries up to five times, with 60 seconds between the
attempts. It removes the first registration that succeeds, so that
Terraform creates and owns the registration. After five failed
attempts, Terraform makes its own attempt, as before.

* Wrap a long CHANGELOG entry (#5135)

The Markdown lint rejects a line with more than 400 characters. The
entry now continues on lines that start with two spaces, as an earlier
entry in the file does. The rendered text does not change.

* Validate OpenAI model prerequisites and private lifecycle (#5150)

* Create the OpenAI private endpoint after the model deployment (#5249)

The private endpoint and the model deployment depended only on the account, so Terraform created them at the same time. Azure rejected the endpoint with AccountProvisioningStateInvalid while the deployment updated the account. Make the endpoint depend on the deployment, and check this order in the bundle contract test.

* Disable public network access on the MySQL server (#5148)

The MySQL flexible server had public network access enabled. It had no
firewall rules, so no public client could connect, but the server kept
a public endpoint. A live MySQL run in Switzerland North showed
publicNetworkAccess Enabled. The Azure SQL bundle disables public
access, and the SQL test checks it.

The server now sets public_network_access to Disabled, so that clients
can connect only through the private endpoint. azurerm 3.117.0 cannot
set this value, so the bundle moves to azurerm 4.27.0, as the Gitea and
OHDSI bundles use. In 4.27.0 a change of this value is an in-place
update, so an upgrade keeps the server and its data.

* Add a private MySQL lifecycle check (#5148)

The MySQL bundle selection ran only the generic install test. It did
not show that a workspace client can use the database.

The new case creates a workspace, a Guacamole service, a Linux VM and
the MySQL service. The Linux VM installs the MySQL client through
Nexus, reads the administrator password from the workspace Key Vault
through a temporary role assignment on that secret, and writes a row
through private DNS with TLS. The case then changes the SKU from
Burstable to General Purpose and reads the row again.

The SQL lifecycle helper now accepts its deadline variable, enters
Nexus as a prerequisite that leaves after the VM, and can get a new
token for clean-up, because this case can outlive the create token.

* Give the MySQL probe script a shebang (#5148)

The Lint job of the MySQL pull request failed on
e2e_tests/resources/mysql_probe.sh. Shellcheck reported SC2148, because
the file had no shebang, and bash-exec reported that the file was not
executable. The probe code added the shebang only to the generated
script.

The file now starts with the shebang and is executable. The probe code
puts the validated values after the first line, so the generated script
does not change.

* Run the Linux VM actions in the Guacamole Linux VM check (#5145)

The Linux VM check proved the install, the Nexus-backed bootstrap and
the delete. It did not run the stop, start and reset_password actions.

After the bootstrap, the check now runs stop and start and reads the
VM power state from Azure after each action. It then runs
reset_password and makes sure that the Key Vault secret with the VM
credentials has a new version. The check reads only the secret
metadata through Azure Resource Manager, not the secret value.

The check now requests the workspace owner token after Nexus, because
the VM delete uses it and Nexus can take a long time.

* Retry the backup registration reads and the unregister of the warm-up (#5135)

The warm-up step read any error of a registration read as "not
registered". On an upgrade, one throttled read could start a PUT and a
DELETE on the registration that Terraform owns. The step now treats only
a not-found answer as "not registered". It reads two more times after
another error, and then it fails.

The step sent the unregister one time. Azure can answer it with a short
CloudInternalError, and the step then failed and left a registration
that Terraform cannot create. The step now sends the unregister again
each minute while the registration exists.

The registration URL has no host, so az rest uses the Resource Manager
endpoint of the current cloud.

* Use one workspace owner token for the MySQL and Linux VM cleanup (#5148)

The MySQL test asked for a new owner token before each step and gave the
cleanup a function that asked for another one. The stated reason was
that a long case can outlive the token. But the owner token is a
TokenProvider, and get_auth_header gets a current token from it for each
request. So one token stays valid for the whole case and its cleanup.
The cleanup token parameter, its unit test and the comment in the Linux
VM test are removed.

* Stop pinning the OpenAI bundle version in the unit tests (#5150)

Two unit tests compared the OpenAI bundle version with "1.1.0". The E2E
helper tests run on each change to a porter.yaml file, so the next
version bump of the bundle would fail them. The schema test now accepts
1.1.0 or later, because 1.1.0 is the first version with the new default
model. The selection test compares the report with the version in
porter.yaml. The coverage text says "1.1.0 or later".

* Use the bundle cloud and login mode in the backup warm-up and wait for a final status (#5135)

The warm-up logged in with the managed identity of the default cloud. It
now selects the cloud from AZURE_ENVIRONMENT and logs in with the
service principal when ARM_USE_MSI is false, as the resource processor
does. The defaults keep the managed identity in AzureCloud.

A failed read of the registration status ended the wait, and a status
that stayed InProgress let the next attempt start. Another PUT could
then overlap a running registration. The step now reads the status again
after a failed read, and it fails if no final status comes within 10
minutes.

* Check the MySQL settings after the SKU change and keep the VM actions outside AzureCloud (#5148, #5145)

The MySQL case checked public network access and secure transport only
before the SKU change. It now checks both again after the change, so the
case fails if the update turns either setting back.

The Linux VM checks read the power state and the secret version through
a Resource Manager client that supports AzureCloud only. In other clouds,
the case now runs the three actions without these reads.

* Stop the backup warm-up when an accepted registration cannot be followed (#5135)

If Azure accepted the registration PUT but the step found no operation
header, the step waited 120 seconds and went on. A missing registration
does not prove that the operation ended, so a later PUT or the Terraform
registration could overlap it. The step now keeps the result of the PUT
and reads the operation header in any case. After a rejected PUT, it
waits and tries again, as before. After an accepted PUT without an
operation, it goes on only if the registration exists, and it fails
otherwise.

* Wait for the backup registration status when no operation is returned (#5135)

Azure can accept the registration PUT without an Azure-AsyncOperation
header. Then the warm-up had no operation to follow, and it treated an
existing container as a completed registration. The container can exist
before the registration ends. So the warm-up now waits until the
container shows the status Registered, for 10 minutes at most. If the
status does not come, the step fails and does not send the unregister.

If the unregister still fails after 10 minutes, the error now tells the
operator to unregister the storage account in the vault and to update
the workspace again. A comment at the early return explains why the
step does not remove a registration that exists.

* Unregister a warm-up registration only after it ended (#5135)

An error from the registration PUT does not prove that Azure rejected
it. For example, Azure can accept the PUT and the response can get
lost. Then the warm-up treated the attempt as rejected, and after 120
seconds it sent the unregister if the container existed, also when the
registration still ran.

Now one check runs before each unregister, for all attempt results:
the container must show the status Registered within 10 minutes. If it
does not, the step fails and does not send the unregister. The attempt
without an operation to follow uses the same check.

* Stop the backup warm-up when a PUT gets no answer from Azure (#5135)

The warm-up treated each failed registration PUT as a rejection. But
Azure can accept a PUT whose response gets lost, and the container of
that registration can show some minutes later. Then the warm-up sent
the next PUT while the first registration still ran. After five such
attempts, it ended with success, and Terraform started its own
registration over the running one.

With --debug, az rest prints the response status when Azure answers.
Now the warm-up treats a failed PUT as a rejection only when Azure
answered with a 4xx or 5xx status. Without an answer, the result is
unknown, so the step stops, and no other registration overlaps it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v0.30.0-prep-v1 Original v0.30.0 preparation: superseded PRs and completed validation issues.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants