Skip to content

Refactor Quota balance#12961

Open
winterhazel wants to merge 1 commit intoapache:mainfrom
scclouds:quota-balance-improvement
Open

Refactor Quota balance#12961
winterhazel wants to merge 1 commit intoapache:mainfrom
scclouds:quota-balance-improvement

Conversation

@winterhazel
Copy link
Copy Markdown
Member

Description

Currently, the quotaBalance API receives the startdate and enddate parameters, and returns what an account's balance was only at the starting and end dates, alongside with some information about the credits added during that period. It is not possible to view how an account's balance changed through multiple days with a single API call, even though balance entries exist in the database containing this information. Also, returning the credits addition history is redundant, as quotaCreditsList already exists and returns a more complete response, and inconsistent with the API's purpose (simply viewing an account's balance).

Hence, this PR proposes changing the behavior of quotaBalance to allow seeing the complete history of an account/project's balance for the specified period, making it easier to address the 2nd suggestion of #11805. In addition, the credits addition history was removed from the API's response. UI changes will be done in a separate PR.

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)

Feature/Enhancement Scale or Bug Severity

Feature/Enhancement Scale

  • Major
  • Minor

How Has This Been Tested?

I tested the API by fuzzing its parameters (account, accountid, domainid, enddate, projectid, startdate) for root admin, domain admin, and user accounts. In the tests, I validated that the API returned the expected response while performing correct permission checking.


Environment configuration

My environment had two domains: ROOT and d1.

MariaDB [cloud]> select uuid,name,path,parent from domain;
+--------------------------------------+------+------+--------+
| uuid                                 | name | path | parent |
+--------------------------------------+------+------+--------+
| de3f3dfb-f84b-11f0-8ace-32e0826870ba | ROOT | /    |   NULL |
| dc5064ad-d4a8-4bb3-ae18-a52e4810ac3d | d1   | /d1/ |      1 |
+--------------------------------------+------+------+--------+

Each domain had a domain admin (dr for ROOT and d1 for d1) and a user account (ur for ROOT and u1 for d1). ROOT also had a project and the root admin.

MariaDB [cloud]> select uuid,account_name,domain_id,role_id from account;
+--------------------------------------+--------------------------+-----------+---------+
| uuid                                 | account_name             | domain_id | role_id |
+--------------------------------------+--------------------------+-----------+---------+
| f684aae8-f84b-11f0-8ace-32e0826870ba | system                   |         1 |       1 |
| f684da1a-f84b-11f0-8ace-32e0826870ba | admin                    |         1 |       1 |
| 17dfdcce-fd02-46d0-85ae-7de91fda634c | baremetal-system-account |         1 |       4 |
| aaba75a0-a575-41be-80e1-dbea14c10e51 | dr                       |         1 |       3 |
| feaffae2-7ba4-45dc-b46f-20ae86192685 | d1                       |         2 |       3 |
| 2f49212d-d6d5-437b-800d-a7bb4a8def4b | u1                       |         2 |       4 |
| b029e8b8-e539-4d1a-906d-3d4f2fb36b82 | ur                       |         1 |       4 |
| b14100c6-d9f6-450e-87a2-0833bb45e5f8 | PrjAcct-aa-1             |         1 |    NULL |
+--------------------------------------+--------------------------+-----------+---------+

