Skip to content

Quantum: Add 'az quantum suite-offer quotas' command - #10285

Draft
v-elegacheva wants to merge 14 commits into
Azure:mainfrom
v-elegacheva:ekat/quantum-suite-offer-quotas
Draft

Quantum: Add 'az quantum suite-offer quotas' command#10285
v-elegacheva wants to merge 14 commits into
Azure:mainfrom
v-elegacheva:ekat/quantum-suite-offer-quotas

Conversation

@v-elegacheva

@v-elegacheva v-elegacheva commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

🤖 PR Validation — ⚠️ Review suggested

Breaking Changes
⚠️ None
⚠️Azure CLI Extensions Breaking Change Test
⚠️quantum
rule cmd_name rule_message suggest_message
⚠️ 1011 - SubgroupAdd quantum suite-offer sub group quantum suite-offer added

Summary

Adds two read-only data-plane (v2) commands to the quantum extension for suite offer
provider accounts, neither of which requires an Azure Quantum workspace:

  • az quantum suite-offer quotas --provider-id <id> — lists the v2 quota allocations
    (limits) for each target of a provider account, merged with their consumed usages
    (standard and high priority minutes over the account lifetime).
  • az quantum suite-offer target list --provider-id <id> — lists the targets exposed
    by a provider account together with their current availability and average queue time.

Both commands resolve the provider account's region from the control-plane suite offer,
then call the corresponding v2 data-plane endpoint directly.

Changes

ProviderStatus parsing fix: the providerStatus endpoint returns a single
ProviderStatus object (not a paged { "value": [...] } envelope or bare array), so
list_provider_status now wraps the single object in a list. A paged envelope / bare
array is still tolerated for forward-compatibility. A regression unit test covers this.

  • Client factory consolidation: the two data-plane suite-offer factories were merged
    into a single cf_suite_offers_data_plane, matching the file's "one factory per
    .services.X accessor" convention.
  • Known model drift (informational): the vendored TargetStatus model declares
    numQubits/targetProfile/metadata (surfaced as null), while the DataPlaneV2
    backend additionally emits averageQueueTimeHighPriority/averageQueueTimeStandardPriority
    that the model doesn't declare. The version-tolerant model passes unknown wire fields
    through, so output is faithful and the table transformer (which uses only
    averageQueueTime) is unaffected. Aligning the vendored model can be handled separately.

Testing

  • Unit tests: azext_quantum/tests/latest/test_quantum_suite_offers.py (build-request,
    deserialization, table-transform, single-object wrap, and merge-logic tests).
  • Live e2e verified against multiple provider accounts (populated targets for active
    providers; valid non-empty results for providers with no targets); table and JSON output
    confirmed. flake8/pylint clean.

List the Quantum suite offers available to the subscription (provider, location, and subscription-level quota allocations) via the control-plane SuiteOffers API. Bumps the extension to 1.0.0b24.
…fer-list

# Conflicts:
#	src/quantum/HISTORY.rst
#	src/quantum/setup.py
Adds 'az quantum suite-offer quotas --provider-id' which returns v2 quota allocations merged with their consumed usages for a suite offer provider account. Combines the control-plane suite offer allocations with the data-plane (-v2 endpoint) quota usages, reporting allocated/used/remaining standard and high priority minutes per subscription and target scope.
@azure-client-tools-bot-prd

Copy link
Copy Markdown

Hi v-elegacheva,
Please write the description of changes which can be perceived by customers into HISTORY.rst.
If you want to release a new extension version, please update the version in pyproject.toml (or setup.py, if the extension has not migrated yet) as well.

@v-elegacheva v-elegacheva changed the title [Quantum] Add 'az quantum suite-offer quotas' command Quantum: Add 'az quantum suite-offer quotas' command Sep 1, 2026
@microsoft-github-policy-service microsoft-github-policy-service Bot added the customer-reported Issues that are reported by GitHub users external to the Azure organization. label Sep 1, 2026
@microsoft-github-policy-service

Copy link
Copy Markdown
Contributor

Thank you for your contribution v-elegacheva! We will review the pull request and get back to you soon.

Comment thread src/quantum/azext_quantum/operations/suite_offers.py Outdated
Comment thread src/quantum/azext_quantum/tests/latest/test_quantum_suite_offers.py Outdated
std_used = usage_values.standard_minutes_lifetime if usage_values is not None else None
high_used = usage_values.high_minutes_lifetime if usage_values is not None else None

