Add a zone import scenario test

This adds a test that imports a zone and checks the zone is created and
goes to active.

The base client's create/update calls now accept either a dict or string
as request data. Serialization is skipped if the data is a string. This
fixes a bug in the tests causing all zone import's to go to error.

Change-Id: I71d406efc5f2c9c35eae50761446c00a328ec5a2
diff --git a/designate_tempest_plugin/services/dns/json/base.py b/designate_tempest_plugin/services/dns/json/base.py
index f9b1de7..ea8fb8f 100644
--- a/designate_tempest_plugin/services/dns/json/base.py
+++ b/designate_tempest_plugin/services/dns/json/base.py
@@ -18,6 +18,7 @@
 from tempest.lib.common import rest_client
 from tempest.lib import exceptions as lib_exc
 from six.moves.urllib import parse as urllib
+import six
 
 from designate_tempest_plugin.common.models import ZoneFile
 
@@ -56,8 +57,10 @@
     UPDATE_STATUS_CODES = []
     DELETE_STATUS_CODES = []
 
-    def serialize(self, object_dict):
-        return json.dumps(object_dict)
+    def serialize(self, data):
+        if isinstance(data, six.string_types):
+            return data
+        return json.dumps(data)
 
     def deserialize(self, resp, object_str):
         if 'application/json' in resp['content-type']:
@@ -99,12 +102,13 @@
                                   uuid=uuid,
                                   params=params)
 
-    def _create_request(self, resource, object_dict=None, params=None,
+    def _create_request(self, resource, data=None, 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 data: A Python dict that represents an object of the
+                     specified type (to be serialized) or a plain string which
+                     is sent as-is.
         :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.
@@ -116,7 +120,7 @@
         :returns: A tuple with the server response and the deserialized created
                  object.
         """
-        body = self.serialize(object_dict)
+        body = self.serialize(data)
         uri = self.get_uri(resource, params=params)
 
         resp, body = self.post(uri, body=body, headers=headers,
@@ -156,17 +160,18 @@
 
         return resp, self.deserialize(resp, body)
 
-    def _put_request(self, resource, uuid, object_dict, params=None):
+    def _put_request(self, resource, uuid, data, params=None):
         """Updates the specified object using PUT request.
         :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 data: A Python dict that represents an object of the
+                     specified type (to be serialized) or a plain string which
+                     is sent as-is.
         :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)
+        body = self.serialize(data)
         uri = self.get_uri(resource, uuid=uuid, params=params)
         resp, body = self.put(uri, body=body)
 
@@ -174,17 +179,18 @@
 
         return resp, self.deserialize(resp, body)
 
-    def _update_request(self, resource, uuid, object_dict, params=None):
+    def _update_request(self, resource, uuid, data, params=None):
         """Updates the specified object using PATCH request.
         :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 data: A Python dict that represents an object of the
+                     specified type (to be serialized) or a plain string which
+                     is sent as-is.
         :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)
+        body = self.serialize(data)
         uri = self.get_uri(resource, uuid=uuid, params=params)
 
         resp, body = self.patch(uri, body=body)
diff --git a/designate_tempest_plugin/services/dns/v2/json/pool_client.py b/designate_tempest_plugin/services/dns/v2/json/pool_client.py
index cd7c540..67e8de8 100644
--- a/designate_tempest_plugin/services/dns/v2/json/pool_client.py
+++ b/designate_tempest_plugin/services/dns/v2/json/pool_client.py
@@ -37,8 +37,7 @@
                  "ns_records": ns_records or dns_data_utils.rand_ns_records()
         }
 
-        resp, body = self._create_request('pools', object_dict=pool,
-                                          params=params)
+        resp, body = self._create_request('pools', data=pool, params=params)
 
         # Create Pool should Return a HTTP 201
         self.expected_success(201, resp.status)
diff --git a/designate_tempest_plugin/services/dns/v2/json/recordset_client.py b/designate_tempest_plugin/services/dns/v2/json/recordset_client.py
index 00b49de..3363e0c 100644
--- a/designate_tempest_plugin/services/dns/v2/json/recordset_client.py
+++ b/designate_tempest_plugin/services/dns/v2/json/recordset_client.py
@@ -33,7 +33,7 @@
         """
         resp, body = self._create_request(
             "/zones/{0}/recordsets".format(zone_uuid), params=params,
-            object_dict=recordset_data)
+            data=recordset_data)
 
         # Create Recordset should Return a HTTP 202
         self.expected_success(202, resp.status)