Test 1 (user accounts)

  • No parameters: returns only the user account's last balance statement.

     (u1) 🐱 > quota balance
     {
       "balance": {
         "balances": [
           {
     	"balance": 26.74781192,
     	"date": "2026-04-09T20:59:59-0300"
           }
         ],
         "currency": "$"
       }
     }
  • account: allows listing their own balance statements, but not the balance statements of other accounts.

     (u1) 🐱 > quota balance account=u1 domainid=dc5064ad-d4a8-4bb3-ae18-a52e4810ac3d
     {
       "balance": {
         "balances": [
           {
     	"balance": 26.74781192,
     	"date": "2026-04-09T20:59:59-0300"
           }
         ],
         "currency": "$"
       }
     }
    
     (u1) 🐱 > quota balance account=d1 domainid=dc5064ad-d4a8-4bb3-ae18-a52e4810ac3d
     🙈 Error: (HTTP 531, error code 4365) Caller does not have permission to operate with provided resource.
  • accountid: allows listing their own balance statements, but not the balance statements of other accounts.

     (u1) 🐱 > quota balance accountid=2f49212d-d6d5-437b-800d-a7bb4a8def4b
     {
       "balance": {
         "balances": [
           {
     	"balance": 26.74781192,
     	"date": "2026-04-09T20:59:59-0300"
           }
         ],
         "currency": "$"
       }
     }
    
     (u1) 🐱 > quota balance accountid=aaba75a0-a575-41be-80e1-dbea14c10e51
     🙈 Error: (HTTP 531, error code 4365) Caller does not have permission to operate with provided resource.
  • domainid: only lists their own balance statements. Returns an error when attempting to obtain the balance of another account. Unable to be used alone.

     (u1) 🐱 > quota balance domainid=dc5064ad-d4a8-4bb3-ae18-a52e4810ac3d account=u1
     {
       "balance": {
         "balances": [
           {
     	"balance": 29.13815933,
     	"date": "2026-04-11T20:59:59-0300"
           }
         ],
         "currency": "$"
       }
     }
    
     (u1) 🐱 > quota balance domainid=de3f3dfb-f84b-11f0-8ace-32e0826870ba account=admin
     🙈 Error: (HTTP 531, error code 4365) Caller does not have permission to operate with provided resource.
    
     (u1) 🐱 > quota balance domainid=dc5064ad-d4a8-4bb3-ae18-a52e4810ac3d
     🙈 Error: (HTTP 431, error code 9999) Both account and domainid are needed if using either. Consider using accountid instead.
  • startdate and enddate: filters balance statements correctly, only returning the calling account's own statements.

     (u1) 🐱 > quota balance startdate=2026-03-01 enddate=2026-04-10
     {
       "balance": {
         "balances": [
           {
     	"balance": 24,
     	"date": "2026-04-03T21:00:00-0300"
           },
           {
     	"balance": 33.41447912,
     	"date": "2026-04-04T20:59:59-0300"
           },
           {
     	"balance": 31.41447896,
     	"date": "2026-04-05T20:59:59-0300"
           },
           {
     	"balance": 29.4144788,
     	"date": "2026-04-06T20:59:59-0300"
           },
           {
     	"balance": 28.08114536,
     	"date": "2026-04-07T20:59:59-0300"
           },
           {
     	"balance": 27.41447864,
     	"date": "2026-04-08T20:59:59-0300"
           },
           {
     	"balance": 26.74781192,
     	"date": "2026-04-09T20:59:59-0300"
           }
         ],
         "currency": "$"
       }
     }
    
     (u1) 🐱 > quota balance startdate="2026-03-01T00:00:00-0300" enddate="2026-04-08T12:00:00-0300"
     {
       "balance": {
         "balances": [
           {
     	"balance": 24,
     	"date": "2026-04-03T21:00:00-0300"
           },
           {
     	"balance": 33.41447912,
     	"date": "2026-04-04T20:59:59-0300"
           },
           {
     	"balance": 31.41447896,
     	"date": "2026-04-05T20:59:59-0300"
           },
           {
     	"balance": 29.4144788,
     	"date": "2026-04-06T20:59:59-0300"
           },
           {
     	"balance": 28.08114536,
     	"date": "2026-04-07T20:59:59-0300"
           }
         ],
         "currency": "$"
       }
     }
    
     (u1) 🐱 > quota balance startdate="2026-04-04T00:00:00-0300" enddate="2026-04-08T12:00:00-0300"
     {
       "balance": {
         "balances": [
           {
     	"balance": 33.41447912,
     	"date": "2026-04-04T20:59:59-0300"
           },
           {
     	"balance": 31.41447896,
     	"date": "2026-04-05T20:59:59-0300"
           },
           {
     	"balance": 29.4144788,
     	"date": "2026-04-06T20:59:59-0300"
           },
           {
     	"balance": 28.08114536,
     	"date": "2026-04-07T20:59:59-0300"
           }
         ],
         "currency": "$"
       }
     }
  • enddate before startdate: returns an error.

     (u1) 🐱 > quota balance startdate=2026-03-02 enddate=2026-03-01
     🙈 Error: (HTTP 431, error code 4350) The start date cannot be after the end date.
  • projectid: user accounts can only obtain balance statements for projects they belong to.

     (u1) 🐱 > quota balance projectid=c1ce41c4-7b01-4386-8264-479db5ace0bc
     🙈 Error: (HTTP 432, error code 9999) The API [quotaBalance] does not exist or is not available for this account/user in project [c1ce41c4-7b01-4386-8264-479db5ace0bc].
    
     (ur) 🐱 > quota balance projectid=c1ce41c4-7b01-4386-8264-479db5ace0bc
     {
       "balance": {
         "balances": [
           {
     	"balance": 8.02719503,
     	"date": "2026-04-11T20:59:59-0300"
           }
         ],
         "currency": "$"
       }
     }
  • projectid combined with accountid: returns an error since these parameters cannot be specified together.

     (ur) 🐱 > quota balance projectid=c1ce41c4-7b01-4386-8264-479db5ace0bc accountid=b029e8b8-e539-4d1a-906d-3d4f2fb36b82
     🙈 Error: (HTTP 431, error code 9999) Project and account can not be specified together.

