diff --git a/ranger-tempest-plugin/ranger_tempest_plugin/clients.py b/ranger-tempest-plugin/ranger_tempest_plugin/clients.py index 084c44e4..ca629a47 100755 --- a/ranger-tempest-plugin/ranger_tempest_plugin/clients.py +++ b/ranger-tempest-plugin/ranger_tempest_plugin/clients.py @@ -16,6 +16,7 @@ from ranger_tempest_plugin.services import base_client from ranger_tempest_plugin.services.cms_client import CmsClient from ranger_tempest_plugin.services.fms_client import FmsClient +from ranger_tempest_plugin.services.grp_client import GrpClient from ranger_tempest_plugin.services.ims_client import ImsClient from ranger_tempest_plugin.services.rms_client import RmsClient @@ -45,3 +46,7 @@ class OrmClientManager(clients.Manager): CONF.identity.catalog_type, CONF.identity.region, CONF.ranger.RANGER_IMS_BASE_URL) + self.grp_client = GrpClient(base_client.RangerAuthProvider(credentials), + CONF.identity.catalog_type, + CONF.identity.region, + CONF.ranger.RANGER_CMS_BASE_URL) diff --git a/ranger-tempest-plugin/ranger_tempest_plugin/schemas/group_schema.py b/ranger-tempest-plugin/ranger_tempest_plugin/schemas/group_schema.py new file mode 100644 index 00000000..ac45662a --- /dev/null +++ b/ranger-tempest-plugin/ranger_tempest_plugin/schemas/group_schema.py @@ -0,0 +1,113 @@ +# Copyright 2017 +# All Rights Reserved. +# +# 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 + +_status = { + 'type': 'string', + 'enum': ['Success', 'no regions', 'Error', 'Pending', 'Submitted'] +} + +_links = { + 'type': 'object', + 'properties': { + 'self': {'type': 'string'} + }, + 'required': ['self'] +} + +_region = { + 'type': 'object', + 'properties': { + 'added': {'type': 'string'}, + 'id': {'type': 'string'}, + 'links': _links + }, + 'required': ['added', 'id', 'links'] +} + +_group = { + 'type': 'object', + 'properties': { + 'id': {'type': 'string'}, + 'links': _links, + 'created': {'type': 'string', 'format': 'date-time'} + }, + 'required': ['id', 'links', 'created'] +} + +create_group = { + 'status_code': [201], + 'response_body': { + 'type': 'object', + 'properties': { + 'group': _group, + 'transaction_id': {'type': 'string'} + }, + 'required': ['group', 'transaction_id'] + } +} + +get_group = { + 'status_code': [200], + 'response_body': { + 'type': 'object', + 'properties': { + 'status': _status, + 'uuid': {'type': 'string'}, + 'enabled': {'type': 'boolean'}, + 'domain_name': {'type': 'string'}, + 'name': {'type': 'string'}, + 'regions': {'type': 'array'}, + 'description': {'type': 'string'} + }, + 'required': ['status', 'uuid', 'enabled', 'domain_name', 'name', + 'regions', 'description'] + } +} + +list_groups = { + 'status_code': [200], + 'response_body': { + 'type': 'object', + 'properties': { + 'groups': { + 'type': 'array', + 'items': { + 'type': 'object', + 'properties': { + 'status': _status, + 'description': {'type': 'string'}, + 'enabled': {'type': 'boolean'}, + 'domain_name': {'type': 'string'}, + 'regions': { + 'type': 'array', + 'items': {'type': 'string'} + }, + 'id': {'type': 'string'}, + 'name': {'type': 'string'} + }, + 'required': ['status', 'description', 'enabled', + 'domain_name', 'regions', 'id', 'name'] + } + } + }, + 'required': ['groups'] + } +} + +delete_group = { + 'status_code': [204] +} + +delete_region_from_group = delete_group diff --git a/ranger-tempest-plugin/ranger_tempest_plugin/services/grp_client.py b/ranger-tempest-plugin/ranger_tempest_plugin/services/grp_client.py new file mode 100755 index 00000000..23e139d8 --- /dev/null +++ b/ranger-tempest-plugin/ranger_tempest_plugin/services/grp_client.py @@ -0,0 +1,56 @@ +# Copyright 2016 AT&T Corp +# All Rights Reserved. +# +# 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. + +import json +import urllib + +from ranger_tempest_plugin.schemas import group_schema as schema +from ranger_tempest_plugin.services import base_client + +from tempest import config + +CONF = config.CONF + + +class GrpClient(base_client.RangerClientBase): + + cms_url = CONF.ranger.RANGER_CMS_BASE_URL + version = 'v1' + + def create_group(self, **kwargs): + uri = '%s/%s/orm/groups' % (self.cms_url, self.version) + post_body = json.dumps(kwargs) + return self.post_request(uri, post_body, schema.create_group) + + def get_group(self, identifier): + uri = '%s/%s/orm/groups/%s' \ + % (self.cms_url, self.version, identifier) + return self.get_request(uri, schema.get_group) + + def list_groups(self, filter=None): + uri = '%s/%s/orm/groups' % (self.cms_url, self.version) + if filter is not None: + uri += '?' + urllib.urlencode(filter) + return self.get_request(uri, schema.list_groups) + + def delete_region_from_group(self, group_id, region_id): + uri = '%s/%s/orm/groups/%s/regions/%s' % ( + self.cms_url, self.version, group_id, region_id) + return self.delete_request(uri, schema.delete_region_from_group) + + def delete_group(self, group_id): + uri = '%s/%s/orm/groups/%s' \ + % (self.cms_url, self.version, group_id) + return self.delete_request(uri, schema.delete_group) diff --git a/ranger-tempest-plugin/ranger_tempest_plugin/tests/api/grp_base.py b/ranger-tempest-plugin/ranger_tempest_plugin/tests/api/grp_base.py new file mode 100755 index 00000000..b5c4dd31 --- /dev/null +++ b/ranger-tempest-plugin/ranger_tempest_plugin/tests/api/grp_base.py @@ -0,0 +1,176 @@ +# Copyright 2016 AT&T Corp +# All Rights Reserved. +# +# 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. + +import time + +from oslo_log import log as logging +from ranger_tempest_plugin.tests.api import base +from tempest.common.utils import data_utils +from tempest import config +from tempest.lib import exceptions + +CONF = config.CONF +LOG = logging.getLogger(__name__) + + +class GrpBaseOrmTest(base.BaseOrmTest): + credentials = ['admin', 'primary', 'alt'] + + @classmethod + def setup_clients(cls): + super(GrpBaseOrmTest, cls).setup_clients() + cls.client = cls.os_primary.grp_client + + @classmethod + def _get_group_params(cls, enabled=True): + region, payload = {}, {} + grp_name = data_utils.rand_name('ormTempestGrp') + domain_name = CONF.auth.admin_domain_name + region['name'] = CONF.identity.region + region['type'] = 'single' + regions = [region] + payload["description"] = grp_name + payload["domain_name"] = domain_name + payload["enabled"] = True if enabled else False + payload["name"] = grp_name + payload["regions"] = regions + return payload + + @classmethod + def _get_user_params(cls, alt=False): + users = [] + if not alt: + users.append({'id': cls.os_primary.credentials.username, + 'role': ['admin']}) + else: + users.append({'id': cls.os_alt.credentials.username, + 'role': ['admin_viewer', 'admin_support']}) + return users + + @classmethod + def _get_region_params(cls): + region = {} + region['name'] = CONF.identity.region + region['type'] = 'single' + return [region] + + @classmethod + def _create_grp_validate_creation_on_dcp_and_lcp(self, **kwargs): + """ Creates a keystone group record: kwargs contains field data + needed for group customer POST body: + - name + - description + - enabled + - domain_name + - regions + """ + _, body = self.client.create_group(**kwargs) + group_id = body["group"]["id"] + _, group = self.client.get_group(group_id) + if group["name"] == kwargs["name"]: + if group["regions"] == []: + group_status = "no regions" + else: + group_status = "Success" + + self._wait_for_group_status(group_id, group_status) + return group_id + else: + message = "group %s not created successfully" % kwargs["name"] + exceptions.TempestException(message) + + @classmethod + def _wait_for_group_status(cls, group_id, status): + group_status = cls.client.get_group(group_id)[1]["status"] + start = int(time.time()) + while group_status != status: + time.sleep(cls.build_interval) + group_status = cls.client.get_group(group_id)[1]["status"] + if group_status == 'Error': + message = ('group %s failed to reach %s status' + ' and is in ERROR status on orm' % + (group_id, status)) + raise exceptions.TempestException(message) + if int(time.time()) - start >= cls.build_timeout: + message = ('group %s failed to reach %s' + 'status within the required time (%s s)' + 'on orm and is in %s status.' + % (group_id, status, + cls.build_timeout, + group_status)) + raise exceptions.TimeoutException(message) + + @classmethod + def _del_group_validate_deletion_on_dcp_and_lcp(cls, group_id): + _, group = cls.client.get_group(group_id) + regions_on_group = [region for region in group["regions"]] + if regions_on_group: + region_name_on_group = regions_on_group[0]["name"] + cls._delete_region_from_group_and_validate_deletion( + group_id, region_name_on_group) + cls.client.delete_group(group_id) + cls._wait_for_group_deletion_on_dcp(group_id) + cls._validate_group_deletion_on_lcp(group_id) + + @classmethod + def _delete_region_from_group_and_validate_deletion( + cls, group_id, rname): + _, region = cls.os_admin.rms_client.get_region(rname) + region_id = region["id"] + cls.client.delete_region_from_group(group_id, region_id) + cls._wait_for_group_status(group_id, "no regions") + _, body = cls.client.get_group(group_id) + regions_on_group = [rgn for rgn in body["regions"]] + if regions_on_group: + message = "Region %s failed to get deleted from group %s " % ( + rname, group_id) + raise exceptions.TempestException(message) + + @classmethod + def _wait_for_group_deletion_on_dcp(cls, group_id): + _, body = cls.client.list_groups() + group_list = body["groups"] + group_ids = [group["id"] for group in group_list + if group["id"] == group_id] + start = int(time.time()) + while group_ids: + time.sleep(cls.build_interval) + _, body = cls.client.list_groups()["groups"] + group_list = body["groups"] + group_ids = [group["id"] for group in group_list + if group["id"] == group_id] + if group_ids: + group_status = group_ids[0]["status"] + if group_status == 'Error': + message = "group %s failed to get deleted and is in\ + error status" % group_id + raise exceptions.TempestException(message) + if int(time.time()) - start >= cls.build_timeout: + message = ( + 'group %s failed to get deleted within ' + 'the required time (%s s) and is in %s status.' + % (group_id, cls.build_timeout, + group_status)) + raise exceptions.TimeoutException(message) + + @classmethod + def _validate_group_deletion_on_lcp(cls, group_id): + _, body = cls.client.list_groups() + group_ids = [group["id"] for group in body["groups"] + if group["id"] == group_id] + if group_ids: + message = "group %s failed to get deleted on lcp" \ + % group_id + raise exceptions.TempestException(message) diff --git a/ranger-tempest-plugin/ranger_tempest_plugin/tests/api/test_groups.py b/ranger-tempest-plugin/ranger_tempest_plugin/tests/api/test_groups.py new file mode 100755 index 00000000..e5848299 --- /dev/null +++ b/ranger-tempest-plugin/ranger_tempest_plugin/tests/api/test_groups.py @@ -0,0 +1,121 @@ +# Copyright 2016 AT&T Corp +# All Rights Reserved. +# +# 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. + +import random + +from ranger_tempest_plugin.tests.api import grp_base +from tempest import config +from tempest.lib import decorators +from tempest.lib import exceptions + +CONF = config.CONF + + +class TestTempestGrp(grp_base.GrpBaseOrmTest): + + @classmethod + def resource_setup(cls): + cls.setup_group = cls._get_group_params() + cls.setup_group_id = \ + cls._create_grp_validate_creation_on_dcp_and_lcp( + **cls.setup_group) + super(TestTempestGrp, cls).resource_setup() + + @classmethod + def resource_cleanup(cls): + cls._del_group_validate_deletion_on_dcp_and_lcp(cls.setup_group_id) + super(TestTempestGrp, cls).resource_cleanup() + + @decorators.idempotent_id('deeb3b8a-fb38-46e1-97ba-c878b0ba890f') + def test_get_group(self): + """ Execute 'get_group' using the following options: + - get group by id + - get group by name + """ + + # execute get_group using group id and group name + for identifier in [self.setup_group_id, + self.setup_group['name']]: + _, body = self.client.get_group(identifier) + self.assertIn(self.setup_group_id, body['uuid']) + + @decorators.idempotent_id('8068e33f-a6aa-416a-9505-048c6ad037b2') + def test_list_groups_with_filters(self): + """ This function executes 'list groups' with all available filters: + - no filter (i.e. list all groups) + - filter by region + - group name contains a substring + - group name starts_with a string + """ + + # format filter parameter values + region_name = [ + region['name'] for region in self.setup_group['regions']] + group_name = self.setup_group['name'] + substr_name = random.randint(0, len(group_name)) + + # define the list groups filters to be used for this test + no_filter = None + region_filter = {'region': region_name[0]} + contains_filter = {'contains': group_name[substr_name:]} + startswith_filter = {'starts_with': group_name[:substr_name]} + + # execute list_groups with the available filters + for list_filter in [no_filter, region_filter, contains_filter, + startswith_filter]: + _, body = self.client.list_groups(list_filter) + groups = [grp['id'] for grp in body['groups']] + self.assertIn(self.setup_group_id, groups) + + @decorators.idempotent_id('880f614f-6317-4973-a244-f2e44443f551') + def test_delete_regions(self): + # setup data for delete_region + post_body = self._get_group_params() + region_name = post_body["regions"][0]["name"] + test_group_id = self._create_grp_validate_creation_on_dcp_and_lcp( + **post_body) + self.addCleanup(self._del_group_validate_deletion_on_dcp_and_lcp, + test_group_id) + _, group = self.client.get_group(test_group_id) + self.assertTrue(group["regions"]) + _, body = self.client.delete_region_from_group(test_group_id, + region_name) + self._wait_for_group_status(test_group_id, 'no regions') + _, group = self.client.get_group(test_group_id) + self.assertFalse(group["regions"]) + + @decorators.idempotent_id('bba25028-d962-47df-9566-557eec48f22d') + def test_create_group(self): + post_body = self._get_group_params() + test_group_name = post_body['name'] + _, body = self.client.create_group(**post_body) + test_group_id = body['group']['id'] + self.addCleanup(self._del_group_validate_deletion_on_dcp_and_lcp, + test_group_id) + self._wait_for_group_status(test_group_id, 'Success') + _, body = self.client.get_group(test_group_name) + self.assertIn(test_group_id, body['uuid']) + + @decorators.idempotent_id('356633f0-c615-4bdc-8f0f-d97b6ca409e0') + def test_delete_group(self): + # setup data for test case + post_body = self._get_group_params() + test_group_id = self._create_grp_validate_creation_on_dcp_and_lcp( + **post_body) + + # delete the data and do get_group to ensure 404-NotFound response + self._del_group_validate_deletion_on_dcp_and_lcp(test_group_id) + self.assertRaises(exceptions.NotFound, self.client.get_group, + test_group_id)