Adds zone client's methods and tests to Designate tempest plugin

Change-Id: Ib1037238da64518c332952d8c88fd767d4d9607b
Depends-On: I58930c40243068e97ff8f6f1684cfbe5565ac7f1
Partially-Implements: blueprint designate-tempest-plugin
diff --git a/designate_tempest_plugin/services/dns/json/base.py b/designate_tempest_plugin/services/dns/json/base.py
index f2574d6..3c2f3c1 100644
--- a/designate_tempest_plugin/services/dns/json/base.py
+++ b/designate_tempest_plugin/services/dns/json/base.py
@@ -91,6 +91,7 @@
 
     def _show_request(self, resource, uuid, params=None):
         """Gets a specific object of the specified type.
+        :param resource: The name of the REST resource, e.g., 'zones'.
         :param uuid: Unique identifier of the object in UUID format.
         :param params: A Python dict that represents the query paramaters to
                        include in the request URI.
@@ -104,6 +105,40 @@
 
         return resp, self.deserialize(body)
 
+    def _list_request(self, resource, params=None):
+        """Gets a list of specific objects.
+        :param resource: The name of the REST resource, e.g., 'zones'.
+        :param params: A Python dict that represents the query paramaters to
+                       include in the request URI.
+        :returns: Serialized object as a dictionary.
+        """
+        uri = self._get_uri(resource, params=params)
+
+        resp, body = self.get(uri)
+
+        self.expected_success(200, resp['status'])
+
+        return resp, self.deserialize(body)
+
+    def _update_request(self, resource, uuid, object_dict, params=None):
+        """Update a specified object.
+        :param resource: The name of the REST resource, e.g., 'zones'
+        :param uuid: Unique identifier of the object in UUID format.
+        :param object_dict: A Python dict that represents an object of the
+                             specified type.
+        :param params: A Python dict that represents the query paramaters to
+                       include in the request URI.
+        :returns: Serialized object as a dictionary.
+        """
+        body = self.serialize(object_dict)
+        uri = self._get_uri(resource, uuid=uuid, params=params)
+
+        resp, body = self.patch(uri, body=body)
+
+        self.expected_success(200, resp['status'])
+
+        return resp, self.deserialize(body)
+
     def _delete_request(self, resource, uuid, params=None):
         """Delete specified object.
         :param resource: The name of the REST resource, e.g., 'zones'.
diff --git a/designate_tempest_plugin/services/dns/v2/json/zones_client.py b/designate_tempest_plugin/services/dns/v2/json/zones_client.py
index 6f5cb95..da77979 100644
--- a/designate_tempest_plugin/services/dns/v2/json/zones_client.py
+++ b/designate_tempest_plugin/services/dns/v2/json/zones_client.py
@@ -64,6 +64,15 @@
         return self._show_request('zones', uuid, params=params)
 
     @base.handle_errors
+    def list_zones(self, params=None):
+        """Gets a list of zones.
+        :param params: A Python dict that represents the query paramaters to
+                       include in the request URI.
+        :return: Serialized zones as a list.
+        """
+        return self._list_request('zones', params=params)
+
+    @base.handle_errors
     def delete_zone(self, uuid, params=None):
         """Deletes a zone having the specified UUID.
         :param uuid: The unique identifier of the zone.
@@ -72,3 +81,32 @@
         :return: A tuple with the server response and the response body.
         """
         return self._delete_request('zones', uuid, params=params)
+
+    @base.handle_errors
+    def update_zone(self, uuid, email=None, ttl=None,
+                    description=None, wait_until=False, params=None):
+        """Update a zone with the specified parameters.
+        :param uuid: The unique identifier of the zone.
+        :param email: The email for the zone.
+            Default: Random Value
+        :param ttl: The ttl for the zone.
+            Default: Random Value
+        :param description: A description of the zone.
+            Default: Random Value
+        :param wait_until: Block until the zone reaches the desiered status
+        :param params: A Python dict that represents the query paramaters to
+                       include in the request URI.
+        :return: A tuple with the server response and the updated zone.
+        """
+        zone = {
+            'email': email or dns_data_utils.rand_email(),
+            'ttl': ttl or dns_data_utils.rand_ttl(),
+            'description': description or data_utils.rand_name('test-zone'),
+        }
+
+        resp, body = self._update_request('zones', uuid, zone, params=params)
+
+        if wait_until:
+            waiters.wait_for_zone_status(self, body['id'], wait_until)
+
+        return resp, body
\ No newline at end of file
diff --git a/designate_tempest_plugin/tests/api/v2/base.py b/designate_tempest_plugin/tests/api/v2/base.py
index d05d9d0..f7634c1 100644
--- a/designate_tempest_plugin/tests/api/v2/base.py
+++ b/designate_tempest_plugin/tests/api/v2/base.py
@@ -28,4 +28,4 @@
     # rest the actual roles.
     # NOTE(kiall) primary will result in a manager @ cls.os, alt will have
     # cls.os_alt, and admin will have cls.os_adm.
