Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(BA-145): Implement CRUD API for managing Harbor per-project Quota #3090

Open
wants to merge 64 commits into
base: topic/11-06-feat_implement_per-project_images_api_based_on_rbac
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
c0a866c
feat: Implement management API for controlling Harbor per-project Quota
jopemachine Nov 13, 2024
0b4d490
chore: Add news fragment
jopemachine Nov 13, 2024
1cdd4f7
fix: Disable user quota mutation
jopemachine Nov 13, 2024
4340b3d
fix: Rename variables
jopemachine Nov 13, 2024
24ef066
feat: Add `registry_quota` to GroupNode
jopemachine Nov 13, 2024
1e9657f
fix: Update `UpdateQuota` mutation
jopemachine Nov 13, 2024
5e8d363
fix: Update `resolve_registry_quota`
jopemachine Nov 13, 2024
b951004
fix: Update `resolve_registry_quota`
jopemachine Nov 14, 2024
b921b84
fix: Only authorized groups view the quota
jopemachine Nov 14, 2024
b4ded10
feat: Add CreateQuota, DeleteQuota mutation
jopemachine Nov 14, 2024
8eb35a7
chore: Rename function
jopemachine Nov 14, 2024
1a8aa1c
fix: Add exception handling for each operation
jopemachine Nov 14, 2024
74b33ff
chore: Update schema
jopemachine Nov 14, 2024
71a5248
chore: Update error msg
jopemachine Nov 14, 2024
3ad7658
chore: Rename variable
jopemachine Nov 14, 2024
deac59b
fix: Remove useless strenum
jopemachine Nov 14, 2024
7413034
refactor: `mutate_harbor_project_quota`
jopemachine Nov 18, 2024
7810008
refactor: Add read operation handling for code reuse
jopemachine Nov 18, 2024
c5ef78b
feat: Add SDK for registry quota mutations
jopemachine Nov 18, 2024
71def5d
fix: Broken CI
jopemachine Nov 18, 2024
5e3372b
feat: Implement REST API
jopemachine Nov 18, 2024
9986638
fix: Wrong exception handling
jopemachine Nov 18, 2024
5cde26d
chore: Update comment
jopemachine Nov 19, 2024
c0b8b01
chore: Rename types
jopemachine Nov 19, 2024
15babbf
chore: update GraphQL schema dump
jopemachine Nov 19, 2024
e0c2733
chore: Rename news fragment
jopemachine Nov 19, 2024
2c18a6b
fix: Use `BigInt`
jopemachine Nov 20, 2024
b842771
chore: update GraphQL schema dump
jopemachine Nov 20, 2024
19fc673
refactor: Add `HarborQuotaManager` *(Reflect feedback)
jopemachine Nov 28, 2024
5282271
fix: Use BigInt
jopemachine Nov 28, 2024
0323070
chore: self -> cls
jopemachine Nov 28, 2024
124f5ab
chore: update GraphQL schema dump
jopemachine Nov 28, 2024
5380b19
fix: Improve exception handling
jopemachine Nov 28, 2024
5d9cc35
feat: Add `test_harbor_read_project_quota`
jopemachine Nov 28, 2024
28581eb
chore: `mock-group` -> `mock_group`
jopemachine Nov 28, 2024
641fb16
fix: Disjoint `FIXTURES_FOR_HARBOR_CRUD_TEST` from `test_harbor_read_…
jopemachine Nov 28, 2024
61b1e3b
chore: Add registry type
jopemachine Nov 28, 2024
d04366d
feat: Add create GQL mutation test case
jopemachine Dec 1, 2024
2f96d2d
feat: Add update, delete GQL mutation test cases
jopemachine Dec 2, 2024
e1b1fa0
chore: fix typo
jopemachine Dec 2, 2024
bdb8392
feat: Add REST API `test_harbor_read_project_quota` test
jopemachine Dec 3, 2024
840c219
chore: Rename variables
jopemachine Dec 3, 2024
bb53fb1
fix: Add `test_harbor_update_project_quota` test
jopemachine Dec 3, 2024
40a954f
chore: Hoist the variable
jopemachine Dec 3, 2024
b4a87c8
feat: Add `test_harbor_delete_project_quota` test
jopemachine Dec 3, 2024
357f7a4
feat: Add `test_harbor_create_project_quota` test
jopemachine Dec 3, 2024
b2dcb58
fix: Change the `test_harbor_read_project_quota` test location
jopemachine Dec 3, 2024
2ff19a0
fix: Change test code location
jopemachine Dec 3, 2024
b002ed3
fix: Reuse `FIXTURES_FOR_HARBOR_CRUD_TEST`
jopemachine Dec 3, 2024
7ea569f
fix: Broken CI
jopemachine Dec 5, 2024
1c566f9
refactor: Add `test_case` parametrize annotation
jopemachine Dec 6, 2024
9c2743c
refactor: `test_group` test cases using `test_case`
jopemachine Dec 8, 2024
4aa7c8f
fix: Broken CI
jopemachine Jan 6, 2025
723fa90
fix: Broken CI
jopemachine Jan 6, 2025
207e09a
fix: Update milestone
jopemachine Jan 8, 2025
4a383db
fix: Update milestone
jopemachine Jan 8, 2025
34ef4b2
fix: Update milestone
jopemachine Jan 10, 2025
d60431e
fix: Remove cyclic import
jopemachine Jan 14, 2025
5970ad2
docs: Update milestone
jopemachine Jan 14, 2025
73f36b0
fix: Delete obsolete mutations
jopemachine Jan 14, 2025
886d323
fix: Broken import
jopemachine Jan 24, 2025
a9e6284
chore: update api schema dump
jopemachine Jan 30, 2025
238f03f
refactor: Implement `services_ctx` and refactoring using this
jopemachine Jan 31, 2025
3242582
refactor: Reflect feedbacks
jopemachine Feb 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/3090.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Implement CRUD API for managing Harbor per-project Quota.
30 changes: 30 additions & 0 deletions docs/manager/graphql-reference/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,9 @@
"""Added in 24.03.7."""
container_registry: JSONString
scaling_groups: [String]

"""Added in 25.2.0."""
registry_quota: BigInt

Check notice on line 715 in docs/manager/graphql-reference/schema.graphql

View workflow job for this annotation

GitHub Actions / GraphQL Inspector

Field 'registry_quota' was added to object type 'GroupNode'

Field 'registry_quota' was added to object type 'GroupNode'
user_nodes(filter: String, order: String, offset: Int, before: String, after: String, first: Int, last: Int): UserConnection
}

Expand Down Expand Up @@ -1933,6 +1936,15 @@
"""Added in 25.1.0."""
delete_endpoint_auto_scaling_rule_node(id: String!): DeleteEndpointAutoScalingRuleNode

"""Added in 25.2.0."""
create_container_registry_quota(quota: BigInt!, scope_id: ScopeField!): CreateContainerRegistryQuota

Check notice on line 1940 in docs/manager/graphql-reference/schema.graphql

View workflow job for this annotation

GitHub Actions / GraphQL Inspector

Field 'create_container_registry_quota' was added to object type 'Mutations'

Field 'create_container_registry_quota' was added to object type 'Mutations'

"""Added in 25.2.0."""
update_container_registry_quota(quota: BigInt!, scope_id: ScopeField!): UpdateContainerRegistryQuota

Check notice on line 1943 in docs/manager/graphql-reference/schema.graphql

View workflow job for this annotation

GitHub Actions / GraphQL Inspector

Field 'update_container_registry_quota' was added to object type 'Mutations'

Field 'update_container_registry_quota' was added to object type 'Mutations'

"""Added in 25.2.0."""
delete_container_registry_quota(scope_id: ScopeField!): DeleteContainerRegistryQuota

Check notice on line 1946 in docs/manager/graphql-reference/schema.graphql

View workflow job for this annotation

GitHub Actions / GraphQL Inspector

Field 'delete_container_registry_quota' was added to object type 'Mutations'

Field 'delete_container_registry_quota' was added to object type 'Mutations'

"""Deprecated since 24.09.0. use `CreateContainerRegistryNode` instead"""
create_container_registry(hostname: String!, props: CreateContainerRegistryInput!): CreateContainerRegistry

Expand Down Expand Up @@ -2787,6 +2799,24 @@
msg: String
}

"""Added in 25.2.0."""
type CreateContainerRegistryQuota {

Check notice on line 2803 in docs/manager/graphql-reference/schema.graphql

View workflow job for this annotation

GitHub Actions / GraphQL Inspector

Type 'CreateContainerRegistryQuota' was added

Type 'CreateContainerRegistryQuota' was added
ok: Boolean
msg: String
}

"""Added in 25.2.0."""
type UpdateContainerRegistryQuota {

Check notice on line 2809 in docs/manager/graphql-reference/schema.graphql

View workflow job for this annotation

GitHub Actions / GraphQL Inspector

Type 'UpdateContainerRegistryQuota' was added

Type 'UpdateContainerRegistryQuota' was added
ok: Boolean
msg: String
}

"""Added in 25.2.0."""
type DeleteContainerRegistryQuota {

Check notice on line 2815 in docs/manager/graphql-reference/schema.graphql

View workflow job for this annotation

GitHub Actions / GraphQL Inspector

Type 'DeleteContainerRegistryQuota' was added

Type 'DeleteContainerRegistryQuota' was added
ok: Boolean
msg: String
}

"""Deprecated since 24.09.0. use `CreateContainerRegistryNode` instead"""
type CreateContainerRegistry {
container_registry: ContainerRegistry
Expand Down
136 changes: 136 additions & 0 deletions docs/manager/rest-reference/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -8462,6 +8462,142 @@
"description": "\n**Preconditions:**\n* Admin privilege required.\n* Manager status required: one of FROZEN, RUNNING\n"
}
},
"/group/registry-quota": {
"post": {
"operationId": "group.create_registry_quota",
"tags": [
"group"
],
"responses": {
"200": {
"description": "Successful response"
}
},
"security": [
{
"TokenAuth": []
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"group_id": {
"type": "string"
},
"quota": {
"type": "integer"
}
},
"required": [
"group_id",
"quota"
]
},
"examples": {}
}
}
},
"parameters": [],
"description": "\n**Preconditions:**\n* Superadmin privilege required.\n* Manager status required: one of FROZEN, RUNNING\n"
},
"get": {
"operationId": "group.read_registry_quota",
"tags": [
"group"
],
"responses": {
"200": {
"description": "Successful response"
}
},
"security": [
{
"TokenAuth": []
}
],
"parameters": [
{
"name": "group_id",
"schema": {
"type": "string"
},
"required": true,
"in": "query"
}
],
"description": "\n**Preconditions:**\n* Superadmin privilege required.\n* Manager status required: one of FROZEN, RUNNING\n"
},
"patch": {
"operationId": "group.update_registry_quota",
"tags": [
"group"
],
"responses": {
"200": {
"description": "Successful response"
}
},
"security": [
{
"TokenAuth": []
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"group_id": {
"type": "string"
},
"quota": {
"type": "integer"
}
},
"required": [
"group_id",
"quota"
]
},
"examples": {}
}
}
},
"parameters": [],
"description": "\n**Preconditions:**\n* Superadmin privilege required.\n* Manager status required: one of FROZEN, RUNNING\n"
},
"delete": {
"operationId": "group.delete_registry_quota",
"tags": [
"group"
],
"responses": {
"200": {
"description": "Successful response"
}
},
"security": [
{
"TokenAuth": []
}
],
"parameters": [
{
"name": "group_id",
"schema": {
"type": "string"
},
"required": true,
"in": "query"
}
],
"description": "\n**Preconditions:**\n* Superadmin privilege required.\n* Manager status required: one of FROZEN, RUNNING\n"
}
},
"/group-config/dotfiles": {
"post": {
"operationId": "group-config.create",
Expand Down
100 changes: 100 additions & 0 deletions src/ai/backend/client/func/group.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import textwrap
from typing import Any, Iterable, Optional, Sequence

from ai.backend.client.output.fields import group_fields
from ai.backend.client.output.types import FieldSpec
from ai.backend.common.utils import b64encode

from ...cli.types import Undefined, undefined
from ..session import api_session
Expand Down Expand Up @@ -293,3 +295,101 @@ async def remove_users(
}
data = await api_session.get().Admin._query(query, variables)
return data["modify_group"]

@api_function
@classmethod
async def get_container_registry_quota(cls, group_id: str) -> int:
"""
Get Quota Limit for the group's container registry.
Currently only HarborV2 registry is supported.

You need an admin privilege for this operation.
"""
query = textwrap.dedent(
"""\
query($id: String!) {
group_node(id: $id) {
registry_quota
}
}
"""
)

variables = {"id": b64encode(f"group_node:{group_id}")}
data = await api_session.get().Admin._query(query, variables)
Copy link
Member Author

@jopemachine jopemachine Nov 18, 2024

Choose a reason for hiding this comment

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

@fregataa Registry quota READ operation can be executed even if the user is not an admin.
However, it seems that GQL queries in the current SDK can only be executed through Admin.
What do you think about adding this query, _query functions to User as well?

return data["group_node"]["registry_quota"]

@api_function
@classmethod
async def create_container_registry_quota(cls, group_id: str, quota: int) -> dict:
"""
Create Quota Limit for the group's container registry.
Currently only HarborV2 registry is supported.

You need an admin privilege for this operation.
"""
query = textwrap.dedent(
"""\
mutation($scope_id: ScopeField!, $quota: Int!) {
create_container_registry_quota(
scope_id: $scope_id, quota: $quota) {
ok msg
}
}
"""
)

scope_id = f"project:{group_id}"
variables = {"scope_id": scope_id, "quota": quota}
data = await api_session.get().Admin._query(query, variables)
return data["create_container_registry_quota"]

@api_function
@classmethod
async def update_container_registry_quota(cls, group_id: str, quota: int) -> dict:
"""
Update Quota Limit for the group's container registry.
Currently only HarborV2 registry is supported.

You need an admin privilege for this operation.
"""
query = textwrap.dedent(
"""\
mutation($scope_id: ScopeField!, $quota: Int!) {
update_container_registry_quota(
scope_id: $scope_id, quota: $quota) {
ok msg
}
}
"""
)

scope_id = f"project:{group_id}"
Copy link
Member Author

@jopemachine jopemachine Nov 18, 2024

Choose a reason for hiding this comment

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

@fregataa If possible, instead of hardcoding the scope_id like this, I’d prefer to create a ProjectScope object and serialize it. However, since the ProjectScope type is currently located under the models directory, this violates the pants visibility rule.

What do you think about moving the types related to ScopeField under the common directory?

variables = {"scope_id": scope_id, "quota": quota}
data = await api_session.get().Admin._query(query, variables)
return data["update_container_registry_quota"]

@api_function
@classmethod
async def delete_container_registry_quota(cls, group_id: str) -> dict:
"""
Delete Quota Limit for the group's container registry.
Currently only HarborV2 registry is supported.

You need an admin privilege for this operation.
"""
query = textwrap.dedent(
"""\
mutation($scope_id: ScopeField!) {
delete_container_registry_quota(
scope_id: $scope_id) {
ok msg
}
}
"""
)

scope_id = f"project:{group_id}"
variables = {"scope_id": scope_id}
data = await api_session.get().Admin._query(query, variables)
return data["delete_container_registry_quota"]
9 changes: 9 additions & 0 deletions src/ai/backend/common/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -425,3 +425,12 @@ def join_non_empty(*args: Optional[str], sep: str) -> str:
"""
filtered_args = [arg for arg in args if arg]
return sep.join(filtered_args)


def b64encode(s: str) -> str:
"""
base64 encoding method of graphql_relay.
Use it in components where the graphql_relay package is unavailable.
"""
b: bytes = s.encode("utf-8") if isinstance(s, str) else s
return base64.b64encode(b).decode("ascii")
1 change: 1 addition & 0 deletions src/ai/backend/manager/api/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ async def _handle_gql_common(request: web.Request, params: Any) -> ExecutionResu
manager_status=manager_status,
known_slot_types=known_slot_types,
background_task_manager=root_ctx.background_task_manager,
services_ctx=root_ctx.services_ctx,
storage_manager=root_ctx.storage_manager,
registry=root_ctx.registry,
idle_checker_host=root_ctx.idle_checker_host,
Expand Down
2 changes: 1 addition & 1 deletion src/ai/backend/manager/api/container_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ async def patch_container_registry(
request: web.Request, params: PatchContainerRegistryRequestModel
) -> PatchContainerRegistryResponseModel:
registry_id = uuid.UUID(request.match_info["registry_id"])
log.info("PATCH_CONTAINER_REGISTRY (cr:{})", registry_id)
log.info("PATCH_CONTAINER_REGISTRY (registry:{})", registry_id)
root_ctx: RootContext = request.app["_root.context"]
registry_row_updates = params.model_dump(exclude={"allowed_groups"}, exclude_none=True)

Expand Down
2 changes: 2 additions & 0 deletions src/ai/backend/manager/api/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from ai.backend.common.metrics.metric import CommonMetricRegistry
from ai.backend.manager.plugin.network import NetworkPluginContext
from ai.backend.manager.service.base import ServicesContext

if TYPE_CHECKING:
from ai.backend.common.bgtask import BackgroundTaskManager
Expand Down Expand Up @@ -50,6 +51,7 @@ class RootContext(BaseContext):
storage_manager: StorageSessionManager
hook_plugin_ctx: HookPluginContext
network_plugin_ctx: NetworkPluginContext
services_ctx: ServicesContext

registry: AgentRegistry
agent_cache: AgentRPCCache
Expand Down
Loading
Loading