Add support for key manager project quota API

Add get, update, and delete methods for ProjectQuota in key-manager.
Include related documentation, unit tests, and functional tests.

Change-Id: I9d995066a2183dd9310ad276a273fa725c0e57b2
Signed-off-by: jbeen <jeongwon.been@gmail.com>
This commit is contained in:
jbeen
2025-09-06 21:16:21 +09:00
parent cf9f38c989
commit 3ba5034924
9 changed files with 259 additions and 0 deletions

View File

@@ -45,3 +45,10 @@ Secret Store Operations
:noindex:
:members: secret_stores, get_global_default_secret_store,
get_preferred_secret_store
ProjectQuota Operations
^^^^^^^^^^^^^^^^^^^^^^^
.. autoclass:: openstack.key_manager.v1._proxy.Proxy
:noindex:
:members: update_project_quota, delete_project_quota, get_project_quota

View File

@@ -6,5 +6,6 @@ KeyManager Resources
v1/container
v1/order
v1/project_quota
v1/secret
v1/secret_store

View File

@@ -0,0 +1,12 @@
openstack.key_manager.v1.project_quota
======================================
.. automodule:: openstack.key_manager.v1.project_quota
The ProjectQuota Class
----------------------
The ``ProjectQuota`` class inherits from :class:`~openstack.resource.Resource`.
.. autoclass:: openstack.key_manager.v1.project_quota.ProjectQuota
:members:

View File

