# Copyright 2015 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 sys import yaml from oslo_config import cfg from oslo_log import log as logging import oslo_messaging as messaging from designate import exceptions from designate import rpc from designate import objects from designate import policy 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__() # NOTE(jh): Cannot do this earlier because we are still missing the config # at that point, see bug #1651576 def _startup(self): rpc.init(cfg.CONF) self.central_api = central_rpcapi.CentralAPI() def _update_zones(self, pool): LOG.info("Updating zone masters for pool: {}".format(pool.id)) def __get_masters_from_pool(pool): masters = [] for target in pool.targets: for master in target.get("masters", []): master = {'host': master['host'], 'port': master['port']} found = False for existing_master in masters: if master == existing_master: found = True if not found: masters.append(master) return masters policy.init() self.context.all_tenants = True zones = self.central_api.find_zones( self.context, criterion={'pool_id': pool.id}) for zone in zones: zone.masters = objects.ZoneMasterList().from_list( __get_masters_from_pool(pool)) self.central_api.update_zone(self.context, zone) @base.args('--file', help='The path to the file the yaml output should be ' 'written to', default='/etc/designate/pools.yaml') def generate_file(self, file): self._startup() try: pools = self.central_api.find_pools(self.context) except messaging.exceptions.MessagingTimeout: LOG.critical("No response received from designate-central. " "Check it is running, and retry") sys.exit(1) with open(file, 'w') as stream: yaml.dump( DesignateAdapter.render('YAML', 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) def show_config(self, pool_id): self._startup() 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("No response received from designate-central. " "Check it is running, and retry") sys.exit(1) @base.args('--file', help='The path to the yaml file describing the pools', 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', action="store_true", default=False) @base.args( '--dry-run', help='This will simulate what will happen when you run this command', action="store_true", default=False) def update(self, file, delete, dry_run): self._startup() print('Updating Pools Configuration') print('****************************') output_msg = [''] with open(file, 'r') as stream: xpools = yaml.safe_load(stream) if dry_run: output_msg.append("The following changes will occur:") output_msg.append("*********************************") for xpool in xpools: try: if 'id' in xpool: try: pool = self.central_api.get_pool( self.context, xpool['id']) except Exception as e: msg = ("Bad ID Supplied for pool. pool_id: " "%(pool)s message: %(res)s") LOG.critical(msg, {'pool': xpool['id'], 'res': e}) continue else: pool = self.central_api.find_pool( self.context, {"name": xpool['name']}) LOG.info('Updating existing pool: %s', pool) # TODO(kiall): Move the below into the pool object pool = DesignateAdapter.parse('YAML', xpool, pool) # TODO(graham): We should be doing a full validation, but right # now there is quirks validating through nested objects. for ns_record in pool.ns_records: try: ns_record.validate() except exceptions.InvalidObject as e: LOG.error(e.errors.to_list()[0]['message']) sys.exit(1) if dry_run: output_msg.append("Update Pool: %s" % pool) else: pool = self.central_api.update_pool(self.context, pool) # Bug: Changes in the pool targets should trigger a # zone masters update LP: #1879798. self._update_zones(pool) except exceptions.PoolNotFound: pool = DesignateAdapter.parse('YAML', xpool, objects.Pool()) for ns_record in pool.ns_records: try: ns_record.validate() except exceptions.InvalidObject as e: LOG.error(e.errors.to_list()[0]['message']) sys.exit(1) if dry_run: output_msg.append("Create Pool: %s" % pool) else: LOG.info('Creating new pool: %s', pool) self.central_api.create_pool(self.context, pool) except messaging.exceptions.MessagingTimeout: LOG.critical("No response received from designate-central. " "Check it is running, and retry") sys.exit(1) 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('Deleting %s', p) self.central_api.delete_pool(self.context, p.id) except messaging.exceptions.MessagingTimeout: LOG.critical( "No response received from designate-central. " "Check it is running, and retry") sys.exit(1) for line in output_msg: print(line)