def _get_zones(self): criterion = { 'shard': "BETWEEN %s,%s" % (self.begin_shard, self.end_shard), 'status': 'ERROR' } error_zones = self.storage.find_zones(self.context, criterion) # Include things that have been hanging out in PENDING # status for longer than they should # Generate the current serial, will provide a UTC Unix TS. current = utils.increment_serial() stale_criterion = { 'shard': "BETWEEN %s,%s" % (self.begin_shard, self.end_shard), 'status': 'PENDING', 'serial': "<%s" % (current - self.max_prop_time) } stale_zones = self.storage.find_zones(self.context, stale_criterion) if stale_zones: LOG.warning('Found %(len)d zones PENDING for more than %(sec)d ' 'seconds', { 'len': len(stale_zones), 'sec': self.max_prop_time }) error_zones.extend(stale_zones) return error_zones
def periodic_sync(self): """ :return: None """ LOG.debug("Calling periodic_sync.") context = DesignateContext.get_admin_context(all_tenants=True) criterion = { 'pool_id': cfg.CONF['service:pool_manager'].pool_id, 'status': '%s%s' % ('!', ERROR_STATUS) } periodic_sync_seconds = \ cfg.CONF['service:pool_manager'].periodic_sync_seconds if periodic_sync_seconds is not None: # Generate the current serial, will provide a UTC Unix TS. current = utils.increment_serial() criterion['serial'] = ">%s" % (current - periodic_sync_seconds) domains = self.central_api.find_domains(context, criterion) try: for domain in domains: self.update_domain(context, domain) except Exception: LOG.exception( _LE('An unhandled exception in periodic sync ' 'occurred. This should never happen!'))
def _get_zones(self): criterion = { 'shard': "BETWEEN %s,%s" % (self.begin_shard, self.end_shard), 'status': 'ERROR' } error_zones = self.storage.find_zones(self.context, criterion) # Include things that have been hanging out in PENDING # status for longer than they should # Generate the current serial, will provide a UTC Unix TS. current = utils.increment_serial() stale_criterion = { 'shard': "BETWEEN %s,%s" % (self.begin_shard, self.end_shard), 'status': 'PENDING', 'serial': "<%s" % (current - self.max_prop_time) } stale_zones = self.storage.find_zones(self.context, stale_criterion) if stale_zones: LOG.warning( 'Found %(len)d zones PENDING for more than %(sec)d ' 'seconds', { 'len': len(stale_zones), 'sec': self.max_prop_time }) error_zones.extend(stale_zones) return error_zones
def periodic_sync(self): """ :return: None """ # NOTE(kiall): Only run this periodic task on the pool leader if self._pool_election.is_leader: context = DesignateContext.get_admin_context(all_tenants=True) LOG.debug("Starting Periodic Synchronization") criterion = { 'pool_id': CONF['service:pool_manager'].pool_id, 'status': '!%s' % ERROR_STATUS } periodic_sync_seconds = \ CONF['service:pool_manager'].periodic_sync_seconds if periodic_sync_seconds is not None: # Generate the current serial, will provide a UTC Unix TS. current = utils.increment_serial() criterion['serial'] = ">%s" % (current - periodic_sync_seconds) zones = self.central_api.find_zones(context, criterion) try: for zone in zones: # TODO(kiall): If the zone was created within the last # periodic_sync_seconds, attempt to recreate # to fill in targets which may have failed. self.update_zone(context, zone) except Exception: LOG.exception(_LE('An unhandled exception in periodic ' 'synchronization occurred.'))
def create_domain(self, context, values): # TODO(kiall): Refactor this method into *MUCH* smaller chunks. # Default to creating in the current users tenant if 'tenant_id' not in values: values['tenant_id'] = context.tenant target = { 'tenant_id': values['tenant_id'], 'domain_name': values['name'] } policy.check('create_domain', context, target) # Ensure the tenant has enough quota to continue self._enforce_domain_quota(context, values['tenant_id']) # Ensure the domain name is valid self._is_valid_domain_name(context, values['name']) # Ensure TTL is above the minimum ttl = values.get('ttl', None) if ttl is not None: self._is_valid_ttl(context, ttl) # Handle sub-domains appropriately parent_domain = self._is_subdomain(context, values['name']) if parent_domain: if parent_domain['tenant_id'] == values['tenant_id']: # Record the Parent Domain ID values['parent_domain_id'] = parent_domain['id'] else: raise exceptions.Forbidden('Unable to create subdomain in ' 'another tenants domain') # TODO(kiall): Handle super-domains properly # NOTE(kiall): Fetch the servers before creating the domain, this way # we can prevent domain creation if no servers are # configured. servers = self.storage_api.find_servers(context) if len(servers) == 0: LOG.critical('No servers configured. Please create at least one ' 'server') raise exceptions.NoServersConfigured() # Set the serial number values['serial'] = utils.increment_serial() with self.storage_api.create_domain(context, values) as domain: with wrap_backend_call(): self.backend.create_domain(context, domain) self.notifier.info(context, 'dns.domain.create', domain) return domain
def create_domain(self, context, values): # TODO(kiall): Refactor this method into *MUCH* smaller chunks. values['tenant_id'] = context.tenant_id target = { 'tenant_id': values['tenant_id'], 'domain_name': values['name'] } policy.check('create_domain', context, target) # Ensure the tenant has enough quota to continue quota_criterion = {'tenant_id': values['tenant_id']} domain_count = self.storage_api.count_domains( context, criterion=quota_criterion) self.quota.limit_check(context, values['tenant_id'], domains=domain_count) # Ensure the domain name is valid self._is_valid_domain_name(context, values['name']) # Handle sub-domains appropriately parent_domain = self._is_subdomain(context, values['name']) if parent_domain: if parent_domain['tenant_id'] == values['tenant_id']: # Record the Parent Domain ID values['parent_domain_id'] = parent_domain['id'] else: raise exceptions.Forbidden('Unable to create subdomain in ' 'another tenants domain') # TODO(kiall): Handle super-domains properly # NOTE(kiall): Fetch the servers before creating the domain, this way # we can prevent domain creation if no servers are # configured. servers = self.storage_api.find_servers(context) if len(servers) == 0: LOG.critical('No servers configured. Please create at least one ' 'server') raise exceptions.NoServersConfigured() # Set the serial number values['serial'] = utils.increment_serial() with self.storage_api.create_domain(context, values) as domain: with wrap_backend_call(): self.backend.create_domain(context, domain) utils.notify(context, 'central', 'domain.create', domain) return domain
def _increment_domain_serial(self, context, domain_id): domain = self.storage_api.get_domain(context, domain_id) # Increment the serial number values = {'serial': utils.increment_serial(domain['serial'])} with self.storage_api.update_domain(context, domain_id, values) as domain: with wrap_backend_call(): self.backend.update_domain(context, domain) return domain
def _increment_domain_serial(self, context, domain_id): domain = self.storage_api.get_domain(context, domain_id) # Increment the serial number values = {'serial': utils.increment_serial(domain['serial'])} with self.storage_api.update_domain( context, domain_id, values) as domain: with wrap_backend_call(): self.backend.update_domain(context, domain) return domain
def create_domain(self, context, values): # TODO(kiall): Refactor this method into *MUCH* smaller chunks. values['tenant_id'] = context.tenant_id target = { 'tenant_id': values['tenant_id'], 'domain_name': values['name'] } policy.check('create_domain', context, target) # Ensure the tenant has enough quota to continue self._enforce_domain_quota(context, values['tenant_id']) # Ensure the domain name is valid self._is_valid_domain_name(context, values['name']) # Handle sub-domains appropriately parent_domain = self._is_subdomain(context, values['name']) if parent_domain: if parent_domain['tenant_id'] == values['tenant_id']: # Record the Parent Domain ID values['parent_domain_id'] = parent_domain['id'] else: raise exceptions.Forbidden('Unable to create subdomain in ' 'another tenants domain') # TODO(kiall): Handle super-domains properly # NOTE(kiall): Fetch the servers before creating the domain, this way # we can prevent domain creation if no servers are # configured. servers = self.storage_api.find_servers(context) if len(servers) == 0: LOG.critical('No servers configured. Please create at least one ' 'server') raise exceptions.NoServersConfigured() # Set the serial number values['serial'] = utils.increment_serial() with self.storage_api.create_domain(context, values) as domain: with wrap_backend_call(): self.backend.create_domain(context, domain) utils.notify(context, 'central', 'domain.create', domain) return domain
def _fetch_healthy_zones(self, context): """Fetch all zones not in error :return: :class:`ZoneList` zones """ criterion = {"pool_id": CONF["service:pool_manager"].pool_id, "status": "!%s" % ERROR_STATUS} periodic_sync_seconds = CONF["service:pool_manager"].periodic_sync_seconds if periodic_sync_seconds is not None: # Generate the current serial, will provide a UTC Unix TS. current = utils.increment_serial() criterion["serial"] = ">%s" % (current - periodic_sync_seconds) zones = self.central_api.find_zones(context, criterion) return zones
def _get_failed_zones(self, context, action): """ Fetch zones that are in ERROR status or have been PENDING for a long time. Used by periodic recovery. After a certain time changes either should have successfully propagated or gone to an ERROR state. However, random failures and undiscovered bugs leave zones hanging out in PENDING state forever. By treating those "stale" zones as failed, periodic recovery will attempt to restore them. :return: :class:`ZoneList` zones """ criterion = { 'pool_id': CONF['service:pool_manager'].pool_id, 'action': action, 'status': ERROR_STATUS } error_zones = self.central_api.find_zones(context, criterion) # Include things that have been hanging out in PENDING # status for longer than they should # Generate the current serial, will provide a UTC Unix TS. current = utils.increment_serial() stale_criterion = { 'pool_id': CONF['service:pool_manager'].pool_id, 'action': action, 'status': PENDING_STATUS, 'serial': "<%s" % (current - self.max_prop_time) } LOG.debug( 'Including zones with action %(action)s and %(status)s ' 'older than %(seconds)ds' % { 'action': action, 'status': PENDING_STATUS, 'seconds': self.max_prop_time }) stale_zones = self.central_api.find_zones(context, stale_criterion) if stale_zones: LOG.warning( _LW('Found %(len)d zones PENDING for more than %(sec)d ' 'seconds'), { 'len': len(stale_zones), 'sec': self.max_prop_time }) error_zones.extend(stale_zones) return error_zones
def _get_failed_zones(self, context, action): """ Fetch zones that are in ERROR status or have been PENDING for a long time. Used by periodic recovery. After a certain time changes either should have successfully propagated or gone to an ERROR state. However, random failures and undiscovered bugs leave zones hanging out in PENDING state forever. By treating those "stale" zones as failed, periodic recovery will attempt to restore them. :return: :class:`ZoneList` zones """ criterion = { 'pool_id': CONF['service:pool_manager'].pool_id, 'action': action, 'status': ERROR_STATUS } error_zones = self.central_api.find_zones(context, criterion) # Include things that have been hanging out in PENDING # status for longer than they should # Generate the current serial, will provide a UTC Unix TS. current = utils.increment_serial() stale_criterion = { 'pool_id': CONF['service:pool_manager'].pool_id, 'action': action, 'status': PENDING_STATUS, 'serial': "<%s" % (current - self.max_prop_time) } LOG.debug('Including zones with action %(action)s and %(status)s ' 'older than %(seconds)ds' % {'action': action, 'status': PENDING_STATUS, 'seconds': self.max_prop_time}) stale_zones = self.central_api.find_zones(context, stale_criterion) if stale_zones: LOG.warning( 'Found %(len)d zones PENDING for more than %(sec)d seconds', { 'len': len(stale_zones), 'sec': self.max_prop_time }) error_zones.extend(stale_zones) return error_zones
def _fetch_healthy_zones(self, context): """Fetch all zones not in error :return: :class:`ZoneList` zones """ criterion = { 'pool_id': CONF['service:pool_manager'].pool_id, 'status': '!%s' % ERROR_STATUS } periodic_sync_seconds = \ CONF['service:pool_manager'].periodic_sync_seconds if periodic_sync_seconds is not None: # Generate the current serial, will provide a UTC Unix TS. current = utils.increment_serial() criterion['serial'] = ">%s" % (current - periodic_sync_seconds) zones = self.central_api.find_zones(context, criterion) return zones
def update_domain(self, context, domain_id, values, increment_serial=True): # TODO(kiall): Refactor this method into *MUCH* smaller chunks. domain = self.storage_api.get_domain(context, domain_id) target = { 'domain_id': domain_id, 'domain_name': domain['name'], 'tenant_id': domain['tenant_id'] } policy.check('update_domain', context, target) if 'tenant_id' in values: # NOTE(kiall): Ensure the user is allowed to delete a domain from # the original tenant. policy.check('delete_domain', context, target) # NOTE(kiall): Ensure the user is allowed to create a domain in # the new tenant. target = {'domain_id': domain_id, 'tenant_id': values['tenant_id']} policy.check('create_domain', context, target) if 'name' in values and values['name'] != domain['name']: raise exceptions.BadRequest('Renaming a domain is not allowed') # Ensure TTL is above the minimum ttl = values.get('ttl', None) if ttl is not None: self._is_valid_ttl(context, ttl) if increment_serial: # Increment the serial number values['serial'] = utils.increment_serial(domain['serial']) with self.storage_api.update_domain( context, domain_id, values) as domain: with wrap_backend_call(): self.backend.update_domain(context, domain) self.notifier.info(context, 'dns.domain.update', domain) return domain
def periodic_sync(self): """ :return: None """ # TODO(kiall): Replace this inter-process-lock with a distributed # lock, likely using the tooz library - see bug 1445127. with lockutils.lock('periodic_sync', external=True, delay=30): context = DesignateContext.get_admin_context(all_tenants=True) LOG.debug("Starting Periodic Synchronization") criterion = { 'pool_id': CONF['service:pool_manager'].pool_id, 'status': '!%s' % ERROR_STATUS } periodic_sync_seconds = \ CONF['service:pool_manager'].periodic_sync_seconds if periodic_sync_seconds is not None: # Generate the current serial, will provide a UTC Unix TS. current = utils.increment_serial() criterion['serial'] = ">%s" % (current - periodic_sync_seconds) domains = self.central_api.find_domains(context, criterion) try: for domain in domains: # TODO(kiall): If the domain was created within the last # periodic_sync_seconds, attempt to recreate # to fill in targets which may have failed. self.update_domain(context, domain) except Exception: LOG.exception( _LE('An unhandled exception in periodic ' 'synchronization occurred.'))
def update_domain(self, context, domain_id, values, increment_serial=True): # TODO(kiall): Refactor this method into *MUCH* smaller chunks. domain = self.storage_api.get_domain(context, domain_id) target = { 'domain_id': domain_id, 'domain_name': domain['name'], 'tenant_id': domain['tenant_id'] } policy.check('update_domain', context, target) if 'tenant_id' in values: # NOTE(kiall): Ensure the user is allowed to delete a domain from # the original tenant. policy.check('delete_domain', context, target) # NOTE(kiall): Ensure the user is allowed to create a domain in # the new tenant. target = {'domain_id': domain_id, 'tenant_id': values['tenant_id']} policy.check('create_domain', context, target) if 'name' in values and values['name'] != domain['name']: raise exceptions.BadRequest('Renaming a domain is not allowed') if increment_serial: # Increment the serial number values['serial'] = utils.increment_serial(domain['serial']) with self.storage_api.update_domain(context, domain_id, values) as domain: with wrap_backend_call(): self.backend.update_domain(context, domain) utils.notify(context, 'central', 'domain.update', domain) return domain
def periodic_sync(self): """ :return: None """ # TODO(kiall): Replace this inter-process-lock with a distributed # lock, likely using the tooz library - see bug 1445127. with lockutils.lock('periodic_sync', external=True, delay=30): context = DesignateContext.get_admin_context(all_tenants=True) LOG.debug("Starting Periodic Synchronization") criterion = { 'pool_id': CONF['service:pool_manager'].pool_id, 'status': '!%s' % ERROR_STATUS } periodic_sync_seconds = \ CONF['service:pool_manager'].periodic_sync_seconds if periodic_sync_seconds is not None: # Generate the current serial, will provide a UTC Unix TS. current = utils.increment_serial() criterion['serial'] = ">%s" % (current - periodic_sync_seconds) domains = self.central_api.find_domains(context, criterion) try: for domain in domains: # TODO(kiall): If the domain was created within the last # periodic_sync_seconds, attempt to recreate # to fill in targets which may have failed. self.update_domain(context, domain) except Exception: LOG.exception(_LE('An unhandled exception in periodic ' 'synchronization occurred.'))
def test_increment_serial_higher_than_ts(self, mock_utcnow_ts): mock_utcnow_ts.return_value = 1561698354 ret_serial = utils.increment_serial(serial=1561698354 * 2) self.assertEqual(1561698354 * 2 + 1, ret_serial)
def test_increment_serial(self): ret_serial = utils.increment_serial(serial=20) self.assertTrue(ret_serial > 20)
def test_increment_serial(self): ret_serial = utils.increment_serial(serial=20) self.assertGreater(ret_serial, 20)