Test 2 (domain admins)

  • No parameters: returns their last balance statement.

     (d1) 🐱 > quota balance
     {
       "balance": {
         "balances": [
           {
     	"balance": 16.13743789,
     	"date": "2026-04-11T20:59:59-0300"
           }
         ],
         "currency": "$"
       }
     }
  • account: allows listing balance statements of accounts belonging to their domain, but not accounts from other domains.

     (d1) 🐱 > quota balance account=u1 domainid=dc5064ad-d4a8-4bb3-ae18-a52e4810ac3d
     {
       "balance": {
         "balances": [
           {
     	"balance": 29.13815933,
     	"date": "2026-04-11T20:59:59-0300"
           }
         ],
         "currency": "$"
       }
     }
     
     (d1) 🐱 > quota balance account=d1 domainid=dc5064ad-d4a8-4bb3-ae18-a52e4810ac3d
     {
       "balance": {
         "balances": [
           {
     	"balance": 16.13743789,
     	"date": "2026-04-11T20:59:59-0300"
           }
         ],
         "currency": "$"
       }
     }
     
     (d1) 🐱 > quota balance account=admin domainid=de3f3dfb-f84b-11f0-8ace-32e0826870ba
     🙈 Error: (HTTP 531, error code 4365) Caller does not have permission to operate with provided resource.
  • accountid: allows listing balance statements of accounts belonging to their domain, but not accounts from other domains.

     (d1) 🐱 > quota balance accountid=2f49212d-d6d5-437b-800d-a7bb4a8def4b
     {
       "balance": {
         "balances": [
           {
     	"balance": 29.13815933,
     	"date": "2026-04-11T20:59:59-0300"
           }
         ],
         "currency": "$"
       }
     }
     
     (d1) 🐱 > quota balance accountid=feaffae2-7ba4-45dc-b46f-20ae86192685
     {
       "balance": {
         "balances": [
           {
     	"balance": 16.13743789,
     	"date": "2026-04-11T20:59:59-0300"
           }
         ],
         "currency": "$"
       }
     }
     
     (d1) 🐱 > quota balance accountid=f684da1a-f84b-11f0-8ace-32e0826870ba
     🙈 Error: (HTTP 531, error code 4365) Caller does not have permission to operate with provided resource.
  • domainid: allows listing balance statements of accounts in their domain, but returns an error for accounts outside it.

     (d1) 🐱 > quota balance account=u1 domainid=dc5064ad-d4a8-4bb3-ae18-a52e4810ac3d
     {
       "balance": {
         "balances": [
           {
     	"balance": 29.13815933,
     	"date": "2026-04-11T20:59:59-0300"
           }
         ],
         "currency": "$"
       }
     }
    
     (d1) 🐱 > quota balance domainid=de3f3dfb-f84b-11f0-8ace-32e0826870ba account=admin
     🙈 Error: (HTTP 531, error code 4365) Caller does not have permission to operate with provided resource.
  • startdate and enddate: filters balance statements correctly.

     (d1) 🐱 > quota balance startdate=2026-04-10 enddate=2026-04-11
     {
       "balance": {
         "balances": [
           {
     	"balance": 16.80410461,
     	"date": "2026-04-10T20:59:59-0300"
           },
           {
     	"balance": 16.13743789,
     	"date": "2026-04-11T20:59:59-0300"
           }
         ],
         "currency": "$"
       }
     }
  • projectid: allows listing balance statements for projects belonging to their domain, but returns an error for projects outside it.

     (d1) 🐱 > quota balance projectid=c1ce41c4-7b01-4386-8264-479db5ace0bc
     🙈 Error: (HTTP 531, error code 4365) Account Account [{"accountName":"d1","id":5,"uuid":"feaffae2-7ba4-45dc-b46f-20ae86192685"}] does not have permission to operate within domain id=de3f3dfb-f84b-11f0-8ace-32e0826870ba
    
     (dr) 🐱 > quota balance projectid=c1ce41c4-7b01-4386-8264-479db5ace0bc
     {
       "balance": {
         "balances": [
           {
     	"balance": 8.02719503,
     	"date": "2026-04-11T20:59:59-0300"
           }
         ],
         "currency": "$"
       }
     }
  • projectid combined with accountid: returns an error.

     (dr) 🐱 >  quota balance projectid=c1ce41c4-7b01-4386-8264-479db5ace0bc accountid=aaba75a0-a575-41be-80e1-dbea14c10e51
     🙈 Error: (HTTP 431, error code 9999) Project and account can not be specified together.

