Accounting

API: Internal/Beta

Tracks resource usage

Rationale

The goal of UCloud’s accounting system is to:

  1. Allow or deny access to a provider’s service catalog

  2. Track consumption of resources at the workspace level

  3. Generate visualizations and reports which track historical consumption data

Allocations: Granting access to a service catalog

UCloud achieves the first point by having the ability to grant resource allocations. A resource allocation is also known as a Alloc. These are grouped into AllocationGroups based on their parent, usage is tracked at a group level up until the point any specific allocation retires. They grant a workspace the ability to use ProductV2s from a specific ProductCategory. Unless otherwise stated, a workspace must always hold an allocation to use a product. If a workspace does not hold an allocation, then the accounting system will deny access to them. An allocation sets several limits on how the workspace can use the products. This includes:

  • An allocation is only valid for the ProductV2s belonging to a single category. For example, if a workspace has an allocation for u1-standard then it does not grant access to u1-gpu.

  • An allocation has a start date and an end date. Outside of this period, the allocation is invalid.

  • Each allocation have an associated quota. If a workspace is using more than the quota allows, then the provider should deny access to the ProductV2.


📝NOTE: It is the responsibility of the provider and not UCloud’s accounting system to deny access to a resource when the quota is exceeded. UCloud assists in this process by telling providers when a workspace exceeds their quota. But the responsibility lies with the providers, as they usually have more information. UCloud will only check for the existence of a valid allocation before forwarding the request.


Resource allocations are hierarchical in UCloud. In practice, this means that all allocations can have either 0 or 1 parent allocation. Allocations which do not have a parent are root allocations. Only UCloud administrators/provider administrators can create root allocations. Administrators of a workspace can “sub-allocate” their own allocations. This will create a new allocation which has one of their existing allocations as the parent. UCloud allows for over-allocation when creating sub-allocations. UCloud avoids over-spending by making sure that the usage in a sub-tree doesn’t exceed the quota specified in the root of the sub-tree. For example, consider the following sub-allocation created by a workspace administrator:

They can even create another which is even larger.

The sub-allocations themselves can continue to create new sub-allocations. These hierarchies can be as complex as they need to be.

In the above example neither “Research 1” or “Research 2” can have a usage above 10GB due to their parent. Similarly, if the combined usage goes above 10GB then UCloud will lock both of the allocations.

Summary

Important concepts:

  • AllocationGroup and Alloc: Stores a resource allocation which grants a workspace access to a ProductCategory

  • WalletV2: Combines multiple allocations, belonging to the same workspace for a specific category. The accounting system spreads out usages evenly across all allocations in a Wallet.

  • Allocations form a hierarchy. Over-allocation is allowed but the combined usage in a single allocation tree must not exceed the quota in the root.

Important calls:

Table of Contents

1. Remote Procedure Calls
Name Description
browseWallets Browses the catalog of accessible Wallets
retrieveDescendants No description
browseProviderAllocations Browses allocations relevant for a specific provider
browseWalletsInternal Retrieves a list of up-to-date wallets
checkProviderUsable No description
findRelevantProviders No description
reportUsage No description
rootAllocate No description
updateAllocation No description
2. Data Models
Name Description
AllocationGroup No description
AllocationGroup.Alloc No description
AllocationGroupWithChild No description
AllocationGroupWithParent No description
ChargeDescription No description
ParentOrChildWallet No description
UsageReportItem No description
WalletOwner No description
WalletOwner.Project No description
WalletOwner.User No description
WalletV2 No description
AccountingV2.BrowseProviderAllocations.Request The base type for requesting paginated content.
AccountingV2.BrowseWallets.Request The base type for requesting paginated content.
AccountingV2.BrowseWalletsInternal.Request No description
AccountingV2.CheckProviderUsable.RequestItem No description
AccountingV2.FindRelevantProviders.RequestItem No description
AccountingV2.RetrieveDescendants.Request No description
AccountingV2.RootAllocate.RequestItem No description
AccountingV2.UpdateAllocation.RequestItem No description
AccountingV2.BrowseProviderAllocations.ResponseItem No description
AccountingV2.BrowseWalletsInternal.Response No description
AccountingV2.CheckProviderUsable.ResponseItem No description
AccountingV2.FindRelevantProviders.Response No description
AccountingV2.RetrieveDescendants.Response No description

