Skip to content

Move checkRoleEscalation outside DB transaction in createAccount#13044

Open
nvazquez wants to merge 1 commit intoapache:4.22from
shapeblue:422-move-checkroleescalation-outside-transaction
Open

Move checkRoleEscalation outside DB transaction in createAccount#13044
nvazquez wants to merge 1 commit intoapache:4.22from
shapeblue:422-move-checkroleescalation-outside-transaction

Conversation

@nvazquez
Copy link
Copy Markdown
Contributor

Description

The read-only role escalation check iterates all API commands and does not need a write transaction open. Using a transient AccountVO for the check avoids holding the DB connection during the permission scan, reducing connection pool pressure and API latency.

Types of changes

  • Breaking change (fix or feature that would cause existing functionality to change)
  • New feature (non-breaking change which adds functionality)
  • Bug fix (non-breaking change which fixes an issue)
  • Enhancement (improves an existing feature and functionality)
  • Cleanup (Code refactoring and cleanup, that may add test cases)
  • Build/CI
  • Test (unit or integration test code)

Feature/Enhancement Scale or Bug Severity

Feature/Enhancement Scale

  • Major
  • Minor

Bug Severity

  • BLOCKER
  • Critical
  • Major
  • Minor
  • Trivial

Screenshots (if appropriate):

How Has This Been Tested?

How did you try to break this feature and the system with this change?

The read-only role escalation check iterates all API commands and
does not need a write transaction open. Using a transient AccountVO
for the check avoids holding the DB connection during the permission
scan, reducing connection pool pressure and API latency.
@nvazquez
Copy link
Copy Markdown
Contributor Author

@blueorangutan package

@blueorangutan
Copy link
Copy Markdown

@nvazquez a [SL] Jenkins job has been kicked to build packages. It will be bundled with KVM, XenServer and VMware SystemVM templates. I'll keep you posted as I make progress.

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 18, 2026

Codecov Report

❌ Patch coverage is 0% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 17.68%. Comparing base (be89e6f) to head (b09e5a1).

Files with missing lines Patch % Lines
...c/main/java/com/cloud/user/AccountManagerImpl.java 0.00% 4 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               4.22   #13044      +/-   ##
============================================
- Coverage     17.68%   17.68%   -0.01%     
+ Complexity    15793    15789       -4     
============================================
  Files          5922     5922              
  Lines        533096   533094       -2     
  Branches      65209    65209              
============================================
- Hits          94275    94263      -12     
- Misses       428181   428190       +9     
- Partials      10640    10641       +1     
Flag Coverage Δ
uitests 3.69% <ø> (ø)
unittests 18.75% <0.00%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@blueorangutan
Copy link
Copy Markdown

Packaging result [SF]: ✔️ el8 ✔️ el9 ✔️ el10 ✔️ debian ✔️ suse15. SL-JID 17540

@nvazquez
Copy link
Copy Markdown
Contributor Author

@blueorangutan test

@blueorangutan
Copy link
Copy Markdown

@nvazquez a [SL] Trillian-Jenkins test job (ol8 mgmt + kvm-ol8) has been kicked to run smoke tests

@blueorangutan
Copy link
Copy Markdown

[SF] Trillian test result (tid-15915)
Environment: kvm-ol8 (x2), zone: Advanced Networking with Mgmt server ol8
Total time taken: 48288 seconds
Marvin logs: https://github.com/blueorangutan/acs-prs/releases/download/trillian/pr13044-t15915-kvm-ol8.zip
Smoke tests completed. 148 look OK, 1 have errors, 0 did not run
Only failed and skipped tests results shown below:

Test Result Time (s) Test File
ContextSuite context=TestClusterDRS>:setup Error 0.00 test_cluster_drs.py

@sureshanaparti sureshanaparti modified the milestones: 4.22.1, 4.22.2 Apr 23, 2026
@RosiKyu RosiKyu self-assigned this Apr 27, 2026
@RosiKyu
Copy link
Copy Markdown
Collaborator

RosiKyu commented Apr 27, 2026

@blueorangutan test

@blueorangutan
Copy link
Copy Markdown

@RosiKyu a [SL] Trillian-Jenkins test job (ol8 mgmt + kvm-ol8) has been kicked to run smoke tests

@RosiKyu
Copy link
Copy Markdown
Collaborator

RosiKyu commented Apr 27, 2026

@blueorangutan test

@blueorangutan
Copy link
Copy Markdown

@RosiKyu a [SL] Trillian-Jenkins test job (ol8 mgmt + kvm-ol8) has been kicked to run smoke tests

Copy link
Copy Markdown
Collaborator

@RosiKyu RosiKyu left a comment

Choose a reason for hiding this comment

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

LGTM

Summary Test Execution Report

Test Results

TC Description Result
TC01 Create User account, default User role, no UUID PASS
TC02 Create User account with explicit UUID PASS
TC03 Create Domain Admin account, default Domain Admin role PASS
TC04 Create Root Admin account in ROOT domain PASS
TC05 Create Resource Domain Admin (accounttype=3) PASS (with pre-existing accounttype 3 to 2 normalization noted)
TC06 Create account with custom (restricted) role PASS
TC07 Create account with networkdomain set PASS
TC08 Create account with accountdetails map PASS (CMK 6.5.0 client-side serialization quirk noted, out of scope)
TC09 Domain admin creates account in own domain PASS
TC10 Domain Admin attempts to create account assigned Root Admin role (orphan-row check + cross-env behavior parity) PASS (with pre-existing finding noted, out of scope)
TC11 User with restricted role attempts to create account, denied at API access layer PASS
TC12 Exception type and message unchanged from baseline PASS
TC13 Concurrent valid + invalid creates, no deadlocks, no orphan rows PASS
TC14 Two rapid sequential creates without UUID, no collision PASS
TC15 Full account lifecycle (create, update, disable, delete) PASS
TC16 Create account with invalid roleId PASS
TC17 Create account with invalid domainId PASS

Notes and Observations

On TC08 (cmk behavior, out of scope)

When passing accountdetails[N].key=foo and accountdetails[N].value=bar syntax via cmk 6.5.0, the literal field names "key" and "value" are sent as the map keys instead of foo. This was reproduced on a baseline build without the current PR code and is therefore a CMK client-side issue, not a PR finding. The management server side of the contract is honored: whatever the details map contains, it is correctly propagated through createAccount after the PR's reorganization.

On TC10 (pre-existing role escalation finding)

During TC10 we found that a Domain Admin can successfully create a User-type account assigned the Root Admin role in their own domain. The escalation went through and the account was persisted with role_id=1. This was reproduced identically on a baseline build without this PR, confirming the issue is pre-existing and is not introduced by this PR. Community PR #12973 in apache/cloudstack (currently in milestone 4.22.2) addresses this in the access-check logic. PR #13044 only changes when checkRoleEscalation runs (before vs inside the DB transaction); PR #12973 changes how the check works internally. The two PRs change different things and don't conflict, both are useful, neither makes the other redundant.

On orphan rows

Across all negative-path tests (TC10, TC11, TC13, TC16, TC17), no orphan rows were ever observed in the account or user tables when a createAccount call was rejected. PR #13044's pre-transaction check ensures rejected creates do not produce partial DB writes. This was verified with explicit DB queries in every relevant test case.

On concurrency (TC13)

20 concurrent createAccount calls (10 valid + 10 invalid) completed without deadlocks, without orphan rows, and without any exception entries in the management-server log. The mgmt server remained responsive throughout. Total wall time ~32 seconds. The valid creates landed correctly with the requested role; the invalid creates were uniformly rejected with no DB side effects.

The pre-existing finding identified during TC10 is independent of this PR and is being addressed separately by community PR #12973.

Detailed Test Report

TC01: Create User account, default User role, no UUID

Description: Verify that a User-type account can be created without an explicit accountid, and that a valid UUID is auto-generated and persisted.

Prerequisites:

  • Test domain test-pr13044 (id: 415b04d7-8251-4ee1-ac43-162d4668be52)
  • Default User role (id: d6e04f12-4224-11f1-a47c-1e00450001b6)
  • Caller: root admin

Steps:

  1. From cmk as root admin, run createAccount without an accountid parameter:
    create account accounttype=0 username=tc01-user password=Password123 firstname=TC01 lastname=User email=tc01@test.local domainid=415b04d7-8251-4ee1-ac43-162d4668be52 roleid=d6e04f12-4224-11f1-a47c-1e00450001b6
  2. Verify the response contains a valid v4 UUID for account.id.
  3. Verify the first user is created and linked to the account.

Expected result:

  • Account is created successfully (no exception).
  • account.id is a valid v4 UUID, auto-generated outside the transaction
  • accounttype=0, roletype=User, state=enabled.
  • First user tc01-user is created and user.accountid equals account.id.

Test Evidence:

(localcloud) 🐱 > create account accounttype=0 username=tc01-user password=Password123 firstname=TC01 lastname=User email=tc01@test.local domainid=415b04d7-8251-4ee1-ac43-162d4668be52 roleid=d6e04f12-4224-11f1-a47c-1e00450001b6
{
  "account": {
    "accounttype": 0,
    "apikeyaccess": "INHERIT",
    "backupavailable": "20",
    "backuplimit": "20",
    "backupstorageavailable": "400",
    "backupstoragelimit": "400",
    "backupstoragetotal": 0,
    "backuptotal": 0,
    "bucketavailable": "20",
    "bucketlimit": "20",
    "buckettotal": 0,
    "cpuavailable": "40",
    "cpulimit": "40",
    "cputotal": 0,
    "created": "2026-04-27T11:02:00+0000",
    "domain": "test-pr13044",
    "domainid": "415b04d7-8251-4ee1-ac43-162d4668be52",
    "domainpath": "ROOT/test-pr13044",
    "gpuavailable": "20",
    "gpulimit": "20",
    "gputotal": 0,
    "groups": [],
    "id": "fca3421a-d8e7-471a-be89-2e36d603f714",
    "ipavailable": "18",
    "iplimit": "20",
    "iptotal": 0,
    "isdefault": false,
    "memoryavailable": "40960",
    "memorylimit": "40960",
    "memorytotal": 0,
    "name": "tc01-user",
    "networkavailable": "20",
    "networklimit": "20",
    "networktotal": 0,
    "objectstorageavailable": "400",
    "objectstoragelimit": "400",
    "objectstoragetotal": 0,
    "primarystorageavailable": "200",
    "primarystoragelimit": "200",
    "primarystoragetotal": 0,
    "projectavailable": "10",
    "projectlimit": "10",
    "projecttotal": 0,
    "roleid": "d6e04f12-4224-11f1-a47c-1e00450001b6",
    "rolename": "User",
    "roletype": "User",
    "secondarystorageavailable": "400.0",
    "secondarystoragelimit": "400",
    "secondarystoragetotal": 0,
    "snapshotavailable": "20",
    "snapshotlimit": "20",
    "snapshottotal": 0,
    "state": "enabled",
    "templateavailable": "20",
    "templatelimit": "20",
    "templatetotal": 0,
    "user": [
      {
        "account": "tc01-user",
        "accountid": "fca3421a-d8e7-471a-be89-2e36d603f714",
        "accounttype": 0,
        "created": "2026-04-27T11:02:00+0000",
        "domain": "test-pr13044",
        "domainid": "415b04d7-8251-4ee1-ac43-162d4668be52",
        "email": "tc01@test.local",
        "firstname": "TC01",
        "id": "2ba2a4c8-2d9a-47fd-85c0-677f467ef926",
        "is2faenabled": false,
        "is2famandated": false,
        "iscallerchilddomain": false,
        "isdefault": false,
        "lastname": "User",
        "roleid": "d6e04f12-4224-11f1-a47c-1e00450001b6",
        "rolename": "User",
        "roletype": "User",
        "state": "enabled",
        "username": "tc01-user",
        "usersource": "native"
      }
    ],
    "vmavailable": "20",
    "vmlimit": "20",
    "vmrunning": 0,
    "vmstopped": 0,
    "vmtotal": 0,
    "volumeavailable": "20",
    "volumelimit": "20",
    "volumetotal": 0,
    "vpcavailable": "20",
    "vpclimit": "20",
    "vpctotal": 0
  }
}