@@ -109,7 +109,7 @@
         """
         resp, body = self._put_request(
             'zones/{0}/recordsets'.format(zone_uuid), recordset_uuid,
-            object_dict=recordset_model, params=params)
+            data=recordset_model, params=params)
 
         # Update Recordset should Return a HTTP 202
         self.expected_success(202, resp.status)
diff --git a/designate_tempest_plugin/services/dns/v2/json/tld_client.py b/designate_tempest_plugin/services/dns/v2/json/tld_client.py
index d6b3c66..86a31de 100644
--- a/designate_tempest_plugin/services/dns/v2/json/tld_client.py
+++ b/designate_tempest_plugin/services/dns/v2/json/tld_client.py
@@ -35,8 +35,7 @@
                                name="description")
         }
 
-        resp, body = self._create_request('tlds', object_dict=tld,
-                                          params=params)
+        resp, body = self._create_request('tlds', data=tld, params=params)
 
         # Create Tld should Return a HTTP 201
         self.expected_success(201, resp.status)
diff --git a/designate_tempest_plugin/services/dns/v2/json/tsigkey_client.py b/designate_tempest_plugin/services/dns/v2/json/tsigkey_client.py
index 0c5f9c2..683c1bb 100644
--- a/designate_tempest_plugin/services/dns/v2/json/tsigkey_client.py
+++ b/designate_tempest_plugin/services/dns/v2/json/tsigkey_client.py
@@ -41,7 +41,7 @@
                  "scope": scope or utils.rand_tsig_scope(),
                  "resource_id": resource_id}
 
-        resp, body = self._create_request('tsigkeys', object_dict=tsig,
+        resp, body = self._create_request('tsigkeys', data=tsig,
                                           params=params)
 
         self.expected_success(201, resp.status)
diff --git a/designate_tempest_plugin/tests/scenario/v2/test_zones_import.py b/designate_tempest_plugin/tests/scenario/v2/test_zones_import.py
new file mode 100644
index 0000000..86af3d2
--- /dev/null
+++ b/designate_tempest_plugin/tests/scenario/v2/test_zones_import.py
@@ -0,0 +1,61 @@
+# Copyright 2016 Rackspace
+#
+# 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 designate_tempest_plugin.common import waiters
+from designate_tempest_plugin import data_utils as dns_data_utils
+from designate_tempest_plugin.tests.api.v2.test_zones_imports import \
+    BaseZonesImportTest
+
+LOG = logging.getLogger(__name__)
+
+
+class ZonesImportTest(BaseZonesImportTest):
+
+    @classmethod
+    def setup_clients(cls):
+        super(ZonesImportTest, cls).setup_clients()
+        cls.client = cls.os.zone_imports_client
+        cls.zones_client = cls.os.zones_client
+
+    @test.attr(type='slow')
+    @test.idempotent_id('679f38d0-2f2f-49c5-934e-8fe0c452f56e')
+    def test_create_zone_import_and_wait_for_zone(self):
+        name = dns_data_utils.rand_zone_name('testimport')
+        zonefile = dns_data_utils.rand_zonefile_data(name=name)
+
+        LOG.info('Import zone %r', name)
+        _, zone_import = self.client.create_zone_import(zonefile)
+        self.addCleanup(self.client.delete_zone_import, zone_import['id'])
+
+        LOG.info('Wait for the zone import to COMPLETE')
+        waiters.wait_for_zone_import_status(self.client, zone_import['id'],
+                                            "COMPLETE")
+
+        LOG.info('Check the zone import looks good')
+        _, zone_import = self.client.show_zone_import(zone_import['id'])
+        self.assertEqual('COMPLETE', zone_import['status'])
+        self.assertIsNotNone(zone_import['zone_id'])
+        self.assertIsNotNone(zone_import['links'].get('zone'))
+
+        LOG.info('Wait for the imported zone to go to ACTIVE')
+        waiters.wait_for_zone_status(self.zones_client, zone_import['zone_id'],
+                                     "ACTIVE")
+
+        LOG.info('Check the imported zone looks good')
+        _, zone = self.zones_client.show_zone(zone_import['zone_id'])
+        self.assertEqual('NONE', zone['action'])
+        self.assertEqual('ACTIVE', zone['status'])
+        self.assertEqual(name, zone['name'])