Remote Procedure Calls

browseWallets

API: Internal/Beta Auth: Users

Browses the catalog of accessible Wallets

Request Response Error
AccountingV2.BrowseWallets.Request PageV2<WalletV2> CommonErrorMessage

retrieveDescendants

API: Internal/Beta Auth: Services

Request Response Error
AccountingV2.RetrieveDescendants.Request AccountingV2.RetrieveDescendants.Response CommonErrorMessage

browseProviderAllocations

API: Internal/Beta Auth: Provider

Browses allocations relevant for a specific provider

Request Response Error
AccountingV2.BrowseProviderAllocations.Request PageV2<AccountingV2.BrowseProviderAllocations.ResponseItem> CommonErrorMessage

This endpoint is only usable by providers. The endpoint will return a stable results.

browseWalletsInternal

API: Internal/Beta Auth: Services

Retrieves a list of up-to-date wallets

Request Response Error
AccountingV2.BrowseWalletsInternal.Request AccountingV2.BrowseWalletsInternal.Response CommonErrorMessage

This endpoint will return a list of Wallets which are related to the active workspace. This is mainly for backend use. For frontend, use the browse call instead for a paginated response

checkProviderUsable

API: Internal/Beta Auth: Provider

Request Response Error
BulkRequest<AccountingV2.CheckProviderUsable.RequestItem> BulkResponse<AccountingV2.CheckProviderUsable.ResponseItem> CommonErrorMessage

findRelevantProviders

API: Internal/Beta Auth: Services

Request Response Error
BulkRequest<AccountingV2.FindRelevantProviders.RequestItem> BulkResponse<AccountingV2.FindRelevantProviders.Response> CommonErrorMessage

reportUsage

API: Internal/Beta Auth: Provider

Request Response Error
BulkRequest<UsageReportItem> BulkResponse<Boolean> CommonErrorMessage

rootAllocate

API: Internal/Beta Auth: Users

Request Response Error
BulkRequest<AccountingV2.RootAllocate.RequestItem> BulkResponse<FindByStringId> CommonErrorMessage

updateAllocation

API: Internal/Beta Auth: Users

Request Response Error
BulkRequest<AccountingV2.UpdateAllocation.RequestItem> Unit CommonErrorMessage

Data Models

AllocationGroup

API: Internal/Beta

data class AllocationGroup(
    val id: Int,
    val allocations: List<AllocationGroup.Alloc>,
    val usage: Long,
)
Properties
id: Int
allocations: List<AllocationGroup.Alloc>
usage: Long

AllocationGroup.Alloc

API: Internal/Beta

data class Alloc(
    val id: Long,
    val startDate: Long,
    val endDate: Long,
    val quota: Long,
    val grantedIn: Long?,
    val retiredUsage: Long?,
)
Properties
id: Long
startDate: Long
endDate: Long
quota: Long
grantedIn: Long?
retiredUsage: Long?

AllocationGroupWithChild

API: Internal/Beta

data class AllocationGroupWithChild(
    val child: ParentOrChildWallet,
    val group: AllocationGroup,
)
Properties
child: ParentOrChildWallet
group: AllocationGroup

AllocationGroupWithParent

API: Internal/Beta

data class AllocationGroupWithParent(
    val parent: ParentOrChildWallet?,
    val group: AllocationGroup,
)
Properties
parent: ParentOrChildWallet?
group: AllocationGroup

ChargeDescription

API: Internal/Beta

data class ChargeDescription(
    val scope: String?,
    val description: String?,
)
Properties
scope: String?
description: String?

ParentOrChildWallet

API: Internal/Beta

data class ParentOrChildWallet(
    val projectId: String?,
    val projectTitle: String,
    val pi: String,
)
Properties
projectId: String?
projectTitle: String
pi: String

UsageReportItem

API: Internal/Beta

