Add namespace tags client and tests

As in the doc following, there are namespace tags apis, this patch
is to add them to the code.

[doc]http://developer.openstack.org/api-ref/image/v2/metadefs-index.html

Change-Id: I92b2c58d11828eafd9f876a46581279bb9b26e3d
diff --git a/releasenotes/notes/add-image-clients-tests-49dbc0a0a4281a77.yaml b/releasenotes/notes/add-image-clients-tests-49dbc0a0a4281a77.yaml
index 9d1a003..eaab1f0 100644
--- a/releasenotes/notes/add-image-clients-tests-49dbc0a0a4281a77.yaml
+++ b/releasenotes/notes/add-image-clients-tests-49dbc0a0a4281a77.yaml
@@ -6,4 +6,5 @@
     there are some apis are not included, add them.
 
       * namespace_objects_client(v2)
+      * namespace_tags_client(v2)
 
diff --git a/tempest/api/image/base.py b/tempest/api/image/base.py
index 23bd628..cd4f820 100644
--- a/tempest/api/image/base.py
+++ b/tempest/api/image/base.py
@@ -143,6 +143,7 @@
         cls.resource_types_client = cls.os.resource_types_client
         cls.namespace_properties_client = cls.os.namespace_properties_client
         cls.namespace_objects_client = cls.os.namespace_objects_client
+        cls.namespace_tags_client = cls.os.namespace_tags_client
         cls.schemas_client = cls.os.schemas_client
 
     def create_namespace(cls, namespace_name=None, visibility='public',
diff --git a/tempest/api/image/v2/test_images_metadefs_namespace_tags.py b/tempest/api/image/v2/test_images_metadefs_namespace_tags.py
new file mode 100644
index 0000000..186d9c8
--- /dev/null
+++ b/tempest/api/image/v2/test_images_metadefs_namespace_tags.py
@@ -0,0 +1,90 @@
+#    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 tempest.api.image import base
+from tempest.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
+from tempest import test
+
+
+class MetadataNamespaceTagsTest(base.BaseV2ImageTest):
+    """Test the Metadata definition namespace tags basic functionality"""
+
+    tags = [
+        {
+            "name": "sample-tag1"
+        },
+        {
+            "name": "sample-tag2"
+        },
+        {
+            "name": "sample-tag3"
+        }
+    ]
+    tag_list = ["sample-tag1", "sample-tag2", "sample-tag3"]
+
+    def _create_namespace_tags(self, namespace):
+        # Create a namespace
+        namespace_tags = self.namespace_tags_client.create_namespace_tags(
+            namespace['namespace'], tags=self.tags)
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.namespace_tags_client.delete_namespace_tags,
+                        namespace['namespace'])
+        return namespace_tags
+
+    @test.idempotent_id('a2a3765e-3a6d-4f6d-a3a7-3cc3476aa876')
+    def test_create_list_delete_namespace_tags(self):
+        # Create a namespace
+        namespace = self.create_namespace()
+        self._create_namespace_tags(namespace)
+        # List namespace tags
+        body = self.namespace_tags_client.list_namespace_tags(
+            namespace['namespace'])
+        self.assertTrue(3, len(body['tags']))
+        self.assertIn(body['tags'][0]['name'], self.tag_list)
+        self.assertIn(body['tags'][1]['name'], self.tag_list)
+        self.assertIn(body['tags'][2]['name'], self.tag_list)
+        # Delete all tag definitions
+        self.namespace_tags_client.delete_namespace_tags(
+            namespace['namespace'])
+        body = self.namespace_tags_client.list_namespace_tags(
+            namespace['namespace'])
+        self.assertEqual([], body['tags'])
+
+    @test.idempotent_id('a2a3765e-1a2c-3f6d-a3a7-3cc3466ab875')
+    def test_create_update_delete_tag(self):
+        # Create a namespace
+        namespace = self.create_namespace()
+        self._create_namespace_tags(namespace)
+        # Create a tag
+        tag_name = data_utils.rand_name('tag_name')
+        self.namespace_tags_client.create_namespace_tag(
+            namespace=namespace['namespace'], tag_name=tag_name)
+
+        body = self.namespace_tags_client.show_namespace_tag(
+            namespace['namespace'], tag_name)
+        self.assertEqual(tag_name, body['name'])
+        # Update tag definition
+        update_tag_definition = data_utils.rand_name('update-tag')
+        body = self.namespace_tags_client.update_namespace_tag(
+            namespace['namespace'], tag_name=tag_name,
+            name=update_tag_definition)
+        self.assertEqual(update_tag_definition, body['name'])
+        # Delete tag definition
+        self.namespace_tags_client.delete_namespace_tag(
+            namespace['namespace'], update_tag_definition)
+        # List namespace tags and validate deletion
+        namespace_tags = [
+            namespace_tag['name'] for namespace_tag in
+            self.namespace_tags_client.list_namespace_tags(
+                namespace['namespace'])['tags']]
+        self.assertNotIn(update_tag_definition, namespace_tags)
diff --git a/tempest/clients.py b/tempest/clients.py
index 8093a72..a4306d8 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -132,6 +132,8 @@
             self.schemas_client = self.image_v2.SchemasClient()
             self.namespace_properties_client = \
                 self.image_v2.NamespacePropertiesClient()
