From b9e716e0cd078da15b0e7ec10635b5b56993f1aa Mon Sep 17 00:00:00 2001 From: BalakB Date: Wed, 20 Jan 2021 10:08:34 -0500 Subject: [PATCH 1/2] Add new Tenant APIs to support Object Scale --- README.rst | 2 + ecsclient/common/multitenancy/tenant.py | 154 ++++++++++++++++++++++++ ecsclient/schemas.py | 27 +++++ ecsclient/v3/client.py | 3 +- ecsclient/v3/multitenancy/__init__.py | 2 + tests/functional/__init__.py | 4 +- tests/functional/test_tenant.py | 58 +++++++++ 7 files changed, 248 insertions(+), 2 deletions(-) create mode 100644 ecsclient/common/multitenancy/tenant.py create mode 100644 tests/functional/test_tenant.py diff --git a/README.rst b/README.rst index db77409..46b860a 100644 --- a/README.rst +++ b/README.rst @@ -189,6 +189,8 @@ The following table shows the supported endpoints per API version. +--------------------------+---------+---------+ | Namespace | ✓* | ✓* | +--------------------------+---------+---------+ +| Tenant(Flex) | ✗ | ✓* | ++--------------------------+---------+---------+ | **Geo-Replication** | +--------------------------+---------+---------+ | Replication Group | ✓ | ✓ | diff --git a/ecsclient/common/multitenancy/tenant.py b/ecsclient/common/multitenancy/tenant.py new file mode 100644 index 0000000..dccefe8 --- /dev/null +++ b/ecsclient/common/multitenancy/tenant.py @@ -0,0 +1,154 @@ +import logging + +log = logging.getLogger(__name__) + +""" +Tenant APIs only supported in ECS Flex +""" + + +class Tenant(object): + def __init__(self, connection): + """ + Initialize a new instance + """ + self.conn = connection + + def list(self): + """ + Gets the identifiers for all configured tenants. + + Required role(s): + + SYSTEM_ADMIN + SYSTEM_MONITOR + + Example JSON result from the API: + + { + u'tenant': [ + { + u'id': u'tenant1' + } + ] + } + """ + log.info("Getting all tenants") + return self.conn.get(url='object/tenants') + + def get(self, tenant): + """ + Gets the details for the given tenant. + + Required role(s): + + SYSTEM_ADMIN + SYSTEM_MONITOR + TENANT_ADMIN + + :param tenant: Tenant identifier for which details needs to + be retrieved. + """ + log.info("Getting info for tenant '{0}'".format(tenant)) + + return self.conn.get( + url='object/tenants/tenant/{0}'.format(tenant)) + + def create(self, account, default_data_services_vpool=None, + is_encryption_enabled=False, default_bucket_block_size=None): + """ + Creates a namespace with the given details + + Required role(s): + + SYSTEM_ADMIN + + Example JSON result from the API: + { + 'account_id':account_id, + 'default_data_services_vpool': default_data_services_vpool, + 'default_bucket_block_size': default_bucket_block_size + 'is_encryption_enabled': is_encryption_enabled + } + """ + payload = { + 'account_id': account, + 'default_bucket_block_size': default_bucket_block_size, + 'default_data_services_vpool': default_data_services_vpool, + 'is_encryption_enabled': is_encryption_enabled, + } + log.info("Creating tenant for account '{0}'".format(account)) + return self.conn.post('object/tenants/tenant', json_payload=payload) + + # def update(self, tenant_id, default_data_services_vpool=None, vpools_added_to_allowed_vpools_list=[], + # vpools_added_to_disallowed_vpools_list=[], vpools_removed_from_allowed_vpools_list=[], + # vpools_removed_from_disallowed_vpools_list=[], tenant_admins=None, user_mapping=None, + # default_bucket_block_size=None, external_group_admins=None, is_encryption_enabled=None, + # is_stale_allowed=None): + # """ + # Updates tenant details like replication group list, tenant admins and user mappings. + # Replication group can be: + # - Added to allowed or disallowed replication group list + # - Removed from allowed or disallowed replication group list + # + # Required role(s): + # + # SYSTEM_ADMIN + # TENANT_ADMIN + # + # There is no response body for this call + # + # Expect: HTTP/1.1 200 OK + # + # :param tenant_id: Tenant identifier whose details needs to be updated + # :param default_data_services_vpool: Default replication group identifier when creating buckets + # :param vpools_added_to_allowed_vpools_list: List of replication group identifier which will be added in the + # allowed List for allowing tenant access + # :param vpools_added_to_disallowed_vpools_list: List of replication group identifier which will be added in the + # disallowed list for prohibiting tenant access + # :param vpools_removed_from_allowed_vpools_list: List of replication group identifier which will be removed + # from allowed list + # :param vpools_removed_from_disallowed_vpools_list: List of replication group identifier which will be removed + # from disallowed list for removing their prohibition tenant access + # :param tenant_admins: Comma separated list of tenant admins + # :param user_mapping: List of user mapping objects + # :param default_bucket_block_size: Default bucket quota size + # :param external_group_admins: List of groups from AD Server + # :param is_encryption_enabled: Update encryption for the tenant. If null then encryption will not be updated. + # :param is_stale_allowed: Flag to allow stale data within the tenant. If null then stale allowance will not be + # updated + # """ + # payload = { + # "default_data_services_vpool": default_data_services_vpool, + # "vpools_added_to_allowed_vpools_list": vpools_added_to_allowed_vpools_list, + # "vpools_added_to_disallowed_vpools_list": vpools_added_to_disallowed_vpools_list, + # "vpools_removed_from_allowed_vpools_list": vpools_removed_from_allowed_vpools_list, + # "vpools_removed_from_disallowed_vpools_list": vpools_removed_from_disallowed_vpools_list, + # "tenant_admins": tenant_admins, + # "user_mapping": user_mapping, + # "default_bucket_block_size": default_bucket_block_size, + # "external_group_admins": external_group_admins, + # "is_encryption_enabled": is_encryption_enabled, + # "is_stale_allowed": is_stale_allowed + # } + # # FIXME: According to the API, this call should return the updated object, but it does not + # log.info("Updating tenant ID '{}'".format(tenant_id)) + # return self.conn.put('object/tenants/tenant/{}'.format(tenant_id), json_payload=payload) + + def delete(self, tenant_id): + """ + Deactivates and deletes the given tenant. + + Required role(s): + + SYSTEM_ADMIN + + There is no response body for this call + + Expect: HTTP/1.1 200 OK + + :param tenant_id: An active tenant identifier which needs to be deleted + """ + log.info("Deleting tenant ID '{}'".format(tenant_id)) + # FIXME: This should be a DELETE request + return self.conn.post('object/tenants/tenant/{}/delete'.format(tenant_id)) diff --git a/ecsclient/schemas.py b/ecsclient/schemas.py index 6b5ce49..86a87ec 100644 --- a/ecsclient/schemas.py +++ b/ecsclient/schemas.py @@ -65,6 +65,33 @@ ] } +TENANTS = { + "type": "object", + "properties": { + "tenant": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "properties": { + "id": {"type": "string"}, + }, + "required": [ + "id", + ] + }, + } + }, + "required": ["tenant"] +} +TENANT = { + "type": "object", + "properties": { + "id": {"type": "string"}, + }, + "required": ["id"] +} + NAMESPACE = { "type": "object", "properties": { diff --git a/ecsclient/v3/client.py b/ecsclient/v3/client.py index b0e4699..9dbfd10 100644 --- a/ecsclient/v3/client.py +++ b/ecsclient/v3/client.py @@ -7,6 +7,7 @@ from ecsclient.v3.metering import billing from ecsclient.v3.monitoring import capacity, dashboard, events, alerts from ecsclient.v3.multitenancy import namespace +from ecsclient.v3.multitenancy import tenant from ecsclient.v3.geo_replication import replication_group, temporary_failed_zone from ecsclient.v3.provisioning import base_url, bucket, data_store, storage_pool, \ virtual_data_center, node, vdc_keystore @@ -52,7 +53,7 @@ def __init__(self, *args, **kwargs): # Multi-tenancy self.namespace = namespace.Namespace(self) - + self.tenant = tenant.Tenant(self) # Geo-replication self.replication_group = replication_group.ReplicationGroup(self) self.temporary_failed_zone = temporary_failed_zone.TemporaryFailedZone(self) diff --git a/ecsclient/v3/multitenancy/__init__.py b/ecsclient/v3/multitenancy/__init__.py index bd47c4c..61edd9a 100644 --- a/ecsclient/v3/multitenancy/__init__.py +++ b/ecsclient/v3/multitenancy/__init__.py @@ -1,3 +1,5 @@ from ecsclient.common.multitenancy import namespace +from ecsclient.common.multitenancy import tenant namespace = namespace +tenant = tenant diff --git a/tests/functional/__init__.py b/tests/functional/__init__.py index 2882734..bc9b296 100644 --- a/tests/functional/__init__.py +++ b/tests/functional/__init__.py @@ -29,6 +29,7 @@ def _get_config(self): license_file = config.get('func_test', 'license_file') with open(license_file) as f: self.license_text = f.read() + self.override_header = config.get("func_test", 'override_header') else: self.skip_tests = True @@ -38,7 +39,8 @@ def _get_client(self): username=self.username, password=self.password, ecs_endpoint=self.ecs_endpoint, - token_endpoint=self.token_endpoint) + token_endpoint=self.token_endpoint, + override_header=self.override_header) def setUp(self): super(BaseTestCase, self).setUp() diff --git a/tests/functional/test_tenant.py b/tests/functional/test_tenant.py new file mode 100644 index 0000000..8b90cd3 --- /dev/null +++ b/tests/functional/test_tenant.py @@ -0,0 +1,58 @@ +import os +from ecsclient import schemas +from ecsclient.common.exceptions import ECSClientException +from tests import functional +from six.moves import configparser + + +class TestTenant(functional.BaseTestCase): + """ + Test conf sample + [func_test] + token_endpoint = https://10.0.1.1:4443/login + ecs_endpoint = https://10.0.1.1:4443 + username = root + password = k912oz2chpsy8tny + api_version = 3 + license_file = /home/user/license.lic + override_header = true + account_id = 6bd95656-42df-4e9e-9b19-b05a660eca81 + """ + def __init__(self, *args, **kwargs): + super(TestTenant, self).__init__(*args, **kwargs) + config_file = os.environ.get('ECS_TEST_CONFIG_FILE', + os.path.join(os.getcwd(), "tests/test.conf")) + config = configparser.ConfigParser() + config.read(config_file) + self.config = config + if config.has_section('func_test'): + self.tenant = config.get('func_test', 'account_id') + else: + self.skip_tests = True + + def setUp(self): + super(TestTenant, self).setUp() + self.create_account() + if self.skip_tests: + self.skipTest('SKIPPING FUNCTIONAL TESTS DUE TO NO CONFIG') + self.client.tenant.create(self.tenant) + + def tearDown(self): + super(TestTenant, self).tearDown() + try: + self.client.tenant.delete(self.tenant) + except ECSClientException: + pass + + def test_tenants_list(self): + response = self.client.tenant.list() + self.assertValidSchema(response, schemas.TENANTS) + + def test_tenants_get_one(self): + response = self.client.tenant.get(self.tenant) + self.assertValidSchema(response, schemas.TENANT) + + def test_tenants_delete(self): + self.client.tenant.delete(self.tenant) + f = self.client.tenant.get + self.assertRaises(ECSClientException, f, self.tenant) From 864973df5d901000a3d2f4b76fee5c812e8c6112 Mon Sep 17 00:00:00 2001 From: BalakB Date: Wed, 20 Jan 2021 14:11:26 -0500 Subject: [PATCH 2/2] Fix whitespace issues --- ecsclient/common/multitenancy/tenant.py | 2 +- ecsclient/v3/multitenancy/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ecsclient/common/multitenancy/tenant.py b/ecsclient/common/multitenancy/tenant.py index dccefe8..a7a41f4 100644 --- a/ecsclient/common/multitenancy/tenant.py +++ b/ecsclient/common/multitenancy/tenant.py @@ -3,7 +3,7 @@ log = logging.getLogger(__name__) """ -Tenant APIs only supported in ECS Flex +Tenant APIs only supported in ECS Flex """ diff --git a/ecsclient/v3/multitenancy/__init__.py b/ecsclient/v3/multitenancy/__init__.py index 61edd9a..70f0868 100644 --- a/ecsclient/v3/multitenancy/__init__.py +++ b/ecsclient/v3/multitenancy/__init__.py @@ -1,5 +1,5 @@ from ecsclient.common.multitenancy import namespace -from ecsclient.common.multitenancy import tenant +from ecsclient.common.multitenancy import tenant namespace = namespace tenant = tenant