Enable use of Pools YAML

This change adds the tooling to use the DB Tables created for pool
config data and the tooling to migrate the config info itself.

Change-Id: If99dbf527ef1ac0f05f15fe77f68f64e357fe0a5
This commit is contained in:
Kiall Mac Innes 2016-02-23 14:07:36 +00:00 committed by Graham Hayes
parent 1375a45a71
commit e612a3974f
44 changed files with 1969 additions and 305 deletions

View File

@ -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']

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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 = {}

View File

@ -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 = {}

View File

@ -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

View File

@ -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 = {}

View File

@ -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 = {}

View File

@ -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 = {}

View File

@ -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 = {}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -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()

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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'