+            self.namespace_tags_client = \
+                self.image_v2.NamespaceTagsClient()
 
     def _set_compute_clients(self):
         self.agents_client = self.compute.AgentsClient()
diff --git a/tempest/lib/services/image/v2/__init__.py b/tempest/lib/services/image/v2/__init__.py
index a35ce17..7d973e5 100644
--- a/tempest/lib/services/image/v2/__init__.py
+++ b/tempest/lib/services/image/v2/__init__.py
@@ -19,11 +19,13 @@
     NamespaceObjectsClient
 from tempest.lib.services.image.v2.namespace_properties_client import \
     NamespacePropertiesClient
+from tempest.lib.services.image.v2.namespace_tags_client import \
+    NamespaceTagsClient
 from tempest.lib.services.image.v2.namespaces_client import NamespacesClient
 from tempest.lib.services.image.v2.resource_types_client import \
     ResourceTypesClient
 from tempest.lib.services.image.v2.schemas_client import SchemasClient
 
 __all__ = ['ImageMembersClient', 'ImagesClient', 'NamespaceObjectsClient',
-           'NamespacePropertiesClient', 'NamespacesClient',
-           'ResourceTypesClient', 'SchemasClient']
+           'NamespacePropertiesClient', 'NamespaceTagsClient',
+           'NamespacesClient', 'ResourceTypesClient', 'SchemasClient']
diff --git a/tempest/lib/services/image/v2/namespace_tags_client.py b/tempest/lib/services/image/v2/namespace_tags_client.py
new file mode 100644
index 0000000..ac8b569
--- /dev/null
+++ b/tempest/lib/services/image/v2/namespace_tags_client.py
@@ -0,0 +1,119 @@
+# Copyright 2016 EasyStack.
+# 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.urllib import parse as urllib
+
+from tempest.lib.common import rest_client
+
+
+class NamespaceTagsClient(rest_client.RestClient):
+    api_version = "v2"
+
+    def create_namespace_tag(self, namespace, tag_name):
+        """Adds a tag to the list of namespace tag definitions.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        http://developer.openstack.org/api-ref/image/v2/metadefs-index.html#create-tag-definition
+        """
+        url = 'metadefs/namespaces/%s/tags/%s' % (namespace,
+                                                  tag_name)
+        resp, body = self.post(url, None)
+        self.expected_success(201, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_namespace_tag(self, namespace, tag_name):
+        """Gets a definition for a tag.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        http://developer.openstack.org/api-ref/image/v2/metadefs-index.html#get_tag_definition
+        """
+        url = 'metadefs/namespaces/%s/tags/%s' % (namespace,
+                                                  tag_name)
+        resp, body = self.get(url)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def update_namespace_tag(self, namespace, tag_name, **kwargs):
+        """Renames a tag definition.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        http://developer.openstack.org/api-ref/image/v2/metadefs-index.html#update-tag-definition
+        """
+        url = 'metadefs/namespaces/%s/tags/%s' % (namespace,
+                                                  tag_name)
+        data = json.dumps(kwargs)
+        resp, body = self.put(url, data)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_namespace_tag(self, namespace, tag_name):
+        """Deletes a tag definition within a namespace.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        http://developer.openstack.org/api-ref/image/v2/metadefs-index.html#delete-tag-definition
+        """
+        url = 'metadefs/namespaces/%s/tags/%s' % (namespace, tag_name)
+        resp, _ = self.delete(url)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp)
+
+    def create_namespace_tags(self, namespace, **kwargs):
+        """Creates one or more tag definitions in a namespace.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        http://developer.openstack.org/api-ref/image/v2/metadefs-index.html#create-tags
+        """
+        url = 'metadefs/namespaces/%s/tags' % namespace
+        data = json.dumps(kwargs)
+        resp, body = self.post(url, data)
+        self.expected_success(201, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def list_namespace_tags(self, namespace, **params):
+        """Lists the tag definitions within a namespace.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        http://developer.openstack.org/api-ref/image/v2/metadefs-index.html#list-tags
+        """
+        url = 'metadefs/namespaces/%s/tags' % namespace
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+        resp, body = self.get(url)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_namespace_tags(self, namespace):
+        """Deletes all tag definitions within a namespace.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        http://developer.openstack.org/api-ref/image/v2/metadefs-index.html#delete-all-tag-definitions
+        """
+        url = 'metadefs/namespaces/%s/tags' % namespace
+        resp, _ = self.delete(url)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp)
diff --git a/tempest/tests/lib/services/image/v2/test_namespace_tags_client.py b/tempest/tests/lib/services/image/v2/test_namespace_tags_client.py
new file mode 100644
index 0000000..2faa5be
--- /dev/null
+++ b/tempest/tests/lib/services/image/v2/test_namespace_tags_client.py
@@ -0,0 +1,126 @@
+# Copyright 2016 EasyStack. 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 tempest.lib.services.image.v2 import namespace_tags_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestNamespaceTagsClient(base.BaseServiceTest):
+    FAKE_CREATE_SHOW_TAGS = {
+        "created_at": "2015-05-09T01:12:31Z",
+        "name": "added-sample-tag",
+        "updated_at": "2015-05-09T01:12:31Z"
+    }
+
+    FAKE_LIST_TAGS = {
+        "tags": [
+            {
+                "name": "sample-tag1"
+            },
+            {
+                "name": "sample-tag2"
+            },
+            {
+                "name": "sample-tag3"
+            }
+        ]
+    }
+
+    FAKE_UPDATE_TAGS = {"name": "new-tag-name"}
+
+    def setUp(self):
+        super(TestNamespaceTagsClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.client = namespace_tags_client.NamespaceTagsClient(
+            fake_auth, 'image', 'regionOne')
+
+    def _test_create_namespace_tags(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.create_namespace_tags,
+            'tempest.lib.common.rest_client.RestClient.post',
+            self.FAKE_CREATE_SHOW_TAGS,
+            bytes_body, status=201,
+            namespace="OS::Compute::Hypervisor",
+            tags=[{"name": "sample-tag1"},
+                  {"name": "sample-tag2"},
+                  {"name": "sample-tag3"}])
+
+    def _test_list_namespace_tags(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_namespace_tags,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_LIST_TAGS,
+            bytes_body,
+            namespace="OS::Compute::Hypervisor")
+
+    def _test_create_namespace_tag_definition(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.create_namespace_tag,
+            'tempest.lib.common.rest_client.RestClient.post',
+            self.FAKE_CREATE_SHOW_TAGS,
+            bytes_body,
+            status=201,
+            namespace="OS::Compute::Hypervisor",
+            tag_name="added-sample-tag")
+
+    def _test_show_namespace_tag_definition(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.show_namespace_tag,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_CREATE_SHOW_TAGS,
+            bytes_body,
+            namespace="OS::Compute::Hypervisor",
+            tag_name="added-sample-tag")
+
+    def _test_update_namespace_tag_definition(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.update_namespace_tag,
+            'tempest.lib.common.rest_client.RestClient.put',
+            self.FAKE_UPDATE_OBJECTS,
+            bytes_body,
+            namespace="OS::Compute::Hypervisor",
+            tag_name="added-sample-tag",
+            name="new-tag-name")
+
+    def test_create_namespace_tags_with_str_body(self):
+        self._test_create_namespace_tags()
+
+    def test_create_namespace_tags_with_bytes_body(self):
+        self._test_create_namespace_tags(bytes_body=True)
+
+    def test_list_namespace_tags_with_str_body(self):
+        self._test_list_namespace_tags()
+
+    def test_list_namespace_tags_with_bytes_body(self):
+        self._test_list_namespace_tags(bytes_body=True)
+
+    def test_create_namespace_tag_with_str_body(self):
+        self._test_create_namespace_tag_definition()
+
+    def test_create_namespace_tag_with_bytes_body(self):
+        self._test_create_namespace_tag_definition(bytes_body=True)
+
+    def test_show_namespace_tag_with_str_body(self):
+        self._test_show_namespace_tag_definition()
+
+    def test_show_namespace_tag_with_bytes_body(self):
+        self._test_show_namespace_tag_definition(bytes_body=True)
+
+    def test_delete_all_namespace_tags(self):
+        self.check_service_client_function(
+            self.client.delete_namespace_tags,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {}, status=200,
+            namespace="OS::Compute::Hypervisor")