Skip to content

Commit 74fc9a5

Browse files
Merge pull request #76 from lipak2345/bugfix-add-tenant-api
Add new Tenant APIs to support Object Scale
2 parents 19e7056 + 864973d commit 74fc9a5

File tree

7 files changed

+248
-2
lines changed

7 files changed

+248
-2
lines changed

README.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,8 @@ The following table shows the supported endpoints per API version.
189189
+--------------------------+---------+---------+
190190
| Namespace | ✓* | ✓* |
191191
+--------------------------+---------+---------+
192+
| Tenant(Flex) || ✓* |
193+
+--------------------------+---------+---------+
192194
| **Geo-Replication** |
193195
+--------------------------+---------+---------+
194196
| Replication Group |||
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import logging
2+
3+
log = logging.getLogger(__name__)
4+
5+
"""
6+
Tenant APIs only supported in ECS Flex
7+
"""
8+
9+
10+
class Tenant(object):
11+
def __init__(self, connection):
12+
"""
13+
Initialize a new instance
14+
"""
15+
self.conn = connection
16+
17+
def list(self):
18+
"""
19+
Gets the identifiers for all configured tenants.
20+
21+
Required role(s):
22+
23+
SYSTEM_ADMIN
24+
SYSTEM_MONITOR
25+
26+
Example JSON result from the API:
27+
28+
{
29+
u'tenant': [
30+
{
31+
u'id': u'tenant1'
32+
}
33+
]
34+
}
35+
"""
36+
log.info("Getting all tenants")
37+
return self.conn.get(url='object/tenants')
38+
39+
def get(self, tenant):
40+
"""
41+
Gets the details for the given tenant.
42+
43+
Required role(s):
44+
45+
SYSTEM_ADMIN
46+
SYSTEM_MONITOR
47+
TENANT_ADMIN
48+
49+
:param tenant: Tenant identifier for which details needs to
50+
be retrieved.
51+
"""
52+
log.info("Getting info for tenant '{0}'".format(tenant))
53+
54+
return self.conn.get(
55+
url='object/tenants/tenant/{0}'.format(tenant))
56+
57+
def create(self, account, default_data_services_vpool=None,
58+
is_encryption_enabled=False, default_bucket_block_size=None):
59+
"""
60+
Creates a namespace with the given details
61+
62+
Required role(s):
63+
64+
SYSTEM_ADMIN
65+
66+
Example JSON result from the API:
67+
{
68+
'account_id':account_id,
69+
'default_data_services_vpool': default_data_services_vpool,
70+
'default_bucket_block_size': default_bucket_block_size
71+
'is_encryption_enabled': is_encryption_enabled
72+
}
73+
"""
74+
payload = {
75+
'account_id': account,
76+
'default_bucket_block_size': default_bucket_block_size,
77+
'default_data_services_vpool': default_data_services_vpool,
78+
'is_encryption_enabled': is_encryption_enabled,
79+
}
80+
log.info("Creating tenant for account '{0}'".format(account))
81+
return self.conn.post('object/tenants/tenant', json_payload=payload)
82+
83+
# def update(self, tenant_id, default_data_services_vpool=None, vpools_added_to_allowed_vpools_list=[],
84+
# vpools_added_to_disallowed_vpools_list=[], vpools_removed_from_allowed_vpools_list=[],
85+
# vpools_removed_from_disallowed_vpools_list=[], tenant_admins=None, user_mapping=None,
86+
# default_bucket_block_size=None, external_group_admins=None, is_encryption_enabled=None,
87+
# is_stale_allowed=None):
88+
# """
89+
# Updates tenant details like replication group list, tenant admins and user mappings.
90+
# Replication group can be:
91+
# - Added to allowed or disallowed replication group list
92+
# - Removed from allowed or disallowed replication group list
93+
#
94+
# Required role(s):
95+
#
96+
# SYSTEM_ADMIN
97+
# TENANT_ADMIN
98+
#
99+
# There is no response body for this call
100+
#
101+
# Expect: HTTP/1.1 200 OK
102+
#
103+
# :param tenant_id: Tenant identifier whose details needs to be updated
104+
# :param default_data_services_vpool: Default replication group identifier when creating buckets
105+
# :param vpools_added_to_allowed_vpools_list: List of replication group identifier which will be added in the
106+
# allowed List for allowing tenant access
107+
# :param vpools_added_to_disallowed_vpools_list: List of replication group identifier which will be added in the
108+
# disallowed list for prohibiting tenant access
109+
# :param vpools_removed_from_allowed_vpools_list: List of replication group identifier which will be removed
110+
# from allowed list
111+
# :param vpools_removed_from_disallowed_vpools_list: List of replication group identifier which will be removed
112+
# from disallowed list for removing their prohibition tenant access
113+
# :param tenant_admins: Comma separated list of tenant admins
114+
# :param user_mapping: List of user mapping objects
115+
# :param default_bucket_block_size: Default bucket quota size
116+
# :param external_group_admins: List of groups from AD Server
117+
# :param is_encryption_enabled: Update encryption for the tenant. If null then encryption will not be updated.
118+
# :param is_stale_allowed: Flag to allow stale data within the tenant. If null then stale allowance will not be
119+
# updated
120+
# """
121+
# payload = {
122+
# "default_data_services_vpool": default_data_services_vpool,
123+
# "vpools_added_to_allowed_vpools_list": vpools_added_to_allowed_vpools_list,
124+
# "vpools_added_to_disallowed_vpools_list": vpools_added_to_disallowed_vpools_list,
125+
# "vpools_removed_from_allowed_vpools_list": vpools_removed_from_allowed_vpools_list,
126+
# "vpools_removed_from_disallowed_vpools_list": vpools_removed_from_disallowed_vpools_list,
127+
# "tenant_admins": tenant_admins,
128+
# "user_mapping": user_mapping,
129+
# "default_bucket_block_size": default_bucket_block_size,
130+
# "external_group_admins": external_group_admins,
131+
# "is_encryption_enabled": is_encryption_enabled,
132+
# "is_stale_allowed": is_stale_allowed
133+
# }
134+
# # FIXME: According to the API, this call should return the updated object, but it does not
135+
# log.info("Updating tenant ID '{}'".format(tenant_id))
136+
# return self.conn.put('object/tenants/tenant/{}'.format(tenant_id), json_payload=payload)
137+
138+
def delete(self, tenant_id):
139+
"""
140+
Deactivates and deletes the given tenant.
141+
142+
Required role(s):
143+
144+
SYSTEM_ADMIN
145+
146+
There is no response body for this call
147+
148+
Expect: HTTP/1.1 200 OK
149+
150+
:param tenant_id: An active tenant identifier which needs to be deleted
151+
"""
152+
log.info("Deleting tenant ID '{}'".format(tenant_id))
153+
# FIXME: This should be a DELETE request
154+
return self.conn.post('object/tenants/tenant/{}/delete'.format(tenant_id))