row = OrderedDict()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Did you consider creating a structure for return type to improve readability? and probably we can skip remaining and stick to initial structure like this:
{
"providerId" : "atom-dev",
"scope" : "SubscriptionTarget",
"targetId" : "msft.sim.ac1000.physical",
"allocation" : {
"standardMinutesLifetime" : 600,
"highMinutesLifetime" : 60
},
"usage" : {
"standardMinutesLifetime" : 120,
"highMinutesLifetime" : 12
}
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I restructured to match your proposed shape. Each row is now:
{
"providerId": "...",
"scope": "SubcriptionTarget",
"targetId": "...",
"allocation": { "standardMinutesLifetime": 0, "highMinutesLifetime": 0 },
"usage": {"standardMinutesLifetime": 0, "highMinutesLifetime": 0}
}

remaining and lastModifiedTime are dropped. I added a small _minutes() helper to build the nested blocks. The table transformer and help text were also updated to match

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

thanks, but it still dict, do you think there will be benefit of creating a type with all of these fields and have dot access to the fields?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The return value of a CLI custom command is serialized straight to the user-facing output, and the CLI's todict serializer uses each object's raw attribute names (vars()). So a plain class/dataclass would emit snake_case keys (provider_id, standard_minutes_lifetime) instead of the providerId / standardMinutesLifetime contract, and a namedTuple serializes as a JSON array. OrderedDict gives exact control over the camelCase keys and ordering that define this command's output, and it is consistent with the rest of the quantum extension (all handlers/ transformers return dicts or SDK models). The dot-access benefit would only apply inside this ~ 15 line builder, which _minutes() already simplifies. If you'd like the shape documented in code, i can switch the row to a TypedDict. That gives type-checking + editor hints and still serializes correctly as a dict. A full dataclass would need custom camelCase serialization to avoid changing the output. Which way would you prefer?

…age output

Per review: build one row per targetQuota (SubscriptionTarget scope only), restructure each row into nested 'allocation' and 'usage' blocks, and drop the computed 'remaining' field.
Non-functional follow-ups from code review: add the canary branch to base_url_v2 for parity with base_url, add a @live_only scenario test for 'suite-offer quotas', correct the 'suite-offer list' help summary, and comment the unused factory args.
@yonzhan

Copy link
Copy Markdown
Collaborator

Quantum

}

rows = []
for target_quota in sorted(offer.properties.target_quotas or [], key=lambda q: q.target_id or ""):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

what is the reason of sorting target quotas here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It is just to give deterministic, stable output ordering. The service does not guarantee an order for targetQuotas (or the usages list), so sorting by targetId keeps the JSON/ table rows consistent across runs. Which also keeps diffs and the live test stable. I can drop it if you'd rather preserve the service's order :)


row = OrderedDict()
row["providerId"] = provider_id
row["scope"] = "SubscriptionTarget"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

we can reuse usage.Scope here instead of magic string

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I agree! I did find something worth attention however: a target row can have no matching usage (usage is None for targets with no consumption), so usage.scope isn't always available to read. Since every target row is SubscriptionTarget - scoped by definition, I'll lift the literal into a named constant so it is not a magic string and stays independent of whether a usage row exists. If you would prefer, I can instead read usage.scope when present and fall back to the constant. Please let me know what you would prefer!

row["providerId"] = provider_id
row["scope"] = "SubscriptionTarget"
row["targetId"] = target_quota.target_id
row["allocation"] = _minutes(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think in UI we show allocation and usage in hours? align with it

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The underlying ARM / DP fields are standardMinutesLifetime / highMinutesLifetime. The values are minutes by contract and the key names literally say "Minutes". The CLI mirrors the service payload, so converting to hours would make the value diagree with its own filed name and with ARM. My instinct was keeping the JSON in minutes (true to contract) and if it helps parity with the UI, adding hours to the table view only. If you would prefer to fully match the UI, we would need new hour-names fields (like standardHoursLifetime) rather than silently dividing the existing ones. Please let me know how you'd like to proceed on this one :)

Lists targets and their status for a suite offer provider account via the data plane, without requiring a workspace. Fixes single-object ProviderStatus parsing, consolidates the data-plane suite-offer client factory, and bumps the extension to 1.0.0b27.
Address review: the data-plane getProviderStatus endpoint returns a single ProviderStatus object per the spec, not a list. Rename the vendored list_provider_status to get_provider_status (sync + async) returning a single ProviderStatus, drop the wrap-in-list workaround, and have the target-list handler wrap the result for the shared table transformer. Update tests accordingly.
…ota allocations merged with usages

Replaces the legacy data-plane quotas listing with v2 workspace target quota allocations (from ARM) merged with their consumed quota usages from the data-plane v2 quotaUsages endpoint. The workspace quotaUsages endpoint requires a providerId query parameter, so usages are fetched per provider. Adds a table transformer and unit tests.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

act-codegen-extensibility-squad Auto-Assign Auto assign by bot customer-reported Issues that are reported by GitHub users external to the Azure organization. Quantum az quantum

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants