Phase 1: portal roles, clean hosts before reuse, working scaling - #35
Merged
Merged
Conversation
- Track the release lifecycle on dbo.VirtualMachines. ReleasedDate drives the sweep, and CleanupPending holds a returned VM out of the pool until the previous user has been removed from the host. The migration also repairs Available and Maintenance rows that were left with a username. - ReturnReleasedVms follows GracePeriodSeconds plus a reconcile buffer instead of a fixed 30 minutes, and retries pending cleanups at most every two minutes. - New procedures back the cleanup, maintenance, and network-status operations. UpdateVmAttributes keeps the lifecycle invariants and skips no-op writes. - Rewrite TriggerScalingLogic. It holds an application lock and applies a single rule with a minimum of at least 1, counts released and pending hosts as in use, never stops booting or assigned hosts, and returns PowerOn/PowerOff. The old procedure returned PoweredOn/PoweredOff, which the API never matched, so no scaling decision ever reached Azure. - Add StopMode to scaling rules, SyncVmPowerStates, sequence-based uid allocation, and the PreserveSessionsOnDisconnect host setting. - CheckoutVm commits instead of rolling back when no host is free. The rollback unwound pymssql's own transaction, so SQL Server raised error 266 and the API answered 500 instead of 409. - Add a contract test harness that applies every script twice to SQL Server 2022 with READ_COMMITTED_SNAPSHOT on. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- create-user.sh --password-stdin creates the account, mounts the home, writes the lease, adds tsusers/appusers, and sets the password read from stdin in one SSH session. Inputs are validated before any privileged step and every step is error checked. The legacy argument form is unchanged, and the previous script rejects the new form with its usage text before changing anything, which the API relies on to fall back. - manage-lease.sh keeps the lease of a user who is still signed in and reports __LEASE_ACTION=in-use__, so the broker keeps the host CleanupPending and retries. It used to delete the lease, which made a retry believe the host was clean. When the user is signed out, it ends leftover processes such as tmux or nohup jobs before unmounting, because they would keep the account usable and make userdel fail. - apply-host-settings.sh accepts PreserveSessionsOnDisconnect. The setting is off unless present, and it is forced off if the screen lock is on. - release-session.sh (RHEL and Ubuntu): with sessions preserved, a disconnect no longer kills Xorg. The agent ends the session at grace expiry and keeps retrying until the processes are gone. Session status is aggregated per user, temporary files stay in the root-only state directory so renames are atomic, and main only runs when the script is executed, so tests can source it. - Add a bash test suite that runs as root in an ubuntu:24.04 container, with shims for mount, loginctl, and the network calls. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…zure Authorization - Every endpoint requires Reader, Operator, or FullAccess, or its service role. The delegated access_as_user scope that every portal user holds no longer grants access, except through the temporary ALLOW_LEGACY_SCOPE_ACCESS upgrade toggle. GET /api/me reports the caller's roles and permissions. A test pins every route to its roles. - The Graph group check only runs when no role already authorizes the call. Release lifecycle - The sweep, manual return, and failed-checkout rollback all claim the VM as CleanupPending, remove the user from the host, and only then call CompleteVmCleanup. A signed-in user, an unreachable host, or a failed userdel keeps the VM pending and the sweep retries it. userdel failures used to be masked, which counted a host with the previous account still on it as clean. - New endpoints: /vms/<id>/cleanup (retry now), /maintenance, and /network-status. - Checkout provisions the user in one SSH call, falling back to the old sequence on hosts whose create-user.sh predates it. Procedure error rows no longer reach responses. Scaling - Read each host's power state from Azure before deciding, execute PowerOn/PowerOff by the rule's StopMode (power off or deallocate), restore the recorded state when Azure refuses an operation, and accept the legacy PoweredOn/PoweredOff spelling. - Validate the rule that results from an update, and answer 409 for a second rule. Throughput - Run gunicorn with gthread workers, and cap SQL connections per process with a 503 when saturated. - Cache the signing keys, Graph token, and SSH key. Every outbound call has a timeout. - Push host settings and sweep cleanups in parallel with deadlines. Host settings - PreserveSessionsOnDisconnect, which cannot be combined with the screen lock, is left out of documents sent to hosts while it is off, so hosts that have not been migrated keep converging. Tests - Add unit tests, and an integration suite that runs the handlers against a real SQL Server through pymssql. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- TestVMConnectivity runs every two minutes instead of hourly, so a host that scaling starts becomes checkout-ready within minutes. Probes run in parallel and are tunable with PROBE_CONCURRENCY, PROBE_TIMEOUT_SECONDS, and PROBE_PORTS. - Powered-off hosts are marked Unreachable without being probed, and a status is only posted when it changes, through the new /network-status endpoint. The task falls back to update-attributes on an API that returns 404 for it. - Every request has a timeout. - Add pytest coverage with fake Azure Functions and identity modules. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
… settings - The BFF reads the signed-in user's roles from the API's /me at sign-in, and again later if that fails, and passes them to the SPA with the session. Users without a role see a No access page. Readers get a read-only banner, and a banner warns while the legacy scope toggle is on. Actions and routes are shown only to the roles the API allows. - Checkout responses no longer carry the Linux password or lease to the browser. - Hosts pending cleanup are badged, can be retried, and cannot be released or returned again. Unassigned hosts can be put into and out of maintenance. Update attributes is an admin-only repair tool with a warning. - The scaling rule form chooses the stop mode and explains the cost, start-latency, and capacity risks of deallocation, with a confirmation step. The rule list marks the active rule and warns when legacy extra rules exist. - Host settings add Keep sessions alive during the grace period, which is mutually exclusive with the screen lock, and correct the idle timeout help. - Apply Now waits up to APPLY_TIMEOUT_SECONDS and reports hosts it did not reach. - Add VM sends blank optional fields as null. An empty username made the new host look assigned. - gunicorn runs gthread workers. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
… every layer in CI - preprovision creates the Reader and Operator app roles. brokerReaderGroupId, brokerOperatorGroupId, and brokerAdminGroupId optionally assign them to groups. - New parameters allowLegacyScopeAccess and sqlDatabaseSkuName are added to the generated parameters file and to Bicep, which maps the SKU name to its tier. main.json is regenerated. - Post-provision applies the SQL scripts before building and restarting images, and apps restart in the order API, task, frontend, so nothing starts ahead of the procedures or endpoints it calls. - DEPLOYMENT.md documents the portal roles, the new values, the upgrade procedure, and troubleshooting for No access and pending cleanup. - CI adds a SQL Server 2022 service container that runs the SQL contract tests and the Broker API integration suite, the host script suite in ubuntu:24.04, and the task function tests. - Test suites are excluded from container images. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- README: portal roles, scaling semantics and stop mode, release timing and cleanup before reuse, the keep-sessions-alive setting, what to plan for when upgrading, and a link to the roadmap. It also corrects what the LinuxHost role can do. - docs/ROADMAP.md: the analysis and design for the admin console (Phase 2), operating system and desktop support (Phase 3), strategic scale (Phase 4), and the security hardening backlog. Each item states why it matters, its design across the database, API, host agents, and portal, its dependencies, open questions, and what done means. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A review of the admin side found several places where the solution did not do what it promised. It also found that the portal's access model made every signed-in tenant user an administrator:
TriggerScalingLogicreturnedPoweredOn/PoweredOff, but the API matchedPowerOn/PowerOff, so decisions were only recorded in SQL.userdelfailures were masked.access_as_userscope was enough to delete VMs or change settings, and the portal's checkout sent the Linux password to the browser.This is Phase 1: correctness, access control and performance. Phases 2-4 (admin console, OS and desktop support, strategic scale) and a security-hardening backlog are designed in
docs/ROADMAP.md.What changed
Access control
Reader,OperatororFullAccessapp role, or its service role. The delegated scope grants nothing on its own.ALLOW_LEGACY_SCOPE_ACCESS(BicepallowLegacyScopeAccess, default false) bridges an upgrade while roles are assigned.GET /api/medrives a role-aware portal: a No access page, a read-only banner and gated actions. A unit test pins every route to its roles.Release lifecycle
CleanupPendingstate. The sweep, a manual return and a failed checkout all claim cleanup, and the VM cannot be checked out until the user is actually gone from the host.manage-lease.shkeeps the lease while the user is signed in. Otherwise it ends leftover processes such as tmux or nohup jobs.userdelexit codes 0, 6 and 12 as success.ReleasedDate.Username IS NULL. Add VM now sends blank fields as null, and migration040repairs existing rows that have blank or leftover usernames.Scaling
TriggerScalingLogicis rewritten:MinVMs >= 1, including recovery from zero running hosts.sp_getapplock, and the activity log records the actual counts and the reason for each decision.instance_viewper VM.Performance and readiness
DB_MAX_CONCURRENCY, default 6, answers 503 when saturated). NewsqlDatabaseSkuNameparameter, default Basic.create-user.sh --password-stdin) instead of 6-8. Hosts running the older script fall back to the old sequence./network-statusendpoint.MAX(uid)+1.Rollout
040-066) and stay compatible with the previous API build.Found along the way
CheckoutVmrolled back pymssql's own transaction. SQL Server raised error 266, so a full pool returned 500 instead of 409. This bug predates this PR and is fixed in042.Upgrade notes
allowLegacyScopeAccess=trueand turn it off afterwards.deploy/Migrate-ExistingEnvironment.ps1so the Linux hosts get the new scripts. Hosts that are not migrated keep their previous behavior.deploy/DEPLOYMENT.md, under "Upgrading To Role-Based Access And Working Scaling".Testing
READ_COMMITTED_SNAPSHOTon.ubuntu:24.04container.main.jsonwas regenerated with Bicep 0.44.1.create-user.shon a real RHEL 9 host with GNOME and xrdp.docs/ROADMAP.mditem 3.4 has a checklist.Review focus
sql_queries/059(scaling),043/044/045(claiming and completing cleanup), and transaction handling under pymssql.api/app.py:token_requiredand the role mapping against the real callers,cleanup_remote_user, andtrigger_scaling_logic.linux_host/create-user.shandmanage-lease.sh, which run as root through sudo.