Add tests for fabric-namespaces and fabric (#8)
Also generic contrail_client that should replace all insanity that is
going on in services.contrail.json.*_client
http://www.opencontrail.org/documentation/api/r5.0/contrail_openapi.html#fabric
http://www.opencontrail.org/documentation/api/r5.0/contrail_openapi.html#fabric-namespace
Change-Id: I6f0d2034ece8f2d5dbd2272934711082b1686d8d
Depends-On: https://gerrit.mtn5.cci.att.com/#/c/52538/
diff --git a/tungsten_tempest_plugin/services/contrail/json/contrail_client.py b/tungsten_tempest_plugin/services/contrail/json/contrail_client.py
new file mode 100644
index 0000000..949df68
--- /dev/null
+++ b/tungsten_tempest_plugin/services/contrail/json/contrail_client.py
@@ -0,0 +1,128 @@
+# Copyright 2018 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.
+
+from oslo_serialization import jsonutils as json
+
+from six.moves import urllib
+from tempest.lib.common import rest_client as service_client
+
+from tungsten_tempest_plugin.services.contrail.json import base
+
+
+class ContrailClient(base.BaseContrailClient):
+ """Generic client for Contrail API
+
+ Client implements all basic CRUD functions for Contrail API.
+ http://www.opencontrail.org/documentation/api/r5.0/contrail_openapi.html
+ """
+
+ def _pluralize(self, resource_name):
+ """Contrail API ignores common rules for pluralization. For example:
+
+ .. Fetch a specific firewall-policy
+ .. GET /firewall-policy/{id}
+ and
+ .. List collection of firewall-policy
+ .. GET /firewall-policys
+
+ This method is created so if it will be ever changed we can update it
+ in one place.
+ """
+ return resource_name + 's'
+
+ def _underscore_to_dash(self, name):
+ return name.replace("_", "-")
+
+ def _build_uri(self, name, **kwargs):
+ uri = "/%s" % self._underscore_to_dash(name)
+ if kwargs:
+ uri += '?' + urllib.parse.urlencode(kwargs, doseq=1)
+ return uri
+
+ def _lister(self, plural_name):
+ def _list(**filters):
+ uri = self._build_uri(plural_name, **filters)
+ resp, body = self.get(uri)
+ result = {plural_name: json.loads(body)}
+ self.expected_success(200, resp.status)
+ return service_client.ResponseBody(resp, result)
+
+ return _list
+
+ def _deleter(self, resource_name):
+ def _delete(resource_id):
+ uri = '%s/%s' % (resource_name, resource_id)
+ uri = self._build_uri(uri)
+ resp, body = self.delete(uri)
+ self.expected_success(200, resp.status)
+ return service_client.ResponseBody(resp, body)
+
+ return _delete
+
+ def _shower(self, resource_name):
+ def _show(resource_id, **fields):
+ uri = '%s/%s' % (resource_name, resource_id)
+ uri = self._build_uri(uri)
+
+ if fields:
+ uri += '?' + urllib.parse.urlencode(fields, doseq=1)
+ resp, body = self.get(uri)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return service_client.ResponseBody(resp, body)
+
+ return _show
+
+ def _creater(self, resource_name):
+ def _create(**kwargs):
+ uri = self._build_uri(self._pluralize(resource_name))
+ post_data = json.dumps({
+ self._underscore_to_dash(resource_name): kwargs})
+ resp, body = self.post(uri, post_data)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return service_client.ResponseBody(resp, body)
+
+ return _create
+
+ def _updater(self, resource_name):
+ def _update(res_id, **kwargs):
+ uri = '%s/%s' % (resource_name, res_id)
+ uri = self._build_uri(uri)
+ put_data = json.dumps({
+ self._underscore_to_dash(resource_name): kwargs})
+ resp, body = self.put(uri, put_data)
+ body = json.loads(body) if body else {}
+ self.expected_success(200, resp.status)
+ return service_client.ResponseBody(resp, body)
+
+ return _update
+
+ def __getattr__(self, name):
+ """Client entry point
+
+ Once user will call CRUD method it will be managed here.
+ """
+ method_prefixes = ["list_", "delete_", "show_", "create_", "update_"]
+ method_functors = [self._lister,
+ self._deleter,
+ self._shower,
+ self._creater,
+ self._updater]
+ for index, prefix in enumerate(method_prefixes):
+ prefix_len = len(prefix)
+ if name[:prefix_len] == prefix:
+ return method_functors[index](name[prefix_len:])
+ raise AttributeError(name)
diff --git a/tungsten_tempest_plugin/tests/api/contrail/rbac_base.py b/tungsten_tempest_plugin/tests/api/contrail/rbac_base.py
index f16ca54..7da4633 100644
--- a/tungsten_tempest_plugin/tests/api/contrail/rbac_base.py
+++ b/tungsten_tempest_plugin/tests/api/contrail/rbac_base.py
@@ -37,6 +37,8 @@
import BGPAsAServiceClient
from tungsten_tempest_plugin.services.contrail.json.config_client import \
ConfigClient
+from tungsten_tempest_plugin.services.contrail.json.contrail_client import \
+ ContrailClient
from tungsten_tempest_plugin.services.contrail.json.database_client import \
ContrailDatabaseClient
from tungsten_tempest_plugin.services.contrail.json.\
@@ -122,6 +124,7 @@
dscv = CONF.identity.disable_ssl_certificate_validation
ca_certs = CONF.identity.ca_certificates_file
cls.setup_rbac_utils()
+
cls.access_control_client = AccessControlClient(
cls.auth_provider,
CONF.sdn.catalog_type,
@@ -367,6 +370,13 @@
CONF.sdn.endpoint_type,
disable_ssl_certificate_validation=dscv,
ca_certs=ca_certs)
+ cls.contrail_client = ContrailClient(
+ cls.auth_provider,
+ CONF.sdn.catalog_type,
+ CONF.identity.region,
+ CONF.sdn.endpoint_type,
+ disable_ssl_certificate_validation=dscv,
+ ca_certs=ca_certs)
@classmethod
def resource_setup(cls):
diff --git a/tungsten_tempest_plugin/tests/api/contrail/test_fabric.py b/tungsten_tempest_plugin/tests/api/contrail/test_fabric.py
new file mode 100644
index 0000000..e100d59
--- /dev/null
+++ b/tungsten_tempest_plugin/tests/api/contrail/test_fabric.py
@@ -0,0 +1,105 @@
+# Copyright 2018 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.
+
+from patrole_tempest_plugin import rbac_rule_validation
+from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+
+from tungsten_tempest_plugin.tests.api.contrail import rbac_base
+
+CONF = config.CONF
+
+
+class FabricContrailTest(rbac_base.BaseContrailTest):
+
+ @classmethod
+ def skip_checks(cls):
+ super(FabricContrailTest, cls).skip_checks()
+ if float(CONF.sdn.contrail_version) < 5:
+ raise cls.skipException("fabric requires Contrail >= 5")
+
+ @classmethod
+ def resource_setup(cls):
+ super(FabricContrailTest, cls).resource_setup()
+ cls.fabric_uuid = cls._create_fabric()
+
+ @classmethod
+ def _create_fabric(cls):
+ fabric = cls.contrail_client.create_fabric(
+ fq_name=["default-global-system-config",
+ data_utils.rand_name('fabric')],
+ parent_type="global-system-config")
+ cls.addClassResourceCleanup(cls._try_delete_resource,
+ cls.contrail_client.delete_fabric,
+ fabric["fabric"]["uuid"])
+ return fabric["fabric"]["uuid"]
+
+ @rbac_rule_validation.action(service="Contrail",
+ rules=["list_fabrics"])
+ @decorators.idempotent_id('9005d1d6-3bd2-4378-b145-0fda130ce1d1')
+ def test_list_fabric_s(self):
+ """List fabrics
+
+ RBAC test for the Contrail list_fabrics policy
+ """
+ with self.rbac_utils.override_role(self):
+ self.contrail_client.list_fabrics()
+
+ @rbac_rule_validation.action(service="Contrail",
+ rules=["create_fabric"])
+ @decorators.idempotent_id('9b216ca5-b332-41ca-9072-6c3ee325ea91')
+ def test_create_fabric(self):
+ """Create fabric
+
+ RBAC test for the Contrail create_fabric policy
+ """
+ with self.rbac_utils.override_role(self):
+ self._create_fabric()
+
+ @rbac_rule_validation.action(service="Contrail",
+ rules=["show_fabric"])
+ @decorators.idempotent_id('244c3530-70e0-4efe-98be-4c2d1ffa0376')
+ def test_show_fabric(self):
+ """Show fabric
+
+ RBAC test for the Contrail show_fabric policy
+ """
+ with self.rbac_utils.override_role(self):
+ self.contrail_client.show_fabric(self.fabric_uuid)
+
+ @rbac_rule_validation.action(service="Contrail",
+ rules=["delete_fabric"])
+ @decorators.idempotent_id('75edc6b9-c66a-46fc-a271-4c54e4cc77b1')
+ def test_delete_fabric(self):
+ """Delete fabric
+
+ RBAC test for the Contrail delete_fabric policy
+ """
+ fab_uuid = self._create_fabric()
+ with self.rbac_utils.override_role(self):
+ self.contrail_client.delete_fabric(fab_uuid)
+
+ @rbac_rule_validation.action(service="Contrail",
+ rules=["update_fabric"])
+ @decorators.idempotent_id('f22c4c26-3843-4fc6-a8ff-7890d5ad1c39')
+ def test_update_fabric(self):
+ """Update fabric
+
+ RBAC test for the Contrail update_fabric policy
+ """
+ with self.rbac_utils.override_role(self):
+ put_body = {'display_name': data_utils.rand_name('update_fab')}
+ self.contrail_client.update_fabric(self.fabric_uuid, **put_body)
diff --git a/tungsten_tempest_plugin/tests/api/contrail/test_fabric_namespaces.py b/tungsten_tempest_plugin/tests/api/contrail/test_fabric_namespaces.py
new file mode 100644
index 0000000..21f8316
--- /dev/null
+++ b/tungsten_tempest_plugin/tests/api/contrail/test_fabric_namespaces.py
@@ -0,0 +1,129 @@
+# Copyright 2018 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.
+
+from patrole_tempest_plugin import rbac_rule_validation
+from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+
+from tungsten_tempest_plugin.tests.api.contrail import rbac_base
+
+CONF = config.CONF
+
+
+class FabricNamespacesContrailTest(rbac_base.BaseContrailTest):
+
+ @classmethod
+ def skip_checks(cls):
+ super(FabricNamespacesContrailTest, cls).skip_checks()
+ if float(CONF.sdn.contrail_version) < 5:
+ raise cls.skipException("fabric-namespace requires Contrail >= 5")
+
+ @classmethod
+ def resource_setup(cls):
+ super(FabricNamespacesContrailTest, cls).resource_setup()
+ cls.fabric_namespace_uuid = cls._create_fabric_namespace()
+
+ @classmethod
+ def _create_fabric(cls):
+ # Fabric object is required as parent for fabric-namespace
+ fabric = cls.contrail_client.create_fabric(
+ fq_name=["default-global-system-config",
+ data_utils.rand_name('fabric')],
+ parent_type="global-system-config")
+ cls.addClassResourceCleanup(cls._try_delete_resource,
+ cls.contrail_client.delete_fabric,
+ fabric["fabric"]["uuid"])
+ return fabric["fabric"]
+
+ @classmethod
+ def _create_fabric_namespace(cls):
+ fabric = cls._create_fabric()
+
+ parent_type = 'fabric'
+ name = data_utils.rand_name('fabric_namespace')
+ fq_name = fabric["fq_name"] + [name]
+
+ post_body = {
+ 'fq_name': fq_name,
+ 'parent_type': parent_type
+ }
+
+ resp_body = cls.contrail_client.create_fabric_namespace(**post_body)
+ cls.addClassResourceCleanup(
+ cls._try_delete_resource,
+ cls.contrail_client.delete_fabric_namespace,
+ resp_body['fabric-namespace']['uuid'])
+
+ return resp_body['fabric-namespace']['uuid']
+
+ @rbac_rule_validation.action(service="Contrail",
+ rules=["list_fabric_namespaces"])
+ @decorators.idempotent_id('f9935e2a-c4b6-4694-8698-6148faf93e1a')
+ def test_list_fabric_namespaces(self):
+ """List fabric namespaces
+
+ RBAC test for the Contrail list_fabric_namespaces policy
+ """
+ with self.rbac_utils.override_role(self):
+ self.contrail_client.list_fabric_namespaces()
+
+ @rbac_rule_validation.action(service="Contrail",
+ rules=["create_fabric_namespace"])
+ @decorators.idempotent_id('5bb85072-130d-4f36-a787-12b65bdd4c03')
+ def test_create_fabric_namespace(self):
+ """Create fabric namespace
+
+ RBAC test for the Contrail create_fabric_namespace policy
+ """
+ with self.rbac_utils.override_role(self):
+ self._create_fabric_namespace()
+
+ @rbac_rule_validation.action(service="Contrail",
+ rules=["show_fabric_namespace"])
+ @decorators.idempotent_id('5ab5bc8f-7209-427a-9868-4fbc7a7e0d85')
+ def test_show_fabric_namespace(self):
+ """Show fabric namespace
+
+ RBAC test for the Contrail show_fabric_namespace policy
+ """
+ with self.rbac_utils.override_role(self):
+ self.contrail_client.show_fabric_namespace(
+ self.fabric_namespace_uuid)
+
+ @rbac_rule_validation.action(service="Contrail",
+ rules=["delete_fabric_namespace"])
+ @decorators.idempotent_id('ff20e4bb-6110-476e-80b4-d6114981e8bf')
+ def test_delete_fabric_namespace(self):
+ """Delete fabric namespace
+
+ RBAC test for the Contrail delete_fabric_namespace policy
+ """
+ ns_uuid = self._create_fabric_namespace()
+ with self.rbac_utils.override_role(self):
+ self.contrail_client.delete_fabric_namespace(ns_uuid)
+
+ @rbac_rule_validation.action(service="Contrail",
+ rules=["update_fabric_namespace"])
+ @decorators.idempotent_id('78514d86-fcdc-4bc6-99b9-11b5e91b5296')
+ def test_update_fabric_namespace(self):
+ """Update fabric namespace
+
+ RBAC test for the Contrail update_fabric_namespace policy
+ """
+ with self.rbac_utils.override_role(self):
+ put_body = {'display_name': data_utils.rand_name('update_fns')}
+ self.contrail_client.update_fabric_namespace(
+ self.fabric_namespace_uuid, **put_body)