Add zones_import_client's methods and tests to Designate tempest plugin

Partially-Implements: blueprint designate-tempest-plugin

Change-Id: If01461617020f39b4da554b127e7b5e5fd704645
diff --git a/designate_tempest_plugin/clients.py b/designate_tempest_plugin/clients.py
index 79d6cba..805eeda 100644
--- a/designate_tempest_plugin/clients.py
+++ b/designate_tempest_plugin/clients.py
@@ -16,7 +16,8 @@
 
 from designate_tempest_plugin.services.dns.v2.json.zones_client import \
     ZonesClient
-
+from designate_tempest_plugin.services.dns.v2.json.zone_imports_client import \
+    ZoneImportsClient
 
 CONF = config.CONF
 
@@ -35,3 +36,5 @@
         params.update(self.default_params)
 
         self.zones_client = ZonesClient(self.auth_provider, **params)
+        self.zone_imports_client = ZoneImportsClient(self.auth_provider,
+                                                     **params)
diff --git a/designate_tempest_plugin/common/waiters.py b/designate_tempest_plugin/common/waiters.py
index a75870b..9563656 100644
--- a/designate_tempest_plugin/common/waiters.py
+++ b/designate_tempest_plugin/common/waiters.py
@@ -81,3 +81,36 @@
                 message = '(%s) %s' % (caller, message)
 
             raise lib_exc.TimeoutException(message)
+
+
+def wait_for_zone_import_status(client, zone_import_id, status):
+    """Waits for an imported zone to reach the given status."""
+    LOG.info('Waiting for zone import %s to reach %s', zone_import_id, status)
+
+    _, zone_import = client.show_zone_import(zone_import_id)
+    start = int(time.time())
+
+    while zone_import['status'] != status:
+        time.sleep(client.build_interval)
+        _, zone_import = client.show_zone_import(zone_import_id)
+        status_curr = zone_import['status']
+        if status_curr == status:
+            LOG.info('Zone import %s reached %s', zone_import_id, status)
+            return
+
+        if int(time.time()) - start >= client.build_timeout:
+            message = ('Zone import %(zone_import_id)s failed to reach '
+                       'status=%(status)s within the required time '
+                       '(%(timeout)s s). Current '
+                       'status: %(status_curr)s' %
+                       {'zone_import_id': zone_import_id,
+                        'status': status,
+                        'status_curr': status_curr,
+                        'timeout': client.build_timeout})
+
+            caller = misc_utils.find_test_caller()
+
+            if caller:
+                message = '(%s) %s' % (caller, message)
+
+            raise lib_exc.TimeoutException(message)
diff --git a/designate_tempest_plugin/data_utils.py b/designate_tempest_plugin/data_utils.py
index 73207d5..8b18785 100644
--- a/designate_tempest_plugin/data_utils.py
+++ b/designate_tempest_plugin/data_utils.py
@@ -11,15 +11,20 @@
 # 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.common.utils import data_utils
 
 
-def rand_zone_name():
+def rand_zone_name(name='', prefix=None, suffix='.com.'):
     """Generate a random zone name
+    :param str name: The name that you want to include
+    :param prefix: the exact text to start the string. Defaults to "rand"
+    :param suffix: the exact text to end the string
     :return: a random zone name e.g. example.org.
     :rtype: string
     """
-    return '%s.com.' % data_utils.rand_name()
+    name = data_utils.rand_name(name=name, prefix=prefix)
+    return name + suffix
 
 
 def rand_email(domain=None):
@@ -37,3 +42,18 @@
     :rtype: string
     """
     return data_utils.rand_int_id(start, end)
+
+
+def rand_zonefile_data(name=None, ttl=None):
+    """Generate random zone data, with optional overrides
+
+    :return: A ZoneModel
+    """
+    zone_base = ('$ORIGIN &\n& # IN SOA ns.& nsadmin.& # # # # #\n'
+                 '& # IN NS ns.&\n& # IN MX 10 mail.&\nns.& 360 IN A 1.0.0.1')
+    if name is None:
+        name = rand_zone_name()
+    if ttl is None:
+        ttl = str(rand_ttl(1200, 8400))
+
+    return zone_base.replace('&', name).replace('#', ttl)
diff --git a/designate_tempest_plugin/services/dns/json/base.py b/designate_tempest_plugin/services/dns/json/base.py
index 94307b8..e49885a 100644
--- a/designate_tempest_plugin/services/dns/json/base.py
+++ b/designate_tempest_plugin/services/dns/json/base.py
@@ -84,20 +84,28 @@
                                   uuid=uuid,
                                   params=params)
 
-    def _create_request(self, resource, object_dict, params=None):
+    def _create_request(self, resource, object_dict, params=None,
+                        headers=None, extra_headers=False):
         """Create an object of the specified type.
         :param resource: The name of the REST resource, e.g., 'zones'.
         :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.
+        :param headers (dict): The headers to use for the request.
+        :param extra_headers (bool): Boolean value than indicates if the
+                                     headers returned by the get_headers()
+                                     method are to be used but additional
+                                     headers are needed in the request
+                                     pass them in as a dict.
         :returns: A tuple with the server response and the deserialized created
                  object.
         """
         body = self.serialize(object_dict)
         uri = self.get_uri(resource, params=params)
 
-        resp, body = self.post(uri, body=body)
+        resp, body = self.post(uri, body=body, headers=headers,
+                               extra_headers=extra_headers)
         self.expected_success([201, 202], resp.status)
 
         return resp, self.deserialize(body)
@@ -119,7 +127,7 @@
         return resp, self.deserialize(body)
 
     def _list_request(self, resource, params=None):
-        """Gets a list of specific objects.
+        """Gets a list of 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.
@@ -134,7 +142,7 @@
         return resp, self.deserialize(body)
 
     def _update_request(self, resource, uuid, object_dict, params=None):
-        """Update a specified object.
+        """Updates the 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
@@ -153,7 +161,7 @@
         return resp, self.deserialize(body)
 
     def _delete_request(self, resource, uuid, params=None):
-        """Delete specified object.
+        """Deletes the specified object.
         :param resource: The name of the REST resource, e.g., 'zones'.
         :param uuid: The unique identifier of an object in UUID format.
         :param params: A Python dict that represents the query paramaters to
@@ -164,4 +172,7 @@
 
         resp, body = self.delete(uri)
         self.expected_success([202, 204], resp.status)
-        return resp, self.deserialize(body)
+        if resp.status == 202:
+            body = self.deserialize(body)
+
+        return resp, body
diff --git a/designate_tempest_plugin/services/dns/v2/json/zone_imports_client.py b/designate_tempest_plugin/services/dns/v2/json/zone_imports_client.py
new file mode 100644
index 0000000..872fc13
--- /dev/null
+++ b/designate_tempest_plugin/services/dns/v2/json/zone_imports_client.py
@@ -0,0 +1,80 @@
+# Copyright 2016 NEC Corporation.  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 designate_tempest_plugin import data_utils as dns_data_utils
+from designate_tempest_plugin.common import waiters
+from designate_tempest_plugin.services.dns.v2.json import base
+
+
+class ZoneImportsClient(base.DnsClientV2Base):
+
+    @base.handle_errors
+    def create_zone_import(self, zonefile_data=None,
+                           params=None, wait_until=None):
+        """Create a zone import.
+        :param zonefile_data: A tuple that represents zone data.
+        :param params: A Python dict that represents the query paramaters to
+                       include in the request URI.
+        :return: Serialized imported zone as a dictionary.
+        """
+
+        headers = {'Content-Type': 'text/dns'}
+        zone_data = zonefile_data or dns_data_utils.rand_zonefile_data()
+        resp, body = self._create_request(
+            'zones/tasks/imports', zone_data, headers=headers)
+
+        # Create Zone should Return a HTTP 202
+        self.expected_success(202, resp.status)
+
+        if wait_until:
+            waiters.wait_for_zone_import_status(self, body['id'], wait_until)
+
+        return resp, body
+
+    @base.handle_errors
+    def show_zone_import(self, uuid, params=None):
+        """Gets a specific zone import.
+        :param uuid: Unique identifier of the imported zone in UUID format.
+        :param params: A Python dict that represents the query paramaters to
+                       include in the request URI.
+        :return: Serialized imported zone as a dictionary.
+        """
+        return self._show_request(
+            'zones/tasks/imports', uuid, params=params)
+
+    @base.handle_errors
+    def list_zone_imports(self, params=None):
+        """Gets all the imported zones.
+        :param params: A Python dict that represents the query paramaters to
+                       include in the request URI.
+        :return: Serialized imported zones as a list.
+        """
+        return self._list_request(
+            'zones/tasks/imports', params=params)
+
+    @base.handle_errors
+    def delete_zone_import(self, uuid, params=None):
+        """Deletes a imported zone having the specified UUID.
+        :param uuid: The unique identifier of the imported zone.
+        :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 response body.
+        """
+        resp, body = self._delete_request(
+            'zones/tasks/imports', uuid, params=params)
+
+        # Delete Zone should Return a HTTP 204
+        self.expected_success(204, resp.status)
+
+        return resp, body
diff --git a/designate_tempest_plugin/tests/api/v2/test_zones_imports.py b/designate_tempest_plugin/tests/api/v2/test_zones_imports.py
new file mode 100644
index 0000000..c2b5585
--- /dev/null
+++ b/designate_tempest_plugin/tests/api/v2/test_zones_imports.py
@@ -0,0 +1,80 @@
+# Copyright 2016 NEC Corporation.  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_log import log as logging
+from tempest import test
+from tempest.lib import exceptions as lib_exc
+
+from designate_tempest_plugin.tests import base
+
+LOG = logging.getLogger(__name__)
+
+
+class BaseZonesImportTest(base.BaseDnsTest):
+    excluded_keys = ['created_at', 'updated_at', 'version', 'links',
+                     'status', 'message']
+
+
+class ZonesImportTest(BaseZonesImportTest):
+    @classmethod
+    def setup_clients(cls):
+        super(ZonesImportTest, cls).setup_clients()
+        cls.client = cls.os.zone_imports_client
+
+    @test.attr(type='smoke')
+    @test.idempotent_id('2e2d907d-0609-405b-9c96-3cb2b87e3dce')
+    def test_create_zone_import(self):
+        LOG.info('Create a zone import')
+        _, zone_import = self.client.create_zone_import()
+        self.addCleanup(self.client.delete_zone_import, zone_import['id'])
+
+        LOG.info('Ensure we respond with PENDING')
+        self.assertEqual('PENDING', zone_import['status'])
+
+    @test.attr(type='smoke')
+    @test.idempotent_id('c8909558-0dc6-478a-9e91-eb97b52e59e0')
+    def test_show_zone_import(self):
+        LOG.info('Create a zone import')
+        _, zone_import = self.client.create_zone_import()
+        self.addCleanup(self.client.delete_zone_import, zone_import['id'])
+
+        LOG.info('Re-Fetch the zone import')
+        resp, body = self.client.show_zone_import(zone_import['id'])
+
+        LOG.info('Ensure the fetched response matches the expected one')
+        self.assertExpected(zone_import, body, self.excluded_keys)
+
+    @test.attr(type='smoke')
+    @test.idempotent_id('56a16e68-b241-4e41-bc5c-c40747fa68e3')
+    def test_delete_zone_import(self):
+        LOG.info('Create a zone import')
+        _, zone_import = self.client.create_zone_import()
+
+        LOG.info('Delete the zone')
+        resp, body = self.client.delete_zone_import(zone_import['id'])
+
+        LOG.info('Ensure successful deletion of imported zones')
+        self.assertRaises(lib_exc.NotFound,
+            lambda: self.client.show_zone_import(zone_import['id']))
+
+    @test.attr(type='smoke')
+    @test.idempotent_id('9eab76af-1995-485f-a2ef-8290c1863aba')
+    def test_list_zones_imports(self):
+        LOG.info('Create a zone import')
+        _, zone = self.client.create_zone_import()
+
+        LOG.info('List zones imports')
+        _, body = self.client.list_zone_imports()
+
+        self.assertTrue(len(body['imports']) > 0)