-    credentials = ['primary']
+    credentials = ['primary', 'alt', 'admin']
diff --git a/designate_tempest_plugin/tests/api/v2/test_zones.py b/designate_tempest_plugin/tests/api/v2/test_zones.py
index 345ff60..8bdebba 100644
--- a/designate_tempest_plugin/tests/api/v2/test_zones.py
+++ b/designate_tempest_plugin/tests/api/v2/test_zones.py
@@ -75,6 +75,50 @@
 
         waiters.wait_for_zone_404(self.client, zone['id'])
 
+    @test.attr(type='smoke')
+    @test.idempotent_id('5bfa3cfe-5bc8-443b-bf48-cfba44cbb247')
+    def test_list_zones(self):
+        LOG.info('Create a zone')
+        _, zone = self.client.create_zone()
+        self.addCleanup(self.client.delete_zone, zone['id'])
+
+        LOG.info('Ensure we respond with CREATE+PENDING')
+        self.assertEqual('CREATE', zone['action'])
+        self.assertEqual('PENDING', zone['status'])
+
+        waiters.wait_for_zone_status(
+            self.client, zone['id'], 'ACTIVE')
+
+        LOG.info('List zones')
+        _, body = self.client.list_zones()
+
+        self.assertTrue(len(body['zones']) > 0)
+
+    @test.attr(type='smoke')
+    @test.idempotent_id('123f51cb-19d5-48a9-aacc-476742c02141')
+    def test_update_zone(self):
+        LOG.info('Create a zone')
+        _, zone = self.client.create_zone()
+        self.addCleanup(self.client.delete_zone, zone['id'])
+
+        LOG.info('Ensure we respond with CREATE+PENDING')
+        self.assertEqual('CREATE', zone['action'])
+        self.assertEqual('PENDING', zone['status'])
+
+        waiters.wait_for_zone_status(
+            self.client, zone['id'], 'ACTIVE')
+
+        LOG.info('Update the zone')
+        resp, body = self.client.update_zone(zone['id'])
+
+        self.assertEqual('UPDATE', body['action'])
+        self.assertEqual('PENDING', body['status'])
+
+        waiters.wait_for_zone_status(
+            self.client, body['id'], 'ACTIVE')
+
+        self.assertEqual(202, resp.status)
+
 
 class ZonesAdminTest(BaseZonesTest):
     credentials = ['primary', 'admin']
@@ -98,3 +142,69 @@
 
         LOG.info('Ensure the fetched response matches the created zone')
         self._assertExpected(zone, body)
+
+
+class ZoneOwnershipTest(BaseZonesTest):
+
+    @classmethod
+    def setup_clients(cls):
+        super(ZoneOwnershipTest, cls).setup_clients()
+
+        cls.client = cls.os.zones_client
+        cls.alt_client = cls.os_alt.zones_client
+
+    @test.attr(type='smoke')
+    @test.idempotent_id('5d28580a-a012-4b57-b211-e077b1a01340')
+    def test_no_create_duplicate_domain(self):
+        LOG.info('Create a zone as a default user')
+        _, zone = self.client.create_zone()
+        self.addCleanup(self.client.delete_zone, zone['id'])
+
+        LOG.info('Ensure we respond with CREATE+PENDING')
+        self.assertEqual('CREATE', zone['action'])
+        self.assertEqual('PENDING', zone['status'])
+
+        waiters.wait_for_zone_status(
+            self.client, zone['id'], 'ACTIVE')
+
+        LOG.info('Create a zone as an alt user with existing domain')
+        self.assertRaises(lib_exc.Conflict,
+            self.alt_client.create_zone, name=zone['name'])
+
+    @test.attr(type='smoke')
+    @test.idempotent_id('a48776fd-b1aa-4a25-9f09-d1d34cfbb175')
+    def test_no_create_subdomain_by_alt_user(self):
+        LOG.info('Create a zone as a default user')
+        _, zone = self.client.create_zone()
+        self.addCleanup(self.client.delete_zone, zone['id'])
+
+        LOG.info('Ensure we respond with CREATE+PENDING')
+        self.assertEqual('CREATE', zone['action'])
+        self.assertEqual('PENDING', zone['status'])
+
+        waiters.wait_for_zone_status(
+            self.client, zone['id'], 'ACTIVE')
+
+        LOG.info('Create a zone as an alt user with  existing subdomain')
+        self.assertRaises(lib_exc.Forbidden,
+            self.alt_client.create_zone, name='sub.' + zone['name'])
+        self.assertRaises(lib_exc.Forbidden,
+            self.alt_client.create_zone, name='sub.sub.' + zone['name'])
+
+    @test.attr(type='smoke')
+    @test.idempotent_id('f1723d48-c082-43cd-94bf-ebeb5b8c9458')
+    def test_no_create_superdomain_by_alt_user(self):
+        LOG.info('Create a zone as a default user')
+        _, zone = self.client.create_zone(name='a.b.' + "example.com.")
+        self.addCleanup(self.client.delete_zone, zone['id'])
+
+        LOG.info('Ensure we respond with CREATE+PENDING')
+        self.assertEqual('CREATE', zone['action'])
+        self.assertEqual('PENDING', zone['status'])
+
+        waiters.wait_for_zone_status(
+            self.client, zone['id'], 'ACTIVE')
+
+        LOG.info('Create a zone as an alt user with existing superdomain')
+        self.assertRaises(lib_exc.Forbidden,
+            self.alt_client.create_zone, name='example.com.')