Actual result:

  • Account tc01-user created successfully.
  • account.id = fca3421a-d8e7-471a-be89-2e36d603f714 - valid v4 UUID, auto-generated by the PR's pre-transaction UUID.randomUUID().toString() call in resolvedAccountUUID.
  • accounttype=0, roletype=User, state=enabled.
  • First user tc01-user (id 2ba2a4c8-2d9a-47fd-85c0-677f467ef926) created with matching accountid.
  • PASS

TC02: Create User account with explicit UUID

Description: Verify that when an explicit accountid is supplied, it is preserved and used for the account row instead of being replaced by an auto-generated UUID.

Prerequisites:

  • Test domain test-pr13044 (id: 415b04d7-8251-4ee1-ac43-162d4668be52)
  • Default User role (id: d6e04f12-4224-11f1-a47c-1e00450001b6)
  • Caller: root admin
  • Pre-chosen test UUID: 11111111-2222-3333-4444-555555555555

Steps:

  1. From cmk as root admin, run createAccount with accountid=11111111-2222-3333-4444-555555555555:
    create account accounttype=0 username=tc02-user password=Password123 firstname=TC02 lastname=User email=tc02@test.local domainid=415b04d7-8251-4ee1-ac43-162d4668be52 roleid=d6e04f12-4224-11f1-a47c-1e00450001b6 accountid=11111111-2222-3333-4444-555555555555
  2. Verify the response contains the exact supplied UUID as account.id.

Expected result:

  • Account is created successfully.
  • account.id equals exactly 11111111-2222-3333-4444-555555555555 (the supplied UUID is preserved by resolvedAccountUUID).
  • The first user's accountid matches the supplied UUID.

Test Evidence:

(localcloud) 🐱 > create account accounttype=0 username=tc02-user password=Password123 firstname=TC02 lastname=User email=tc02@test.local domainid=415b04d7-8251-4ee1-ac43-162d4668be52 roleid=d6e04f12-4224-11f1-a47c-1e00450001b6 accountid=11111111-2222-3333-4444-555555555555
{
  "account": {
    "accounttype": 0,
    "apikeyaccess": "INHERIT",
    "backupavailable": "20",
    "backuplimit": "20",
    "backupstorageavailable": "400",
    "backupstoragelimit": "400",
    "backupstoragetotal": 0,
    "backuptotal": 0,
    "bucketavailable": "20",
    "bucketlimit": "20",
    "buckettotal": 0,
    "cpuavailable": "40",
    "cpulimit": "40",
    "cputotal": 0,
    "created": "2026-04-27T11:02:11+0000",
    "domain": "test-pr13044",
    "domainid": "415b04d7-8251-4ee1-ac43-162d4668be52",
    "domainpath": "ROOT/test-pr13044",
    "gpuavailable": "20",
    "gpulimit": "20",
    "gputotal": 0,
    "groups": [],
    "id": "11111111-2222-3333-4444-555555555555",
    "ipavailable": "18",
    "iplimit": "20",
    "iptotal": 0,
    "isdefault": false,
    "memoryavailable": "40960",
    "memorylimit": "40960",
    "memorytotal": 0,
    "name": "tc02-user",
    "networkavailable": "20",
    "networklimit": "20",
    "networktotal": 0,
    "objectstorageavailable": "400",
    "objectstoragelimit": "400",
    "objectstoragetotal": 0,
    "primarystorageavailable": "200",
    "primarystoragelimit": "200",
    "primarystoragetotal": 0,
    "projectavailable": "10",
    "projectlimit": "10",
    "projecttotal": 0,
    "roleid": "d6e04f12-4224-11f1-a47c-1e00450001b6",
    "rolename": "User",
    "roletype": "User",
    "secondarystorageavailable": "400.0",
    "secondarystoragelimit": "400",
    "secondarystoragetotal": 0,
    "snapshotavailable": "20",
    "snapshotlimit": "20",
    "snapshottotal": 0,
    "state": "enabled",
    "templateavailable": "20",
    "templatelimit": "20",
    "templatetotal": 0,
    "user": [
      {
        "account": "tc02-user",
        "accountid": "11111111-2222-3333-4444-555555555555",
        "accounttype": 0,
        "created": "2026-04-27T11:02:12+0000",
        "domain": "test-pr13044",
        "domainid": "415b04d7-8251-4ee1-ac43-162d4668be52",
        "email": "tc02@test.local",
        "firstname": "TC02",
        "id": "f2e48496-c07c-41d2-a4c6-2fc59f3f7cee",
        "is2faenabled": false,
        "is2famandated": false,
        "iscallerchilddomain": false,
        "isdefault": false,
        "lastname": "User",
        "roleid": "d6e04f12-4224-11f1-a47c-1e00450001b6",
        "rolename": "User",
        "roletype": "User",
        "state": "enabled",
        "username": "tc02-user",
        "usersource": "native"
      }
    ],
    "vmavailable": "20",
    "vmlimit": "20",
    "vmrunning": 0,
    "vmstopped": 0,
    "vmtotal": 0,
    "volumeavailable": "20",
    "volumelimit": "20",
    "volumetotal": 0,
    "vpcavailable": "20",
    "vpclimit": "20",
    "vpctotal": 0
  }
}

Actual result:

  • Account tc02-user created successfully.
  • account.id = 11111111-2222-3333-4444-555555555555 - the supplied UUID is preserved exactly.
  • user.accountid matches the supplied UUID, confirming the UUID is consistently propagated through both account and user creation under the new pre-transaction resolution.
  • PASS

TC03: Create Domain Admin account, default Domain Admin role

Description: Verify that a Domain Admin (accounttype=2) account can be created with the default Domain Admin role.

Prerequisites:

  • Test domain test-pr13044 (id: 415b04d7-8251-4ee1-ac43-162d4668be52)
  • Default Domain Admin role (id: d6e03570-4224-11f1-a47c-1e00450001b6)
  • Caller: root admin

Steps:

  1. From cmk as root admin, run createAccount with accounttype=2 and the Domain Admin role:
    create account accounttype=2 username=tc03-domadmin password=Password123 firstname=TC03 lastname=DomAdmin email=tc03@test.local domainid=415b04d7-8251-4ee1-ac43-162d4668be52 roleid=d6e03570-4224-11f1-a47c-1e00450001b6
  2. Verify accounttype=2 and roletype=DomainAdmin in the response.

Expected result:

  • Account is created successfully.
  • accounttype=2, rolename=Domain Admin, roletype=DomainAdmin.
  • First user is created and linked.

Test Evidence:

(localcloud) 🐱 > create account accounttype=2 username=tc03-domadmin password=Password123 firstname=TC03 lastname=DomAdmin email=tc03@test.local domainid=415b04d7-8251-4ee1-ac43-162d4668be52 roleid=d6e03570-4224-11f1-a47c-1e00450001b6
{
  "account": {
    "accounttype": 2,
    "apikeyaccess": "INHERIT",
    "backupavailable": "20",
    "backuplimit": "20",
    "backupstorageavailable": "400",
    "backupstoragelimit": "400",
    "backupstoragetotal": 0,
    "backuptotal": 0,
    "bucketavailable": "20",
    "bucketlimit": "20",
    "buckettotal": 0,
    "cpuavailable": "40",
    "cpulimit": "40",
    "cputotal": 0,
    "created": "2026-04-27T11:02:20+0000",
    "domain": "test-pr13044",
    "domainid": "415b04d7-8251-4ee1-ac43-162d4668be52",
    "domainpath": "ROOT/test-pr13044",
    "gpuavailable": "20",
    "gpulimit": "20",
    "gputotal": 0,
    "groups": [],
    "id": "a8bc08ee-3ea1-42b2-b9cb-9ea1735a292e",
    "ipavailable": "18",
    "iplimit": "20",
    "iptotal": 0,
    "isdefault": false,
    "memoryavailable": "40960",
    "memorylimit": "40960",
    "memorytotal": 0,
    "name": "tc03-domadmin",
    "networkavailable": "20",
    "networklimit": "20",
    "networktotal": 0,
    "objectstorageavailable": "400",
    "objectstoragelimit": "400",
    "objectstoragetotal": 0,
    "primarystorageavailable": "200",
    "primarystoragelimit": "200",
    "primarystoragetotal": 0,
    "projectavailable": "10",
    "projectlimit": "10",
    "projecttotal": 0,
    "roleid": "d6e03570-4224-11f1-a47c-1e00450001b6",
    "rolename": "Domain Admin",
    "roletype": "DomainAdmin",
    "secondarystorageavailable": "400.0",
    "secondarystoragelimit": "400",
    "secondarystoragetotal": 0,
    "snapshotavailable": "20",
    "snapshotlimit": "20",
    "snapshottotal": 0,
    "state": "enabled",
    "templateavailable": "20",
    "templatelimit": "20",
    "templatetotal": 0,
    "user": [
      {
        "account": "tc03-domadmin",
        "accountid": "a8bc08ee-3ea1-42b2-b9cb-9ea1735a292e",
        "accounttype": 2,
        "created": "2026-04-27T11:02:20+0000",
        "domain": "test-pr13044",
        "domainid": "415b04d7-8251-4ee1-ac43-162d4668be52",
        "email": "tc03@test.local",
        "firstname": "TC03",
        "id": "ea0ea3d5-97e6-4370-bfc4-cb495f89712c",
        "is2faenabled": false,
        "is2famandated": false,
        "iscallerchilddomain": false,
        "isdefault": false,
        "lastname": "DomAdmin",
        "roleid": "d6e03570-4224-11f1-a47c-1e00450001b6",
        "rolename": "Domain Admin",
        "roletype": "DomainAdmin",
        "state": "enabled",
        "username": "tc03-domadmin",
        "usersource": "native"
      }
    ],
    "vmavailable": "20",
    "vmlimit": "20",
    "vmrunning": 0,
    "vmstopped": 0,
    "vmtotal": 0,
    "volumeavailable": "20",
    "volumelimit": "20",
    "volumetotal": 0,
    "vpcavailable": "20",
    "vpclimit": "20",
    "vpctotal": 0
  }
}

Actual result:

  • Account tc03-domadmin created successfully with id a8bc08ee-3ea1-42b2-b9cb-9ea1735a292e.
  • accounttype=2, rolename=Domain Admin, roletype=DomainAdmin, state=enabled.
  • First user tc03-domadmin (id ea0ea3d5-97e6-4370-bfc4-cb495f89712c) created and linked.
  • PASS

TC04: Create Root Admin account in ROOT domain

Description: Verify that a Root Admin (accounttype=1) account can be created in the ROOT domain with the default Root Admin role. As caller is already root admin, the role escalation check (now performed before the transaction) must allow this creation.

