Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,8 @@ The following table shows the supported endpoints per API version.
+--------------------------+---------+---------+
| Namespace | ✓* | ✓* |
+--------------------------+---------+---------+
| Tenant(Flex) | ✗ | ✓* |
+--------------------------+---------+---------+
| **Geo-Replication** |
+--------------------------+---------+---------+
| Replication Group | ✓ | ✓ |
Expand Down
154 changes: 154 additions & 0 deletions ecsclient/common/multitenancy/tenant.py
Original file line number Diff line number Diff line change
@@ -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))
27 changes: 27 additions & 0 deletions ecsclient/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
3 changes: 2 additions & 1 deletion ecsclient/v3/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions ecsclient/v3/multitenancy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from ecsclient.common.multitenancy import namespace
from ecsclient.common.multitenancy import tenant

namespace = namespace
tenant = tenant
4 changes: 3 additions & 1 deletion tests/functional/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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()
Expand Down
58 changes: 58 additions & 0 deletions tests/functional/test_tenant.py
Original file line number Diff line number Diff line change
@@ -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)