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)