def test_missing_env_config(self): with self.assertRaises(Exception) as ctx: Manager(get_config_filename('missing-provider-env.yaml')).sync() self.assertTrue('missing env var' in ctx.exception.message)
def test_bad_provider_class_no_module(self): with self.assertRaises(Exception) as ctx: Manager(get_config_filename('bad-provider-class-no-module.yaml')) \ .sync() self.assertTrue('Unknown provider class' in ctx.exception.message)
def test_missing_provider_config(self): # Missing provider config with self.assertRaises(Exception) as ctx: Manager(get_config_filename('missing-provider-config.yaml')).sync() self.assertTrue('provider config' in ctx.exception.message)
def test_source_only_as_a_target(self): with self.assertRaises(ManagerException) as ctx: Manager(get_config_filename('provider-problems.yaml')) \ .sync(['not.targetable.']) self.assertTrue( 'does not support targeting' in text_type(ctx.exception))
def main(): parser = ArgumentParser(description=__doc__.split('\n')[1]) parser.add_argument('--config-file', required=True, help='The Manager configuration file to use') parser.add_argument('--zone', required=True, help='Zone to dump') parser.add_argument('--source', required=True, default=[], action='append', help='Source(s) to pull data from') parser.add_argument('--num-workers', default=4, help='Number of background workers') parser.add_argument('--timeout', default=1, help='Number seconds to wait for an answer') parser.add_argument('server', nargs='+', help='Servers to query') args = parser.parse_args() manager = Manager(args.config_file) log = getLogger('report') try: sources = [manager.providers[source] for source in args.source] except KeyError as e: raise Exception('Unknown source: {}'.format(e.args[0])) zone = Zone(args.zone, manager.configured_sub_zones(args.zone)) for source in sources: source.populate(zone) print('name,type,ttl,{},consistent'.format(','.join(args.server))) resolvers = [] ip_addr_re = re.compile(r'^[\d\.]+$') for server in args.server: resolver = AsyncResolver(configure=False, num_workers=int(args.num_workers)) if not ip_addr_re.match(server): server = str(query(server, 'A')[0]) log.info('server=%s', server) resolver.nameservers = [server] resolver.lifetime = int(args.timeout) resolvers.append(resolver) queries = {} for record in sorted(zone.records): queries[record] = [ r.query(record.fqdn, record._type) for r in resolvers ] for record, futures in sorted(queries.items(), key=lambda d: d[0]): stdout.write(record.fqdn) stdout.write(',') stdout.write(record._type) stdout.write(',') stdout.write(str(record.ttl)) compare = {} for future in futures: stdout.write(',') try: answers = [str(r) for r in future.result()] except (NoAnswer, NoNameservers): answers = ['*no answer*'] except NXDOMAIN: answers = ['*does not exist*'] except Timeout: answers = ['*timeout*'] stdout.write(' '.join(answers)) # sorting to ignore order answers = '*:*'.join(sorted(answers)).lower() compare[answers] = True stdout.write(',True\n' if len(compare) == 1 else ',False\n')
def test_source_only_as_a_target(self): with self.assertRaises(Exception) as ctx: Manager(get_config_filename('unknown-provider.yaml')) \ .sync(['not.targetable.']) self.assertTrue('does not support targeting' in ctx.exception.message)
def test_dump_output_provider(self): with TemporaryDirectory() as tmpdir: environ['YAML_TMP_DIR'] = tmpdir.dirname # this time we'll use seperate tmp dirs with TemporaryDirectory() as tmpdir2: environ['YAML_TMP_DIR2'] = tmpdir2.dirname manager = Manager(get_config_filename('simple.yaml')) # we're going to tell it to use dump2 to do the dumping, but a # copy should be made and directory set to tmpdir.dirname # rather than 2's tmpdir2.dirname manager.dump( zone='unit.tests.', output_dir=tmpdir.dirname, output_provider='dump2', sources=['in'], ) self.assertTrue(isfile(join(tmpdir.dirname, 'unit.tests.yaml'))) self.assertFalse( isfile(join(tmpdir2.dirname, 'unit.tests.yaml'))) # let's run that again, this time telling it to use tmpdir2 and # dump2 which should allow it to skip the copying manager.dump( zone='unit.tests.', output_dir=tmpdir2.dirname, output_provider='dump2', sources=['in'], ) self.assertTrue( isfile(join(tmpdir2.dirname, 'unit.tests.yaml'))) # tell it to use an output_provider that doesn't exist with self.assertRaises(ManagerException) as ctx: manager.dump( zone='unit.tests.', output_dir=tmpdir.dirname, output_provider='nope', sources=['in'], ) self.assertEqual('Unknown output_provider: nope', str(ctx.exception)) # tell it to use an output_provider that doesn't support # directory with self.assertRaises(ManagerException) as ctx: manager.dump( zone='unit.tests.', output_dir=tmpdir.dirname, output_provider='simple', sources=['in'], ) self.assertEqual( 'output_provider=simple, does not support ' 'directory property', str(ctx.exception), ) # hack a directory property onto the simple provider so that # it'll pass that check and fail the copy one instead manager.providers['simple'].directory = 42 with self.assertRaises(ManagerException) as ctx: manager.dump( zone='unit.tests.', output_dir=tmpdir.dirname, output_provider='simple', sources=['in'], ) self.assertEqual( 'output_provider=simple, does not support ' 'copy method', str(ctx.exception), )
def test_missing_targets(self): with self.assertRaises(ManagerException) as ctx: Manager(get_config_filename('provider-problems.yaml')) \ .sync(['missing.targets.']) self.assertTrue('missing targets' in text_type(ctx.exception))
def test_unknown_target(self): with self.assertRaises(ManagerException) as ctx: Manager(get_config_filename('provider-problems.yaml')) \ .sync(['unknown.target.']) self.assertTrue('unknown target' in text_type(ctx.exception))
def test_missing_provider_class(self): with self.assertRaises(ManagerException) as ctx: Manager(get_config_filename('missing-provider-class.yaml')).sync() self.assertTrue('missing class' in text_type(ctx.exception))
def test_bad_provider_class_module(self): with self.assertRaises(ManagerException) as ctx: Manager(get_config_filename('bad-provider-class-module.yaml')) \ .sync() self.assertTrue('Unknown provider class' in text_type(ctx.exception))
def test_unknown_source(self): with self.assertRaises(ManagerException) as ctx: Manager(get_config_filename('provider-problems.yaml')).sync( ['unknown.source.']) self.assertTrue('unknown source' in str(ctx.exception))
def test_missing_source(self): with self.assertRaises(ManagerException) as ctx: Manager(get_config_filename('provider-problems.yaml')).sync( ['missing.sources.']) self.assertTrue('missing sources' in str(ctx.exception))
def test_bad_provider_class(self): with self.assertRaises(ManagerException) as ctx: Manager(get_config_filename('bad-provider-class.yaml')).sync() self.assertTrue('Unknown provider class' in str(ctx.exception))
def test_missing_targets(self): with self.assertRaises(Exception) as ctx: Manager(get_config_filename('unknown-provider.yaml')) \ .sync(['missing.targets.']) self.assertTrue('missing targets' in ctx.exception.message)
def test_bad_plan_output_class(self): with self.assertRaises(ManagerException) as ctx: name = 'bad-plan-output-missing-class.yaml' Manager(get_config_filename(name)).sync() self.assertEquals('plan_output bad is missing class', text_type(ctx.exception))
def test_unknown_target(self): with self.assertRaises(Exception) as ctx: Manager(get_config_filename('unknown-provider.yaml')) \ .sync(['unknown.target.']) self.assertTrue('unknown target' in ctx.exception.message)
def test_bad_plan_output_config(self): with self.assertRaises(ManagerException) as ctx: Manager(get_config_filename('bad-plan-output-config.yaml')).sync() self.assertEqual('Incorrect plan_output config for bad', text_type(ctx.exception))
def main(): """check-zone based on octodns config file and dns zone Will query all 4 DNS servers configured for the zone in GCP. """ parser = ArgumentParser(description=__doc__.split('\n')[1]) parser.add_argument('--config-file', required=True, help='The OctoDNS configuration file to use') parser.add_argument('--zone', action='append', required=True, help='zone to check') args = parser.parse_args() manager = Manager(args.config_file) for zone_name in args.zone: print('Checking records for {}'.format(zone_name)) zone = Zone(zone_name, manager.configured_sub_zones(zone_name)) # Read our YAML configuration yaml_config = manager.providers['config'] # Build a GCP provider in our project to read the nameservers from it gcp = manager.providers['gcp'] project = gcp.gcloud_client.project # Retrieve the DNS Servers directly from the GCP configuration dns_servers = gcp.gcloud_zones[zone_name].name_servers # k8s.io resolvers for testing without access to gcp #dns_servers = ["NS-CLOUD-D1.GOOGLEDOMAINS.COM", "NS-CLOUD-D2.GOOGLEDOMAINS.COM", "NS-CLOUD-D3.GOOGLEDOMAINS.COM", "NS-CLOUD-D4.GOOGLEDOMAINS.COM"] print('Using GCP project {}'.format(project)) print('name,type,ttl,{},consistent'.format(','.join(dns_servers))) # Populate the zone with those records defined in our YAML config yaml_config.populate(zone) # This would populate the zone with records already defined in Google Cloud DNS # gcp.populate(zone) # Configure Resolvers (one per DNS server) resolvers = configure_resolvers(dns_servers) # Populate the queries to make based on zone record configuration queries = {} for record in sorted(zone.records): queries[record] = [ r.query(record.fqdn, record._type) for r in resolvers ] # No dns_error unless we find one dns_error = False dns_error = verify_dns(queries) if dns_error: sys.exit(1) sys.exit(0)
def test_processors(self): manager = Manager(get_config_filename('simple.yaml')) targets = [PlannableProvider('prov')] zone = Zone('unit.tests.', []) record = Record.new(zone, 'a', { 'ttl': 30, 'type': 'A', 'value': '1.2.3.4', }) # muck with sources class MockProcessor(BaseProcessor): def process_source_zone(self, zone, sources): zone = self._clone_zone(zone) zone.add_record(record) return zone mock = MockProcessor('mock') plans, zone = manager._populate_and_plan('unit.tests.', [mock], [], targets) # Our mock was called and added the record self.assertEquals(record, list(zone.records)[0]) # We got a create for the thing added to the expected state (source) self.assertIsInstance(plans[0][1].changes[0], Create) # muck with targets class MockProcessor(BaseProcessor): def process_target_zone(self, zone, target): zone = self._clone_zone(zone) zone.add_record(record) return zone mock = MockProcessor('mock') plans, zone = manager._populate_and_plan('unit.tests.', [mock], [], targets) # No record added since it's target this time self.assertFalse(zone.records) # We got a delete for the thing added to the existing state (target) self.assertIsInstance(plans[0][1].changes[0], Delete) # muck with plans class MockProcessor(BaseProcessor): def process_target_zone(self, zone, target): zone = self._clone_zone(zone) zone.add_record(record) return zone def process_plan(self, plans, sources, target): # get rid of the change plans.changes.pop(0) mock = MockProcessor('mock') plans, zone = manager._populate_and_plan('unit.tests.', [mock], [], targets) # We planned a delete again, but this time removed it from the plan, so # no plans self.assertFalse(plans)