@@ -14,6 +14,7 @@ import typing as ty
from openstack.key_manager.v1 import container as _container
from openstack.key_manager.v1 import order as _order
from openstack.key_manager.v1 import project_quota as _project_quota
from openstack.key_manager.v1 import secret as _secret
from openstack.key_manager.v1 import secret_store as _secret_store
from openstack import proxy
@@ -24,6 +25,7 @@ class Proxy(proxy.Proxy):
_resource_registry = {
"container": _container.Container,
"order": _order.Order,
"project_quota": _project_quota.ProjectQuota,
"secret": _secret.Secret,
"secret_store": _secret_store.SecretStore,
}
@@ -313,6 +315,47 @@ class Proxy(proxy.Proxy):
base_path='/secret-stores/preferred',
)
def delete_project_quota(self, project_id, ignore_missing=True):
"""Delete a project quota
:param project_id: A project ID.
:param bool ignore_missing: When set to ``False``
:class:`~openstack.exceptions.NotFoundException` will be
raised when the project quota does not exist.
When set to ``True``, no exception will be set when
attempting to delete a nonexistent project quota.
:returns: ``None``
"""
self._delete(
_project_quota.ProjectQuota,
project_id,
ignore_missing=ignore_missing,
)
def get_project_quota(self, project_id):
"""Get a single project quota
:param project_id: A project ID.
:returns: One :class:`~openstack.key_manager.v1.project_quota.ProjectQuota`
:raises: :class:`~openstack.exceptions.NotFoundException`
when no resource can be found.
"""
return self._get(_project_quota.ProjectQuota, project_id)
def update_project_quota(self, project_id, **attrs):
"""Update a project quota
:param project_id: A project ID.
:param attrs: The attributes to update on the project quota represented
by ``project quota``.
:returns: The updated project quota
:rtype: :class:`~openstack.key_manager.v1.project_quota.ProjectQuota`
"""
return self._update(_project_quota.ProjectQuota, project_id, **attrs)
# ========== Utilities ==========
def wait_for_status(

View File

@@ -0,0 +1,38 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from openstack import resource
class ProjectQuota(resource.Resource):
resource_key = 'project_quotas'
resources_key = 'project_quotas'
base_path = '/project-quotas'
# capabilities
allow_create = True
allow_fetch = True
allow_commit = True
allow_delete = True
allow_list = True
# Properties
#: Contains the configured quota value of the requested project for the secret resource.
secrets = resource.Body("secrets")
#: Contains the configured quota value of the requested project for the orders resource.
orders = resource.Body("orders")
#: Contains the configured quota value of the requested project for the containers resource.
containers = resource.Body("containers")
#: Contains the configured quota value of the requested project for the consumers resource.
consumers = resource.Body("consumers")
#: Contains the configured quota value of the requested project for the CAs resource.
cas = resource.Body("cas")

View File

@@ -0,0 +1,87 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from openstack import exceptions as sdk_exc
from openstack.key_manager.v1 import project_quota as _project_quota
from openstack.tests.functional import base
# NOTE(jbeen): Barbican policy may require 'key-manager:service-admin' for
# project quotas. Create and assign it per test project to avoid 403 errors.
ADMIN_ROLE_NAME = 'key-manager:service-admin'
class TestProjectQuota(base.BaseFunctionalTest):
def setUp(self):
super().setUp()
self.require_service('key-manager')
self.project_name = self.getUniqueString('project')
self.project = self.system_admin_cloud.identity.create_project(
name=self.project_name,
)
self.addCleanup(
self.system_admin_cloud.identity.delete_project, self.project
)
self.role = self.system_admin_cloud.identity.create_role(
name=ADMIN_ROLE_NAME
)
self.addCleanup(
self.system_admin_cloud.identity.delete_role, self.role.id
)
self.user_id = self.system_admin_cloud.current_user_id
self.system_admin_cloud.identity.assign_project_role_to_user(
project=self.project, user=self.user_id, role=self.role
)
self.addCleanup(
self.system_admin_cloud.identity.unassign_project_role_from_user,
project=self.project,
user=self.user_id,
role=self.role,
)
self._set_operator_cloud(project_id=self.project.id)
def test_project_quotas(self):
# update project quota
project_quota = self.operator_cloud.key_manager.update_project_quota(
self.project.id,
secrets=1,
orders=2,
containers=3,
consumers=4,
cas=5,
)
self.assertIsInstance(project_quota, _project_quota.ProjectQuota)
self.assertIsNotNone(project_quota.id)
self.assertEqual(1, project_quota.secrets)
self.assertEqual(2, project_quota.orders)
self.assertEqual(3, project_quota.containers)
self.assertEqual(4, project_quota.consumers)
self.assertEqual(5, project_quota.cas)
# get project quota
project_id = self.project.id
project_quota = self.operator_cloud.key_manager.get_project_quota(
project_id
)
self.assertIsInstance(project_quota, _project_quota.ProjectQuota)
# delete project quota
self.operator_cloud.key_manager.delete_project_quota(project_quota)
self.assertRaises(
sdk_exc.NotFoundException,
self.operator_cloud.key_manager.get_project_quota,
project_quota,
)

View File

@@ -0,0 +1,44 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from openstack.key_manager.v1 import project_quota
from openstack.tests.unit import base
EXAMPLE = {
'secrets': 10,
'orders': 20,
'containers': -1,
'consumers': 10,
'cas': 5,
}
class TestProjectQuota(base.TestCase):
def test_basic(self):
sot = project_quota.ProjectQuota()
self.assertEqual('project_quotas', sot.resource_key)
self.assertEqual('project_quotas', sot.resources_key)
self.assertEqual('/project-quotas', sot.base_path)
self.assertTrue(sot.allow_create)
self.assertTrue(sot.allow_fetch)
self.assertTrue(sot.allow_commit)
self.assertTrue(sot.allow_delete)
self.assertTrue(sot.allow_list)
def test_make_it(self):
sot = project_quota.ProjectQuota(**EXAMPLE)
self.assertEqual(EXAMPLE['secrets'], sot.secrets)
self.assertEqual(EXAMPLE['orders'], sot.orders)
self.assertEqual(EXAMPLE['containers'], sot.containers)
self.assertEqual(EXAMPLE['consumers'], sot.consumers)
self.assertEqual(EXAMPLE['cas'], sot.cas)

View File

@@ -13,6 +13,7 @@
from openstack.key_manager.v1 import _proxy
from openstack.key_manager.v1 import container
from openstack.key_manager.v1 import order
from openstack.key_manager.v1 import project_quota
from openstack.key_manager.v1 import secret
from openstack.key_manager.v1 import secret_store
from openstack.tests.unit import test_proxy_base
@@ -103,3 +104,25 @@ class TestKeyManagerSecret(TestKeyManagerProxy):
class TestKeyManagerSecretStore(TestKeyManagerProxy):
def test_secret_stores(self):
self.verify_list(self.proxy.secret_stores, secret_store.SecretStore)
class TestKeyManagerProjectQuota(TestKeyManagerProxy):
def test_project_quota_delete(self):
self.verify_delete(
self.proxy.delete_project_quota, project_quota.ProjectQuota, False
)
def test_project_quota_delete_ignore(self):
self.verify_delete(
self.proxy.delete_project_quota, project_quota.ProjectQuota, True
)
def test_project_quota_get(self):
self.verify_get(
self.proxy.get_project_quota, project_quota.ProjectQuota
)
def test_project_quota_update(self):
self.verify_update(
self.proxy.update_project_quota, project_quota.ProjectQuota
)

View File

@@ -0,0 +1,4 @@
---
features:
- |
Add support for key manager project quota API