ecsclient/schemas.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,33 @@
6565
]
6666
}
6767

68+
TENANTS = {
69+
"type": "object",
70+
"properties": {
71+
"tenant": {
72+
"type": "array",
73+
"minItems": 1,
74+
"items": {
75+
"type": "object",
76+
"properties": {
77+
"id": {"type": "string"},
78+
},
79+
"required": [
80+
"id",
81+
]
82+
},
83+
}
84+
},
85+
"required": ["tenant"]
86+
}
87+
TENANT = {
88+
"type": "object",
89+
"properties": {
90+
"id": {"type": "string"},
91+
},
92+
"required": ["id"]
93+
}
94+
6895
NAMESPACE = {
6996
"type": "object",
7097
"properties": {

ecsclient/v3/client.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from ecsclient.v3.metering import billing
88
from ecsclient.v3.monitoring import capacity, dashboard, events, alerts
99
from ecsclient.v3.multitenancy import namespace
10+
from ecsclient.v3.multitenancy import tenant
1011
from ecsclient.v3.geo_replication import replication_group, temporary_failed_zone
1112
from ecsclient.v3.provisioning import base_url, bucket, data_store, storage_pool, \
1213
virtual_data_center, node, vdc_keystore
@@ -52,7 +53,7 @@ def __init__(self, *args, **kwargs):
5253

5354
# Multi-tenancy
5455
self.namespace = namespace.Namespace(self)
55-
56+
self.tenant = tenant.Tenant(self)
5657
# Geo-replication
5758
self.replication_group = replication_group.ReplicationGroup(self)
5859
self.temporary_failed_zone = temporary_failed_zone.TemporaryFailedZone(self)

ecsclient/v3/multitenancy/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
from ecsclient.common.multitenancy import namespace
2+
from ecsclient.common.multitenancy import tenant
23

34
namespace = namespace
5+
tenant = tenant

tests/functional/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ def _get_config(self):
2929
license_file = config.get('func_test', 'license_file')
3030
with open(license_file) as f:
3131
self.license_text = f.read()
32+
self.override_header = config.get("func_test", 'override_header')
3233
else:
3334
self.skip_tests = True
3435

@@ -38,7 +39,8 @@ def _get_client(self):
3839
username=self.username,
3940
password=self.password,
4041
ecs_endpoint=self.ecs_endpoint,
41-
token_endpoint=self.token_endpoint)
42+
token_endpoint=self.token_endpoint,
43+
override_header=self.override_header)
4244

