diff --git a/designate/api/v2/controllers/pools.py b/designate/api/v2/controllers/pools.py index 39cda62b2..245a3d5c4 100644 --- a/designate/api/v2/controllers/pools.py +++ b/designate/api/v2/controllers/pools.py @@ -16,6 +16,7 @@ import pecan from oslo_log import log as logging from designate import utils +from designate.i18n import _LW from designate.api.v2.controllers import rest from designate.objects import Pool from designate.objects.adapters import DesignateAdapter @@ -63,6 +64,11 @@ class PoolsController(rest.RestController): @pecan.expose(template='json:', content_type='application/json') def post_all(self): """Create a Pool""" + + LOG.warning(_LW("Use of this API Method is DEPRICATED. This will have " + "unforseen side affects when used with the " + "designate-manage pool commands")) + request = pecan.request response = pecan.response context = request.environ['context'] @@ -86,6 +92,11 @@ class PoolsController(rest.RestController): @utils.validate_uuid('pool_id') def patch_one(self, pool_id): """Update the specific pool""" + + LOG.warning(_LW("Use of this API Method is DEPRICATED. This will have " + "unforseen side affects when used with the " + "designate-manage pool commands")) + request = pecan.request context = request.environ['context'] body = request.body_dict @@ -111,6 +122,11 @@ class PoolsController(rest.RestController): @utils.validate_uuid('pool_id') def delete_one(self, pool_id): """Delete the specific pool""" + + LOG.warning(_LW("Use of this API Method is DEPRICATED. This will have " + "unforseen side affects when used with the " + "designate-manage pool commands")) + request = pecan.request response = pecan.response context = request.environ['context'] diff --git a/designate/manage/pool.py b/designate/manage/pool.py index 325073d7f..9825e99a6 100644 --- a/designate/manage/pool.py +++ b/designate/manage/pool.py @@ -13,24 +13,172 @@ # 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 pprint - +import yaml from oslo_config import cfg from oslo_log import log as logging +import oslo_messaging as messaging -from designate.manage import base +from designate import exceptions +from designate import rpc +from designate.i18n import _LI +from designate.i18n import _LC from designate import objects +from designate.central import rpcapi as central_rpcapi +from designate.manage import base +from designate.objects.adapters import DesignateAdapter LOG = logging.getLogger(__name__) + + CONF = cfg.CONF class PoolCommands(base.Commands): + def __init__(self): + super(PoolCommands, self).__init__() + rpc.init(cfg.CONF) + self.central_api = central_rpcapi.CentralAPI() + + @base.args('--file', help='The path to the file the yaml output should be ' + 'writen to', + default='/etc/designate/pools.yaml') + def generate_file(self, file): + try: + pools = self.central_api.find_pools(self.context) + except messaging.exceptions.MessagingTimeout: + LOG.critical(_LC("No response recieved from designate-central. " + "Check it is running, and retry")) + with open(file, 'w') as stream: + yaml.dump( + DesignateAdapter.render('YAML', pools), + stream, + default_flow_style=False + ) + + @base.args('--file', help='The path to the file the yaml output should be ' + 'writen to', + default='/etc/designate/pools.yaml') + def export_from_config(self, file): + try: + pools = self.central_api.find_pools(self.context) + except messaging.exceptions.MessagingTimeout: + LOG.critical(_LC("No response recieved from designate-central. " + "Check it is running, and retry")) + r_pools = objects.PoolList() + for pool in pools: + r_pool = objects.Pool.from_config(CONF, pool.id) + r_pool.id = pool.id + r_pool.ns_records = pool.ns_records + r_pool.attributes = pool.attributes + r_pools.append(r_pool) + with open(file, 'w') as stream: + yaml.dump( + DesignateAdapter.render('YAML', r_pools), + stream, + default_flow_style=False + ) @base.args('--pool_id', help='ID of the pool to be examined', - default=CONF['service:central'].default_pool_id) + default=CONF['service:central'].default_pool_id) def show_config(self, pool_id): - print('*' * 100) - print('Pool Configuration:') - print('*' * 100) - pprint.pprint(objects.Pool.from_config(CONF, pool_id).to_dict()) + try: + pool = self.central_api.find_pool(self.context, {"id": pool_id}) + + print('Pool Configuration:') + print('-------------------') + + print(yaml.dump(DesignateAdapter.render('YAML', pool), + default_flow_style=False)) + + except messaging.exceptions.MessagingTimeout: + LOG.critical(_LC("No response recieved from designate-central. " + "Check it is running, and retry")) + + @base.args('--file', help='The path to the yaml file describing the pools', + default='/etc/designate/pools.yaml') + @base.args( + '--delete', + help='Any Pools not listed in the config file will be deleted. ' + ' WARNING: This will delete any zones left in this pool', + default=False) + @base.args( + '--dry_run', + help='This will simulate what will happen when you run this command', + default=False) + def update(self, file, delete, dry_run): + 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("*********************************") + + for xpool in xpools: + try: + if 'id' in xpool: + try: + pool = self.central_api.get_pool( + self.context, xpool['id']) + except Exception: + LOG.critical( + _LC("Bad ID Supplied for pool %s"), xpool['name']) + continue + else: + pool = self.central_api.find_pool( + self.context, {"name": xpool['name']}) + + LOG.info(_LI('Updating existing pool: %s'), pool) + + # TODO(kiall): Move the below into the pool object + + pool = DesignateAdapter.parse('YAML', xpool, pool) + + if dry_run: + output_msg.append("Update Pool: %s" % pool) + else: + pool = self.central_api.update_pool(self.context, pool) + + except exceptions.PoolNotFound: + pool = DesignateAdapter.parse('YAML', xpool, objects.Pool()) + # pool = objects.Pool.from_dict(xpool) + if dry_run: + output_msg.append("Create Pool: %s" % pool) + else: + LOG.info(_LI('Creating new pool: %s'), pool) + self.central_api.create_pool(self.context, pool) + except messaging.exceptions.MessagingTimeout: + LOG.critical(_LC("No response recieved from designate-central." + " Check it is running, and retry")) + + if delete: + pools = self.central_api.find_pools(self.context) + pools_in_db = {pool.name for pool in pools} + pools_in_yaml = {xpool['name'] for xpool in xpools} + + pools_to_delete = pools_in_db - pools_in_yaml + + for pool in pools_to_delete: + try: + p = self.central_api.find_pool( + self.context, + criterion={'name': pool}) + + if dry_run: + output_msg.append("Delete Pool: %s" % p) + + else: + LOG.info(_LI('Deleting %s'), p) + self.central_api.delete_pool(self.context, p.id) + + except messaging.exceptions.MessagingTimeout: + LOG.critical(_LC("No response recieved from " + "designate-central. " + "Check it is running, and retry")) + + for line in output_msg: + print(line) diff --git a/designate/manage/powerdns.py b/designate/manage/powerdns.py index 5b0c45633..3dc0b6645 100644 --- a/designate/manage/powerdns.py +++ b/designate/manage/powerdns.py @@ -21,7 +21,9 @@ from oslo_db.sqlalchemy.migration_cli import manager as migration_manager from oslo_log import log as logging from designate.manage import base +from designate import rpc from designate import utils +from designate.central import rpcapi as central_rpcapi LOG = logging.getLogger(__name__) @@ -32,33 +34,50 @@ CONF = cfg.CONF utils.register_plugin_opts() -def get_manager(pool_target_id): - pool_target_options = CONF['pool_target:%s' % pool_target_id].options - connection = pool_target_options['connection'] +def get_manager(pool_target): + connection = pool_target.options.get('connection', None) migration_config = { 'migration_repo_path': REPOSITORY, 'db_url': connection} + return migration_manager.MigrationManager(migration_config) class DatabaseCommands(base.Commands): - @base.args('pool-target-id', help="Pool Target to Migrate", type=str) - def version(self, pool_target_id): - current = get_manager(pool_target_id).version() - latest = versioning_api.version(repository=REPOSITORY).value - print("Current: %s Latest: %s" % (current, latest)) + def __init__(self): + super(DatabaseCommands, self).__init__() + rpc.init(cfg.CONF) + self.central_api = central_rpcapi.CentralAPI() - @base.args('pool-target-id', help="Pool Target to Migrate", type=str) - def sync(self, pool_target_id): - get_manager(pool_target_id).upgrade(None) + @base.args('pool-id', help="Pool to Migrate", type=str) + def version(self, pool_id): + pool = self.central_api.find_pool(self.context, {"id": pool_id}) - @base.args('pool-target-id', help="Pool Target to Migrate", type=str) + for pool_target in pool.targets: + current = get_manager(pool_target).version() + latest = versioning_api.version(repository=REPOSITORY).value + print("Current: %s Latest: %s" % (current, latest)) + + @base.args('pool-id', help="Pool to Migrate", type=str) + def sync(self, pool_id): + pool = self.central_api.find_pool(self.context, {"id": pool_id}) + + for pool_target in pool.targets: + get_manager(pool_target).upgrade(None) + + @base.args('pool-id', help="Pool to Migrate", type=str) @base.args('revision', nargs='?') - def upgrade(self, pool_target_id, revision): - get_manager(pool_target_id).upgrade(revision) + def upgrade(self, pool_id, revision): + pool = self.central_api.find_pool(self.context, {"id": pool_id}) - @base.args('pool-target-id', help="Pool Target to Migrate", type=str) + for pool_target in pool.targets: + get_manager(pool_target).upgrade(revision) + + @base.args('pool-id', help="Pool to Migrate", type=str) @base.args('revision', nargs='?') - def downgrade(self, pool_target_id, revision): - get_manager(pool_target_id).downgrade(revision) + def downgrade(self, pool_id, revision): + pool = self.central_api.find_pool(self.context, {"id": pool_id}) + + for pool_target in pool.targets: + get_manager(pool_target).downgrade(revision) diff --git a/designate/objects/adapters/__init__.py b/designate/objects/adapters/__init__.py index 620c31035..17aa830a5 100644 --- a/designate/objects/adapters/__init__.py +++ b/designate/objects/adapters/__init__.py @@ -32,3 +32,14 @@ from designate.objects.adapters.api_v2.zone_transfer_request import ZoneTransfer from designate.objects.adapters.api_v2.validation_error import ValidationErrorAPIv2Adapter, ValidationErrorListAPIv2Adapter # noqa from designate.objects.adapters.api_v2.zone_import import ZoneImportAPIv2Adapter, ZoneImportListAPIv2Adapter # noqa from designate.objects.adapters.api_v2.zone_export import ZoneExportAPIv2Adapter, ZoneExportListAPIv2Adapter # noqa + +# YAML + +from designate.objects.adapters.yaml.pool import PoolYAMLAdapter, PoolListYAMLAdapter # noqa +from designate.objects.adapters.yaml.pool_attribute import PoolAttributeYAMLAdapter, PoolAttributeListYAMLAdapter # noqa +from designate.objects.adapters.yaml.pool_also_notify import PoolAlsoNotifyYAMLAdapter, PoolAlsoNotifyListYAMLAdapter # noqa +from designate.objects.adapters.yaml.pool_nameserver import PoolNameserverYAMLAdapter, PoolNameserverListYAMLAdapter # noqa +from designate.objects.adapters.yaml.pool_ns_record import PoolNsRecordYAMLAdapter, PoolNsRecordListYAMLAdapter # noqa +from designate.objects.adapters.yaml.pool_target import PoolTargetYAMLAdapter, PoolTargetListYAMLAdapter # noqa +from designate.objects.adapters.yaml.pool_target_master import PoolTargetMasterYAMLAdapter, PoolTargetMasterListYAMLAdapter # noqa +from designate.objects.adapters.yaml.pool_target_option import PoolTargetOptionYAMLAdapter, PoolTargetOptionListYAMLAdapter # noqa diff --git a/designate/objects/adapters/yaml/__init__.py b/designate/objects/adapters/yaml/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/designate/objects/adapters/yaml/base.py b/designate/objects/adapters/yaml/base.py new file mode 100644 index 000000000..4b65a7207 --- /dev/null +++ b/designate/objects/adapters/yaml/base.py @@ -0,0 +1,81 @@ +# Copyright 2016 Hewlett Packard Enterprise Development Company, L.P. +# +# 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.objects.adapters import base + +LOG = logging.getLogger(__name__) + + +class YAMLAdapter(base.DesignateAdapter): + + ADAPTER_FORMAT = 'YAML' + + ##################### + # Parsing methods # + ##################### + + @classmethod + def parse(cls, values, output_object, *args, **kwargs): + obj = super(YAMLAdapter, cls).parse( + cls.ADAPTER_FORMAT, values, output_object, *args, **kwargs) + return obj + + @classmethod + def _render_object(cls, object, *args, **kwargs): + # The dict we will return to be rendered to JSON / output format + r_obj = {} + # Loop over all fields that are supposed to be output + for key, value in cls.MODIFICATIONS['fields'].items(): + # Get properties for this field + field_props = cls.MODIFICATIONS['fields'][key] + # Check if it has to be renamed + if field_props.get('rename', False): + obj = getattr(object, field_props.get('rename')) + # if rename is specified we need to change the key + obj_key = field_props.get('rename') + else: + # if not, move on + obj = getattr(object, key, None) + obj_key = key + # Check if this item is a relation (another DesignateObject that + # will need to be converted itself + if object.FIELDS.get(obj_key, {}).get('relation'): + # Get a adapter for the nested object + # Get the class the object is and get its adapter, then set + # the item in the dict to the output + r_obj[key] = cls.get_object_adapter( + cls.ADAPTER_FORMAT, + object.FIELDS[obj_key].get('relation_cls')).render( + cls.ADAPTER_FORMAT, obj, *args, **kwargs) + elif object.FIELDS.get( + obj_key, {}).get('schema', {}).get('type') == 'integer': + r_obj[key] = int(obj) + elif obj is not None: + # Just attach the damn item if there is no weird edge cases + r_obj[key] = str(obj) + # Send it back + return r_obj + + @classmethod + def _render_list(cls, list_object, *args, **kwargs): + # The list we will return to be rendered to JSON / output format + r_list = [] + # iterate and convert each DesignateObject in the list, and append to + # the object we are returning + for object in list_object: + r_list.append(cls.get_object_adapter( + cls.ADAPTER_FORMAT, + object).render(cls.ADAPTER_FORMAT, object, *args, **kwargs)) + return r_list diff --git a/designate/objects/adapters/yaml/pool.py b/designate/objects/adapters/yaml/pool.py new file mode 100644 index 000000000..2e534eeee --- /dev/null +++ b/designate/objects/adapters/yaml/pool.py @@ -0,0 +1,59 @@ +# Copyright 2014 Hewlett-Packard Development Company, L.P. +# +# 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.objects.adapters.yaml import base +from designate import objects +LOG = logging.getLogger(__name__) + + +class PoolYAMLAdapter(base.YAMLAdapter): + + ADAPTER_OBJECT = objects.Pool + + MODIFICATIONS = { + 'fields': { + 'id': { + 'read_only': False + }, + 'name': { + 'read_only': False + }, + 'description': { + 'read_only': False + }, + 'attributes': { + 'read_only': False + }, + 'ns_records': { + 'read_only': False + }, + 'nameservers': { + 'read_only': False + }, + 'targets': { + 'read_only': False + }, + 'also_notifies': { + 'read_only': False + }, + } + } + + +class PoolListYAMLAdapter(base.YAMLAdapter): + + ADAPTER_OBJECT = objects.PoolList + + MODIFICATIONS = {} diff --git a/designate/objects/adapters/yaml/pool_also_notify.py b/designate/objects/adapters/yaml/pool_also_notify.py new file mode 100644 index 000000000..a04c0c4e9 --- /dev/null +++ b/designate/objects/adapters/yaml/pool_also_notify.py @@ -0,0 +1,41 @@ +# Copyright 2014 Hewlett-Packard Development Company, L.P. +# +# 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.objects.adapters.yaml import base +from designate import objects +LOG = logging.getLogger(__name__) + + +class PoolAlsoNotifyYAMLAdapter(base.YAMLAdapter): + + ADAPTER_OBJECT = objects.PoolAlsoNotify + + MODIFICATIONS = { + 'fields': { + 'host': { + 'read_only': False + }, + 'port': { + 'read_only': False + } + } + } + + +class PoolAlsoNotifyListYAMLAdapter(base.YAMLAdapter): + + ADAPTER_OBJECT = objects.PoolAlsoNotifyList + + MODIFICATIONS = {} diff --git a/designate/objects/adapters/yaml/pool_attribute.py b/designate/objects/adapters/yaml/pool_attribute.py new file mode 100644 index 000000000..b6986047a --- /dev/null +++ b/designate/objects/adapters/yaml/pool_attribute.py @@ -0,0 +1,86 @@ +# Copyright 2014 Hewlett-Packard Development Company, L.P. +# +# 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 six +from oslo_log import log as logging + +from designate.objects.adapters.yaml import base +from designate import objects +LOG = logging.getLogger(__name__) + + +class PoolAttributeYAMLAdapter(base.YAMLAdapter): + + ADAPTER_OBJECT = objects.PoolAttribute + + MODIFICATIONS = { + 'fields': { + 'key': { + 'read_only': False + }, + 'value': { + 'read_only': False + }, + } + } + + @classmethod + def _render_object(cls, object, *arg, **kwargs): + return {str(object.key): str(object.value)} + + @classmethod + def _parse_object(cls, values, object, *args, **kwargs): + for key in six.iterkeys(values): + object.key = key + object.value = values[key] + + return object + + +class PoolAttributeListYAMLAdapter(base.YAMLAdapter): + + ADAPTER_OBJECT = objects.PoolAttributeList + + MODIFICATIONS = {} + + @classmethod + def _render_list(cls, list_object, *args, **kwargs): + + r_list = {} + + for object in list_object: + value = cls.get_object_adapter( + cls.ADAPTER_FORMAT, + object).render(cls.ADAPTER_FORMAT, object, *args, **kwargs) + for key in six.iterkeys(value): + r_list[key] = value[key] + + return r_list + + @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( + # Get the right Adapter + cls.get_object_adapter( + cls.ADAPTER_FORMAT, + # This gets the internal type of the list, and parses it + # We need to do `get_object_adapter` as we need a new + # instance of the Adapter + output_object.LIST_ITEM_TYPE()).parse( + {key: value}, output_object.LIST_ITEM_TYPE())) + + # Return the filled list + return output_object diff --git a/designate/objects/adapters/yaml/pool_nameserver.py b/designate/objects/adapters/yaml/pool_nameserver.py new file mode 100644 index 000000000..4086b6ca1 --- /dev/null +++ b/designate/objects/adapters/yaml/pool_nameserver.py @@ -0,0 +1,41 @@ +# Copyright 2014 Hewlett-Packard Development Company, L.P. +# +# 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.objects.adapters.yaml import base +from designate import objects +LOG = logging.getLogger(__name__) + + +class PoolNameserverYAMLAdapter(base.YAMLAdapter): + + ADAPTER_OBJECT = objects.PoolNameserver + + MODIFICATIONS = { + 'fields': { + 'host': { + 'read_only': False + }, + 'port': { + 'read_only': False + } + } + } + + +class PoolNameserverListYAMLAdapter(base.YAMLAdapter): + + ADAPTER_OBJECT = objects.PoolNameserverList + + MODIFICATIONS = {} diff --git a/designate/objects/adapters/yaml/pool_ns_record.py b/designate/objects/adapters/yaml/pool_ns_record.py new file mode 100644 index 000000000..fa90d2b92 --- /dev/null +++ b/designate/objects/adapters/yaml/pool_ns_record.py @@ -0,0 +1,41 @@ +# Copyright 2014 Hewlett-Packard Development Company, L.P. +# +# 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.objects.adapters.yaml import base +from designate import objects +LOG = logging.getLogger(__name__) + + +class PoolNsRecordYAMLAdapter(base.YAMLAdapter): + + ADAPTER_OBJECT = objects.PoolNsRecord + + MODIFICATIONS = { + 'fields': { + 'priority': { + 'read_only': False + }, + 'hostname': { + 'read_only': False + } + } + } + + +class PoolNsRecordListYAMLAdapter(base.YAMLAdapter): + + ADAPTER_OBJECT = objects.PoolNsRecordList + + MODIFICATIONS = {} diff --git a/designate/objects/adapters/yaml/pool_target.py b/designate/objects/adapters/yaml/pool_target.py new file mode 100644 index 000000000..4da70bab2 --- /dev/null +++ b/designate/objects/adapters/yaml/pool_target.py @@ -0,0 +1,50 @@ +# Copyright 2014 Hewlett-Packard Development Company, L.P. +# +# 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.objects.adapters.yaml import base +from designate import objects +LOG = logging.getLogger(__name__) + + +class PoolTargetYAMLAdapter(base.YAMLAdapter): + + ADAPTER_OBJECT = objects.PoolTarget + + MODIFICATIONS = { + 'fields': { + 'type': { + 'read_only': False + }, + 'tsigkey_id': { + 'read_only': False + }, + 'description': { + 'read_only': False + }, + 'masters': { + 'read_only': False + }, + 'options': { + 'read_only': False + } + } + } + + +class PoolTargetListYAMLAdapter(base.YAMLAdapter): + + ADAPTER_OBJECT = objects.PoolTargetList + + MODIFICATIONS = {} diff --git a/designate/objects/adapters/yaml/pool_target_master.py b/designate/objects/adapters/yaml/pool_target_master.py new file mode 100644 index 000000000..9f6491b14 --- /dev/null +++ b/designate/objects/adapters/yaml/pool_target_master.py @@ -0,0 +1,41 @@ +# Copyright 2014 Hewlett-Packard Development Company, L.P. +# +# 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.objects.adapters.yaml import base +from designate import objects +LOG = logging.getLogger(__name__) + + +class PoolTargetMasterYAMLAdapter(base.YAMLAdapter): + + ADAPTER_OBJECT = objects.PoolTargetMaster + + MODIFICATIONS = { + 'fields': { + 'host': { + 'read_only': False + }, + 'port': { + 'read_only': False + }, + } + } + + +class PoolTargetMasterListYAMLAdapter(base.YAMLAdapter): + + ADAPTER_OBJECT = objects.PoolTargetMasterList + + MODIFICATIONS = {} diff --git a/designate/objects/adapters/yaml/pool_target_option.py b/designate/objects/adapters/yaml/pool_target_option.py new file mode 100644 index 000000000..cb17d9410 --- /dev/null +++ b/designate/objects/adapters/yaml/pool_target_option.py @@ -0,0 +1,86 @@ +# Copyright 2014 Hewlett-Packard Development Company, L.P. +# +# 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 six +from oslo_log import log as logging + +from designate.objects.adapters.yaml import base +from designate import objects +LOG = logging.getLogger(__name__) + + +class PoolTargetOptionYAMLAdapter(base.YAMLAdapter): + + ADAPTER_OBJECT = objects.PoolTargetOption + + MODIFICATIONS = { + 'fields': { + 'key': { + 'read_only': False + }, + 'value': { + 'read_only': False + }, + } + } + + @classmethod + def _render_object(cls, object, *arg, **kwargs): + return {str(object.key): str(object.value)} + + @classmethod + def _parse_object(cls, values, object, *args, **kwargs): + for key in six.iterkeys(values): + object.key = key + object.value = values[key] + + return object + + +class PoolTargetOptionListYAMLAdapter(base.YAMLAdapter): + + ADAPTER_OBJECT = objects.PoolTargetOptionList + + MODIFICATIONS = {} + + @classmethod + def _render_list(cls, list_object, *args, **kwargs): + + r_list = {} + + for object in list_object: + value = cls.get_object_adapter( + cls.ADAPTER_FORMAT, + object).render(cls.ADAPTER_FORMAT, object, *args, **kwargs) + for key in six.iterkeys(value): + r_list[key] = value[key] + + return r_list + + @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( + # Get the right Adapter + cls.get_object_adapter( + cls.ADAPTER_FORMAT, + # This gets the internal type of the list, and parses it + # We need to do `get_object_adapter` as we need a new + # instance of the Adapter + output_object.LIST_ITEM_TYPE()).parse( + {key: value}, output_object.LIST_ITEM_TYPE())) + + # Return the filled list + return output_object diff --git a/designate/objects/pool_also_notify.py b/designate/objects/pool_also_notify.py index e8203dd5a..60ab5d981 100644 --- a/designate/objects/pool_also_notify.py +++ b/designate/objects/pool_also_notify.py @@ -18,11 +18,34 @@ from designate.objects import base class PoolAlsoNotify(base.DictObjectMixin, base.PersistentObjectMixin, base.DesignateObject): FIELDS = { - 'pool_id': {}, - 'host': {}, - 'port': {}, + 'pool_id': { + 'schema': { + 'type': 'string', + 'description': 'Pool identifier', + 'format': 'uuid', + }, + }, + 'host': { + 'schema': { + 'type': 'string', + 'format': 'ip-or-host', + 'required': True, + }, + }, + 'port': { + 'schema': { + 'type': 'integer', + 'minimum': 1, + 'maximum': 65535, + 'required': True, + }, + } } + STRING_KEYS = [ + 'id', 'host', 'port', 'pool_id' + ] + class PoolAlsoNotifyList(base.ListObjectMixin, base.DesignateObject): LIST_ITEM_TYPE = PoolAlsoNotify diff --git a/designate/objects/pool_nameserver.py b/designate/objects/pool_nameserver.py index b1e5da3b0..5eaef2572 100644 --- a/designate/objects/pool_nameserver.py +++ b/designate/objects/pool_nameserver.py @@ -18,11 +18,34 @@ from designate.objects import base class PoolNameserver(base.DictObjectMixin, base.PersistentObjectMixin, base.DesignateObject): FIELDS = { - 'pool_id': {}, - 'host': {}, - 'port': {}, + 'pool_id': { + 'schema': { + 'type': 'string', + 'description': 'Pool identifier', + 'format': 'uuid', + }, + }, + 'host': { + 'schema': { + 'type': 'string', + 'format': 'ip-or-host', + 'required': True, + }, + }, + 'port': { + 'schema': { + 'type': 'integer', + 'minimum': 1, + 'maximum': 65535, + 'required': True, + }, + } } + STRING_KEYS = [ + 'id', 'host', 'port', 'pool_id' + ] + class PoolNameserverList(base.ListObjectMixin, base.DesignateObject): LIST_ITEM_TYPE = PoolNameserver diff --git a/designate/objects/pool_target.py b/designate/objects/pool_target.py index d5338615b..37c2339d9 100644 --- a/designate/objects/pool_target.py +++ b/designate/objects/pool_target.py @@ -18,11 +18,28 @@ from designate.objects import base class PoolTarget(base.DictObjectMixin, base.PersistentObjectMixin, base.DesignateObject): FIELDS = { - 'pool_id': {}, + 'pool_id': { + 'schema': { + 'type': 'string', + 'description': 'Pool identifier', + 'format': 'uuid', + }, + }, 'type': {}, - 'tsigkey_id': {}, - 'description': {}, - + 'tsigkey_id': { + 'schema': { + 'type': ['string', 'null'], + 'description': 'TSIG identifier', + 'format': 'uuid', + }, + }, + 'description': { + 'schema': { + 'type': ['string', 'null'], + 'description': 'Description for the pool', + 'maxLength': 160 + } + }, 'masters': { 'relation': True, 'relation_cls': 'PoolTargetMasterList' @@ -33,6 +50,10 @@ class PoolTarget(base.DictObjectMixin, base.PersistentObjectMixin, }, } + STRING_KEYS = [ + 'id', 'type', 'pool_id' + ] + class PoolTargetList(base.ListObjectMixin, base.DesignateObject): LIST_ITEM_TYPE = PoolTarget diff --git a/designate/objects/pool_target_master.py b/designate/objects/pool_target_master.py index 17e8ff1c9..f26d30104 100644 --- a/designate/objects/pool_target_master.py +++ b/designate/objects/pool_target_master.py @@ -18,11 +18,34 @@ from designate.objects import base class PoolTargetMaster(base.DictObjectMixin, base.PersistentObjectMixin, base.DesignateObject): FIELDS = { - 'pool_target_id': {}, - 'host': {}, - 'port': {} + 'pool_target_id': { + 'schema': { + 'type': 'string', + 'description': 'Pool Target identifier', + 'format': 'uuid', + }, + }, + 'host': { + 'schema': { + 'type': 'string', + 'format': 'ip-or-host', + 'required': True, + }, + }, + 'port': { + 'schema': { + 'type': 'integer', + 'minimum': 1, + 'maximum': 65535, + 'required': True, + }, + } } + STRING_KEYS = [ + 'id', 'host', 'port', 'pool_target_id' + ] + class PoolTargetMasterList(base.ListObjectMixin, base.DesignateObject): LIST_ITEM_TYPE = PoolTargetMaster diff --git a/designate/objects/pool_target_option.py b/designate/objects/pool_target_option.py index 7d2c7fddf..5d19bb09c 100644 --- a/designate/objects/pool_target_option.py +++ b/designate/objects/pool_target_option.py @@ -18,11 +18,33 @@ from designate.objects import base class PoolTargetOption(base.DictObjectMixin, base.PersistentObjectMixin, base.DesignateObject): FIELDS = { - 'pool_target_id': {}, - 'key': {}, - 'value': {}, + 'pool_target_id': { + 'schema': { + 'type': 'string', + 'description': 'Pool Target identifier', + 'format': 'uuid', + }, + }, + 'key': { + 'schema': { + 'type': 'string', + 'maxLength': 255, + }, + 'required': True, + }, + 'value': { + 'schema': { + 'type': 'string', + 'maxLength': 255, + }, + 'required': True + } } + STRING_KEYS = [ + 'id', 'key', 'value', 'pool_target_id' + ] + class PoolTargetOptionList(base.AttributeListObjectMixin, base.DesignateObject): diff --git a/designate/pool_manager/service.py b/designate/pool_manager/service.py index 142bb4154..00d121422 100644 --- a/designate/pool_manager/service.py +++ b/designate/pool_manager/service.py @@ -97,10 +97,6 @@ class Service(service.RPCService, coordination.CoordinationMixin, def __init__(self, threads=None): super(Service, self).__init__(threads=threads) - # Build the Pool (and related) Object from Config - self.pool = objects.Pool.from_config( - CONF, CONF['service:pool_manager'].pool_id) - # Get a pool manager cache connection. self.cache = cache.get_pool_manager_cache( CONF['service:pool_manager'].cache_driver) @@ -118,11 +114,8 @@ class Service(service.RPCService, coordination.CoordinationMixin, # Compute a time (seconds) by which things should have propagated self.max_prop_time = (self.timeout * self.max_retries + - self.max_retries * self.retry_interval + - self.delay) - - # Create the necessary Backend instances for each target - self._setup_target_backends() + self.max_retries * self.retry_interval + + self.delay) def _setup_target_backends(self): self.target_backends = {} @@ -154,6 +147,34 @@ class Service(service.RPCService, coordination.CoordinationMixin, return topic def start(self): + + # Build the Pool (and related) Object from Config + context = DesignateContext.get_admin_context() + pool_id = CONF['service:pool_manager'].pool_id + + has_targets = False + + while not has_targets: + try: + self.pool = self.central_api.get_pool(context, pool_id) + + if len(self.pool.targets) > 0: + has_targets = True + else: + LOG.error(_LE("No targets for %s found."), self.pool) + time.sleep(5) + + # Pool data may not have migrated to the DB yet + except exceptions.PoolNotFound: + LOG.error(_LE("Pool ID %s not found."), pool_id) + time.sleep(5) + # designate-central service may not have started yet + except messaging.exceptions.MessagingTimeout: + time.sleep(0.2) + + # Create the necessary Backend instances for each target + self._setup_target_backends() + for target in self.pool.targets: self.target_backends[target.id].start() diff --git a/designate/tests/__init__.py b/designate/tests/__init__.py index d61f25e8b..46aad11e3 100644 --- a/designate/tests/__init__.py +++ b/designate/tests/__init__.py @@ -382,9 +382,14 @@ class TestCase(base.BaseTestCase): # Fetch the default pool pool = self.storage.get_pool(self.admin_context, default_pool_id) - # Add a NS record to it - pool.ns_records.append( - objects.PoolNsRecord(priority=0, hostname='ns1.example.org.')) + # Fill out the necessary pool details + pool.ns_records = objects.PoolNsRecordList.from_list([ + {'hostname': 'ns1.example.org.', 'priority': 0} + ]) + + pool.targets = objects.PoolTargetList.from_list([ + {'type': 'fake', u'description': "Fake PoolTarget for Tests"} + ]) # Save the default pool self.storage.update_pool(self.admin_context, pool) diff --git a/designate/tests/resources/pools_yaml/multiple-pools.yaml b/designate/tests/resources/pools_yaml/multiple-pools.yaml new file mode 100644 index 000000000..6255e4bda --- /dev/null +++ b/designate/tests/resources/pools_yaml/multiple-pools.yaml @@ -0,0 +1,63 @@ +--- +- name: pool-1 + description: Default PowerDNS Pool + attributes: + internal: true + ns_records: + - hostname: ns1-1.example.org. + priority: 1 + - hostname: ns1-2.example.org. + priority: 2 + nameservers: + - host: 192.0.2.2 + port: 53 + - host: 192.0.2.3 + port: 53 + targets: + - type: powerdns + description: PowerDNS Database Cluster + masters: + - host: 192.0.2.1 + port: 5354 + options: + connection: 'mysql+pymysql://designate:password@127.0.0.1/designate_pdns?charset=utf8' + also_notifies: + - host: 192.0.2.4 + port: 53 + +- name: pool-2 + id: cf2e8eab-76cd-4162-bf76-8aeee3556de0 + description: Default PowerDNS Pool + + attributes: + external: true + ns_records: + - hostname: ns1-1.example.org. + priority: 1 + - hostname: ns1-2.example.org. + priority: 2 + nameservers: + - host: 192.0.2.2 + port: 53 + - host: 192.0.2.3 + port: 53 + + targets: + - type: bind + description: BIND9 Server 1 + masters: + - host: 192.0.2.1 + port: 5354 + options: + rndc_host: 192.0.2.2 + rndc_port: 953 + rndc_key_file: /etc/designate/rndc.key + - type: bind + description: BIND9 Server 2 + masters: + - host: 192.0.2.1 + port: 5354 + options: + rndc_host: 192.0.2.3 + rndc_port: 953 + rndc_key_file: /etc/designate/rndc.key diff --git a/designate/tests/resources/pools_yaml/pools.yaml b/designate/tests/resources/pools_yaml/pools.yaml new file mode 100644 index 000000000..af22b3f8d --- /dev/null +++ b/designate/tests/resources/pools_yaml/pools.yaml @@ -0,0 +1,32 @@ +--- + +- name: default + description: Default PowerDNS Pool + + attributes: + type: internal + + ns_records: + - hostname: ns1-1.example.org. + priority: 1 + - hostname: ns1-2.example.org. + priority: 2 + + nameservers: + - host: 192.0.2.2 + port: 53 + - host: 192.0.2.3 + port: 53 + + targets: + - type: powerdns + description: PowerDNS Database Cluster + masters: + - host: 192.0.2.1 + port: 5354 + options: + connection: 'mysql+pymysql://designate:password@127.0.0.1/designate_pdns?charset=utf8' + also_notifies: + - host: 192.0.2.4 + port: 53 + diff --git a/designate/tests/resources/pools_yaml/sample_output.yaml b/designate/tests/resources/pools_yaml/sample_output.yaml new file mode 100644 index 000000000..d4d534789 --- /dev/null +++ b/designate/tests/resources/pools_yaml/sample_output.yaml @@ -0,0 +1,25 @@ +- also_notifies: + - host: 192.0.2.4 + port: 53 + attributes: {} + description: Default PowerDNS Pool + id: cf2e8eab-76cd-4162-bf76-8aeee3556de0 + name: default + nameservers: + - host: 192.0.2.2 + port: 53 + - host: 192.0.2.3 + port: 53 + ns_records: + - hostname: ns1-1.example.org. + priority: 1 + - hostname: ns1-2.example.org. + priority: 2 + targets: + - description: PowerDNS Database Cluster + masters: + - host: 192.0.2.1 + port: 5354 + options: + connection: mysql+pymysql://designate:password@127.0.0.1/designate_pdns?charset=utf8 + type: powerdns diff --git a/designate/tests/test_pool_manager/__init__.py b/designate/tests/test_pool_manager/__init__.py index 47fbddda1..0fd5285ba 100644 --- a/designate/tests/test_pool_manager/__init__.py +++ b/designate/tests/test_pool_manager/__init__.py @@ -15,6 +15,49 @@ # under the License. from designate.tests import TestCase +POOL_DICT = { + 'id': u'794ccc2c-d751-44fe-b57f-8894c9f5c842', + 'name': u'default', + 'targets': [ + { + 'id': 'f278782a-07dc-4502-9177-b5d85c5f7c7e', + 'type': 'fake', + 'masters': [ + { + 'host': '127.0.0.1', + 'port': 5354 + } + ], + 'options': {} + }, + { + 'id': 'a38703f2-b71e-4e5b-ab22-30caaed61dfd', + 'type': 'fake', + 'masters': [ + { + 'host': '127.0.0.1', + 'port': 5354 + } + ], + 'options': {} + }, + ], + 'nameservers': [ + { + 'id': 'c5d64303-4cba-425a-9f3c-5d708584dde4', + 'host': '127.0.0.1', + 'port': 5355 + + }, + { + 'id': 'c67cdc95-9a9e-4d2a-98ed-dc78cbd85234', + 'host': '127.0.0.1', + 'port': 5356 + }, + ], + 'also_notifies': [], +} + class PoolManagerTestCase(TestCase): pass diff --git a/designate/tests/test_pool_manager/test_service.py b/designate/tests/test_pool_manager/test_service.py index e81055a29..a3e796848 100644 --- a/designate/tests/test_pool_manager/test_service.py +++ b/designate/tests/test_pool_manager/test_service.py @@ -18,7 +18,6 @@ import logging import uuid import oslo_messaging as messaging -from oslo_config import cfg from mock import call from mock import Mock from mock import patch @@ -30,6 +29,7 @@ from designate.central import rpcapi as central_rpcapi from designate.mdns import rpcapi as mdns_rpcapi from designate.storage.impl_sqlalchemy import tables from designate.tests.test_pool_manager import PoolManagerTestCase +from designate.tests.test_pool_manager import POOL_DICT import designate.pool_manager.service as pm_module LOG = logging.getLogger(__name__) @@ -55,61 +55,13 @@ class PoolManagerServiceNoopTest(PoolManagerTestCase): pool_id='794ccc2c-d751-44fe-b57f-8894c9f5c842', group='service:pool_manager') - # Configure the Pool - section_name = 'pool:794ccc2c-d751-44fe-b57f-8894c9f5c842' - section_opts = [ - cfg.ListOpt('targets', default=[ - 'f278782a-07dc-4502-9177-b5d85c5f7c7e', - 'a38703f2-b71e-4e5b-ab22-30caaed61dfd', - ]), - cfg.ListOpt('nameservers', default=[ - 'c5d64303-4cba-425a-9f3c-5d708584dde4', - 'c67cdc95-9a9e-4d2a-98ed-dc78cbd85234', - ]), - cfg.ListOpt('also_notifies', default=[]), - ] - cfg.CONF.register_group(cfg.OptGroup(name=section_name)) - cfg.CONF.register_opts(section_opts, group=section_name) - - # Configure the Pool Targets - section_name = 'pool_target:f278782a-07dc-4502-9177-b5d85c5f7c7e' - section_opts = [ - cfg.StrOpt('type', default='fake'), - cfg.ListOpt('masters', default=['127.0.0.1:5354']), - cfg.DictOpt('options', default={}) - ] - cfg.CONF.register_group(cfg.OptGroup(name=section_name)) - cfg.CONF.register_opts(section_opts, group=section_name) - - section_name = 'pool_target:a38703f2-b71e-4e5b-ab22-30caaed61dfd' - section_opts = [ - cfg.StrOpt('type', default='fake'), - cfg.ListOpt('masters', default=['127.0.0.1:5354']), - cfg.DictOpt('options', default={}) - ] - cfg.CONF.register_group(cfg.OptGroup(name=section_name)) - cfg.CONF.register_opts(section_opts, group=section_name) - - # Configure the Pool Nameservers - section_name = 'pool_nameserver:c5d64303-4cba-425a-9f3c-5d708584dde4' - section_opts = [ - cfg.StrOpt('host', default='127.0.0.1'), - cfg.StrOpt('port', default=5355), - ] - cfg.CONF.register_group(cfg.OptGroup(name=section_name)) - cfg.CONF.register_opts(section_opts, group=section_name) - - section_name = 'pool_nameserver:c67cdc95-9a9e-4d2a-98ed-dc78cbd85234' - section_opts = [ - cfg.StrOpt('host', default='127.0.0.1'), - cfg.StrOpt('port', default=5356), - ] - cfg.CONF.register_group(cfg.OptGroup(name=section_name)) - cfg.CONF.register_opts(section_opts, group=section_name) - # Start the Service - self.service = self.start_service('pool_manager') - self.cache = self.service.cache + with patch.object( + central_rpcapi.CentralAPI, + 'get_pool', + return_value=objects.Pool.from_dict(POOL_DICT)): + self.service = self.start_service('pool_manager') + self.cache = self.service.cache @staticmethod def _build_zone(name, action, status, id=None): @@ -230,7 +182,11 @@ class PoolManagerServiceNoopTest(PoolManagerTestCase): self.config( threshold_percentage=50, group='service:pool_manager') - self.service = self.start_service('pool_manager') + with patch.object( + central_rpcapi.CentralAPI, + 'get_pool', + return_value=objects.Pool.from_dict(POOL_DICT)): + self.service = self.start_service('pool_manager') zone = self._build_zone('example.org.', 'CREATE', 'PENDING') @@ -349,7 +305,11 @@ class PoolManagerServiceNoopTest(PoolManagerTestCase): self.config( threshold_percentage=50, group='service:pool_manager') - self.service = self.start_service('pool_manager') + with patch.object( + central_rpcapi.CentralAPI, + 'get_pool', + return_value=objects.Pool.from_dict(POOL_DICT)): + self.service = self.start_service('pool_manager') zone = self._build_zone('example.org.', 'UPDATE', 'PENDING') diff --git a/designate/tests/test_storage/__init__.py b/designate/tests/test_storage/__init__.py index ba22aaa7f..670d391df 100644 --- a/designate/tests/test_storage/__init__.py +++ b/designate/tests/test_storage/__init__.py @@ -116,7 +116,7 @@ class StorageTestCase(object): context, pool.id, objects.PoolAlsoNotify.from_dict(values)) # Paging Tests - def _ensure_paging(self, data, method): + def _ensure_paging(self, data, method, criterion=None): """ Given an array of created items we iterate through them making sure they match up to things returned by paged results. @@ -124,20 +124,27 @@ class StorageTestCase(object): results = None item_number = 0 + criterion = criterion or {} + for current_page in range(0, int(math.ceil(float(len(data)) / 2))): - LOG.debug('Validating results on page %d', current_page) + LOG.critical('Validating results on page %d', current_page) if results is not None: results = method( - self.admin_context, limit=2, marker=results[-1]['id']) + self.admin_context, + limit=2, + marker=results[-1]['id'], + criterion=criterion + ) else: - results = method(self.admin_context, limit=2) + results = method(self.admin_context, limit=2, + criterion=criterion) LOG.critical('Results: %d', len(results)) for result_number, result in enumerate(results): - LOG.debug('Validating result %d on page %d', result_number, - current_page) + LOG.critical('Validating result %d on page %d', result_number, + current_page) self.assertEqual( data[item_number]['id'], results[result_number]['id']) @@ -2402,7 +2409,7 @@ class StorageTestCase(object): criterion = dict(host=pool_nameserver_two['host']) results = self.storage.find_pool_nameservers(self.admin_context, - criterion) + criterion) self.assertEqual(1, len(results)) self.assertEqual(pool_nameserver_two['host'], results[0]['host']) @@ -2535,15 +2542,19 @@ class StorageTestCase(object): def test_find_pool_targets(self): pool = self.create_pool(fixture=0) - # Verify that there are no pool_targets created - actual = self.storage.find_pool_targets(self.admin_context) + # Verify that there are no new pool_targets created + actual = self.storage.find_pool_targets( + self.admin_context, + criterion={'pool_id': pool.id}) self.assertEqual(0, len(actual)) # Create a PoolTarget pool_target = self.create_pool_target(pool, fixture=0) - # Fetch the PoolTargets and ensure only 1 exists - actual = self.storage.find_pool_targets(self.admin_context) + # Fetch the PoolTargets and ensure only 2 exist + actual = self.storage.find_pool_targets( + self.admin_context, + criterion={'pool_id': pool.id}) self.assertEqual(1, len(actual)) self.assertEqual(pool_target['pool_id'], actual[0]['pool_id']) @@ -2557,7 +2568,8 @@ class StorageTestCase(object): for i in range(10)] # Ensure we can page through the results. - self._ensure_paging(created, self.storage.find_pool_targets) + self._ensure_paging(created, self.storage.find_pool_targets, + criterion={'pool_id': pool.id}) def test_find_pool_targets_with_criterion(self): pool = self.create_pool(fixture=0) @@ -2582,7 +2594,7 @@ class StorageTestCase(object): criterion = dict(description=pool_target_two['description']) results = self.storage.find_pool_targets(self.admin_context, - criterion) + criterion) self.assertEqual(1, len(results)) self.assertEqual( pool_target_two['description'], results[0]['description']) diff --git a/designate/tests/unit/test_objects/test_yaml_adapters.py b/designate/tests/unit/test_objects/test_yaml_adapters.py new file mode 100644 index 000000000..13059a1b9 --- /dev/null +++ b/designate/tests/unit/test_objects/test_yaml_adapters.py @@ -0,0 +1,156 @@ +# Copyright 2014 Hewlett-Packard Development Company, L.P. +# +# Author: Kiall Mac Innes +# +# 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 os + +import yaml +from oslo_log import log as logging +import oslotest.base + +from designate import objects +from designate.objects import adapters +from designate.tests import resources + + +LOG = logging.getLogger(__name__) + + +class DesignateYAMLAdapterTest(oslotest.base.BaseTestCase): + + def assertNestedDictContainsSubset(self, expected, actual): + for key, value in expected.items(): + if isinstance(value, dict): + self.assertNestedDictContainsSubset(value, actual.get(key, {})) + + elif isinstance(value, list): + self.assertEqual(len(value), len(actual[key])) + + for index, item in enumerate(value): + self.assertNestedDictContainsSubset( + item, actual[key][index]) + + else: + self.assertEqual(value, actual[key]) + + def test_yaml_parsing(self): + + file = os.path.join(resources.path, 'pools_yaml/pools.yaml') + with open(file, 'r') as stream: + xpools = yaml.safe_load(stream) + + for xpool in xpools: + r_pool = adapters.DesignateAdapter.parse( + 'YAML', xpool, objects.Pool()) + self.assertEqual('default', r_pool.name) + self.assertEqual('Default PowerDNS Pool', r_pool.description) + self.assertEqual(2, len(r_pool.ns_records)) + self.assertEqual(1, r_pool.ns_records[0].priority) + self.assertEqual(2, r_pool.ns_records[1].priority) + self.assertEqual( + 'ns1-1.example.org.', r_pool.ns_records[0].hostname) + self.assertEqual( + 'ns1-2.example.org.', r_pool.ns_records[1].hostname) + self.assertEqual(1, len(r_pool.targets)) + self.assertEqual('powerdns', r_pool.targets[0].type) + self.assertEqual( + 'PowerDNS Database Cluster', r_pool.targets[0].description) + self.assertEqual(1, len(r_pool.targets[0].masters)) + self.assertEqual('192.0.2.1', r_pool.targets[0].masters[0].host) + self.assertEqual(5354, r_pool.targets[0].masters[0].port) + self.assertEqual(1, len(r_pool.targets[0].options)) + self.assertEqual('connection', r_pool.targets[0].options[0].key) + self.assertEqual( + 'mysql+pymysql://designate:password@127.0.0.1/designate_pdns?charset=utf8', # noqa + r_pool.targets[0].options[0].value) + self.assertEqual(1, len(r_pool.also_notifies)) + self.assertEqual('192.0.2.4', r_pool.also_notifies[0].host) + self.assertEqual(53, r_pool.also_notifies[0].port) + self.assertEqual(1, len(r_pool.attributes)) + self.assertEqual('type', r_pool.attributes[0].key) + self.assertEqual('internal', r_pool.attributes[0].value) + + def test_yaml_rendering(self): + + pool_dict = { + 'also_notifies': [ + { + 'host': u'192.0.2.4', + 'pool_id': u'cf2e8eab-76cd-4162-bf76-8aeee3556de0', + 'port': 53, + } + ], + 'attributes': [], + 'description': u'Default PowerDNS Pool', + 'id': u'cf2e8eab-76cd-4162-bf76-8aeee3556de0', + 'name': u'default', + 'nameservers': [ + { + 'host': u'192.0.2.2', + 'pool_id': u'cf2e8eab-76cd-4162-bf76-8aeee3556de0', + 'port': 53, + }, + { + 'host': u'192.0.2.3', + 'pool_id': u'cf2e8eab-76cd-4162-bf76-8aeee3556de0', + 'port': 53, + } + ], + 'ns_records': [ + { + 'hostname': u'ns1-1.example.org.', + 'pool_id': u'cf2e8eab-76cd-4162-bf76-8aeee3556de0', + 'priority': 1, + }, + { + 'hostname': u'ns1-2.example.org.', + 'pool_id': u'cf2e8eab-76cd-4162-bf76-8aeee3556de0', + 'priority': 2, + } + ], + 'provisioner': u'UNMANAGED', + 'targets': [ + { + 'description': u'PowerDNS Database Cluster', + 'masters': [ + { + 'host': u'192.0.2.1', + 'pool_target_id': u'd567d569-2d69-41d5-828d-f7054bb10b5c', # noqa + 'port': 5354, + } + ], + 'options': [ + { + 'key': u'connection', + 'pool_target_id': u'd567d569-2d69-41d5-828d-f7054bb10b5c', # noqa + 'value': u'mysql+pymysql://designate:password@127.0.0.1/designate_pdns?charset=utf8', # noqa + } + ], + 'pool_id': u'cf2e8eab-76cd-4162-bf76-8aeee3556de0', + 'type': u'powerdns', + } + ] + } + + file = os.path.join(resources.path, 'pools_yaml/sample_output.yaml') + with open(file, 'r') as stream: + self.assertEqual( + stream.read(), + yaml.dump( + adapters.DesignateAdapter.render( + 'YAML', objects.PoolList.from_list([pool_dict]) + ), + default_flow_style=False + ) + ) diff --git a/designate/tests/unit/test_pool_manager/test_service.py b/designate/tests/unit/test_pool_manager/test_service.py index 875c732d2..36e280875 100644 --- a/designate/tests/unit/test_pool_manager/test_service.py +++ b/designate/tests/unit/test_pool_manager/test_service.py @@ -33,6 +33,55 @@ from designate.tests.unit import RwObject import designate.pool_manager.service as pm_module +POOL_DICT = { + 'also_notifies': [ + { + 'host': u'192.0.2.4', + 'pool_id': u'cf2e8eab-76cd-4162-bf76-8aeee3556de0', + 'port': 53, + } + ], + 'attributes': [], + 'description': u'Default PowerDNS Pool', + 'id': u'cf2e8eab-76cd-4162-bf76-8aeee3556de0', + 'name': u'default', + 'nameservers': [ + { + 'host': u'192.0.2.2', + 'pool_id': u'cf2e8eab-76cd-4162-bf76-8aeee3556de0', + 'port': 53, + }, + { + 'host': u'192.0.2.3', + 'pool_id': u'cf2e8eab-76cd-4162-bf76-8aeee3556de0', + 'port': 53, + } + ], + 'ns_records': [ + { + 'hostname': u'ns1-1.example.org.', + 'pool_id': u'cf2e8eab-76cd-4162-bf76-8aeee3556de0', + 'priority': 1, + }, + { + 'hostname': u'ns1-2.example.org.', + 'pool_id': u'cf2e8eab-76cd-4162-bf76-8aeee3556de0', + 'priority': 2, + } + ], + 'provisioner': u'UNMANAGED', + 'targets': [ + { + 'description': u'PowerDNS Database Cluster', + 'masters': [], + 'options': [], + 'type': 'fake', + 'pool_id': u'cf2e8eab-76cd-4162-bf76-8aeee3556de0', + } + ] +} + + class PoolManagerInitTest(test.BaseTestCase): def __setUp(self): super(PoolManagerTest, self).setUp() @@ -44,21 +93,20 @@ class PoolManagerInitTest(test.BaseTestCase): self.assertRaises(exceptions.NoPoolTargetsConfigured, Service) def test_init(self): - with patch.object(objects.Pool, 'from_config', - return_value=Mock()): - Service._setup_target_backends = Mock() Service() def test_start(self): with patch.object(objects.Pool, 'from_config', return_value=Mock()): - Service._setup_target_backends = Mock() pm = Service() - pm.pool.targets = () pm.tg.add_timer = Mock() pm._pool_election = Mock() with patch("designate.service.RPCService.start"): - pm.start() + with patch.object( + pm.central_api, + 'get_pool', + return_value=objects.Pool.from_dict(POOL_DICT)): + pm.start() call1 = pm.tg.add_timer.call_args_list[0][0] self.assertEqual(120, call1[0]) @@ -88,8 +136,11 @@ class PoolManagerTest(test.BaseTestCase): def setUp(self, *mocks): super(PoolManagerTest, self).setUp() self.pm = Service() - self.pm.pool.targets = () self.pm.tg.add_timer = Mock() + self.pm.pool = Mock() + setattr(self.pm.pool, 'targets', ()) + setattr(self.pm.pool, 'also_notifies', ()) + setattr(self.pm.pool, 'nameservers', ()) self.pm._pool_election = Mock() self.pm.target_backends = {} diff --git a/devstack/designate_plugins/backend-akamai b/devstack/designate_plugins/backend-akamai index 3f9b9dee6..6c593582b 100644 --- a/devstack/designate_plugins/backend-akamai +++ b/devstack/designate_plugins/backend-akamai @@ -29,7 +29,7 @@ DESIGNATE_AKAMAI_USERNAME=${DESIGNATE_AKAMAI_USERNAME:-username} DESIGNATE_AKAMAI_PASSWORD=${DESIGNATE_AKAMAI_PASSWORD:-password} DESIGNATE_AKAMAI_MASTERS=${DESIGNATE_AKAMAI_MASTERS:-"$DESIGNATE_SERVICE_HOST:$DESIGNATE_SERVICE_PORT_MDNS"} DESIGNATE_AKAMAI_NAMESERVERS=${DESIGNATE_AKAMAI_NAMESERVERS:-""} -DESIGNATE_AKAMAI_ALSO_NOTIFIES=${DESIGNATE_AKAMAI_ALSO_NOTIFIES:-"193.108.155.34:53,23.73.134.141:53,80.67.64.148:53,23.73.134.237:53,23.73.133.141:53,23.73.133.237:53,80.67.64.10:53,72.246.0.10:53,72.247.45.157:53,72.246.192.168:53,193.108.152.143:53,60.254.128.45:53,72.247.45.110:53,72.247.45.65:53,72.247.45.25:53"} +DESIGNATE_AKAMAI_ALSO_NOTIFIES=${DESIGNATE_AKAMAI_ALSO_NOTIFIES:-"193.108.155.34,23.73.134.141,80.67.64.148,23.73.134.237,23.73.133.141,23.73.133.237,80.67.64.10,72.246.0.10,72.247.45.157,72.246.192.168,193.108.152.143,60.254.128.45,72.247.45.110,72.247.45.65,72.247.45.25"} # Pull in DESIGNATE_3RDPARTY_CREDS user/pass if set if [ -n "$DESIGNATE_3RDPARTY_CREDS" ]; then @@ -57,38 +57,72 @@ function install_designate_backend { # configure_designate_backend - make configuration changes, including those to other services function configure_designate_backend { - iniset $DESIGNATE_CONF pool_target:$DESIGNATE_TARGET_ID type akamai - iniset $DESIGNATE_CONF pool_target:$DESIGNATE_TARGET_ID masters $DESIGNATE_AKAMAI_MASTERS - iniset $DESIGNATE_CONF pool_target:$DESIGNATE_TARGET_ID options "username: $DESIGNATE_AKAMAI_USERNAME, password: $DESIGNATE_AKAMAI_PASSWORD" + # Generate Designate pool.yaml file + sudo tee $DESIGNATE_CONF_DIR/pools.yaml > /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null <