Test 3 (root admins)

  • No parameters: returns their last balance statement.

     (admin) 🐱 > quota balance
     {
       "balance": {
         "balances": [
           {
     	"balance": 77.33786514,
     	"date": "2026-04-11T20:59:59-0300"
           }
         ],
         "currency": "$"
       }
     }
  • account: allows listing balance statements of all accounts.

     (admin) 🐱 > quota balance account=dr domainid=de3f3dfb-f84b-11f0-8ace-32e0826870ba
     {
       "balance": {
         "balances": [],
         "currency": "$"
       }
     }
     
     (admin) 🐱 > quota balance account=u1 domainid=dc5064ad-d4a8-4bb3-ae18-a52e4810ac3d
     {
       "balance": {
         "balances": [
           {
     	"balance": 29.13815933,
     	"date": "2026-04-11T20:59:59-0300"
           }
         ],
         "currency": "$"
       }
     }
     
     (admin) 🐱 > quota balance account=ur domainid=dc5064ad-d4a8-4bb3-ae18-a52e4810ac3d
     🙈 Error: (HTTP 431, error code 4350) Unable to find account by name [ur] on domain [2].
  • accountid: allows listing balance statements of all accounts.

     (admin) 🐱 > quota balance accountid=f684da1a-f84b-11f0-8ace-32e0826870ba
     {
       "balance": {
         "balances": [
           {
     	"balance": 77.33786514,
     	"date": "2026-04-11T20:59:59-0300"
           }
         ],
         "currency": "$"
       }
     }
     
     (admin) 🐱 > quota balance accountid=b029e8b8-e539-4d1a-906d-3d4f2fb36b82
     {
       "balance": {
         "balances": [],
         "currency": "$"
       }
     }
     
     (admin) 🐱 > quota balance accountid=feaffae2-7ba4-45dc-b46f-20ae86192685
     {
       "balance": {
         "balances": [
           {
     	"balance": 16.13743789,
     	"date": "2026-04-11T20:59:59-0300"
           }
         ],
         "currency": "$"
       }
     }
     
     (admin) 🐱 > quota balance accountid=2f49212d-d6d5-437b-800d-a7bb4a8def4b
     {
       "balance": {
         "balances": [
           {
     	"balance": 29.13815933,
     	"date": "2026-04-11T20:59:59-0300"
           }
         ],
         "currency": "$"
       }
     }
  • domainid: allows listing balance statements for accounts in all domains.

     (admin) 🐱 > quota balance domainid=de3f3dfb-f84b-11f0-8ace-32e0826870ba account=admin
     {
       "balance": {
         "balances": [
           {
     	"balance": 77.33786514,
     	"date": "2026-04-11T20:59:59-0300"
           }
         ],
         "currency": "$"
       }
     }
    
     (admin) 🐱 > quota balance domainid=dc5064ad-d4a8-4bb3-ae18-a52e4810ac3d account=d1
     {
       "balance": {
         "balances": [
           {
     	"balance": 16.13743789,
     	"date": "2026-04-11T20:59:59-0300"
           }
         ],
         "currency": "$"
       }
     }
    
     (admin) 🐱 > quota balance domainid=dc5064ad-d4a8-4bb3-ae18-a52e4810ac3d account=admin
     🙈 Error: (HTTP 431, error code 4350) Unable to find account by name [admin] on domain [2].
  • startdate and enddate: filters balance statements correctly.

     (admin) 🐱 > quota balance accountid=f684da1a-f84b-11f0-8ace-32e0826870ba startdate=2026-04-10 enddate=2026-04-11
     {
       "balance": {
         "balances": [
           {
     	"balance": 77.6711985,
     	"date": "2026-04-10T20:59:59-0300"
           },
           {
     	"balance": 77.33786514,
     	"date": "2026-04-11T20:59:59-0300"
           }
         ],
         "currency": "$"
       }
     }
  • projectid: allows listing balance statements for all projects in the environment.

     (admin) 🐱 > quota balance projectid=c1ce41c4-7b01-4386-8264-479db5ace0bc
     {
       "balance": {
         "balances": [
           {
     	"balance": 8.02719503,
     	"date": "2026-04-11T20:59:59-0300"
           }
         ],
         "currency": "$"
       }
     }
    
     (admin) 🐱 > quota balance projectid=082b7869-dd29-46a5-9350-6edc7d94f825
     {
       "balance": {
         "balances": [],
         "currency": "$"
       }
     }

