From fd45aa5ded8760f01fd5d8bfc4b5996e4ad09acf Mon Sep 17 00:00:00 2001 From: Ye Chen Date: Fri, 11 Apr 2025 16:05:16 -0400 Subject: [PATCH 1/3] obj quota --- linode_api4/groups/object_storage.py | 16 +++++++ linode_api4/objects/object_storage.py | 39 +++++++++++++++ test/fixtures/object-storage_quotas.json | 25 ++++++++++ ...t-storage_quotas_obj-objects-us-ord-1.json | 9 ++++ ...age_quotas_obj-objects-us-ord-1_usage.json | 4 ++ test/unit/objects/object_storage_test.py | 48 +++++++++++++++++++ 6 files changed, 141 insertions(+) create mode 100644 test/fixtures/object-storage_quotas.json create mode 100644 test/fixtures/object-storage_quotas_obj-objects-us-ord-1.json create mode 100644 test/fixtures/object-storage_quotas_obj-objects-us-ord-1_usage.json diff --git a/linode_api4/groups/object_storage.py b/linode_api4/groups/object_storage.py index eb6a296b7..77fc57eda 100644 --- a/linode_api4/groups/object_storage.py +++ b/linode_api4/groups/object_storage.py @@ -21,6 +21,7 @@ ObjectStorageCluster, ObjectStorageKeyPermission, ObjectStorageKeys, + ObjectStorageQuota, ) from linode_api4.util import drop_null_keys @@ -517,3 +518,18 @@ def object_url_create( ) return MappedObject(**result) + + def quotas(self, *filters): + """ + Lists the active ObjectStorage-related quotas applied to your account. + + API Documentation: TBD + + :param filters: Any number of filters to apply to this query. + See :doc:`Filtering Collections` + for more details on filtering. + + :returns: A list of Object Storage Quotas that matched the query. + :rtype: PaginatedList of ObjectStorageQuota + """ + return self.client._get_and_filter(ObjectStorageQuota, *filters) diff --git a/linode_api4/objects/object_storage.py b/linode_api4/objects/object_storage.py index be1fd0cc7..f717915ad 100644 --- a/linode_api4/objects/object_storage.py +++ b/linode_api4/objects/object_storage.py @@ -566,3 +566,42 @@ class ObjectStorageKeys(Base): "limited": Property(), "regions": Property(unordered=True), } + + +class ObjectStorageQuota(Base): + """ + An Object Storage related quota information on your account. + Object Storage Quota related features are under v4beta and may not currently be available to all users. + + API documentation: TBD + """ + + api_endpoint = "/object-storage/quotas/{quota_id}" + id_attribute = "quota_id" + + properties = { + "quota_id": Property(identifier=True), + "quota_name": Property(), + "endpoint_type": Property(), + "s3_endpoint": Property(), + "description": Property(), + "quota_limit": Property(), + "resource_metric": Property(), + } + + def usage(self): + """ + Gets usage data for a specific ObjectStorage Quota resource you can have on your account and the current usage for that resource. + + API documentation: TBD + + :returns: The Object Storage Quota usage. + :rtype: MappedObject + """ + + result = self._client.get( + f"{type(self).api_endpoint}/usage", + model=self, + ) + + return MappedObject(**result) diff --git a/test/fixtures/object-storage_quotas.json b/test/fixtures/object-storage_quotas.json new file mode 100644 index 000000000..e831d7303 --- /dev/null +++ b/test/fixtures/object-storage_quotas.json @@ -0,0 +1,25 @@ +{ + "data": [ + { + "quota_id": "obj-objects-us-ord-1", + "quota_name": "Object Storage Maximum Objects", + "description": "Maximum number of Objects this customer is allowed to have on this endpoint.", + "endpoint_type": "E1", + "s3_endpoint": "us-iad-1.linodeobjects.com", + "quota_limit": 50, + "resource_metric": "object" + }, + { + "quota_id": "obj-bucket-us-ord-1", + "quota_name": "Object Storage Maximum Buckets", + "description": "Maximum number of buckets this customer is allowed to have on this endpoint.", + "endpoint_type": "E1", + "s3_endpoint": "us-iad-1.linodeobjects.com", + "quota_limit": 50, + "resource_metric": "bucket" + } + ], + "page": 1, + "pages": 1, + "results": 2 +} \ No newline at end of file diff --git a/test/fixtures/object-storage_quotas_obj-objects-us-ord-1.json b/test/fixtures/object-storage_quotas_obj-objects-us-ord-1.json new file mode 100644 index 000000000..e01d743c3 --- /dev/null +++ b/test/fixtures/object-storage_quotas_obj-objects-us-ord-1.json @@ -0,0 +1,9 @@ +{ + "quota_id": "obj-objects-us-ord-1", + "quota_name": "Object Storage Maximum Objects", + "description": "Maximum number of Objects this customer is allowed to have on this endpoint.", + "endpoint_type": "E1", + "s3_endpoint": "us-iad-1.linodeobjects.com", + "quota_limit": 50, + "resource_metric": "object" +} \ No newline at end of file diff --git a/test/fixtures/object-storage_quotas_obj-objects-us-ord-1_usage.json b/test/fixtures/object-storage_quotas_obj-objects-us-ord-1_usage.json new file mode 100644 index 000000000..59b306044 --- /dev/null +++ b/test/fixtures/object-storage_quotas_obj-objects-us-ord-1_usage.json @@ -0,0 +1,4 @@ +{ + "quota_limit": 100, + "usage": 10 +} diff --git a/test/unit/objects/object_storage_test.py b/test/unit/objects/object_storage_test.py index 396813b3d..3ec841ab1 100644 --- a/test/unit/objects/object_storage_test.py +++ b/test/unit/objects/object_storage_test.py @@ -6,6 +6,7 @@ ObjectStorageACL, ObjectStorageBucket, ObjectStorageCluster, + ObjectStorageQuota, ) @@ -284,3 +285,50 @@ def test_object_acl_config_update(self): "name": "example", }, ) + + def test_quota_get_and_list(self): + quota = ObjectStorageQuota( + self.client, + "obj-objects-us-ord-1", + ) + + self.assertIsNotNone(quota) + self.assertEqual(quota.quota_id, "obj-objects-us-ord-1") + self.assertEqual(quota.quota_name, "Object Storage Maximum Objects") + self.assertEqual( + quota.description, + "Maximum number of Objects this customer is allowed to have on this endpoint.", + ) + self.assertEqual(quota.endpoint_type, "E1") + self.assertEqual(quota.s3_endpoint, "us-iad-1.linodeobjects.com") + self.assertEqual(quota.quota_limit, 50) + self.assertEqual(quota.resource_metric, "object") + + quota_usage_url = "/object-storage/quotas/obj-objects-us-ord-1/usage" + with self.mock_get(quota_usage_url) as m: + usage = quota.usage() + self.assertIsNotNone(usage) + self.assertEqual(m.call_url, quota_usage_url) + self.assertEqual(usage.quota_limit, 100) + self.assertEqual(usage.usage, 10) + + quota_list_url = "/object-storage/quotas" + with self.mock_get(quota_list_url) as m: + quotas = self.client.object_storage.quotas() + self.assertIsNotNone(quotas) + self.assertEqual(m.call_url, quota_list_url) + self.assertEqual(len(quotas), 2) + self.assertEqual(quotas[0].quota_id, "obj-objects-us-ord-1") + self.assertEqual( + quotas[0].quota_name, "Object Storage Maximum Objects" + ) + self.assertEqual( + quotas[0].description, + "Maximum number of Objects this customer is allowed to have on this endpoint.", + ) + self.assertEqual(quotas[0].endpoint_type, "E1") + self.assertEqual( + quotas[0].s3_endpoint, "us-iad-1.linodeobjects.com" + ) + self.assertEqual(quotas[0].quota_limit, 50) + self.assertEqual(quotas[0].resource_metric, "object") From 2a6d0da9781c6e1876c60f7b8ef509eefa505b87 Mon Sep 17 00:00:00 2001 From: Ye Chen Date: Fri, 11 Apr 2025 16:06:29 -0400 Subject: [PATCH 2/3] add comment --- test/unit/objects/object_storage_test.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/unit/objects/object_storage_test.py b/test/unit/objects/object_storage_test.py index 3ec841ab1..b7ff7e49c 100644 --- a/test/unit/objects/object_storage_test.py +++ b/test/unit/objects/object_storage_test.py @@ -287,6 +287,9 @@ def test_object_acl_config_update(self): ) def test_quota_get_and_list(self): + """ + Test that you can get and list an Object storage quota and usage information. + """ quota = ObjectStorageQuota( self.client, "obj-objects-us-ord-1", From 348b9a536beceb0d1a08fa673441aa0dac0b9f69 Mon Sep 17 00:00:00 2001 From: Ye Chen Date: Wed, 16 Apr 2025 14:09:39 -0400 Subject: [PATCH 3/3] build json object --- linode_api4/objects/object_storage.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/linode_api4/objects/object_storage.py b/linode_api4/objects/object_storage.py index f717915ad..b0d095b98 100644 --- a/linode_api4/objects/object_storage.py +++ b/linode_api4/objects/object_storage.py @@ -51,6 +51,16 @@ class ObjectStorageEndpoint(JSONObject): s3_endpoint: Optional[str] = None +@dataclass +class ObjectStorageQuotaUsage(JSONObject): + """ + ObjectStorageQuotaUsage contains the fields of an object storage quota usage information. + """ + + quota_limit: int = 0 + usage: int = 0 + + class ObjectStorageType(Base): """ An ObjectStorageType represents the structure of a valid Object Storage type. @@ -596,7 +606,7 @@ def usage(self): API documentation: TBD :returns: The Object Storage Quota usage. - :rtype: MappedObject + :rtype: ObjectStorageQuotaUsage """ result = self._client.get( @@ -604,4 +614,4 @@ def usage(self): model=self, ) - return MappedObject(**result) + return ObjectStorageQuotaUsage.from_json(result)