diff options
19 files changed, 466 insertions, 122 deletions
diff --git a/designate/api/v2/controllers/quotas.py b/designate/api/v2/controllers/quotas.py index 9fbc6d3a..e4168c5e 100644 --- a/designate/api/v2/controllers/quotas.py +++ b/designate/api/v2/controllers/quotas.py @@ -39,17 +39,17 @@ class QuotasController(rest.RestController): return DesignateAdapter.render('API_v2', quotas) @pecan.expose(template='json:', content_type='application/json') - def get_one(self, tenant_id): + def get_one(self, project_id): context = pecan.request.environ['context'] - quotas = self.central_api.get_quotas(context, tenant_id) + quotas = self.central_api.get_quotas(context, project_id) quotas = QuotaList.from_dict(quotas) return DesignateAdapter.render('API_v2', quotas) @pecan.expose(template='json:', content_type='application/json') - def patch_one(self, tenant_id): + def patch_one(self, project_id): """Modify a Quota""" request = pecan.request context = request.environ['context'] @@ -60,7 +60,7 @@ class QuotasController(rest.RestController): # this will raise only if KeystoneV3 endpoint is not found at all, # or the creds are passing but the project is not found if cfg.CONF['service:api'].quotas_verify_project_id: - keystone.verify_project_id(context, tenant_id) + keystone.verify_project_id(context, project_id) quotas = DesignateAdapter.parse('API_v2', body, QuotaList()) @@ -74,23 +74,23 @@ class QuotasController(rest.RestController): "scoped tokens.") for quota in quotas: - self.central_api.set_quota(context, tenant_id, quota.resource, + self.central_api.set_quota(context, project_id, quota.resource, quota.hard_limit) - quotas = self.central_api.get_quotas(context, tenant_id) + quotas = self.central_api.get_quotas(context, project_id) quotas = QuotaList.from_dict(quotas) return DesignateAdapter.render('API_v2', quotas) @pecan.expose(template=None, content_type='application/json') - def delete_one(self, tenant_id): + def delete_one(self, project_id): """Reset to the Default Quotas""" request = pecan.request response = pecan.response context = request.environ['context'] - self.central_api.reset_quotas(context, tenant_id) + self.central_api.reset_quotas(context, project_id) response.status_int = 204 diff --git a/designate/backend/__init__.py b/designate/backend/__init__.py index a95a7641..668bb56f 100644 --- a/designate/backend/__init__.py +++ b/designate/backend/__init__.py @@ -27,12 +27,13 @@ GOOD_STATUSES = [ def get_backend(target): cls = base.Backend.get_driver(target.type) - msg = "Backend Driver '%s' loaded. Has status of '%s'" \ - % (target.type, cls.__backend_status__) + message = "Backend Driver '%s' loaded. Has status of '%s'" % ( + target.type, cls.__backend_status__ + ) if cls.__backend_status__ in GOOD_STATUSES: - LOG.info(msg) + LOG.info(message) else: - LOG.warning(msg) + LOG.warning(message) return cls(target) diff --git a/designate/backend/base.py b/designate/backend/base.py index 2014b3d4..1904474c 100644 --- a/designate/backend/base.py +++ b/designate/backend/base.py @@ -51,12 +51,6 @@ class Backend(DriverPlugin): self.max_retries = CONF['service:worker'].poll_max_retries self.delay = CONF['service:worker'].poll_delay - def start(self): - LOG.info('Starting %s backend', self.get_canonical_name()) - - def stop(self): - LOG.info('Stopped %s backend', self.get_canonical_name()) - # Core Backend Interface @abc.abstractmethod def create_zone(self, context, zone): diff --git a/designate/central/service.py b/designate/central/service.py index 34e39338..9ce54515 100644 --- a/designate/central/service.py +++ b/designate/central/service.py @@ -1980,8 +1980,6 @@ class Service(service.RPCService): policy.check('create_pool', context) - self._is_valid_project_id(pool.tenant_id) - created_pool = self.storage.create_pool(context, pool) return created_pool diff --git a/designate/conf/api.py b/designate/conf/api.py index 87e6957d..29895e14 100644 --- a/designate/conf/api.py +++ b/designate/conf/api.py @@ -42,7 +42,7 @@ API_OPTS = [ help='The strategy to use for auth. Supports noauth or ' 'keystone'), cfg.BoolOpt('enable_api_v2', default=True, - help='enable-api-v2 which enable in a future'), + help='Enable the Designate V2 API'), cfg.BoolOpt('enable_api_admin', default=False, help='enable-api-admin'), cfg.IntOpt('max_header_line', default=16384, diff --git a/designate/locale/en_GB/LC_MESSAGES/designate.po b/designate/locale/en_GB/LC_MESSAGES/designate.po index bd81a074..a2ef75e2 100644 --- a/designate/locale/en_GB/LC_MESSAGES/designate.po +++ b/designate/locale/en_GB/LC_MESSAGES/designate.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: designate VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2021-03-26 20:30+0000\n" +"POT-Creation-Date: 2022-08-18 22:28+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -22,10 +22,6 @@ msgstr "" msgid "%s is not a valid project ID." msgstr "%s is not a valid project ID." -#, python-format -msgid "<%(type)s count:'%(count)s' object:'%(list_type)s'>" -msgstr "<%(type)s count:'%(count)s' object:'%(list_type)s'>" - msgid "An unknown exception occurred." msgstr "An unknown exception occurred." diff --git a/designate/locale/ru/LC_MESSAGES/designate.po b/designate/locale/ru/LC_MESSAGES/designate.po index 44cb7072..a4cc9cca 100644 --- a/designate/locale/ru/LC_MESSAGES/designate.po +++ b/designate/locale/ru/LC_MESSAGES/designate.po @@ -5,11 +5,11 @@ msgid "" msgstr "" "Project-Id-Version: designate VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2021-08-31 00:59+0000\n" +"POT-Creation-Date: 2022-08-18 22:28+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2021-09-06 04:06+0000\n" +"PO-Revision-Date: 2021-09-06 04:05+0000\n" "Last-Translator: Roman Gorshunov <roman.gorshunov@att.com>\n" "Language-Team: Russian\n" "Language: ru\n" @@ -21,10 +21,6 @@ msgstr "" msgid "%s is not a valid project ID." msgstr "%s не является допустимым ID проекта." -#, python-format -msgid "<%(type)s count:'%(count)s' object:'%(list_type)s'>" -msgstr "<%(type)s в количестве:'%(count)s' объект:'%(list_type)s'>" - msgid "An unknown exception occurred." msgstr "Обнаружено неизвестное исключение." diff --git a/designate/manage/base.py b/designate/manage/base.py index 0de7b8e9..b275408d 100644 --- a/designate/manage/base.py +++ b/designate/manage/base.py @@ -13,7 +13,7 @@ # 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.context import DesignateContext +from designate import context # Decorators for actions @@ -36,5 +36,6 @@ def name(name): class Commands(object): def __init__(self): - self.context = DesignateContext.get_admin_context( - request_id='designate-manage') + self.context = context.DesignateContext.get_admin_context( + request_id='designate-manage' + ) diff --git a/designate/manage/pool.py b/designate/manage/pool.py index 122b2135..1c7344e5 100644 --- a/designate/manage/pool.py +++ b/designate/manage/pool.py @@ -37,6 +37,7 @@ CONF = cfg.CONF class PoolCommands(base.Commands): def __init__(self): super(PoolCommands, self).__init__() + self.output_msg = [''] # NOTE(jh): Cannot do this earlier because we are still missing the config # at that point, see bug #1651576 @@ -44,13 +45,28 @@ class PoolCommands(base.Commands): rpc.init(cfg.CONF) self.central_api = central_rpcapi.CentralAPI() + def _create_pool(self, pool, dry_run): + pool = DesignateAdapter.parse('YAML', pool, objects.Pool()) + for ns_record in pool.ns_records: + try: + ns_record.validate() + except exceptions.InvalidObject as e: + LOG.error(e.errors.to_list()[0]['message']) + sys.exit(1) + + if dry_run: + self.output_msg.append('Create Pool: %s' % pool) + else: + LOG.info('Creating new pool: %s', pool) + self.central_api.create_pool(self.context, pool) + def _update_zones(self, pool): - LOG.info("Updating zone masters for pool: {}".format(pool.id)) + LOG.info('Updating zone masters for pool: %s', pool.id) def __get_masters_from_pool(pool): masters = [] for target in pool.targets: - for master in target.get("masters", []): + for master in target.get('masters', []): master = {'host': master['host'], 'port': master['port']} found = False for existing_master in masters: @@ -69,9 +85,11 @@ class PoolCommands(base.Commands): for zone in zones: zone.masters = objects.ZoneMasterList().from_list( - __get_masters_from_pool(pool)) - self.central_api.update_zone(self.context, - zone) + __get_masters_from_pool(pool) + ) + self.central_api.update_zone( + self.context, zone + ) @base.args('--file', help='The path to the file the yaml output should be ' 'written to', @@ -81,8 +99,10 @@ class PoolCommands(base.Commands): try: pools = self.central_api.find_pools(self.context) except messaging.exceptions.MessagingTimeout: - LOG.critical("No response received from designate-central. " - "Check it is running, and retry") + LOG.critical( + 'No response received from designate-central. ' + 'Check it is running, and retry' + ) sys.exit(1) with open(file, 'w') as stream: yaml.dump( @@ -96,7 +116,7 @@ class PoolCommands(base.Commands): def show_config(self, pool_id): self._startup() try: - pool = self.central_api.find_pool(self.context, {"id": pool_id}) + pool = self.central_api.find_pool(self.context, {'id': pool_id}) print('Pool Configuration:') print('-------------------') @@ -105,8 +125,10 @@ class PoolCommands(base.Commands): default_flow_style=False)) except messaging.exceptions.MessagingTimeout: - LOG.critical("No response received from designate-central. " - "Check it is running, and retry") + LOG.critical( + 'No response received from designate-central. ' + 'Check it is running, and retry' + ) sys.exit(1) @base.args('--file', help='The path to the yaml file describing the pools', @@ -115,40 +137,45 @@ class PoolCommands(base.Commands): '--delete', help='Any Pools not listed in the config file will be deleted. ' ' WARNING: This will delete any zones left in this pool', - action="store_true", + action='store_true', default=False) @base.args( '--dry-run', help='This will simulate what will happen when you run this command', - action="store_true", + action='store_true', default=False) def update(self, file, delete, dry_run): self._startup() print('Updating Pools Configuration') print('****************************') - output_msg = [''] with open(file, 'r') as stream: xpools = yaml.safe_load(stream) if dry_run: - output_msg.append("The following changes will occur:") - output_msg.append("*********************************") + self.output_msg.append('The following changes will occur:') + self.output_msg.append('*********************************') for xpool in xpools: try: if 'id' in xpool: try: pool = self.central_api.get_pool( - self.context, xpool['id']) + self.context, xpool['id'] + ) except Exception as e: - msg = ("Bad ID Supplied for pool. pool_id: " - "%(pool)s message: %(res)s") - LOG.critical(msg, {'pool': xpool['id'], 'res': e}) + LOG.critical( + 'Bad ID Supplied for pool. pool_id: ' + '%(pool)s message: %(res)s', + { + 'pool': xpool['id'], 'res': e + } + ) continue else: pool = self.central_api.find_pool( - self.context, {"name": xpool['name']}) + self.context, {'name': xpool['name']} + ) LOG.info('Updating existing pool: %s', pool) @@ -167,7 +194,7 @@ class PoolCommands(base.Commands): sys.exit(1) if dry_run: - output_msg.append("Update Pool: %s" % pool) + self.output_msg.append('Update Pool: %s' % pool) else: pool = self.central_api.update_pool(self.context, pool) # Bug: Changes in the pool targets should trigger a @@ -175,21 +202,12 @@ class PoolCommands(base.Commands): self._update_zones(pool) except exceptions.PoolNotFound: - pool = DesignateAdapter.parse('YAML', xpool, objects.Pool()) - for ns_record in pool.ns_records: - try: - ns_record.validate() - except exceptions.InvalidObject as e: - LOG.error(e.errors.to_list()[0]['message']) - sys.exit(1) - if dry_run: - output_msg.append("Create Pool: %s" % pool) - else: - LOG.info('Creating new pool: %s', pool) - self.central_api.create_pool(self.context, pool) + self._create_pool(xpool, dry_run) except messaging.exceptions.MessagingTimeout: - LOG.critical("No response received from designate-central. " - "Check it is running, and retry") + LOG.critical( + 'No response received from designate-central. ' + 'Check it is running, and retry' + ) sys.exit(1) if delete: @@ -206,7 +224,7 @@ class PoolCommands(base.Commands): criterion={'name': pool}) if dry_run: - output_msg.append("Delete Pool: %s" % p) + self.output_msg.append('Delete Pool: %s' % p) else: LOG.info('Deleting %s', p) @@ -214,9 +232,10 @@ class PoolCommands(base.Commands): except messaging.exceptions.MessagingTimeout: LOG.critical( - "No response received from designate-central. " - "Check it is running, and retry") + 'No response received from designate-central. ' + 'Check it is running, and retry' + ) sys.exit(1) - for line in output_msg: + for line in self.output_msg: print(line) diff --git a/designate/objects/adapters/yaml/pool_attribute.py b/designate/objects/adapters/yaml/pool_attribute.py index 3ca3508f..41bc8b77 100644 --- a/designate/objects/adapters/yaml/pool_attribute.py +++ b/designate/objects/adapters/yaml/pool_attribute.py @@ -59,7 +59,6 @@ class PoolAttributeListYAMLAdapter(base.YAMLAdapter): @classmethod def parse_list(cls, values, output_object, *args, **kwargs): - for key, value in values.items(): # Add the object to the list output_object.append( diff --git a/designate/tests/test_api/test_v2/__init__.py b/designate/tests/test_api/test_v2/__init__.py index 27337031..cd49c1b5 100644 --- a/designate/tests/test_api/test_v2/__init__.py +++ b/designate/tests/test_api/test_v2/__init__.py @@ -37,9 +37,6 @@ class ApiV2TestCase(ApiTestCase): def setUp(self): super(ApiV2TestCase, self).setUp() - # Ensure the v2 API is enabled - self.config(enable_api_v2=True, group='service:api') - # Create the application self.app = api_v2.factory({}) diff --git a/designate/tests/test_backend/__init__.py b/designate/tests/test_api/test_v2/test_api.py index 92493e0a..3d9bbddc 100644 --- a/designate/tests/test_backend/__init__.py +++ b/designate/tests/test_api/test_v2/test_api.py @@ -1,7 +1,3 @@ -# Copyright 2015 Hewlett-Packard Development Company, L.P. -# -# Author: Kiall Mac Innes <kiall@hpe.com> -# # 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 @@ -13,8 +9,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 designate.tests import TestCase +from designate.tests.test_api.test_v2 import ApiV2TestCase + + +class ApiV2DisableTest(ApiV2TestCase): + def setUp(self): + self.config(enable_api_v2=False, group='service:api') + super(ApiV2DisableTest, self).setUp() + + def test_disable_v2_api(self): + urls = ['zones', 'pools', 'service_statuses'] + + for url in urls: + response = self.client.get('/%s/' % url, expect_errors=True) -class BackendTestCase(TestCase): - pass + self.assertEqual(404, response.status_code) + self.assertEqual(b'', response.body) diff --git a/designate/tests/test_api/test_v2/test_import_export.py b/designate/tests/test_api/test_v2/test_import_export.py index c8477f0f..a8af5f05 100644 --- a/designate/tests/test_api/test_v2/test_import_export.py +++ b/designate/tests/test_api/test_v2/test_import_export.py @@ -110,8 +110,8 @@ class APIV2ZoneImportExportTest(ApiV2TestCase): self.policy({'zone_export': '@'}) get_response = self.adminclient.get('/zones/export/%s' % - response.json['zone_id'], - headers={'Accept': 'text/dns'}) + response.json['zone_id'], + headers={'Accept': 'text/dns'}) exported_zonefile = get_response.body.decode('utf-8') imported = dnszone.from_text(self.get_zonefile_fixture()) diff --git a/designate/tests/test_api/test_v2/test_quotas.py b/designate/tests/test_api/test_v2/test_quotas.py new file mode 100644 index 00000000..be79981e --- /dev/null +++ b/designate/tests/test_api/test_v2/test_quotas.py @@ -0,0 +1,159 @@ +# 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 designate.tests.test_api.test_v2 import ApiV2TestCase + +LOG = logging.getLogger(__name__) + + +class ApiV2QuotasTest(ApiV2TestCase): + def setUp(self): + super(ApiV2QuotasTest, self).setUp() + + def test_get_quotas(self): + self.config(quota_api_export_size=1) + + context = self.get_context(project_id='a') + + result = self.client.get( + '/quotas/%s' % context.project_id, status=200, + headers={'X-Test-Tenant-Id': context.project_id} + ) + self.assertEqual( + { + 'zones': 10, + 'zone_recordsets': 500, + 'zone_records': 500, + 'recordset_records': 20, + 'api_export_size': 1 + }, + result.json + ) + + def test_get_all_quotas(self): + self.config(quota_zone_recordsets=1) + + result = self.client.get( + '/quotas', status=200, + ) + + self.assertEqual( + { + 'api_export_size': 1000, + 'recordset_records': 20, + 'zone_records': 500, + 'zone_recordsets': 1, + 'zones': 10 + }, + result.json + ) + + def test_set_quotas(self): + self.policy({'set_quota': '@'}) + + context = self.get_context(project_id='a') + self.client.patch_json( + '/quotas/%s' % context.project_id, {'zones': 123}, status=200, + headers={'X-Test-Tenant-Id': context.project_id} + ) + + result = self.client.get( + '/quotas/%s' % context.project_id, status=200, + headers={'X-Test-Tenant-Id': context.project_id} + ) + self.assertEqual( + { + 'zones': 123, + 'zone_recordsets': 500, + 'zone_records': 500, + 'recordset_records': 20, + 'api_export_size': 1000 + }, + result.json + ) + + def test_set_quotas_with_verify_project_id(self): + self.config( + quotas_verify_project_id=True, + group='service:api' + ) + + self.policy({'set_quota': '@'}) + + context = self.get_context(project_id='a') + self.client.patch_json( + '/quotas/%s' % context.project_id, {'zones': 123}, status=200, + headers={'X-Test-Tenant-Id': context.project_id} + ) + + result = self.client.get( + '/quotas/%s' % context.project_id, status=200, + headers={'X-Test-Tenant-Id': context.project_id} + ) + self.assertEqual( + { + 'zones': 123, + 'zone_recordsets': 500, + 'zone_records': 500, + 'recordset_records': 20, + 'api_export_size': 1000 + }, + result.json + ) + + def test_delete_quotas(self): + self.config(quota_zone_records=1) + + self.policy({'set_quota': '@'}) + + context = self.get_context(project_id='a') + + # Update recordset_records quota. + result = self.client.patch_json( + '/quotas/%s' % context.project_id, {'recordset_records': 123}, + status=200, + headers={'X-Test-Tenant-Id': context.project_id} + ) + self.assertEqual( + { + 'zones': 10, + 'zone_recordsets': 500, + 'zone_records': 1, + 'recordset_records': 123, + 'api_export_size': 1000 + }, + result.json + ) + + # Delete quota. + self.client.delete( + '/quotas/%s' % context.project_id, status=204, + headers={'X-Test-Tenant-Id': context.project_id} + ) + + # Make sure we are back to the default quotas. + result = self.client.get( + '/quotas/%s' % context.project_id, status=200, + headers={'X-Test-Tenant-Id': context.project_id} + ) + self.assertEqual( + { + 'zones': 10, + 'zone_recordsets': 500, + 'zone_records': 1, + 'recordset_records': 20, + 'api_export_size': 1000 + }, + result.json + ) diff --git a/designate/tests/test_api/test_v2/test_zone_transfers.py b/designate/tests/test_api/test_v2/test_zone_transfers.py index 343a0a37..ae4e7ed6 100644 --- a/designate/tests/test_api/test_v2/test_zone_transfers.py +++ b/designate/tests/test_api/test_v2/test_zone_transfers.py @@ -28,7 +28,7 @@ class ApiV2ZoneTransfersTest(ApiV2TestCase): def test_create_zone_transfer_request(self): response = self.client.post_json( - '/zones/%s/tasks/transfer_requests' % (self.zone.id), + '/zones/%s/tasks/transfer_requests' % self.zone.id, {}) # Check the headers are what we expect @@ -53,7 +53,7 @@ class ApiV2ZoneTransfersTest(ApiV2TestCase): def test_create_zone_transfer_request_scoped(self): response = self.client.post_json( - '/zones/%s/tasks/transfer_requests' % (self.zone.id), + '/zones/%s/tasks/transfer_requests' % self.zone.id, {'target_project_id': str(self.tenant_1_context.project_id)}) # Check the headers are what we expect @@ -79,7 +79,7 @@ class ApiV2ZoneTransfersTest(ApiV2TestCase): def test_create_zone_transfer_request_empty_body(self): # Send an empty ("None") body response = self.client.post_json( - '/zones/%s/tasks/transfer_requests' % (self.zone.id), + '/zones/%s/tasks/transfer_requests' % self.zone.id, None) # Check the headers are what we expect @@ -88,7 +88,7 @@ class ApiV2ZoneTransfersTest(ApiV2TestCase): def test_get_zone_transfer_request(self): initial = self.client.post_json( - '/zones/%s/tasks/transfer_requests' % (self.zone.id), + '/zones/%s/tasks/transfer_requests' % self.zone.id, {}) response = self.client.get( @@ -129,7 +129,7 @@ class ApiV2ZoneTransfersTest(ApiV2TestCase): self.assertEqual(0, len(response.json['transfer_requests'])) self.client.post_json( - '/zones/%s/tasks/transfer_requests' % (self.zone.id), + '/zones/%s/tasks/transfer_requests' % self.zone.id, {}) data = self.client.get( @@ -139,7 +139,7 @@ class ApiV2ZoneTransfersTest(ApiV2TestCase): def test_update_zone_transfer_request(self): initial = self.client.post_json( - '/zones/%s/tasks/transfer_requests' % (self.zone.id), + '/zones/%s/tasks/transfer_requests' % self.zone.id, {}) response = self.client.patch_json( @@ -168,7 +168,7 @@ class ApiV2ZoneTransfersTest(ApiV2TestCase): def test_delete_zone_transfer_request(self): initial = self.client.post_json( - '/zones/%s/tasks/transfer_requests' % (self.zone.id), + '/zones/%s/tasks/transfer_requests' % self.zone.id, {}) response = self.client.delete( @@ -180,7 +180,7 @@ class ApiV2ZoneTransfersTest(ApiV2TestCase): def test_create_zone_transfer_accept(self): initial = self.client.post_json( - '/zones/%s/tasks/transfer_requests' % (self.zone.id), + '/zones/%s/tasks/transfer_requests' % self.zone.id, {}) response = self.client.post_json( @@ -216,7 +216,7 @@ class ApiV2ZoneTransfersTest(ApiV2TestCase): def test_get_zone_transfer_accept(self): initial = self.client.post_json( - '/zones/%s/tasks/transfer_requests' % (self.zone.id), + '/zones/%s/tasks/transfer_requests' % self.zone.id, {}) transfer_accept = self.client.post_json( @@ -272,7 +272,7 @@ class ApiV2ZoneTransfersTest(ApiV2TestCase): self.assertEqual(0, len(response.json['transfer_accepts'])) initial = self.client.post_json( - '/zones/%s/tasks/transfer_requests' % (self.zone.id), + '/zones/%s/tasks/transfer_requests' % self.zone.id, {}) self.client.post_json( @@ -288,14 +288,14 @@ class ApiV2ZoneTransfersTest(ApiV2TestCase): self.assertEqual(1, len(data.json['transfer_accepts'])) def test_create_zone_transfer_request_deleting_zone(self): - url = '/zones/%s/tasks/transfer_requests' % (self.zone.id) + url = '/zones/%s/tasks/transfer_requests' % self.zone.id body = {} self.client.delete('/zones/%s' % self.zone['id'], status=202) self._assert_exception('bad_request', 400, self.client.post_json, url, body) def test_create_zone_transfer_accept_deleting_zone(self): - url = '/zones/%s/tasks/transfer_requests' % (self.zone.id) + url = '/zones/%s/tasks/transfer_requests' % self.zone.id body = {} self.client.delete('/zones/%s' % self.zone['id'], status=202) self._assert_exception('bad_request', 400, self.client.post_json, url, @@ -304,7 +304,7 @@ class ApiV2ZoneTransfersTest(ApiV2TestCase): # Metadata tests def test_metadata_exists_zone_transfer_accepts(self): initial = self.client.post_json( - '/zones/%s/tasks/transfer_requests' % (self.zone.id), + '/zones/%s/tasks/transfer_requests' % self.zone.id, {}) self.client.post_json( @@ -324,7 +324,7 @@ class ApiV2ZoneTransfersTest(ApiV2TestCase): def test_total_count_zone_transfer_accepts(self): initial = self.client.post_json( - '/zones/%s/tasks/transfer_requests' % (self.zone.id), + '/zones/%s/tasks/transfer_requests' % self.zone.id, {}) self.client.post_json( diff --git a/designate/tests/test_manage/test_update_pool.py b/designate/tests/test_manage/test_update_pool.py index f759d178..70087977 100644 --- a/designate/tests/test_manage/test_update_pool.py +++ b/designate/tests/test_manage/test_update_pool.py @@ -13,6 +13,7 @@ from unittest import mock from oslo_log import log as logging +from designate import context from designate.manage.pool import PoolCommands from designate import objects from designate.tests import fixtures @@ -27,6 +28,10 @@ class UpdatePoolTestCase(DesignateManageTestCase): self.stdlog = fixtures.StandardLogging() self.useFixture(self.stdlog) + self.context = context.DesignateContext.get_admin_context( + request_id='designate-manage' + ) + def hydrate_pool_targets(self, target_masters): pool_targets = objects.PoolTargetList() masters = objects.PoolTargetMasterList() @@ -49,19 +54,21 @@ class UpdatePoolTestCase(DesignateManageTestCase): # Ensure the correct NS Records are in place pool = self.central_service.get_pool( - self.admin_context, zone.pool_id) + self.admin_context, zone.pool_id + ) pool.targets = self.hydrate_pool_targets([objects.PoolTargetMaster( - pool_target_id=pool.id, - host="127.0.0.1", - port="53")]) + pool_target_id=pool.id, + host='192.0.2.2', + port='53')] + ) command = PoolCommands() - command.context = self.admin_context + command.context = self.context command.central_api = self.central_service - with mock.patch.object(self.central_service, - "update_zone") as mock_update_zone: + with mock.patch.object( + self.central_service, 'update_zone') as mock_update_zone: command._update_zones(pool) mock_update_zone.assert_called_once() @@ -77,25 +84,69 @@ class UpdatePoolTestCase(DesignateManageTestCase): # Ensure the correct NS Records are in place pool = self.central_service.get_pool( - self.admin_context, zone.pool_id) + self.admin_context, zone.pool_id + ) targets1 = self.hydrate_pool_targets([ objects.PoolTargetMaster( - pool_target_id=pool.id, - host="127.0.0.1", - port="53") + pool_target_id=pool.id, + host='192.0.2.3', + port='53') ]) targets2 = self.hydrate_pool_targets([ objects.PoolTargetMaster( - pool_target_id=pool.id, - host="127.0.0.1", - port="53") + pool_target_id=pool.id, + host='192.0.2.4', + port='53') ]) pool.targets = objects.PoolTargetList() pool.targets.extend(targets1.objects + targets2.objects) command = PoolCommands() - command.context = self.admin_context + command.context = self.context command.central_api = self.central_service command._update_zones(pool) + + def test_create_new_pool(self): + pool = { + 'name': 'new_pool', + 'description': 'New PowerDNS Pool', + 'attributes': {}, + 'ns_records': [ + {'hostname': 'ns1-1.example.org.', 'priority': 1}, + {'hostname': 'ns1-2.example.org.', 'priority': 2} + ], + 'nameservers': [ + {'host': '192.0.2.2', 'port': 53} + ], + 'targets': [ + { + 'type': 'powerdns', + 'description': 'PowerDNS Database Cluster', + 'masters': [ + {'host': '192.0.2.1', 'port': 5354} + ], + 'options': { + 'host': '192.0.2.2', 'port': 53, + 'connection': 'connection' + } + } + ], + 'also_notifies': [ + {'host': '192.0.2.4', 'port': 53} + ] + } + + command = PoolCommands() + command.context = self.context + command.central_api = self.central_service + + command._create_pool(pool, dry_run=False) + + pool = self.central_service.find_pool( + self.admin_context, {'name': 'new_pool'} + ) + + self.assertEqual('new_pool', pool.name) + self.assertEqual('New PowerDNS Pool', pool.description) diff --git a/designate/tests/unit/backend/test_base.py b/designate/tests/unit/backend/test_base.py new file mode 100644 index 00000000..45b58e0b --- /dev/null +++ b/designate/tests/unit/backend/test_base.py @@ -0,0 +1,93 @@ +# 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 unittest import mock + +import oslotest.base +import stevedore.exception + +from designate import backend +from designate.backend import base +from designate.backend import impl_pdns4 +from designate import context +from designate import objects +from designate.tests import fixtures + + +class BaseBackendTestCase(oslotest.base.BaseTestCase): + def setUp(self): + super(BaseBackendTestCase, self).setUp() + self.stdlog = fixtures.StandardLogging() + self.useFixture(self.stdlog) + + self.context = mock.Mock() + self.admin_context = mock.Mock() + mock.patch.object( + context.DesignateContext, 'get_admin_context', + return_value=self.admin_context).start() + + self.target = { + 'type': 'pdns4', + 'masters': [ + ], + 'options': [ + ], + } + + @mock.patch.object(base.Backend, 'get_driver') + def test_untested_backend(self, mock_get_driver): + driver = mock.Mock() + driver.__backend_status__ = 'untested' + mock_get_driver.return_value = driver + + self.target['type'] = 'test' + pool_target = objects.PoolTarget.from_dict(self.target) + + backend.get_backend(pool_target) + + self.assertIn('WARNING', self.stdlog.logger.output) + self.assertIn( + "Backend Driver 'test' loaded. Has status of 'untested'", + self.stdlog.logger.output + ) + + @mock.patch.object(base.Backend, 'get_driver') + def test_tested_backend(self, mock_get_driver): + driver = mock.Mock() + driver.__backend_status__ = 'integrated' + mock_get_driver.return_value = driver + + self.target['type'] = 'test' + pool_target = objects.PoolTarget.from_dict(self.target) + + backend.get_backend(pool_target) + + self.assertNotIn('WARNING', self.stdlog.logger.output) + self.assertIn( + "Backend Driver 'test' loaded. Has status of 'integrated'", + self.stdlog.logger.output + ) + + def test_get_backend(self): + pool_target = objects.PoolTarget.from_dict(self.target) + self.assertIsInstance( + backend.get_backend(pool_target), + impl_pdns4.PDNS4Backend + ) + + def test_get_backend_does_not_exist(self): + self.target['type'] = 'unknown' + pool_target = objects.PoolTarget.from_dict(self.target) + self.assertRaises( + stevedore.exception.NoMatches, + backend.get_backend, pool_target + ) diff --git a/releasenotes/notes/fix-designate-manage-pool-7d812f938e894133.yaml b/releasenotes/notes/fix-designate-manage-pool-7d812f938e894133.yaml new file mode 100644 index 00000000..7d8868ed --- /dev/null +++ b/releasenotes/notes/fix-designate-manage-pool-7d812f938e894133.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fixed an issue in central where "designate manage pool update" may return + an error designate.exceptions.MissingProjectID when attempting to create a + new pool. diff --git a/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po b/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po index 06f76fa9..b9de214e 100644 --- a/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po +++ b/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po @@ -8,11 +8,11 @@ msgid "" msgstr "" "Project-Id-Version: Designate Release Notes\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-06-25 06:02+0000\n" +"POT-Creation-Date: 2022-08-22 18:40+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2022-07-19 09:55+0000\n" +"PO-Revision-Date: 2022-08-23 01:25+0000\n" "Last-Translator: Andi Chandler <andi@gowling.com>\n" "Language-Team: English (United Kingdom)\n" "Language: en_GB\n" @@ -28,8 +28,8 @@ msgstr "1.0.2" msgid "10.0.0" msgstr "10.0.0" -msgid "10.0.2-2" -msgstr "10.0.2-2" +msgid "10.0.2-3" +msgstr "10.0.2-3" msgid "11.0.0" msgstr "11.0.0" @@ -37,6 +37,9 @@ msgstr "11.0.0" msgid "11.0.2" msgstr "11.0.2" +msgid "11.0.2-2" +msgstr "11.0.2-2" + msgid "12.0.0" msgstr "12.0.0" @@ -52,8 +55,8 @@ msgstr "13.0.1" msgid "14.0.0" msgstr "14.0.0" -msgid "14.0.0-48" -msgstr "14.0.0-48" +msgid "14.0.0-73" +msgstr "14.0.0-73" msgid "14.0.1" msgstr "14.0.1" @@ -424,6 +427,15 @@ msgstr "" "and 'bytes', causing designate-producer to crash." msgid "" +"Fixed an issue in central where \"designate manage pool update\" may return " +"an error designate.exceptions.MissingProjectID when attempting to create a " +"new pool." +msgstr "" +"Fixed an issue in central where \"designate manage pool update\" may return " +"an error designate.exceptions.MissingProjectID when attempting to create a " +"new pool." + +msgid "" "Fixed an issue that caused the recordset_records quota to not be enforced." msgstr "" "Fixed an issue that caused the recordset_records quota to not be enforced." @@ -916,6 +928,13 @@ msgstr "" "effect since SSLMiddleware was removed during the Ussuri cycle." msgid "" +"The netaddr python module has been removed as a Designate requirement. It " +"has been replaced with the python standard library 'ipaddress' module." +msgstr "" +"The netaddr python module has been removed as a Designate requirement. It " +"has been replaced with the Python standard library 'ipaddress' module." + +msgid "" "The pool-manager has been removed and can no longer be used. Instead the " "worker and producer service should be used." msgstr "" @@ -1051,6 +1070,13 @@ msgstr "V1 API has been removed" msgid "V1 API removal is complete in this version of designate." msgstr "V1 API removal is complete in this version of designate." +msgid "" +"Verify that if a TXT record starts with a double quote, it also ends with a " +"double quote." +msgstr "" +"Verify that if a TXT record starts with a double quote, it also ends with a " +"double quote." + msgid "Victoria Series Release Notes" msgstr "Victoria Series Release Notes" |