diff options
Diffstat (limited to 'designate/tests/unit/backend/test_akamai_v2.py')
-rw-r--r-- | designate/tests/unit/backend/test_akamai_v2.py | 494 |
1 files changed, 494 insertions, 0 deletions
diff --git a/designate/tests/unit/backend/test_akamai_v2.py b/designate/tests/unit/backend/test_akamai_v2.py new file mode 100644 index 00000000..f3dcfa35 --- /dev/null +++ b/designate/tests/unit/backend/test_akamai_v2.py @@ -0,0 +1,494 @@ +# Copyright 2019 Cloudification GmbH +# +# Author: Sergey Kraynev <contact@cloudification.io> +# +# 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. + +import json +import mock +import requests + +import designate.tests +from designate import exceptions +from designate import objects +from designate.backend import impl_akamai_v2 as akamai +from designate.tests import fixtures + + +class AkamaiBackendTestCase(designate.tests.TestCase): + def setUp(self): + super(AkamaiBackendTestCase, self).setUp() + self.zone = objects.Zone( + id='cca7908b-dad4-4c50-adba-fb67d4c556e8', + name='example.com.', + email='example@example.com' + ) + + self.target = { + 'id': '4588652b-50e7-46b9-b688-a9bad40a873e', + 'type': 'akamai_v2', + 'masters': [ + {'host': '192.168.1.1', 'port': 53}, + {'host': '192.168.1.2', 'port': 35} + ], + 'options': [ + {'key': 'host', 'value': '192.168.2.3'}, + {'key': 'port', 'value': '53'}, + {'key': 'akamai_client_secret', 'value': 'client_secret'}, + {'key': 'akamai_host', 'value': 'host_value'}, + {'key': 'akamai_access_token', 'value': 'access_token'}, + {'key': 'akamai_client_token', 'value': 'client_token'}, + {'key': 'akamai_contract_id', 'value': 'G-XYW'}, + {'key': 'akamai_gid', 'value': '777'} + ], + } + + def gen_response(self, status_code, reason, json_data=None): + response = requests.models.Response() + response.status_code = status_code + response.reason = reason + response._content = json.dumps(json_data or {}).encode('utf-8') + return response + + @mock.patch.object(akamai, 'edgegrid') + @mock.patch.object(akamai.requests.Session, 'post') + def test_create_zone_missed_contract_id(self, mock_post, mock_auth): + self.target['options'].remove( + {'key': 'akamai_contract_id', 'value': 'G-XYW'}) + backend = akamai.AkamaiBackend( + objects.PoolTarget.from_dict(self.target) + ) + mock_auth.EdgeGridAuth.assert_called_once_with( + access_token='access_token', + client_secret='client_secret', + client_token='client_token' + ) + + with fixtures.random_seed(0): + self.assertRaisesRegex( + exceptions.Backend, + 'contractId is required for zone creation', + backend.create_zone, self.admin_context, self.zone) + + mock_post.assert_not_called() + + @mock.patch.object(akamai, 'edgegrid') + @mock.patch.object(akamai.requests.Session, 'post') + def test_create_zone(self, mock_post, mock_auth): + backend = akamai.AkamaiBackend( + objects.PoolTarget.from_dict(self.target) + ) + mock_auth.EdgeGridAuth.assert_called_once_with( + access_token='access_token', + client_secret='client_secret', + client_token='client_token' + ) + + with fixtures.random_seed(0): + backend.create_zone(self.admin_context, self.zone) + + project_id = self.admin_context.project_id or self.zone.tenant_id + mock_post.assert_called_once_with( + json={ + 'comment': 'Created by Designate for Tenant %s' % project_id, + 'masters': ['192.168.1.1', '192.168.1.2'], + 'type': 'secondary', 'zone': u'example.com.' + }, + params={ + 'gid': '777', + 'contractId': 'G-XYW' + }, + url='https://host_value/config-dns/v2/zones' + ) + + @mock.patch.object(akamai, 'edgegrid') + @mock.patch.object(akamai.requests.Session, 'post') + def test_create_zone_duplicate_zone(self, mock_post, mock_auth): + backend = akamai.AkamaiBackend( + objects.PoolTarget.from_dict(self.target) + ) + mock_auth.EdgeGridAuth.assert_called_once_with( + access_token='access_token', + client_secret='client_secret', + client_token='client_token' + ) + + mock_post.return_value = self.gen_response(409, 'Conflict') + + with fixtures.random_seed(0): + backend.create_zone(self.admin_context, self.zone) + + project_id = self.admin_context.project_id or self.zone.tenant_id + mock_post.assert_called_once_with( + json={ + 'comment': 'Created by Designate for Tenant %s' % project_id, + 'masters': ['192.168.1.1', '192.168.1.2'], + 'type': 'secondary', 'zone': u'example.com.' + }, + params={ + 'gid': '777', + 'contractId': 'G-XYW' + }, + url='https://host_value/config-dns/v2/zones' + ) + + @mock.patch.object(akamai, 'edgegrid') + @mock.patch.object(akamai.requests.Session, 'post') + def test_create_zone_with_tsig_key(self, mock_post, mock_auth): + self.target['options'].extend([ + {'key': 'tsig_key_name', 'value': 'test_key'}, + {'key': 'tsig_key_algorithm', 'value': 'hmac-sha512'}, + {'key': 'tsig_key_secret', 'value': 'aaaabbbbccc'} + ]) + backend = akamai.AkamaiBackend( + objects.PoolTarget.from_dict(self.target) + ) + mock_auth.EdgeGridAuth.assert_called_once_with( + access_token='access_token', + client_secret='client_secret', + client_token='client_token' + ) + + with fixtures.random_seed(0): + backend.create_zone(self.admin_context, self.zone) + + project_id = self.admin_context.project_id or self.zone.tenant_id + mock_post.assert_called_once_with( + json={ + 'comment': 'Created by Designate for Tenant %s' % project_id, + 'masters': ['192.168.1.1', '192.168.1.2'], + 'type': 'secondary', + 'zone': 'example.com.', + 'tsigKey': { + 'name': 'test_key', + 'algorithm': 'hmac-sha512', + 'secret': 'aaaabbbbccc', + } + }, + params={ + 'gid': '777', + 'contractId': 'G-XYW' + }, + url='https://host_value/config-dns/v2/zones' + ) + + @mock.patch.object(akamai, 'edgegrid') + @mock.patch.object(akamai.requests.Session, 'post') + def test_create_zone_raise_error(self, mock_post, mock_auth): + backend = akamai.AkamaiBackend( + objects.PoolTarget.from_dict(self.target) + ) + mock_auth.EdgeGridAuth.assert_called_once_with( + + access_token='access_token', + client_secret='client_secret', + client_token='client_token' + ) + + json_data = { + 'title': 'Missing parameter', + 'detail': 'Missed A option' + } + mock_post.return_value = self.gen_response( + 400, 'Bad Request', json_data) + + with fixtures.random_seed(0): + self.assertRaisesRegex( + exceptions.Backend, + 'Zone creation failed due to: Missed A option', + backend.create_zone, self.admin_context, self.zone) + + project_id = self.admin_context.project_id or self.zone.tenant_id + mock_post.assert_called_once_with( + json={ + 'comment': 'Created by Designate for Tenant %s' % project_id, + 'masters': ['192.168.1.1', '192.168.1.2'], + 'type': 'secondary', 'zone': 'example.com.' + }, + params={ + 'gid': '777', + 'contractId': 'G-XYW' + }, + url='https://host_value/config-dns/v2/zones' + ) + + @mock.patch.object(akamai, 'edgegrid') + @mock.patch.object(akamai.requests.Session, 'post') + def test_force_delete_zone(self, mock_post, mock_auth): + backend = akamai.AkamaiBackend( + objects.PoolTarget.from_dict(self.target) + ) + mock_auth.EdgeGridAuth.assert_called_once_with( + + access_token='access_token', + client_secret='client_secret', + client_token='client_token' + ) + + mock_post.return_value = self.gen_response(200, 'Success') + + with fixtures.random_seed(0): + backend.delete_zone(self.admin_context, self.zone) + + mock_post.assert_called_once_with( + json={ + 'zones': ['example.com.'] + }, + params={ + 'force': True + }, + url='https://host_value/config-dns/v2/zones/delete-requests' + ) + + @mock.patch.object(akamai, 'edgegrid') + @mock.patch.object(akamai.requests.Session, 'post') + def test_force_delete_zone_raise_error(self, mock_post, mock_auth): + backend = akamai.AkamaiBackend( + objects.PoolTarget.from_dict(self.target) + ) + mock_auth.EdgeGridAuth.assert_called_once_with( + + access_token='access_token', + client_secret='client_secret', + client_token='client_token' + ) + + mock_post.return_value = self.gen_response( + 403, 'Bad Request', {'detail': 'Unexpected error'}) + + with fixtures.random_seed(0): + self.assertRaisesRegex( + exceptions.Backend, + 'Zone deletion failed due to: Unexpected error', + backend.delete_zone, self.admin_context, self.zone) + + mock_post.assert_called_once_with( + json={ + 'zones': ['example.com.'] + }, + params={ + 'force': True + }, + url='https://host_value/config-dns/v2/zones/delete-requests' + ) + + @mock.patch.object(akamai, 'edgegrid') + @mock.patch.object(akamai.requests.Session, 'post') + def test_force_delete_zone_raise_error_404(self, mock_post, mock_auth): + backend = akamai.AkamaiBackend( + objects.PoolTarget.from_dict(self.target) + ) + mock_auth.EdgeGridAuth.assert_called_once_with( + + access_token='access_token', + client_secret='client_secret', + client_token='client_token' + ) + + mock_post.return_value = self.gen_response( + 404, 'Bad Request', {'detail': 'Unexpected error'}) + + with fixtures.random_seed(0): + backend.delete_zone(self.admin_context, self.zone) + + mock_post.assert_called_once_with( + json={ + 'zones': ['example.com.'] + }, + params={ + 'force': True + }, + url='https://host_value/config-dns/v2/zones/delete-requests' + ) + + @mock.patch.object(akamai, 'edgegrid') + @mock.patch.object(akamai.requests.Session, 'post') + @mock.patch.object(akamai.requests.Session, 'get') + def test_soft_delete_zone(self, mock_get, mock_post, mock_auth): + backend = akamai.AkamaiBackend( + objects.PoolTarget.from_dict(self.target) + ) + mock_auth.EdgeGridAuth.assert_called_once_with( + + access_token='access_token', + client_secret='client_secret', + client_token='client_token' + ) + + mock_post.side_effect = [ + # emulate, when Force=True is forbidden + self.gen_response(403, 'Forbidden'), + # emulate request, when Force=False + self.gen_response(200, 'Success', {'requestId': 'nice_id'}), + ] + + # emulate max 9 failed attempts and 1 success + mock_get.side_effect = 9 * [ + self.gen_response(200, 'Success', {'isComplete': False}) + ] + [ + self.gen_response(200, 'Success', {'isComplete': True}) + ] + + with fixtures.random_seed(0), \ + mock.patch.object(akamai.time, 'sleep') as mock_sleep: + mock_sleep.return_value = None + backend.delete_zone(self.admin_context, self.zone) + + self.assertEqual(10, mock_sleep.call_count) + + url = 'https://host_value/config-dns/v2/zones/delete-requests/nice_id' + mock_get.assert_has_calls(9 * [mock.call(url=url)]) + + mock_post.assert_has_calls([ + mock.call( + json={'zones': ['example.com.']}, + params={'force': True}, + url='https://host_value/config-dns/v2/zones/delete-requests' + ), + mock.call( + json={'zones': ['example.com.']}, + params={'force': False}, + url='https://host_value/config-dns/v2/zones/delete-requests' + ) + ]) + + @mock.patch.object(akamai, 'edgegrid') + @mock.patch.object(akamai.requests.Session, 'post') + @mock.patch.object(akamai.requests.Session, 'get') + def test_soft_delete_zone_failed_after_10_attempts( + self, mock_get, mock_post, mock_auth): + backend = akamai.AkamaiBackend( + objects.PoolTarget.from_dict(self.target) + ) + mock_auth.EdgeGridAuth.assert_called_once_with( + + access_token='access_token', + client_secret='client_secret', + client_token='client_token' + ) + + mock_post.side_effect = [ + # emulate, when Force=True is forbidden + self.gen_response(403, 'Forbidden'), + # emulate request, when Force=False + self.gen_response(200, 'Success', {'requestId': 'nice_id'}), + ] + + # emulate max 10 failed attempts + mock_get.side_effect = 10 * [ + self.gen_response(200, 'Success', {'isComplete': False}) + ] + + with fixtures.random_seed(0), \ + mock.patch.object(akamai.time, 'sleep') as mock_sleep: + mock_sleep.return_value = None + self.assertRaisesRegex( + exceptions.Backend, + 'Zone was not deleted after 10 attempts', + backend.delete_zone, self.admin_context, self.zone) + + self.assertEqual(10, mock_sleep.call_count) + + url = 'https://host_value/config-dns/v2/zones/delete-requests/nice_id' + mock_get.assert_has_calls(10 * [mock.call(url=url)]) + + mock_post.assert_has_calls([ + mock.call( + json={'zones': ['example.com.']}, + params={'force': True}, + url='https://host_value/config-dns/v2/zones/delete-requests' + ), + mock.call( + json={'zones': ['example.com.']}, + params={'force': False}, + url='https://host_value/config-dns/v2/zones/delete-requests' + ) + ]) + + @mock.patch.object(akamai, 'edgegrid') + @mock.patch.object(akamai.requests.Session, 'post') + def test_soft_delete_zone_raise_error(self, mock_post, mock_auth): + backend = akamai.AkamaiBackend( + objects.PoolTarget.from_dict(self.target) + ) + mock_auth.EdgeGridAuth.assert_called_once_with( + + access_token='access_token', + client_secret='client_secret', + client_token='client_token' + ) + + mock_post.side_effect = [ + # emulate, when Force=True is forbidden + self.gen_response(403, 'Forbidden'), + # emulate request, when Force=False + self.gen_response(409, 'Conflict', {'detail': 'Intenal Error'}) + ] + + with fixtures.random_seed(0): + self.assertRaisesRegex( + exceptions.Backend, + 'Zone deletion failed due to: Intenal Error', + backend.delete_zone, self.admin_context, self.zone) + + mock_post.assert_has_calls([ + mock.call( + json={'zones': [u'example.com.']}, + params={'force': True}, + url='https://host_value/config-dns/v2/zones/delete-requests' + ), + mock.call( + json={'zones': [u'example.com.']}, + params={'force': False}, + url='https://host_value/config-dns/v2/zones/delete-requests' + ) + ]) + + @mock.patch.object(akamai, 'edgegrid') + @mock.patch.object(akamai.requests.Session, 'post') + def test_soft_delete_zone_missed_request_id(self, mock_post, mock_auth): + backend = akamai.AkamaiBackend( + objects.PoolTarget.from_dict(self.target) + ) + mock_auth.EdgeGridAuth.assert_called_once_with( + + access_token='access_token', + client_secret='client_secret', + client_token='client_token' + ) + + mock_post.side_effect = [ + # emulate, when Force=True is forbidden + self.gen_response(403, 'Forbidden'), + # emulate request, when Force=False + self.gen_response(200, 'Success') + ] + + with fixtures.random_seed(0): + self.assertRaisesRegex( + exceptions.Backend, + 'Zone deletion failed due to: requestId missed in response', + backend.delete_zone, self.admin_context, self.zone) + + mock_post.assert_has_calls([ + mock.call( + json={'zones': [u'example.com.']}, + params={'force': True}, + url='https://host_value/config-dns/v2/zones/delete-requests' + ), + mock.call( + json={'zones': [u'example.com.']}, + params={'force': False}, + url='https://host_value/config-dns/v2/zones/delete-requests' + ) + ]) |