data class UsageReportItem(
    val isDeltaCharge: Boolean,
    val owner: WalletOwner,
    val categoryIdV2: ProductCategoryIdV2,
    val usage: Long,
    val description: ChargeDescription,
)
Properties
isDeltaCharge: Boolean
owner: WalletOwner
categoryIdV2: ProductCategoryIdV2
usage: Long
description: ChargeDescription

WalletOwner

API: Internal/Beta

sealed class WalletOwner {
    class Project : WalletOwner()
    class User : WalletOwner()
}

WalletOwner.Project

API: Internal/Beta

data class Project(
    val projectId: String,
    val type: String /* "project" */,
)
Properties
projectId: String
type: String /* "project" */ The type discriminator

API: Stable


WalletOwner.User

API: Internal/Beta

data class User(
    val username: String,
    val type: String /* "user" */,
)
Properties
username: String
type: String /* "user" */ The type discriminator

API: Stable


WalletV2

API: Internal/Beta

data class WalletV2(
    val owner: WalletOwner,
    val paysFor: ProductCategory,
    val allocationGroups: List<AllocationGroupWithParent>,
    val children: List<AllocationGroupWithChild>?,
    val totalUsage: Long,
    val localUsage: Long,
    val maxUsable: Long,
    val quota: Long,
    val totalAllocated: Long,
    val lastSignificantUpdateAt: Long,
)
Properties
owner: WalletOwner
paysFor: ProductCategory
allocationGroups: List<AllocationGroupWithParent>
children: List<AllocationGroupWithChild>?
totalUsage: Long
localUsage: Long
maxUsable: Long
quota: Long
totalAllocated: Long
lastSignificantUpdateAt: Long

AccountingV2.BrowseProviderAllocations.Request

API: Internal/Beta

The base type for requesting paginated content.

data class Request(
    val itemsPerPage: Int?,
    val next: String?,
    val consistency: PaginationRequestV2Consistency?,
    val itemsToSkip: Long?,
    val filterOwnerId: String?,
    val filterOwnerIsProject: Boolean?,
    val filterCategory: String?,
)

Paginated content can be requested with one of the following consistency guarantees, this greatly changes the semantics of the call:

Consistency Description
PREFER Consistency is preferred but not required. An inconsistent snapshot might be returned.
REQUIRE Consistency is required. A request will fail if consistency is no longer guaranteed.

The consistency refers to if collecting all the results via the pagination API are consistent. We consider the results to be consistent if it contains a complete view at some point in time. In practice this means that the results must contain all the items, in the correct order and without duplicates.

If you use the PREFER consistency then you may receive in-complete results that might appear out-of-order and can contain duplicate items. UCloud will still attempt to serve a snapshot which appears mostly consistent. This is helpful for user-interfaces which do not strictly depend on consistency but would still prefer something which is mostly consistent.

The results might become inconsistent if the client either takes too long, or a service instance goes down while fetching the results. UCloud attempts to keep each next token alive for at least one minute before invalidating it. This does not mean that a client must collect all results within a minute but rather that they must fetch the next page within a minute of the last page. If this is not feasible and consistency is not required then PREFER should be used.


📝 NOTE: Services are allowed to ignore extra criteria of the request if the next token is supplied. This is needed in order to provide a consistent view of the results. Clients should provide the same criterion as they paginate through the results.


Properties
itemsPerPage: Int? Requested number of items per page. Supported values: 10, 25, 50, 100, 250.
next: String? A token requesting the next page of items
consistency: PaginationRequestV2Consistency? Controls the consistency guarantees provided by the backend
itemsToSkip: Long? Items to skip ahead
filterOwnerId: String?
filterOwnerIsProject: Boolean?
filterCategory: String?

AccountingV2.BrowseWallets.Request

API: Internal/Beta

The base type for requesting paginated content.

data class Request(
    val itemsPerPage: Int?,
    val next: String?,
    val consistency: PaginationRequestV2Consistency?,
    val itemsToSkip: Long?,
    val filterType: ProductType?,
    val includeChildren: Boolean?,
    val childrenQuery: String?,
)

Paginated content can be requested with one of the following consistency guarantees, this greatly changes the semantics of the call:

Consistency Description
PREFER Consistency is preferred but not required. An inconsistent snapshot might be returned.
REQUIRE Consistency is required. A request will fail if consistency is no longer guaranteed.

The consistency refers to if collecting all the results via the pagination API are consistent. We consider the results to be consistent if it contains a complete view at some point in time. In practice this means that the results must contain all the items, in the correct order and without duplicates.

If you use the PREFER consistency then you may receive in-complete results that might appear out-of-order and can contain duplicate items. UCloud will still attempt to serve a snapshot which appears mostly consistent. This is helpful for user-interfaces which do not strictly depend on consistency but would still prefer something which is mostly consistent.

The results might become inconsistent if the client either takes too long, or a service instance goes down while fetching the results. UCloud attempts to keep each next token alive for at least one minute before invalidating it. This does not mean that a client must collect all results within a minute but rather that they must fetch the next page within a minute of the last page. If this is not feasible and consistency is not required then PREFER should be used.


📝 NOTE: Services are allowed to ignore extra criteria of the request if the next token is supplied. This is needed in order to provide a consistent view of the results. Clients should provide the same criterion as they paginate through the results.


Properties
itemsPerPage: Int? Requested number of items per page. Supported values: 10, 25, 50, 100, 250.
next: String? A token requesting the next page of items
consistency: PaginationRequestV2Consistency? Controls the consistency guarantees provided by the backend
itemsToSkip: Long? Items to skip ahead
filterType: ProductType?
includeChildren: Boolean?
childrenQuery: String?

AccountingV2.BrowseWalletsInternal.Request

API: Internal/Beta

data class Request(
    val owner: WalletOwner,
)
Properties
owner: WalletOwner

AccountingV2.CheckProviderUsable.RequestItem

API: Internal/Beta

data class RequestItem(
    val owner: WalletOwner,
    val category: ProductCategoryIdV2,
)
Properties
owner: WalletOwner
category: ProductCategoryIdV2

AccountingV2.FindRelevantProviders.RequestItem

API: Internal/Beta

data class RequestItem(
    val username: String,
    val project: String?,
    val useProject: Boolean,
    val filterProductType: ProductType?,
)
Properties
username: String
project: String?
useProject: Boolean
filterProductType: ProductType?

AccountingV2.RetrieveDescendants.Request

API: Internal/Beta

data class Request(
    val project: String,
)
Properties
project: String

AccountingV2.RootAllocate.RequestItem

API: Internal/Beta

data class RequestItem(
    val category: ProductCategoryIdV2,
    val quota: Long,
    val start: Long,
    val end: Long,
)
Properties
category: ProductCategoryIdV2
quota: Long
start: Long
end: Long

AccountingV2.UpdateAllocation.RequestItem

API: Internal/Beta

data class RequestItem(
    val allocationId: Long,
    val newQuota: Long?,
    val newStart: Long?,
    val newEnd: Long?,
    val reason: String,
)
Properties
allocationId: Long
newQuota: Long?
newStart: Long?
newEnd: Long?
reason: String

AccountingV2.BrowseProviderAllocations.ResponseItem

API: Internal/Beta

data class ResponseItem(
    val id: String,
    val owner: WalletOwner,
    val categoryId: ProductCategory,
    val notBefore: Long,
    val notAfter: Long,
    val quota: Long,
)
Properties
id: String
owner: WalletOwner
categoryId: ProductCategory
notBefore: Long The earliest timestamp which allows for the balance to be consumed
notAfter: Long The earliest timestamp at which the reported balance is no longer fully usable
quota: Long

AccountingV2.BrowseWalletsInternal.Response

API: Internal/Beta

data class Response(
    val wallets: List<WalletV2>,
)
Properties
wallets: List<WalletV2>

AccountingV2.CheckProviderUsable.ResponseItem

API: Internal/Beta

data class ResponseItem(
    val maxUsable: Long,
)
Properties
maxUsable: Long

AccountingV2.FindRelevantProviders.Response

API: Internal/Beta

data class Response(
    val providers: List<String>,
)
Properties
providers: List<String>

AccountingV2.RetrieveDescendants.Response

API: Internal/Beta

data class Response(
    val descendants: List<String>,
)
Properties
descendants: List<String>