Prerequisites:

  • ROOT domain (id: afa4aae4-4224-11f1-a47c-1e00450001b6)
  • Default Root Admin role (id: d6dfd5cf-4224-11f1-a47c-1e00450001b6)
  • Caller: root admin

Steps:

  1. From cmk as root admin, run createAccount with accounttype=1 and the Root Admin role in the ROOT domain:
    create account accounttype=1 username=tc04-rootadmin password=Password123 firstname=TC04 lastname=RootAdmin email=tc04@test.local domainid=afa4aae4-4224-11f1-a47c-1e00450001b6 roleid=d6dfd5cf-4224-11f1-a47c-1e00450001b6
  2. Verify accounttype=1 and roletype=Admin in the response.

Expected result:

  • Account is created successfully (root admin can create root admin).
  • accounttype=1, rolename=Root Admin, roletype=Admin.
  • Limits show as Unlimited (root admin is unrestricted).

Test Evidence:

(localcloud) 🐱 > create account accounttype=1 username=tc04-rootadmin password=Password123 firstname=TC04 lastname=RootAdmin email=tc04@test.local domainid=afa4aae4-4224-11f1-a47c-1e00450001b6 roleid=d6dfd5cf-4224-11f1-a47c-1e00450001b6
{
  "account": {
    "accounttype": 1,
    "apikeyaccess": "INHERIT",
    "backupavailable": "Unlimited",
    "backuplimit": "Unlimited",
    "backupstorageavailable": "Unlimited",
    "backupstoragelimit": "Unlimited",
    "backupstoragetotal": 0,
    "backuptotal": 0,
    "bucketavailable": "Unlimited",
    "bucketlimit": "Unlimited",
    "buckettotal": 0,
    "cpuavailable": "Unlimited",
    "cpulimit": "Unlimited",
    "cputotal": 0,
    "created": "2026-04-27T11:02:23+0000",
    "domain": "ROOT",
    "domainid": "afa4aae4-4224-11f1-a47c-1e00450001b6",
    "domainpath": "ROOT",
    "gpuavailable": "Unlimited",
    "gpulimit": "Unlimited",
    "gputotal": 0,
    "groups": [],
    "id": "be37f999-793d-4d56-8997-44547d6fe0d8",
    "ipavailable": "Unlimited",
    "iplimit": "Unlimited",
    "iptotal": 0,
    "isdefault": false,
    "memoryavailable": "Unlimited",
    "memorylimit": "Unlimited",
    "memorytotal": 0,
    "name": "tc04-rootadmin",
    "networkavailable": "Unlimited",
    "networklimit": "Unlimited",
    "networktotal": 0,
    "objectstorageavailable": "Unlimited",
    "objectstoragelimit": "Unlimited",
    "objectstoragetotal": 0,
    "primarystorageavailable": "Unlimited",
    "primarystoragelimit": "Unlimited",
    "primarystoragetotal": 0,
    "projectavailable": "Unlimited",
    "projectlimit": "Unlimited",
    "projecttotal": 0,
    "roleid": "d6dfd5cf-4224-11f1-a47c-1e00450001b6",
    "rolename": "Root Admin",
    "roletype": "Admin",
    "secondarystorageavailable": "Unlimited",
    "secondarystoragelimit": "Unlimited",
    "secondarystoragetotal": 0,
    "snapshotavailable": "Unlimited",
    "snapshotlimit": "Unlimited",
    "snapshottotal": 0,
    "state": "enabled",
    "templateavailable": "Unlimited",
    "templatelimit": "Unlimited",
    "templatetotal": 0,
    "user": [
      {
        "account": "tc04-rootadmin",
        "accountid": "be37f999-793d-4d56-8997-44547d6fe0d8",
        "accounttype": 1,
        "created": "2026-04-27T11:02:23+0000",
        "domain": "ROOT",
        "domainid": "afa4aae4-4224-11f1-a47c-1e00450001b6",
        "email": "tc04@test.local",
        "firstname": "TC04",
        "id": "eef1411e-504f-44ff-925d-0bcabe76d184",
        "is2faenabled": false,
        "is2famandated": false,
        "iscallerchilddomain": false,
        "isdefault": false,
        "lastname": "RootAdmin",
        "roleid": "d6dfd5cf-4224-11f1-a47c-1e00450001b6",
        "rolename": "Root Admin",
        "roletype": "Admin",
        "state": "enabled",
        "username": "tc04-rootadmin",
        "usersource": "native"
      }
    ],
    "vmavailable": "Unlimited",
    "vmlimit": "Unlimited",
    "vmrunning": 0,
    "vmstopped": 0,
    "vmtotal": 0,
    "volumeavailable": "Unlimited",
    "volumelimit": "Unlimited",
    "volumetotal": 0,
    "vpcavailable": "Unlimited",
    "vpclimit": "Unlimited",
    "vpctotal": 0
  }
}

Actual result:

  • Account tc04-rootadmin created successfully with id be37f999-793d-4d56-8997-44547d6fe0d8 in the ROOT domain.
  • accounttype=1, rolename=Root Admin, roletype=Admin, state=enabled.
  • All resource limits show Unlimited (consistent with root admin).
  • First user tc04-rootadmin created and linked.
  • The pre-transaction checkRoleEscalation correctly allowed root admin → root admin (no escalation).
  • PASS

TC05: Create Resource Domain Admin (accounttype=3)

Description: Verify account creation with accounttype=3 (Resource Domain Admin) succeeds.

Prerequisites:

  • Test domain test-pr13044 (id: 415b04d7-8251-4ee1-ac43-162d4668be52)
  • Default Domain Admin role (id: d6e03570-4224-11f1-a47c-1e00450001b6)
  • Caller: root admin

Steps:

  1. From cmk as root admin, run createAccount with accounttype=3:
    create account accounttype=3 username=tc05-resdomadmin password=Password123 firstname=TC05 lastname=ResDomAdmin email=tc05@test.local domainid=415b04d7-8251-4ee1-ac43-162d4668be52 roleid=d6e03570-4224-11f1-a47c-1e00450001b6
  2. Verify the account is created without exception.

Expected result:

  • Account is created successfully.
  • The account is linked to the Domain Admin role. (The reported accounttype may be normalized to 2 because the assigned role is roletype=DomainAdmin - this normalization predates the PR and is unrelated to the change being tested.)

Test Evidence:

(localcloud) 🐱 > create account accounttype=3 username=tc05-resdomadmin password=Password123 firstname=TC05 lastname=ResDomAdmin email=tc05@test.local domainid=415b04d7-8251-4ee1-ac43-162d4668be52 roleid=d6e03570-4224-11f1-a47c-1e00450001b6
{
  "account": {
    "accounttype": 2,
    "apikeyaccess": "INHERIT",
    "backupavailable": "20",
    "backuplimit": "20",
    "backupstorageavailable": "400",
    "backupstoragelimit": "400",
    "backupstoragetotal": 0,
    "backuptotal": 0,
    "bucketavailable": "20",
    "bucketlimit": "20",
    "buckettotal": 0,
    "cpuavailable": "40",
    "cpulimit": "40",
    "cputotal": 0,
    "created": "2026-04-27T11:02:38+0000",
    "domain": "test-pr13044",
    "domainid": "415b04d7-8251-4ee1-ac43-162d4668be52",
    "domainpath": "ROOT/test-pr13044",
    "gpuavailable": "20",
    "gpulimit": "20",
    "gputotal": 0,
    "groups": [],
    "id": "670a3e68-1daf-4e3c-b1ac-0ca868e01901",
    "ipavailable": "18",
    "iplimit": "20",
    "iptotal": 0,
    "isdefault": false,
    "memoryavailable": "40960",
    "memorylimit": "40960",
    "memorytotal": 0,
    "name": "tc05-resdomadmin",
    "networkavailable": "20",
    "networklimit": "20",
    "networktotal": 0,
    "objectstorageavailable": "400",
    "objectstoragelimit": "400",
    "objectstoragetotal": 0,
    "primarystorageavailable": "200",
    "primarystoragelimit": "200",
    "primarystoragetotal": 0,
    "projectavailable": "10",
    "projectlimit": "10",
    "projecttotal": 0,
    "roleid": "d6e03570-4224-11f1-a47c-1e00450001b6",
    "rolename": "Domain Admin",
    "roletype": "DomainAdmin",
    "secondarystorageavailable": "400.0",
    "secondarystoragelimit": "400",
    "secondarystoragetotal": 0,
    "snapshotavailable": "20",
    "snapshotlimit": "20",
    "snapshottotal": 0,
    "state": "enabled",
    "templateavailable": "20",
    "templatelimit": "20",
    "templatetotal": 0,
    "user": [
      {
        "account": "tc05-resdomadmin",
        "accountid": "670a3e68-1daf-4e3c-b1ac-0ca868e01901",
        "accounttype": 2,
        "created": "2026-04-27T11:02:38+0000",
        "domain": "test-pr13044",
        "domainid": "415b04d7-8251-4ee1-ac43-162d4668be52",
        "email": "tc05@test.local",
        "firstname": "TC05",
        "id": "39ea1fc8-71ed-458c-a3b8-b0b40991d0b9",
        "is2faenabled": false,
        "is2famandated": false,
        "iscallerchilddomain": false,
        "isdefault": false,
        "lastname": "ResDomAdmin",
        "roleid": "d6e03570-4224-11f1-a47c-1e00450001b6",
        "rolename": "Domain Admin",
        "roletype": "DomainAdmin",
        "state": "enabled",
        "username": "tc05-resdomadmin",
        "usersource": "native"
      }
    ],
    "vmavailable": "20",
    "vmlimit": "20",
    "vmrunning": 0,
    "vmstopped": 0,
    "vmtotal": 0,
    "volumeavailable": "20",
    "volumelimit": "20",
    "volumetotal": 0,
    "vpcavailable": "20",
    "vpclimit": "20",
    "vpctotal": 0
  }
}

Actual result:

  • Account tc05-resdomadmin created successfully with id 670a3e68-1daf-4e3c-b1ac-0ca868e01901.
  • Reported accounttype=2 (normalized from the requested accounttype=3 because the assigned role is DomainAdmin). This normalization is pre-existing CloudStack behavior unrelated to this PR
  • First user created and linked.
  • PASS (with the noted pre-existing normalization)

TC06: Create account with custom (restricted) role

Description: Verify that account creation with a custom (non-default) role succeeds and the role is correctly persisted.

Prerequisites:

  • Test domain test-pr13044 (id: 415b04d7-8251-4ee1-ac43-162d4668be52)
  • Custom role tpr13044-restricted (id: 02401b36-e85d-4981-a433-3a49845df70c, type=User, only listVirtualMachines allowed)
  • Caller: root admin

Steps:

  1. From cmk as root admin, run createAccount assigning the custom role:
    create account accounttype=0 username=tc06-customrole password=Password123 firstname=TC06 lastname=Custom email=tc06@test.local domainid=415b04d7-8251-4ee1-ac43-162d4668be52 roleid=02401b36-e85d-4981-a433-3a49845df70c
  2. Verify roleid and rolename in the response match the custom role.

Expected result:

  • Account is created successfully.
  • roleid=02401b36-e85d-4981-a433-3a49845df70c, rolename=tpr13044-restricted, roletype=User.

Test Evidence:

(localcloud) 🐱 > create account accounttype=0 username=tc06-customrole password=Password123 firstname=TC06 lastname=Custom email=tc06@test.local domainid=415b04d7-8251-4ee1-ac43-162d4668be52 roleid=02401b36-e85d-4981-a433-3a49845df70c
{
  "account": {
    "accounttype": 0,
    "apikeyaccess": "INHERIT",
    "backupavailable": "20",
    "backuplimit": "20",
    "backupstorageavailable": "400",
    "backupstoragelimit": "400",
    "backupstoragetotal": 0,
    "backuptotal": 0,
    "bucketavailable": "20",
    "bucketlimit": "20",
    "buckettotal": 0,
    "cpuavailable": "40",
    "cpulimit": "40",
    "cputotal": 0,
    "created": "2026-04-27T11:02:41+0000",
    "domain": "test-pr13044",
    "domainid": "415b04d7-8251-4ee1-ac43-162d4668be52",
    "domainpath": "ROOT/test-pr13044",
    "gpuavailable": "20",
    "gpulimit": "20",
    "gputotal": 0,
    "groups": [],
    "id": "346785d6-a506-412b-b8ea-b5fd9dfad349",
    "ipavailable": "18",
    "iplimit": "20",
    "iptotal": 0,
    "isdefault": false,
    "memoryavailable": "40960",
    "memorylimit": "40960",
    "memorytotal": 0,
    "name": "tc06-customrole",
    "networkavailable": "20",
    "networklimit": "20",
    "networktotal": 0,
    "objectstorageavailable": "400",
    "objectstoragelimit": "400",
    "objectstoragetotal": 0,
    "primarystorageavailable": "200",
    "primarystoragelimit": "200",
    "primarystoragetotal": 0,
    "projectavailable": "10",
    "projectlimit": "10",
    "projecttotal": 0,
    "roleid": "02401b36-e85d-4981-a433-3a49845df70c",
    "rolename": "tpr13044-restricted",
    "roletype": "User",
    "secondarystorageavailable": "400.0",
    "secondarystoragelimit": "400",
    "secondarystoragetotal": 0,
    "snapshotavailable": "20",
    "snapshotlimit": "20",
    "snapshottotal": 0,
    "state": "enabled",
    "templateavailable": "20",
    "templatelimit": "20",
    "templatetotal": 0,
    "user": [
      {
        "account": "tc06-customrole",
        "accountid": "346785d6-a506-412b-b8ea-b5fd9dfad349",
        "accounttype": 0,
        "created": "2026-04-27T11:02:42+0000",
        "domain": "test-pr13044",
        "domainid": "415b04d7-8251-4ee1-ac43-162d4668be52",
        "email": "tc06@test.local",
        "firstname": "TC06",
        "id": "1d58fcb5-5450-464d-9391-12c22cb65934",
        "is2faenabled": false,
        "is2famandated": false,
        "iscallerchilddomain": false,
        "isdefault": false,
        "lastname": "Custom",
        "roleid": "02401b36-e85d-4981-a433-3a49845df70c",
        "rolename": "tpr13044-restricted",
        "roletype": "User",
        "state": "enabled",
        "username": "tc06-customrole",
        "usersource": "native"
      }
    ],
    "vmavailable": "20",
    "vmlimit": "20",
    "vmrunning": 0,
    "vmstopped": 0,
    "vmtotal": 0,
    "volumeavailable": "20",
    "volumelimit": "20",
    "volumetotal": 0,
    "vpcavailable": "20",
    "vpclimit": "20",
    "vpctotal": 0
  }
}

Actual result:

  • Account tc06-customrole created successfully with id 346785d6-a506-412b-b8ea-b5fd9dfad349.
  • roleid=02401b36-e85d-4981-a433-3a49845df70c, rolename=tpr13044-restricted, roletype=User - custom role correctly persisted.
  • First user created and linked.
  • PASS

TC07: Create account with networkdomain set

Description: Verify that networkdomain is correctly persisted to the account.network_domain column.

Prerequisites:

  • Test domain test-pr13044 (id: 415b04d7-8251-4ee1-ac43-162d4668be52)
  • Default User role (id: d6e04f12-4224-11f1-a47c-1e00450001b6)
  • Caller: root admin
  • DB access on management server

Steps:

  1. From cmk as root admin, run createAccount with networkdomain=tc07.test.local:
    create account accounttype=0 username=tc07-netdom password=Password123 firstname=TC07 lastname=NetDom email=tc07@test.local domainid=415b04d7-8251-4ee1-ac43-162d4668be52 roleid=d6e04f12-4224-11f1-a47c-1e00450001b6 networkdomain=tc07.test.local
  2. Verify networkdomain is reflected in the API response.
  3. Verify the network_domain column in the DB matches.

Expected result:

  • Account is created successfully.
  • API response contains networkdomain=tc07.test.local.
  • DB account.network_domain column equals tc07.test.local.
  • account.uuid matches account.id in the API response.

Test Evidence:

(localcloud) 🐱 > create account accounttype=0 username=tc07-netdom password=Password123 firstname=TC07 lastname=NetDom email=tc07@test.local domainid=415b04d7-8251-4ee1-ac43-162d4668be52 roleid=d6e04f12-4224-11f1-a47c-1e00450001b6 networkdomain=tc07.test.local
{
  "account": {
    "accounttype": 0,
    "apikeyaccess": "INHERIT",
    "backupavailable": "20",
    "backuplimit": "20",
    "backupstorageavailable": "400",
    "backupstoragelimit": "400",
    "backupstoragetotal": 0,
    "backuptotal": 0,
    "bucketavailable": "20",
    "bucketlimit": "20",
    "buckettotal": 0,
    "cpuavailable": "40",
    "cpulimit": "40",
    "cputotal": 0,
    "created": "2026-04-27T11:02:49+0000",
    "domain": "test-pr13044",
    "domainid": "415b04d7-8251-4ee1-ac43-162d4668be52",
    "domainpath": "ROOT/test-pr13044",
    "gpuavailable": "20",
    "gpulimit": "20",
    "gputotal": 0,
    "groups": [],
    "id": "c917c5be-5cbf-4992-8266-e79e7a1547c3",
    "ipavailable": "18",
    "iplimit": "20",
    "iptotal": 0,
    "isdefault": false,
    "memoryavailable": "40960",
    "memorylimit": "40960",
    "memorytotal": 0,
    "name": "tc07-netdom",
    "networkavailable": "20",
    "networkdomain": "tc07.test.local",
    "networklimit": "20",
    "networktotal": 0,
    "objectstorageavailable": "400",
    "objectstoragelimit": "400",
    "objectstoragetotal": 0,
    "primarystorageavailable": "200",
    "primarystoragelimit": "200",
    "primarystoragetotal": 0,
    "projectavailable": "10",
    "projectlimit": "10",
    "projecttotal": 0,
    "roleid": "d6e04f12-4224-11f1-a47c-1e00450001b6",
    "rolename": "User",
    "roletype": "User",
    "secondarystorageavailable": "400.0",
    "secondarystoragelimit": "400",
    "secondarystoragetotal": 0,
    "snapshotavailable": "20",
    "snapshotlimit": "20",
    "snapshottotal": 0,
    "state": "enabled",
    "templateavailable": "20",
    "templatelimit": "20",
    "templatetotal": 0,
    "user": [
      {
        "account": "tc07-netdom",
        "accountid": "c917c5be-5cbf-4992-8266-e79e7a1547c3",
        "accounttype": 0,
        "created": "2026-04-27T11:02:49+0000",
        "domain": "test-pr13044",
        "domainid": "415b04d7-8251-4ee1-ac43-162d4668be52",
        "email": "tc07@test.local",
        "firstname": "TC07",
        "id": "421739d1-4332-42ee-9d7a-9269b948f7aa",
        "is2faenabled": false,
        "is2famandated": false,
        "iscallerchilddomain": false,
        "isdefault": false,
        "lastname": "NetDom",
        "roleid": "d6e04f12-4224-11f1-a47c-1e00450001b6",
        "rolename": "User",
        "roletype": "User",
        "state": "enabled",
        "username": "tc07-netdom",
        "usersource": "native"
      }
    ],
    "vmavailable": "20",
    "vmlimit": "20",
    "vmrunning": 0,
    "vmstopped": 0,
    "vmtotal": 0,
    "volumeavailable": "20",
    "volumelimit": "20",
    "volumetotal": 0,
    "vpcavailable": "20",
    "vpclimit": "20",
    "vpctotal": 0
  }
}
  • DB results
mysql> SELECT id, uuid, account_name, network_domain FROM account WHERE account_name='tc07-netdom';
+----+--------------------------------------+--------------+-----------------+
| id | uuid                                 | account_name | network_domain  |
+----+--------------------------------------+--------------+-----------------+
| 13 | c917c5be-5cbf-4992-8266-e79e7a1547c3 | tc07-netdom  | tc07.test.local |
+----+--------------------------------------+--------------+-----------------+
1 row in set (0.00 sec)

Actual result:

  • Account tc07-netdom created successfully with id c917c5be-5cbf-4992-8266-e79e7a1547c3.
  • API response includes networkdomain=tc07.test.local.
  • DB confirms account.network_domain='tc07.test.local' and account.uuid='c917c5be-5cbf-4992-8266-e79e7a1547c3' matching the API-reported account.id.
  • The pre-transaction UUID resolution and the persisted network_domain value are consistent.
  • PASS

TC08: Create account with accountdetails map

Description: Verify that account creation with an accountdetails map persists details rows in the account_details table.

Prerequisites:

  • Test domain test-pr13044 (id: 415b04d7-8251-4ee1-ac43-162d4668be52)
  • Default User role (id: d6e04f12-4224-11f1-a47c-1e00450001b6)
  • Caller: root admin
  • DB access on management server
  • Reference un-patched env for cross-validation

Steps:

  1. From cmk as root admin on patched env, run createAccount with two accountdetails[N].key/accountdetails[N].value pairs:
    create account accounttype=0 username=tc08-details password=Password123 firstname=TC08 lastname=Details email=tc08@test.local domainid=415b04d7-8251-4ee1-ac43-162d4668be52 roleid=d6e04f12-4224-11f1-a47c-1e00450001b6 accountdetails[0].key=foo accountdetails[0].value=bar accountdetails[1].key=env accountdetails[1].value=test
  2. Verify the account is created and the response contains the accountdetails field.
  3. Query account_details in the DB.
  4. Cross-validate by running the same exact CMK command against the un-patched env

Expected result:

  • Account is created successfully on patched env.
  • account_details rows are persisted for the new account.
  • UUID consistency: account.uuid in DB matches account.id in API response.
  • DB rows on patched env match DB rows on un-patched env

Test Evidence:

Patched env:

(localcloud) 🐱 > create account accounttype=0 username=tc08-details password=Password123 firstname=TC08 lastname=Details email=tc08@test.local domainid=415b04d7-8251-4ee1-ac43-162d4668be52 roleid=d6e04f12-4224-11f1-a47c-1e00450001b6 accountdetails[0].key=foo accountdetails[0].value=bar accountdetails[1].key=env accountdetails[1].value=test
{
  "account": {
    "accountdetails": {
      "key": "foo",
      "value": "bar"
    },
    "accounttype": 0,
    "apikeyaccess": "INHERIT",
    "backupavailable": "20",
    "backuplimit": "20",
    "backupstorageavailable": "400",
    "backupstoragelimit": "400",
    "backupstoragetotal": 0,
    "backuptotal": 0,
    "bucketavailable": "20",
    "bucketlimit": "20",
    "buckettotal": 0,
    "cpuavailable": "40",
    "cpulimit": "40",
    "cputotal": 0,
    "created": "2026-04-27T11:02:55+0000",
    "domain": "test-pr13044",
    "domainid": "415b04d7-8251-4ee1-ac43-162d4668be52",
    "domainpath": "ROOT/test-pr13044",
    "gpuavailable": "20",
    "gpulimit": "20",
    "gputotal": 0,
    "groups": [],
    "id": "118c2841-e9c9-4e46-affa-e2b3d040beab",
    "ipavailable": "18",
    "iplimit": "20",
    "iptotal": 0,
    "isdefault": false,
    "memoryavailable": "40960",
    "memorylimit": "40960",
    "memorytotal": 0,
    "name": "tc08-details",
    "networkavailable": "20",
    "networklimit": "20",
    "networktotal": 0,
    "objectstorageavailable": "400",
    "objectstoragelimit": "400",
    "objectstoragetotal": 0,
    "primarystorageavailable": "200",
    "primarystoragelimit": "200",
    "primarystoragetotal": 0,
    "projectavailable": "10",
    "projectlimit": "10",
    "projecttotal": 0,
    "roleid": "d6e04f12-4224-11f1-a47c-1e00450001b6",
    "rolename": "User",
    "roletype": "User",
    "secondarystorageavailable": "400.0",
    "secondarystoragelimit": "400",
    "secondarystoragetotal": 0,
    "snapshotavailable": "20",
    "snapshotlimit": "20",
    "snapshottotal": 0,
    "state": "enabled",
    "templateavailable": "20",
    "templatelimit": "20",
    "templatetotal": 0,
    "user": [
      {
        "account": "tc08-details",
        "accountid": "118c2841-e9c9-4e46-affa-e2b3d040beab",
        "accounttype": 0,
        "created": "2026-04-27T11:02:55+0000",
        "domain": "test-pr13044",
        "domainid": "415b04d7-8251-4ee1-ac43-162d4668be52",
        "email": "tc08@test.local",
        "firstname": "TC08",
        "id": "310c8704-7efc-4fc0-a715-d77b51ccbc44",
        "is2faenabled": false,
        "is2famandated": false,
        "iscallerchilddomain": false,
        "isdefault": false,
        "lastname": "Details",
        "roleid": "d6e04f12-4224-11f1-a47c-1e00450001b6",
        "rolename": "User",
        "roletype": "User",
        "state": "enabled",
        "username": "tc08-details",
        "usersource": "native"
      }
    ],
    "vmavailable": "20",
    "vmlimit": "20",
    "vmrunning": 0,
    "vmstopped": 0,
    "vmtotal": 0,
    "volumeavailable": "20",
    "volumelimit": "20",
    "volumetotal": 0,
    "vpcavailable": "20",
    "vpclimit": "20",
    "vpctotal": 0
  }
}
  • DB results
mysql> SELECT account_id, name, value FROM account_details WHERE account_id IN (SELECT id FROM account WHERE account_name='tc08-details');
+------------+-------+-------+
| account_id | name  | value |
+------------+-------+-------+
|         14 | value | bar   |
|         14 | key   | foo   |
+------------+-------+-------+
2 rows in set (0.00 sec)
  • Un-patched env, same CMK command, same client version:
(localcloud) 🐱 > create account accounttype=0 username=cmk-test-detail password=Password123 firstname=Test lastname=Detail email=test@test.local domainid=41e1cd1d-420a-11f1-ad94-1e001e000468 roleid=63378b83-420a-11f1-ad94-1e001e000468 accountdetails[0].key=foo accountdetails[0].value=bar accountdetails[1].key=env accountdetails[1].value=test
{
  "account": {
    "accountdetails": {
      "key": "foo",
      "value": "bar"
    },
    "accounttype": 0,
    "id": "144b0c3b-a336-47fb-b284-a16a560e60d4",
    "name": "cmk-test-detail",
    ...
  }
}

mysql> SELECT account_id, name, value FROM account_details WHERE account_id IN (SELECT id FROM account WHERE account_name='cmk-test-detail');
+------------+-------+-------+
| account_id | name  | value |
+------------+-------+-------+
|          5 | value | bar   |
|          5 | key   | foo   |
+------------+-------+-------+
2 rows in set (0.00 sec)

Actual result:

  • Account tc08-details created successfully with id 118c2841-e9c9-4e46-affa-e2b3d040beab (DB id 14).
  • account_details rows persisted (2 rows).
  • The literal name values stored are key and value rather than foo and env. Reproducible on a baseline build without the change, so this is a CMK input handling detail and out of scope.
  • The details map is correctly propagated through createAccount, persisted in account_details, and round-tripped in the API response.
  • UUID consistency confirmed: API account.id = DB account.uuid.
  • PASS

TC09: Domain admin creates account in own domain

Description: Verify that a Domain Admin can create a User-type account in their own domain (no escalation, check must pass).

Prerequisites:

  • Test domain test-pr13044 (id: 415b04d7-8251-4ee1-ac43-162d4668be52)
  • Domain admin tpr13044-domadmin (registered API keys, profile loaded into cmk)
  • Default User role (id: d6e04f12-4224-11f1-a47c-1e00450001b6)
  • Caller: tpr13044-domadmin (Domain Admin)

Steps:

  1. Switch cmk to the tpr13044-domadmin profile
  2. Verify authentication is correct via list users listall=true - should return only domain-scoped users.
  3. Run createAccount as the domain admin:
    create account accounttype=0 username=tc09-user password=Password123 firstname=TC09 lastname=User email=tc09@test.local domainid=415b04d7-8251-4ee1-ac43-162d4668be52 roleid=d6e04f12-4224-11f1-a47c-1e00450001b6

Expected result:

  • Authentication scope is correct: Domain Admin sees ~420 APIs (vs ~880 for root admin) and only test-pr13044 users.
  • Account is created successfully - pre-transaction checkRoleEscalation allows Domain Admin to assign the User role (no escalation).
  • accounttype=0, rolename=User, state=enabled.

Test Evidence:

(tpr13044-domadmin) 🐱 > sync
Discovered 420 APIs

(tpr13044-domadmin) 🐱 > list users listall=true
{
  "count": 9,
  ... (9 users, all in test-pr13044 domain - admin/ROOT not visible) ...
}

(tpr13044-domadmin) 🐱 > create account accounttype=0 username=tc09-user password=Password123 firstname=TC09 lastname=User email=tc09@test.local domainid=415b04d7-8251-4ee1-ac43-162d4668be52 roleid=d6e04f12-4224-11f1-a47c-1e00450001b6
{
  "account": {
    "accounttype": 0,
    "apikeyaccess": "INHERIT",
    "backupavailable": "20",
    "backuplimit": "20",
    "backupstorageavailable": "400",
    "backupstoragelimit": "400",
    "backupstoragetotal": 0,
    "backuptotal": 0,
    "bucketavailable": "20",
    "bucketlimit": "20",
    "buckettotal": 0,
    "cpuavailable": "40",
    "cpulimit": "40",
    "cputotal": 0,
    "created": "2026-04-27T11:05:43+0000",
    "domain": "test-pr13044",
    "domainid": "415b04d7-8251-4ee1-ac43-162d4668be52",
    "domainpath": "ROOT/test-pr13044",
    "gpuavailable": "20",
    "gpulimit": "20",
    "gputotal": 0,
    "groups": [],
    "id": "beb17dda-1e38-4617-aa2a-fcae29721f9a",
    "ipavailable": "18",
    "iplimit": "20",
    "iptotal": 0,
    "isdefault": false,
    "memoryavailable": "40960",
    "memorylimit": "40960",
    "memorytotal": 0,
    "name": "tc09-user",
    "networkavailable": "20",
    "networklimit": "20",
    "networktotal": 0,
    "objectstorageavailable": "400",
    "objectstoragelimit": "400",
    "objectstoragetotal": 0,
    "primarystorageavailable": "200",
    "primarystoragelimit": "200",
    "primarystoragetotal": 0,
    "projectavailable": "10",
    "projectlimit": "10",
    "projecttotal": 0,
    "roleid": "d6e04f12-4224-11f1-a47c-1e00450001b6",
    "rolename": "User",
    "roletype": "User",
    "secondarystorageavailable": "400.0",
    "secondarystoragelimit": "400",
    "secondarystoragetotal": 0,
    "snapshotavailable": "20",
    "snapshotlimit": "20",
    "snapshottotal": 0,
    "state": "enabled",
    "templateavailable": "20",
    "templatelimit": "20",
    "templatetotal": 0,
    "user": [
      {
        "account": "tc09-user",
        "accountid": "beb17dda-1e38-4617-aa2a-fcae29721f9a",
        "accounttype": 0,
        "created": "2026-04-27T11:05:44+0000",
        "domain": "test-pr13044",
        "domainid": "415b04d7-8251-4ee1-ac43-162d4668be52",
        "email": "tc09@test.local",
        "firstname": "TC09",
        "id": "e5723783-7641-4adb-91ea-8ebd36330c3d",
        "is2faenabled": false,
        "is2famandated": false,
        "iscallerchilddomain": false,
        "isdefault": false,
        "lastname": "User",
        "roleid": "d6e04f12-4224-11f1-a47c-1e00450001b6",
        "rolename": "User",
        "roletype": "User",
        "state": "enabled",
        "username": "tc09-user",
        "usersource": "native"
      }
    ],
    "vmavailable": "20",
    "vmlimit": "20",
    "vmrunning": 0,
    "vmstopped": 0,
    "vmtotal": 0,
    "volumeavailable": "20",
    "volumelimit": "20",
    "volumetotal": 0,
    "vpcavailable": "20",
    "vpclimit": "20",
    "vpctotal": 0
  }
}

Actual result:

  • Domain admin profile authenticates with correctly reduced scope (420 APIs visible).
  • Account tc09-user created successfully (id beb17dda-1e38-4617-aa2a-fcae29721f9a).
  • accounttype=0, rolename=User, roletype=User, state=enabled.
  • First user created and linked.
  • Pre-transaction checkRoleEscalation correctly allowed the Domain Admin to assign the User role.
  • PASS

TC10: Domain Admin attempts to create account assigned Root Admin role - orphan-row + cross-env behavior parity

Description: Verify observable behavior is unchanged when a Domain Admin attempts to assign the Root Admin role to a new account.

Prerequisites:

  • Patched env
  • Un-patched reference env
  • Patched env: domain admin tpr13044-domadmin (account id: c2a8578c-e845-44c8-b21c-dc8625ff8f9f) in test-pr13044 domain (id: 415b04d7-8251-4ee1-ac43-162d4668be52)
  • Un-patched env: domain admin ref-domadmin (account id: 636c59a2-379d-4c26-a73c-10d2bf6f1979) in test-pr13044-unpatched domain (id: 013d643f-cc9e-49c4-8967-44df98363afc)
  • DB access on both mgmt servers

Steps:

  1. On the patched env, as tpr13044-domadmin Domain Admin, attempt to create a User-type account assigned the Root Admin role:
    create account accounttype=0 username=tc13044-escalation-v2 password=Password123 firstname=TC13044 lastname=Escalation email=tc13044-escalation-v2@test.local domainid=415b04d7-8251-4ee1-ac43-162d4668be52 roleid=d6dfd5cf-4224-11f1-a47c-1e00450001b6
  2. Capture API response. Query DB to confirm the account/user state and the persisted role.
  3. Grep management-server log for checkRoleEscalation invocation.
  4. Reproduce identically on un-patched env: as ref-domadmin Domain Admin, attempt to create a User-type account assigned the Root Admin role with the un-patched env's IDs.
  5. Capture and compare DB state and log output.

Expected result:

  • The same observable behavior on both envs (PR #13044 only moves the call site, does not change check semantics).
  • No orphan rows on either env (transaction integrity preserved).
  • checkRoleEscalation is invoked on both envs and arrives at the same verdict.

Test Evidence:

=== Patched env  ===

(tpr13044-domadmin) 🐱 > create account accounttype=0 username=tc13044-escalation-v2 password=Password123 firstname=TC13044 lastname=Escalation email=tc13044-escalation-v2@test.local domainid=415b04d7-8251-4ee1-ac43-162d4668be52 roleid=d6dfd5cf-4224-11f1-a47c-1e00450001b6
{
  "account": {
    "accounttype": 0,
    "apikeyaccess": "INHERIT",
    "backupavailable": "20",
    "backuplimit": "20",
    "backupstorageavailable": "400",
    "backupstoragelimit": "400",
    "backupstoragetotal": 0,
    "backuptotal": 0,
    "bucketavailable": "20",
    "bucketlimit": "20",
    "buckettotal": 0,
    "cpuavailable": "40",
    "cpulimit": "40",
    "cputotal": 0,
    "created": "2026-04-27T12:08:28+0000",
    "domain": "test-pr13044",
    "domainid": "415b04d7-8251-4ee1-ac43-162d4668be52",
    "domainpath": "ROOT/test-pr13044",
    "gpuavailable": "20",
    "gpulimit": "20",
    "gputotal": 0,
    "groups": [],
    "id": "c78a5131-5c4b-4606-a61b-fa5acf2fe963",
    "ipavailable": "18",
    "iplimit": "20",
    "iptotal": 0,
    "isdefault": false,
    "memoryavailable": "40960",
    "memorylimit": "40960",
    "memorytotal": 0,
    "name": "tc13044-escalation-v2",
    "networkavailable": "20",
    "networklimit": "20",
    "networktotal": 0,
    "objectstorageavailable": "400",
    "objectstoragelimit": "400",
    "objectstoragetotal": 0,
    "primarystorageavailable": "200",
    "primarystoragelimit": "200",
    "primarystoragetotal": 0,
    "projectavailable": "10",
    "projectlimit": "10",
    "projecttotal": 0,
    "secondarystorageavailable": "400.0",
    "secondarystoragelimit": "400",
    "secondarystoragetotal": 0,
    "snapshotavailable": "20",
    "snapshotlimit": "20",
    "snapshottotal": 0,
    "state": "enabled",
    "templateavailable": "20",
    "templatelimit": "20",
    "templatetotal": 0,
    "user": [
      {
        "account": "tc13044-escalation-v2",
        "accountid": "c78a5131-5c4b-4606-a61b-fa5acf2fe963",
        "accounttype": 0,
        "created": "2026-04-27T12:08:29+0000",
        "domain": "test-pr13044",
        "domainid": "415b04d7-8251-4ee1-ac43-162d4668be52",
        "email": "tc13044-escalation-v2@test.local",
        "firstname": "TC13044",
        "id": "bfb9b0e8-ba19-47ec-aaf2-71599348d6c0",
        "is2faenabled": false,
        "is2famandated": false,
        "iscallerchilddomain": false,
        "isdefault": false,
        "lastname": "Escalation",
        "state": "enabled",
        "username": "tc13044-escalation-v2",
        "usersource": "native"
      }
    ],
    "vmavailable": "20",
    "vmlimit": "20",
    "vmrunning": 0,
    "vmstopped": 0,
    "vmtotal": 0,
    "volumeavailable": "20",
    "volumelimit": "20",
    "volumetotal": 0,
    "vpcavailable": "20",
    "vpclimit": "20",
    "vpctotal": 0
  }
}

mysql> SELECT a.id, a.account_name, a.role_id, r.uuid AS role_uuid, r.name AS role_name, r.role_type FROM account a LEFT JOIN roles r ON a.role_id = r.id WHERE a.account_name='tc13044-escalation-v2';
+----+-----------------------+---------+--------------------------------------+------------+-----------+
| id | account_name          | role_id | role_uuid                            | role_name  | role_type |
+----+-----------------------+---------+--------------------------------------+------------+-----------+
| 16 | tc13044-escalation-v2 |       1 | d6dfd5cf-4224-11f1-a47c-1e00450001b6 | Root Admin | Admin     |
+----+-----------------------+---------+--------------------------------------+------------+-----------+
1 row in set (0.00 sec)

Patched env management-server log:

2026-04-27 12:08:27,333 DEBUG [c.c.u.AccountManagerImpl] (qtp1438988851-17:[ctx-54d9b52e, ctx-94b04ca5, ctx-90354f6f]) (logid:30831fbd) Checking if user of account tpr13044-domadmin [c2a8578c-e845-44c8-b21c-dc8625ff8f9f] with role-id [3] can create an account of type tc13044-escalation-v2 [c78a5131-5c4b-4606-a61b-fa5acf2fe963] with role-id [1]
2026-04-27 12:08:28,086 DEBUG [c.c.u.AccountManagerImpl] (qtp1438988851-17:[ctx-54d9b52e, ctx-94b04ca5, ctx-90354f6f]) (logid:30831fbd) Verifying whether the caller has the correct privileges based on the user's role type and API permissions: Account [{"accountName":"tc13044-escalation-v2","id":0,"uuid":"c78a5131-5c4b-4606-a61b-fa5acf2fe963"}]
2026-04-27 12:08:28,087 DEBUG [c.c.u.AccountManagerImpl] (qtp1438988851-17:[ctx-54d9b52e, ctx-94b04ca5, ctx-90354f6f]) (logid:30831fbd) Checking calling account [tpr13044-domadmin, c2a8578c-e845-44c8-b21c-dc8625ff8f9f] permission to perform this operation for user account [tc13044-escalation-v2, c78a5131-5c4b-4606-a61b-fa5acf2fe963]
2026-04-27 12:08:28,087 DEBUG [c.c.u.AccountManagerImpl] (qtp1438988851-17:[ctx-54d9b52e, ctx-94b04ca5, ctx-90354f6f]) (logid:30831fbd) Checking if user of account tpr13044-domadmin [c2a8578c-e845-44c8-b21c-dc8625ff8f9f] with role-id [3] can create an account of type tc13044-escalation-v2 [c78a5131-5c4b-4606-a61b-fa5acf2fe963] with role-id [1]
2026-04-27 12:08:28,812 DEBUG [c.c.u.AccountManagerImpl] (qtp1438988851-17:[ctx-54d9b52e, ctx-94b04ca5, ctx-90354f6f]) (logid:30831fbd) Calling account [tpr13044-domadmin, c2a8578c-e845-44c8-b21c-dc8625ff8f9f] is allowed to perform this operation for user account [tc13044-escalation-v2, c78a5131-5c4b-4606-a61b-fa5acf2fe963]
2026-04-27 12:08:28,828 DEBUG [c.c.n.s.SecurityGroupManagerImpl2] (qtp1438988851-17:[ctx-54d9b52e, ctx-94b04ca5, ctx-90354f6f]) (logid:30831fbd) Created security group SecurityGroup {"id":14,"name":"default","uuid":"2c7fc864-f208-4d38-90d0-99aab1b0c9c2"} for account [id: 16, name: tc13044-escalation-v2]
2026-04-27 12:08:28,828 DEBUG [c.c.u.AccountManagerImpl] (qtp1438988851-17:[ctx-54d9b52e, ctx-94b04ca5, ctx-90354f6f]) (logid:30831fbd) Creating user: tc13044-escalation-v2, accountId: 16 timezone:null

=== Un-patched reference env  ===

Same exact escalation attempt with un-patched env's IDs:

(ref-domadmin) 🐱 > create account accounttype=0 username=ref-escalation-v2 password=Password123 firstname=Ref lastname=Escalation email=ref-esc-v2@test.local domainid=013d643f-cc9e-49c4-8967-44df98363afc roleid=63376c5f-420a-11f1-ad94-1e001e000468
{
  "account": {
    "accounttype": 0,
    "apikeyaccess": "INHERIT",
    "backupavailable": "20",
    "backuplimit": "20",
    "backupstorageavailable": "400",
    "backupstoragelimit": "400",
    "backupstoragetotal": 0,
    "backuptotal": 0,
    "bucketavailable": "20",
    "bucketlimit": "20",
    "buckettotal": 0,
    "cpuavailable": "40",
    "cpulimit": "40",
    "cputotal": 0,
    "created": "2026-04-27T12:13:54+0000",
    "domain": "test-pr13044-unpatched",
    "domainid": "013d643f-cc9e-49c4-8967-44df98363afc",
    "domainpath": "ROOT/test-pr13044-unpatched",
    "gpuavailable": "20",
    "gpulimit": "20",
    "gputotal": 0,
    "groups": [],
    "id": "445341bd-9b7a-4fc0-9d60-d8427f311ba1",
    "ipavailable": "17",
    "iplimit": "20",
    "iptotal": 0,
    "isdefault": false,
    "memoryavailable": "40960",
    "memorylimit": "40960",
    "memorytotal": 0,
    "name": "ref-escalation-v2",
    "networkavailable": "20",
    "networklimit": "20",
    "networktotal": 0,
    "objectstorageavailable": "400",
    "objectstoragelimit": "400",
    "objectstoragetotal": 0,
    "primarystorageavailable": "200",
    "primarystoragelimit": "200",
    "primarystoragetotal": 0,
    "projectavailable": "10",
    "projectlimit": "10",
    "projecttotal": 0,
    "secondarystorageavailable": "400.0",
    "secondarystoragelimit": "400",
    "secondarystoragetotal": 0,
    "snapshotavailable": "20",
    "snapshotlimit": "20",
    "snapshottotal": 0,
    "state": "enabled",
    "templateavailable": "20",
    "templatelimit": "20",
    "templatetotal": 0,
    "user": [
      {
        "account": "ref-escalation-v2",
        "accountid": "445341bd-9b7a-4fc0-9d60-d8427f311ba1",
        "accounttype": 0,
        "created": "2026-04-27T12:13:55+0000",
        "domain": "test-pr13044-unpatched",
        "domainid": "013d643f-cc9e-49c4-8967-44df98363afc",
        "email": "ref-esc-v2@test.local",
        "firstname": "Ref",
        "id": "89cc6137-99c0-48fb-8d11-050003976733",
        "is2faenabled": false,
        "is2famandated": false,
        "iscallerchilddomain": false,
        "isdefault": false,
        "lastname": "Escalation",
        "state": "enabled",
        "username": "ref-escalation-v2",
        "usersource": "native"
      }
    ],
    "vmavailable": "20",
    "vmlimit": "20",
    "vmrunning": 0,
    "vmstopped": 0,
    "vmtotal": 0,
    "volumeavailable": "20",
    "volumelimit": "20",
    "volumetotal": 0,
    "vpcavailable": "20",
    "vpclimit": "20",
    "vpctotal": 0
  }
}

mysql> SELECT a.id, a.account_name, a.role_id, r.uuid AS role_uuid, r.name AS role_name, r.role_type FROM account a LEFT JOIN roles r ON a.role_id = r.id WHERE a.account_name='ref-escalation-v2';
+----+-------------------+---------+--------------------------------------+------------+-----------+
| id | account_name      | role_id | role_uuid                            | role_name  | role_type |
+----+-------------------+---------+--------------------------------------+------------+-----------+
|  7 | ref-escalation-v2 |       1 | 63376c5f-420a-11f1-ad94-1e001e000468 | Root Admin | Admin     |
+----+-------------------+---------+--------------------------------------+------------+-----------+
1 row in set (0.00 sec)

Un-patched env management-server log:

2026-04-27 12:13:54,054 DEBUG [c.c.u.AccountManagerImpl] (qtp2038105753-18:[ctx-ee83a20e, ctx-b58e1f18, ctx-0e236b8c]) (logid:03c271cd) Verifying whether the caller has the correct privileges based on the user's role type and API permissions: Account [{"accountName":"ref-escalation-v2","id":0,"uuid":"445341bd-9b7a-4fc0-9d60-d8427f311ba1"}]
2026-04-27 12:13:54,055 DEBUG [c.c.u.AccountManagerImpl] (qtp2038105753-18:[ctx-ee83a20e, ctx-b58e1f18, ctx-0e236b8c]) (logid:03c271cd) Checking calling account [ref-domadmin, 636c59a2-379d-4c26-a73c-10d2bf6f1979] permission to perform this operation for user account [ref-escalation-v2, 445341bd-9b7a-4fc0-9d60-d8427f311ba1]
2026-04-27 12:13:54,055 DEBUG [c.c.u.AccountManagerImpl] (qtp2038105753-18:[ctx-ee83a20e, ctx-b58e1f18, ctx-0e236b8c]) (logid:03c271cd) Checking if user of account ref-domadmin [636c59a2-379d-4c26-a73c-10d2bf6f1979] with role-id [3] can create an account of type ref-escalation-v2 [445341bd-9b7a-4fc0-9d60-d8427f311ba1] with role-id [1]
2026-04-27 12:13:54,462 DEBUG [c.c.u.AccountManagerImpl] (qtp2038105753-18:[ctx-ee83a20e, ctx-b58e1f18, ctx-0e236b8c]) (logid:03c271cd) Calling account [ref-domadmin, 636c59a2-379d-4c26-a73c-10d2bf6f1979] is allowed to perform this operation for user account [ref-escalation-v2, 445341bd-9b7a-4fc0-9d60-d8427f311ba1]
2026-04-27 12:13:54,471 DEBUG [c.c.n.s.SecurityGroupManagerImpl2] (qtp2038105753-18:[ctx-ee83a20e, ctx-b58e1f18, ctx-0e236b8c]) (logid:03c271cd) Created security group SecurityGroup {"id":5,"name":"default","uuid":"0c51d91d-946a-4b53-ae66-663cafd7228e"} for account [id: 7, name: ref-escalation-v2]
2026-04-27 12:13:54,471 DEBUG [c.c.u.AccountManagerImpl] (qtp2038105753-18:[ctx-ee83a20e, ctx-b58e1f18, ctx-0e236b8c]) (logid:03c271cd) Checking if user of account ref-domadmin [636c59a2-379d-4c26-a73c-10d2bf6f1979] with role-id [3] can create an account of type ref-escalation-v2 [445341bd-9b7a-4fc0-9d60-d8427f311ba1] with role-id [1]
2026-04-27 12:13:54,860 DEBUG [c.c.u.AccountManagerImpl] (qtp2038105753-18:[ctx-ee83a20e, ctx-b58e1f18, ctx-0e236b8c]) (logid:03c271cd) Creating user: ref-escalation-v2, accountId: 7 timezone:null

Actual result:

  • Patched env: account tc13044-escalation-v2 created with role_id=1 (Root Admin) persisted in DB.
  • Un-patched env: account ref-escalation-v2 created with role_id=1 (Root Admin) persisted in DB.
  • checkRoleEscalation is invoked on both envs and reaches the same verdict ("is allowed to perform this operation").
  • Behavior is byte-for-byte identical between patched and un-patched builds.
  • PASS - observable behavior is preserved.

Note (out of scope): A pre-existing bug in checkRoleEscalation lets a Domain Admin assign the Root Admin role to a User-type account in their own domain. Reproducible on baseline builds, so not introduced here. Community PR #12973 addresses it.


TC11: User with restricted role attempts to create account - denied at API access layer

Description: Verify that a user with a restricted role (only listVirtualMachines allowed) is blocked from invoking createAccount at the API access layer.

Prerequisites:

  • Test domain test-pr13044 (id: 415b04d7-8251-4ee1-ac43-162d4668be52)
  • Default User role (id: d6e04f12-4224-11f1-a47c-1e00450001b6)
  • Custom restricted role tpr13044-restricted (id: 02401b36-e85d-4981-a433-3a49845df70c, type=User, only listVirtualMachines allowed)
  • Restricted user account tpr13044-restricteduser (account id: 8c0b4fd9-435d-4dd6-978a-71653c6aa625, user id: 76669853-043e-4d90-837a-aed2337a984d) with API keys
  • Caller: tpr13044-restricteduser (custom restricted role)

Steps:

  1. Switch cmk to the tpr13044-restricteduser profile
  2. Verify scope: 127 APIs visible (custom-role permitted set + baseline auto-allowed APIs like login/logout/listApis).
  3. Sanity check: list virtualmachines (the only explicitly allowed business API).
  4. Attempt to create an account with the User role:
    create account accounttype=0 username=tc13044-restricted-create password=Password123 firstname=TC13044 lastname=Restricted email=tc13044-r@test.local domainid=415b04d7-8251-4ee1-ac43-162d4668be52 roleid=d6e04f12-4224-11f1-a47c-1e00450001b6
  5. Verify the call is rejected.
  6. Verify DB: zero rows in account and user tables for the attempted username.

Expected result:

  • API count is reduced to the restricted role's permitted set + baseline (much smaller than full DA or admin scopes).
  • list virtualmachines succeeds.
  • create account is rejected - createAccount is not in the role's allowed APIs.
  • No rows persisted in account or user tables.

Test Evidence:

(tpr13044-restricteduser) 🐱 > sync
Discovered 127 APIs

(tpr13044-restricteduser) 🐱 > list virtualmachines
(tpr13044-restricteduser) 🐱 >

(tpr13044-restricteduser) 🐱 > create account accounttype=0 username=tc13044-restricted-create password=Password123 firstname=TC13044 lastname=Restricted email=tc13044-r@test.local domainid=415b04d7-8251-4ee1-ac43-162d4668be52 roleid=d6e04f12-4224-11f1-a47c-1e00450001b6
🙈 Error: unknown command or API requested

mysql> SELECT id, uuid, account_name FROM account WHERE account_name='tc13044-restricted-create';
Empty set (0.00 sec)

mysql> SELECT id, uuid, username FROM user WHERE username='tc13044-restricted-create';
Empty set (0.00 sec)

Actual result:

  • Restricted role's scope correctly enforced: 127 APIs visible (listVirtualMachines plus baseline APIs like login/logout/listApis).
  • list virtualmachines succeeded (empty result).
  • create account rejected with "unknown command or API requested" — denied at the API access layer before reaching account-creation logic.
  • DB confirms no rows persisted in account or user.
  • PASS

TC12: Exception type and message unchanged from baseline

Description: Verify exception type, error text, and HTTP status returned by checkRoleEscalation are unchanged.

Prerequisites:

  • Patched env and un-patched env, same KVM/4.22.1.0 base
  • Same scenario as TC10 (Domain Admin assigning Root Admin role to a User account)

Steps:

  1. Compare the API response and log line from TC10 on both envs.

Expected result:

  • Same HTTP status, same log message format, same fields.

Test Evidence:

Patched env (PR 13044):

2026-04-27 12:08:28,812 DEBUG [c.c.u.AccountManagerImpl] Calling account [tpr13044-domadmin, c2a8578c-e845-44c8-b21c-dc8625ff8f9f] is allowed to perform this operation for user account [tc13044-escalation-v2, c78a5131-5c4b-4606-a61b-fa5acf2fe963]
HTTP 200, account row created.

Un-patched env:

2026-04-27 12:13:54,462 DEBUG [c.c.u.AccountManagerImpl] Calling account [ref-domadmin, 636c59a2-379d-4c26-a73c-10d2bf6f1979] is allowed to perform this operation for user account [ref-escalation-v2, 445341bd-9b7a-4fc0-9d60-d8427f311ba1]
HTTP 200, account row created.

Actual result:

  • Both envs return HTTP 200 with identical log message format. No regression in error reporting introduced by PR 13044.
  • PASS

TC13: Concurrent valid + invalid creates - no deadlocks, no orphan rows

Description: Stress the createAccount path with 20 parallel API calls (10 valid, 10 escalation attempts) to confirm no deadlocks or orphan rows under concurrent load.

Prerequisites:

  • Test domain test-pr13044 (id: 415b04d7-8251-4ee1-ac43-162d4668be52)
  • Default User role (id: d6e04f12-4224-11f1-a47c-1e00450001b6, internal id 4)
  • Default Root Admin role (id: d6dfd5cf-4224-11f1-a47c-1e00450001b6, internal id 1)
  • Admin API keys (re-registered fresh for this test)
  • Python script using HMAC-signed direct API calls with ThreadPoolExecutor (max_workers=20) tc13_parallel.py

Steps:

  1. Fire 20 concurrent createAccount calls: 10 with username tc13044-par-valid-NN and roleid=User; 10 with username tc13044-par-escal-NN and roleid=Root Admin (in the non-ROOT test domain to trigger domain-validation rejection).
  2. Verify status of each call (success/failure with timing).
  3. Query DB for created accounts and confirm role_id values.
  4. Grep management-server log for deadlock or exception entries related to the test usernames.

Expected result:

  • All 10 valid creates succeed (HTTP 200) with User role assigned.
  • All 10 escalation attempts are denied with no DB rows persisted.
  • No deadlocks, no NPEs, no rolled-back partial writes in the log.

Test Evidence:

Firing 20 concurrent createAccount calls...
Total wall time: 32.30s
Username                         Kind   Status       Time
------------------------------------------------------------
tc13044-par-valid-00             valid  OK         32.02s
tc13044-par-valid-01             valid  OK         31.76s
tc13044-par-valid-02             valid  OK         31.98s
tc13044-par-valid-03             valid  OK         32.29s
tc13044-par-valid-04             valid  OK         32.26s
tc13044-par-valid-05             valid  OK         31.48s
tc13044-par-valid-06             valid  OK         32.04s
tc13044-par-valid-07             valid  OK         32.18s
tc13044-par-valid-08             valid  OK         32.11s
tc13044-par-valid-09             valid  OK         32.01s
tc13044-par-escal-00             escal  HTTP 431   13.57s
tc13044-par-escal-01             escal  HTTP 431   14.67s
tc13044-par-escal-02             escal  HTTP 431   14.44s
tc13044-par-escal-03             escal  HTTP 431   15.35s
tc13044-par-escal-04             escal  HTTP 431   14.90s
tc13044-par-escal-05             escal  HTTP 431   15.41s
tc13044-par-escal-06             escal  HTTP 431   14.06s
tc13044-par-escal-07             escal  HTTP 431   14.82s
tc13044-par-escal-08             escal  HTTP 431   14.29s
tc13044-par-escal-09             escal  HTTP 431   14.46s

mysql> SELECT COUNT(*) AS valid_count FROM account WHERE account_name LIKE 'tc13044-par-valid-%';
+-------------+
| valid_count |
+-------------+
|          10 |
+-------------+

mysql> SELECT COUNT(*) AS escal_count FROM account WHERE account_name LIKE 'tc13044-par-escal-%';
+-------------+
| escal_count |
+-------------+
|           0 |
+-------------+

mysql> SELECT account_name, role_id FROM account WHERE account_name LIKE 'tc13044-par-%' ORDER BY account_name LIMIT 25;
+----------------------+---------+
| account_name         | role_id |
+----------------------+---------+
| tc13044-par-valid-00 |       4 |
| tc13044-par-valid-01 |       4 |
| tc13044-par-valid-02 |       4 |
| tc13044-par-valid-03 |       4 |
| tc13044-par-valid-04 |       4 |
| tc13044-par-valid-05 |       4 |
| tc13044-par-valid-06 |       4 |
| tc13044-par-valid-07 |       4 |
| tc13044-par-valid-08 |       4 |
| tc13044-par-valid-09 |       4 |
+----------------------+---------+

**Logs check:**

# grep -iE "deadlock|exception" /var/log/cloudstack/management/management-server.log | grep -i "tc13044-par" | tail -30
(no output)

Actual result:

  • All 10 valid creates succeeded with role_id=4 (User role) persisted.
  • All 10 escalation attempts denied with HTTP 431 and zero orphan rows.
  • No deadlock or exception log entries.
  • Escalation rejection path here is the early domain-validation check (rejecting Admin role in non-ROOT domain), a different code path than TC10's checkRoleEscalation. Confirms early validation also remains correct under concurrent load.
  • PASS — no deadlocks, no orphan rows, mgmt server remained responsive.

TC14: Two rapid sequential creates without UUID - no collision

Description: Confirm that PR 13044's pre-transaction UUID.randomUUID() resolution does not produce duplicate UUIDs when called in rapid succession.

Prerequisites:

  • Test domain test-pr13044, default User role
  • Caller: root admin

Steps:

  1. Run two createAccount calls back-to-back without supplying accountid:
    create account accounttype=0 username=tc14-rapid-01 ... domainid=415b04d7-... roleid=d6e04f12-...
    create account accounttype=0 username=tc14-rapid-02 ... domainid=415b04d7-... roleid=d6e04f12-...
  2. Compare the auto-generated UUIDs.

Expected result:

  • Both creates succeed.
  • The two account.id UUIDs are distinct and valid v4 format.

Test Evidence:

(localcloud) 🐱 > create account accounttype=0 username=tc14-rapid-01 password=Password123 firstname=TC14 lastname=Rapid1 email=tc14-rapid-01@test.local domainid=415b04d7-8251-4ee1-ac43-162d4668be52 roleid=d6e04f12-4224-11f1-a47c-1e00450001b6
{
  "account": {
    "id": "9f4c731b-8b2b-469a-9871-e5fb038ff251",
    "name": "tc14-rapid-01",
    "state": "enabled",
    ...
  }
}

(localcloud) 🐱 > create account accounttype=0 username=tc14-rapid-02 password=Password123 firstname=TC14 lastname=Rapid2 email=tc14-rapid-02@test.local domainid=415b04d7-8251-4ee1-ac43-162d4668be52 roleid=d6e04f12-4224-11f1-a47c-1e00450001b6
{
  "account": {
    "id": "70a0f1c2-da73-45bf-8dda-057ad55e174d",
    "name": "tc14-rapid-02",
    "state": "enabled",
    ...
  }
}

Actual result:

  • Both creates succeeded.
  • Distinct UUIDs assigned: 9f4c731b-8b2b-469a-9871-e5fb038ff251 and 70a0f1c2-da73-45bf-8dda-057ad55e174d.
  • PR 13044's pre-transaction UUID resolution does not produce collisions on rapid sequential calls.
  • PASS

TC15: Full account lifecycle (create, update, disable, delete)

Description: Verify the standard account lifecycle (create → update → disable → delete) still works end to end.

Prerequisites:

  • CloudStack 4.22.1.0-shapeblue18497 with PR 13044 applied
  • Test domain test-pr13044, default User role
  • Caller: root admin
  • DB access on management server

Steps:

  1. Create a User account tc15-lifecycle.
  2. Update the account, changing networkdomain to tc15-updated.test.local.
  3. Disable the account.
  4. Delete the account.
  5. Verify in DB that the account row has a non-NULL removed timestamp (soft delete).

Expected result:

  • Each step succeeds.
  • After delete, the account row exists in DB but has a removed timestamp set.

Test Evidence:

(localcloud) 🐱 > create account accounttype=0 username=tc15-lifecycle password=Password123 firstname=TC15 lastname=Lifecycle email=tc15@test.local domainid=415b04d7-8251-4ee1-ac43-162d4668be52 roleid=d6e04f12-4224-11f1-a47c-1e00450001b6
{
  "account": {
    "id": "07e0b7fe-3aac-495b-bba0-d2bca19b33c2",
    "name": "tc15-lifecycle",
    "state": "enabled",
    "rolename": "User",
    "roletype": "User",
    ...
  }
}

(localcloud) 🐱 > update account id=07e0b7fe-3aac-495b-bba0-d2bca19b33c2 networkdomain=tc15-updated.test.local
{
  "account": {
    "id": "07e0b7fe-3aac-495b-bba0-d2bca19b33c2",
    "name": "tc15-lifecycle",
    "networkdomain": "tc15-updated.test.local",
    ...
  }
}

(localcloud) 🐱 > disable account id=07e0b7fe-3aac-495b-bba0-d2bca19b33c2 lock=false
{
  "account": {
    "id": "07e0b7fe-3aac-495b-bba0-d2bca19b33c2",
    "state": "disabled",
    ...
  }
}

(localcloud) 🐱 > delete account id=07e0b7fe-3aac-495b-bba0-d2bca19b33c2
{
  "success": true
}

mysql> SELECT id, account_name, removed FROM account WHERE account_name='tc15-lifecycle';
+----+----------------+---------------------+
| id | account_name   | removed             |
+----+----------------+---------------------+
| 29 | tc15-lifecycle | 2026-04-27 21:25:14 |
+----+----------------+---------------------+
1 row in set (0.00 sec)

Actual result:

  • Create succeeded, account id 07e0b7fe-3aac-495b-bba0-d2bca19b33c2.
  • Update succeeded, networkdomain set to tc15-updated.test.local.
  • Disable succeeded, state changed to disabled.
  • Delete returned success: true.
  • DB confirms soft delete: removed=2026-04-27 21:25:14.
  • PASS

TC16: Create account with invalid roleId

Description: Verify that an invalid roleId is rejected with no orphan rows. Confirms that PR 13044's pre-transaction setup does not bypass parameter validation.

Prerequisites:

  • Test domain test-pr13044
  • Caller: root admin
  • DB access on management server

Steps:

  1. Run createAccount with a non-existent UUID for roleid:
    create account accounttype=0 username=tc16-invalid-role ... roleid=00000000-0000-0000-0000-000000000000
  2. Verify the call is rejected.
  3. Verify no rows in account and user tables for this username.

Expected result:

  • Error returned, mentioning the invalid roleid.
  • Zero rows in account and user tables.

Test Evidence:

(localcloud) 🐱 > create account accounttype=0 username=tc16-invalid-role password=Password123 firstname=TC16 lastname=InvalidRole email=tc16@test.local domainid=415b04d7-8251-4ee1-ac43-162d4668be52 roleid=00000000-0000-0000-0000-000000000000
🙈 Error: (HTTP 431, error code 9999) Unable to execute API command createaccount due to invalid value. Invalid parameter roleid value=00000000-0000-0000-0000-000000000000 due to incorrect long value format, or entity does not exist or due to incorrect parameter annotation for the field in api cmd class.

mysql> SELECT id, account_name FROM account WHERE account_name='tc16-invalid-role';
Empty set (0.00 sec)

mysql> SELECT id, username FROM user WHERE username='tc16-invalid-role';
Empty set (0.00 sec)

Actual result:

  • Call rejected with HTTP 431 and a clear error message: "Invalid parameter roleid value=00000000-0000-0000-0000-000000000000 due to incorrect long value format, or entity does not exist".
  • Rejection happens at the parameter validation layer, before the createAccount logic and before any DB write.
  • Zero orphan rows in account and user tables.
  • PASS

TC17: Create account with invalid domainId

Description: Verify that an invalid domainId is rejected with no orphan rows.

Prerequisites:

  • CloudStack 4.22.1.0-shapeblue18497 with PR 13044 applied
  • Default User role
  • Caller: root admin
  • DB access on management server

Steps:

  1. Run createAccount with a non-existent UUID for domainid:
    create account accounttype=0 username=tc17-invalid-domain ... domainid=00000000-0000-0000-0000-000000000000 roleid=d6e04f12-...
  2. Verify the call is rejected.
  3. Verify no rows in account and user tables for this username.

Expected result:

  • Error returned, mentioning the invalid domainid.
  • Zero rows in account and user tables.

Test Evidence:

(localcloud) 🐱 > create account accounttype=0 username=tc17-invalid-domain password=Password123 firstname=TC17 lastname=InvalidDomain email=tc17@test.local domainid=00000000-0000-0000-0000-000000000000 roleid=d6e04f12-4224-11f1-a47c-1e00450001b6
🙈 Error: (HTTP 431, error code 9999) Unable to execute API command createaccount due to invalid value. Invalid parameter domainid value=00000000-0000-0000-0000-000000000000 due to incorrect long value format, or entity does not exist or due to incorrect parameter annotation for the field in api cmd class.

mysql> SELECT id, account_name FROM account WHERE account_name='tc17-invalid-domain';
Empty set (0.00 sec)

mysql> SELECT id, username FROM user WHERE username='tc17-invalid-domain';
Empty set (0.00 sec)

Actual result:

  • Call rejected with HTTP 431 and a clear error message about the invalid domainid.
  • Rejection happens at the parameter validation layer, before any DB write.
  • Zero orphan rows.
  • PASS

@RosiKyu
Copy link
Copy Markdown
Collaborator

RosiKyu commented Apr 27, 2026

@blueorangutan test

@blueorangutan
Copy link
Copy Markdown

@RosiKyu a [SL] Trillian-Jenkins test job (ol8 mgmt + kvm-ol8) has been kicked to run smoke tests

@blueorangutan
Copy link
Copy Markdown

[SF] Trillian test result (tid-15952)
Environment: kvm-ol8 (x2), zone: Advanced Networking with Mgmt server ol8
Total time taken: 50173 seconds
Marvin logs: https://github.com/blueorangutan/acs-prs/releases/download/trillian/pr13044-t15952-kvm-ol8.zip
Smoke tests completed. 149 look OK, 0 have errors, 0 did not run
Only failed and skipped tests results shown below:

Test Result Time (s) Test File

@apache apache deleted a comment from blueorangutan Apr 28, 2026
@blueorangutan
Copy link
Copy Markdown

[SF] Trillian test result (tid-15958)
Environment: kvm-ol8 (x2), zone: Advanced Networking with Mgmt server ol8
Total time taken: 50940 seconds
Marvin logs: https://github.com/blueorangutan/acs-prs/releases/download/trillian/pr13044-t15958-kvm-ol8.zip
Smoke tests completed. 149 look OK, 0 have errors, 0 did not run
Only failed and skipped tests results shown below:

Test Result Time (s) Test File

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants