diff --git a/designate/backend/impl_ns1.py b/designate/backend/impl_ns1.py index ce7f71191..f85873643 100644 --- a/designate/backend/impl_ns1.py +++ b/designate/backend/impl_ns1.py @@ -33,14 +33,14 @@ class NS1Backend(base.Backend): def __init__(self, target): super().__init__(target) - self.api_endpoint = "https://" + self.options.get('api_endpoint') + self.api_endpoint = 'https://' + self.options.get('api_endpoint') self.api_token = self.options.get('api_token') self.tsigkey_name = self.options.get('tsigkey_name', None) self.tsigkey_hash = self.options.get('tsigkey_hash', None) self.tsigkey_value = self.options.get('tsigkey_value', None) self.headers = { - "X-NSONE-Key": self.api_token + 'X-NSONE-Key': self.api_token } def _build_url(self, zone): @@ -56,7 +56,6 @@ class NS1Backend(base.Backend): raise exceptions.Backend(e) def _check_zone_exists(self, zone): - try: requests.get( self._build_url(zone), @@ -75,23 +74,22 @@ class NS1Backend(base.Backend): return True def create_zone(self, context, zone): - master = self._get_master() # designate requires "." at end of zone name, NS1 requires omitting data = { - "zone": zone.name.rstrip('.'), - "secondary": { - "enabled": True, - "primary_ip": master.host, - "primary_port": master.port + 'zone': zone.name.rstrip('.'), + 'secondary': { + 'enabled': True, + 'primary_ip': master.host, + 'primary_port': master.port } } if self.tsigkey_name: tsig = { - "enabled": True, - "hash": self.tsigkey_hash, - "name": self.tsigkey_name, - "key": self.tsigkey_value + 'enabled': True, + 'hash': self.tsigkey_hash, + 'name': self.tsigkey_name, + 'key': self.tsigkey_value } data['secondary']['tsig'] = tsig @@ -105,17 +103,21 @@ class NS1Backend(base.Backend): except requests.HTTPError as e: # check if the zone was actually created if self._check_zone_exists(zone): - LOG.info("%s was created with an error. Deleting zone", - zone.name) + LOG.info( + '%s was created with an error. Deleting zone', + zone.name + ) try: self.delete_zone(context, zone) except exceptions.Backend: - LOG.error('Could not delete errored zone %s', - zone.name) + LOG.error( + 'Could not delete errored zone %s', zone.name + ) raise exceptions.Backend(e) else: - LOG.info("Can't create zone %s because it already exists", - zone.name) + LOG.info( + "Can't create zone %s because it already exists", zone.name + ) def delete_zone(self, context, zone, zone_params=None): """Delete a DNS zone""" @@ -130,6 +132,7 @@ class NS1Backend(base.Backend): except requests.HTTPError as e: raise exceptions.Backend(e) else: - LOG.warning("Trying to delete zone %s but that zone is not " - "present in the ns1 backend. Assuming success.", - zone) + LOG.warning( + 'Trying to delete zone %s but that zone is not present in the ' + 'ns1 backend. Assuming success.', zone + ) diff --git a/designate/tests/unit/backend/test_ns1.py b/designate/tests/unit/backend/test_ns1.py index e99c04e61..760f9d56e 100644 --- a/designate/tests/unit/backend/test_ns1.py +++ b/designate/tests/unit/backend/test_ns1.py @@ -14,9 +14,11 @@ # License for the specific language governing permissions and limitations # under the License. + from unittest import mock import oslotest.base +import requests import requests_mock from designate.backend import impl_ns1 @@ -141,7 +143,6 @@ class NS1BackendTestCase(oslotest.base.BaseTestCase): @requests_mock.mock() def test_create_zone_already_exists(self, req_mock): - req_mock.get(self.api_address, status_code=200) req_mock.put(self.api_address) @@ -178,6 +179,49 @@ class NS1BackendTestCase(oslotest.base.BaseTestCase): req_mock.last_request.headers.get('X-NSONE-Key'), 'test_key' ) + @requests_mock.mock() + def test_create_zone_fail_http_request(self, req_mock): + req_mock.put( + self.api_address, + status_code=500, + ) + req_mock.get( + self.api_address, + status_code=404, + ) + + self.backend._check_zone_exists = mock.Mock() + self.backend._check_zone_exists.side_effect = [False, True] + self.backend.delete_zone = mock.Mock() + + self.assertRaisesRegex( + exceptions.Backend, + '500 Server Error', + self.backend.create_zone, self.context, self.zone + ) + + @requests_mock.mock() + def test_create_zone_fail_delete_zone(self, req_mock): + req_mock.put( + self.api_address, + status_code=500, + ) + req_mock.get( + self.api_address, + status_code=404, + ) + + self.backend._check_zone_exists = mock.Mock() + self.backend._check_zone_exists.side_effect = [False, True] + self.backend.delete_zone = mock.Mock() + self.backend.delete_zone.side_effect = exceptions.Backend() + + self.assertRaisesRegex( + exceptions.Backend, + '500 Server Error', + self.backend.create_zone, self.context, self.zone + ) + @requests_mock.mock() def test_delete_zone_success(self, req_mock): req_mock.delete(self.api_address, status_code=200) @@ -224,3 +268,57 @@ class NS1BackendTestCase(oslotest.base.BaseTestCase): self.assertEqual( req_mock.last_request.headers.get('X-NSONE-Key'), 'test_key' ) + + def test_get_master(self): + target_master = objects.PoolTargetMaster(host='192.0.2.1', port=53) + + self.backend.options = objects.PoolTargetMasterList( + objects=[target_master] + ) + + self.assertEqual(target_master, self.backend._get_master()) + + def test_get_master_no_master(self): + backend = impl_ns1.NS1Backend( + objects.PoolTarget.from_dict({ + 'id': '4588652b-50e7-46b9-b688-a9bad40a873e', + 'type': 'ns1', + 'masters': [], + 'options': [ + {'key': 'api_endpoint', 'value': '192.0.2.3'}, + {'key': 'api_token', 'value': 'test_key'}, + ], + }) + ) + + self.assertRaisesRegex( + exceptions.Backend, + 'list index out of range', + backend._get_master + ) + + @requests_mock.mock() + def test_check_zone_exists_server_error(self, req_mock): + req_mock.get( + self.api_address, + status_code=500 + ) + + self.assertRaisesRegex( + exceptions.Backend, + '500 Server Error', + self.backend._check_zone_exists, self.zone + ) + + @requests_mock.mock() + def test_check_zone_exists_connection_error(self, req_mock): + req_mock.get( + self.api_address, + exc=requests.ConnectionError('error') + ) + + self.assertRaisesRegex( + exceptions.Backend, + 'error', + self.backend._check_zone_exists, self.zone + )