Merge "Correctly parse IP:Port nameserver pairs"
diff --git a/designate_tempest_plugin/clients.py b/designate_tempest_plugin/clients.py
index 17df788..9986a43 100644
--- a/designate_tempest_plugin/clients.py
+++ b/designate_tempest_plugin/clients.py
@@ -38,6 +38,10 @@
     TldClient
 from designate_tempest_plugin.services.dns.query.query_client import \
     QueryClient
+from designate_tempest_plugin.services.dns.v2.json.transfer_request_client \
+    import TransferRequestClient
+from designate_tempest_plugin.services.dns.v2.json.transfer_accepts_client \
+    import TransferAcceptClient
 
 CONF = config.CONF
 
@@ -92,3 +96,7 @@
             build_interval=CONF.dns.build_interval,
             build_timeout=CONF.dns.build_timeout,
         )
+        self.transfer_request_client = TransferRequestClient(
+                                           self.auth_provider, **params)
+        self.transfer_accept_client = TransferAcceptClient(
+                                           self.auth_provider, **params)
diff --git a/designate_tempest_plugin/data_utils.py b/designate_tempest_plugin/data_utils.py
index 448095e..ae2b1a7 100644
--- a/designate_tempest_plugin/data_utils.py
+++ b/designate_tempest_plugin/data_utils.py
@@ -216,3 +216,20 @@
         "name": rand_zone_name(prefix='tld', suffix='')
     }
     return data
+
+
+def rand_transfer_request_data(description=None, target_project_id=None):
+    """Generate random transfer request data, with optional overrides
+
+    :return: A TransferRequest data
+    """
+
+    data = {}
+
+    if description is None:
+        data['description'] = data_utils.rand_name(prefix='Description ')
+
+    if target_project_id:
+        data['target_project_id'] = target_project_id
+
+    return data
diff --git a/designate_tempest_plugin/services/dns/v2/json/transfer_accepts_client.py b/designate_tempest_plugin/services/dns/v2/json/transfer_accepts_client.py
new file mode 100644
index 0000000..fe0fd0f
--- /dev/null
+++ b/designate_tempest_plugin/services/dns/v2/json/transfer_accepts_client.py
@@ -0,0 +1,49 @@
+# 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.services.dns.v2.json import base
+
+
+class TransferAcceptClient(base.DnsClientV2Base):
+
+    @base.handle_errors
+    def create_transfer_accept(self, transfer_accept_data,
+                               params=None):
+        """Create a zone transfer_accept.
+        :param transfer_accept_data: A python dictionary representing
+                                data for the zone transfer accept.
+        :param params: A Python dict that represents the query paramaters to
+                       include in the accept URI.
+        :return: Serialized accepted zone transfer as a dictionary.
+        """
+
+        transfer_accept_uri = 'zones/tasks/transfer_accepts'
+        resp, body = self._create_request(
+            transfer_accept_uri, transfer_accept_data, params=params)
+
+        # Create Transfer accept should Return a HTTP 201
+        self.expected_success(201, resp.status)
+
+        return resp, body
+
+    @base.handle_errors
+    def show_transfer_accept(self, uuid, params=None):
+        """Gets a specific accepted zone transfer..
+        :param uuid: Unique identifier of the transfer_accept.
+        :param params: A Python dict that represents the query paramaters to
+                       include in the accept URI.
+        :return: Serialized accepted zone transfer as a dictionary.
+        """
+        return self._show_request(
+            'zones/tasks/transfer_accepts', uuid, params=params)
diff --git a/designate_tempest_plugin/services/dns/v2/json/transfer_request_client.py b/designate_tempest_plugin/services/dns/v2/json/transfer_request_client.py
new file mode 100644
index 0000000..513a8ad
--- /dev/null
+++ b/designate_tempest_plugin/services/dns/v2/json/transfer_request_client.py
@@ -0,0 +1,103 @@
+# 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.services.dns.v2.json import base
+
+
+class TransferRequestClient(base.DnsClientV2Base):
+
+    @base.handle_errors
+    def create_transfer_request(self, uuid, transfer_request_data=None,
+                                params=None):
+        """Create a zone transfer_requests.
+        :param uuid: Unique identifier of the zone in UUID format.
+        :transfer_request_data: A python dictionary representing
+                                data for zone transfer request
+        :param params: A Python dict that represents the query paramaters to
+                       include in the request URI.
+        :return: Serialized imported zone as a dictionary.
+        """
+
+        transfer_request_uri = 'zones/{0}/tasks/transfer_requests'.format(uuid)
+        transfer_request_data = (transfer_request_data or
+                                 dns_data_utils.rand_transfer_request_data())
+        resp, body = self._create_request(
+            transfer_request_uri, transfer_request_data, params=params)
+
+        # Create Transfer request should Return a HTTP 201
+        self.expected_success(201, resp.status)
+
+        return resp, body
+
+    @base.handle_errors
+    def show_transfer_request(self, uuid, params=None):
+        """Gets a specific transfer_requestsed zone.
+        :param uuid: Unique identifier of the transfer_requestsed zone in
+                     UUID format.
+        :param params: A Python dict that represents the query paramaters to
+                       include in the request URI.
+        :return: Serialized transfer_requestsed zone as a dictionary.
+        """
+        return self._show_request(
+            'zones/tasks/transfer_requests', uuid, params=params)
+
+    @base.handle_errors
+    def list_transfer_requests(self, params=None):
+        """Gets all the transfer_requestsed zones
+        :param params: A Python dict that represents the query paramaters to
+                       include in the request URI.
+        :return: Serialized transfer_requestsed zone as a list.
+        """
+        return self._list_request(
+            'zones/tasks/transfer_requests', params=params)
+
+    @base.handle_errors
+    def delete_transfer_request(self, uuid, params=None):
+        """Deletes an transfer_requestsed zone having the specified UUID.
+        :param uuid: The unique identifier of the transfer_requestsed 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/transfer_requests', uuid, params=params)
+
+        # Delete Zone transfer_requests should Return a HTTP 204
+        self.expected_success(204, resp.status)
+
+        return resp, body
+
+    @base.handle_errors
+    def update_transfer_request(self, uuid, transfer_request_data=None,
+                                params=None):
+        """Update a zone transfer_requests.
+        :param uuid: Unique identifier of the zone transfer request in UUID
+                     format.
+        :transfer_request_data: A python dictionary representing
+                                data for zone transfer request
+        :param params: A Python dict that represents the query paramaters to
+                       include in the request URI.
+        :return: Serialized imported zone as a dictionary.
+        """
+        transfer_request_uri = 'zones/tasks/transfer_requests'
+        transfer_request_data = (transfer_request_data or
+                                 dns_data_utils.rand_transfer_request_data())
+        resp, body = self._update_request(
+            transfer_request_uri, uuid, transfer_request_data, params=params)
+
+        # Create Transfer request should Return a HTTP 200
+        self.expected_success(200, resp.status)
+
+        return resp, body
diff --git a/designate_tempest_plugin/tests/api/v2/test_transfer_accepts.py b/designate_tempest_plugin/tests/api/v2/test_transfer_accepts.py
new file mode 100644
index 0000000..cdf9388
--- /dev/null
+++ b/designate_tempest_plugin/tests/api/v2/test_transfer_accepts.py
@@ -0,0 +1,85 @@
+# 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 designate_tempest_plugin.tests import base
+
+LOG = logging.getLogger(__name__)
+
+
+class BaseTransferAcceptTest(base.BaseDnsV2Test):
+    excluded_keys = ['created_at', 'updated_at', 'key', 'links',
+                    'zone_name']
+
+
+class TransferAcceptTest(BaseTransferAcceptTest):
+    @classmethod
+    def setup_clients(cls):
+        super(TransferAcceptTest, cls).setup_clients()
+
+        cls.zone_client = cls.os.zones_client
+        cls.request_client = cls.os.transfer_request_client
+        cls.client = cls.os.transfer_accept_client
+
+    @test.attr(type='smoke')
+    @test.idempotent_id('1c6baf97-a83e-4d2e-a5d8-9d37fb7808f3')
+    def test_create_transfer_accept(self):
+        LOG.info('Create a zone')
+        _, zone = self.zone_client.create_zone()
+        self.addCleanup(self.zone_client.delete_zone, zone['id'])
+
+        LOG.info('Create a zone transfer_request')
+        _, transfer_request = self.request_client.create_transfer_request(
+                                  zone['id'])
+        self.addCleanup(self.request_client.delete_transfer_request,
+                        transfer_request['id'])
+
+        data = {
+                 "key": transfer_request['key'],
+                 "zone_transfer_request_id": transfer_request['id']
+        }
+        LOG.info('Create a zone transfer_accept')
+        _, transfer_accept = self.client.create_transfer_accept(data)
+
+        LOG.info('Ensure we respond with ACTIVE status')
+        self.assertEqual('COMPLETE', transfer_accept['status'])
+
+    @test.attr(type='smoke')
+    @test.idempotent_id('37c6afbb-3ea3-4fd8-94ea-a426244f019a')
+    def test_show_transfer_accept(self):
+        LOG.info('Create a zone')
+        _, zone = self.zone_client.create_zone()
+        self.addCleanup(self.zone_client.delete_zone, zone['id'])
+
+        LOG.info('Create a zone transfer_request')
+        _, transfer_request = self.request_client.create_transfer_request(
+                                  zone['id'])
+        self.addCleanup(self.request_client.delete_transfer_request,
+                        transfer_request['id'])
+
+        data = {
+                 "key": transfer_request['key'],
+                 "zone_transfer_request_id": transfer_request['id']
+        }
+
+        LOG.info('Create a zone transfer_accept')
+        _, transfer_accept = self.client.create_transfer_accept(data)
+
+        LOG.info('Fetch the transfer_accept')
+        _, body = self.client.show_transfer_accept(transfer_accept['id'])
+
+        LOG.info('Ensure the fetched response matches the '
+                 'created transfer_accept')
+        self.assertExpected(transfer_accept, body, self.excluded_keys)
diff --git a/designate_tempest_plugin/tests/api/v2/test_transfer_request.py b/designate_tempest_plugin/tests/api/v2/test_transfer_request.py
new file mode 100644
index 0000000..e7badd9
--- /dev/null
+++ b/designate_tempest_plugin/tests/api/v2/test_transfer_request.py
@@ -0,0 +1,133 @@
+# 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 BaseTransferRequestTest(base.BaseDnsV2Test):
+    excluded_keys = ['created_at', 'updated_at', 'key', 'links',
+                    'zone_name']
+
+
+class TransferRequestTest(BaseTransferRequestTest):
+    @classmethod
+    def setup_clients(cls):
+        super(TransferRequestTest, cls).setup_clients()
+
+        cls.zone_client = cls.os.zones_client
+        cls.client = cls.os.transfer_request_client
+
+    @test.attr(type='smoke')
+    @test.idempotent_id('2381d489-ad84-403d-b0a2-8b77e4e966bf')
+    def test_create_transfer_request(self):
+        LOG.info('Create a zone')
+        _, zone = self.zone_client.create_zone()
+        self.addCleanup(self.zone_client.delete_zone, zone['id'])
+
+        LOG.info('Create a zone transfer_request')
+        _, transfer_request = self.client.create_transfer_request(zone['id'])
+        self.addCleanup(self.client.delete_transfer_request,
+                        transfer_request['id'])
+
+        LOG.info('Ensure we respond with ACTIVE status')
+        self.assertEqual('ACTIVE', transfer_request['status'])
+
+    @test.attr(type='smoke')
+    @test.idempotent_id('64a7be9f-8371-4ce1-a242-c1190de7c985')
+    def test_show_transfer_request(self):
+        LOG.info('Create a zone')
+        _, zone = self.zone_client.create_zone()
+        self.addCleanup(self.zone_client.delete_zone, zone['id'])
+
+        LOG.info('Create a zone transfer_request')
+        _, transfer_request = self.client.create_transfer_request(zone['id'])
+        self.addCleanup(self.client.delete_transfer_request,
+                        transfer_request['id'])
+
+        LOG.info('Fetch the transfer_request')
+        _, body = self.client.show_transfer_request(transfer_request['id'])
+
+        LOG.info('Ensure the fetched response matches the '
+                 'created transfer_request')
+        self.assertExpected(transfer_request, body, self.excluded_keys)
+
+    @test.attr(type='smoke')
+    @test.idempotent_id('7d81c487-aa15-44c4-b3e5-424ab9e6a3e5')
+    def test_delete_transfer_request(self):
+        LOG.info('Create a zone')
+        _, zone = self.zone_client.create_zone()
+        self.addCleanup(self.zone_client.delete_zone, zone['id'])
+
+        LOG.info('Create a transfer_request')
+        _, transfer_request = self.client.create_transfer_request(zone['id'])
+        self.addCleanup(self.client.delete_transfer_request,
+                        transfer_request['id'],
+                        ignore_errors=lib_exc.NotFound)
+
+        LOG.info('Delete the transfer_request')
+        _, body = self.client.delete_transfer_request(transfer_request['id'])
+        self.assertRaises(lib_exc.NotFound,
+            lambda: self.client.show_transfer_request(transfer_request['id']))
+
+    @test.attr(type='smoke')
+    @test.idempotent_id('ddd42a19-1768-428c-846e-32f9d6493011')
+    def test_list_transfer_requests(self):
+        LOG.info('Create a zone')
+        _, zone = self.zone_client.create_zone()
+        self.addCleanup(self.zone_client.delete_zone, zone['id'])
+
+        LOG.info('Create a zone transfer_request')
+        _, transfer_request = self.client.create_transfer_request(zone['id'])
+        self.addCleanup(self.client.delete_transfer_request,
+                        transfer_request['id'])
+
+        LOG.info('List transfer_requests')
+        _, body = self.client.list_transfer_requests()
+
+        self.assertGreater(len(body['transfer_requests']), 0)
+
+    @test.attr(type='smoke')
+    @test.idempotent_id('de5e9d32-c723-4518-84e5-58da9722cc13')
+    def test_update_transfer_request(self):
+        LOG.info('Create a zone')
+        _, zone = self.zone_client.create_zone()
+        self.addCleanup(self.zone_client.delete_zone, zone['id'])
+
+        LOG.info('Create a zone transfer_request')
+        _, transfer_request = self.client.create_transfer_request(zone['id'])
+        self.addCleanup(self.client.delete_transfer_request,
+                        transfer_request['id'])
+
+        LOG.info('Update the transfer_request')
+        data = {
+                 "description": "demo descripion"
+               }
+        _, transfer_request_patch = self.client.update_transfer_request(
+            transfer_request['id'], transfer_request_data=data)
+
+        self.assertEqual(data['description'],
+                         transfer_request_patch['description'])
+
+    @test.attr(type='smoke')
+    @test.idempotent_id('73b754a9-e856-4fd6-80ba-e8d1b80f5dfa')
+    def test_list_transfer_requests_dot_json_fails(self):
+        uri = self.client.get_uri('transfer_requests.json')
+
+        self.assertRaises(lib_exc.NotFound,
+            lambda: self.client.get(uri))
diff --git a/requirements.txt b/requirements.txt
index d9368e0..8ba92bf 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,6 +2,6 @@
 # of appearance. Changing the order has an impact on the overall integration
 # process, which may cause wedges in the gate later.
 
-dnspython>=1.12.0,!=1.13.0;python_version<'3.0'  # http://www.dnspython.org/LICENSE
-dnspython3>=1.12.0;python_version>='3.0'  # http://www.dnspython.org/LICENSE
+dnspython!=1.13.0,>=1.12.0;python_version<'3.0' # http://www.dnspython.org/LICENSE
+dnspython3>=1.12.0;python_version>='3.0' # http://www.dnspython.org/LICENSE
 tempest>=11.0.0 # Apache-2.0