4345
def setUp(self):
4446
super(BaseTestCase, self).setUp()

tests/functional/test_tenant.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import os
2+
from ecsclient import schemas
3+
from ecsclient.common.exceptions import ECSClientException
4+
from tests import functional
5+
from six.moves import configparser
6+
7+
8+
class TestTenant(functional.BaseTestCase):
9+
"""
10+
Test conf sample
11+
[func_test]
12+
token_endpoint = https://10.0.1.1:4443/login
13+
ecs_endpoint = https://10.0.1.1:4443
14+
username = root
15+
password = k912oz2chpsy8tny
16+
api_version = 3
17+
license_file = /home/user/license.lic
18+
override_header = true
19+
account_id = 6bd95656-42df-4e9e-9b19-b05a660eca81
20+
"""
21+
def __init__(self, *args, **kwargs):
22+
super(TestTenant, self).__init__(*args, **kwargs)
23+
config_file = os.environ.get('ECS_TEST_CONFIG_FILE',
24+
os.path.join(os.getcwd(), "tests/test.conf"))
25+
config = configparser.ConfigParser()
26+
config.read(config_file)
27+
self.config = config
28+
if config.has_section('func_test'):
29+
self.tenant = config.get('func_test', 'account_id')
30+
else:
31+
self.skip_tests = True
32+
33+
def setUp(self):
34+
super(TestTenant, self).setUp()
35+
self.create_account()
36+
if self.skip_tests:
37+
self.skipTest('SKIPPING FUNCTIONAL TESTS DUE TO NO CONFIG')
38+
self.client.tenant.create(self.tenant)
39+
40+
def tearDown(self):
41+
super(TestTenant, self).tearDown()
42+
try:
43+
self.client.tenant.delete(self.tenant)
44+
except ECSClientException:
45+
pass
46+
47+
def test_tenants_list(self):
48+
response = self.client.tenant.list()
49+
self.assertValidSchema(response, schemas.TENANTS)
50+
51+
def test_tenants_get_one(self):
52+
response = self.client.tenant.get(self.tenant)
53+
self.assertValidSchema(response, schemas.TENANT)
54+
55+
def test_tenants_delete(self):
56+
self.client.tenant.delete(self.tenant)
57+
f = self.client.tenant.get
58+
self.assertRaises(ECSClientException, f, self.tenant)

0 commit comments

Comments
 (0)