@winterhazel
Copy link
Copy Markdown
Member Author

@blueorangutan package

@blueorangutan
Copy link
Copy Markdown

@winterhazel a [SL] Jenkins job has been kicked to build packages. It will be bundled with no SystemVM templates. I'll keep you posted as I make progress.

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 4, 2026

Codecov Report

❌ Patch coverage is 50.40000% with 62 lines in your changes missing coverage. Please review.
✅ Project coverage is 18.01%. Comparing base (4f93ba8) to head (0f94fab).
⚠️ Report is 10 commits behind head on main.

Files with missing lines Patch % Lines
...ache/cloudstack/quota/dao/QuotaBalanceDaoImpl.java 22.64% 41 Missing ⚠️
.../org/apache/cloudstack/quota/QuotaServiceImpl.java 72.41% 1 Missing and 7 partials ⚠️
.../org/apache/cloudstack/quota/QuotaManagerImpl.java 0.00% 5 Missing ⚠️
...apache/cloudstack/api/command/QuotaBalanceCmd.java 50.00% 3 Missing and 1 partial ⚠️
...c/main/java/com/cloud/user/AccountManagerImpl.java 0.00% 4 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main   #12961      +/-   ##
============================================
+ Coverage     18.00%   18.01%   +0.01%     
- Complexity    16455    16469      +14     
============================================
  Files          5976     5977       +1     
  Lines        537582   537552      -30     
  Branches      66006    65993      -13     
============================================
+ Hits          96784    96864      +80     
+ Misses       429885   429769     -116     
- Partials      10913    10919       +6     
Flag Coverage Δ
uitests 3.52% <ø> (ø)
unittests 19.18% <50.40%> (+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 17356

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.

2 participants