def is_in_dnsaas(ip): if not settings.ENABLE_DNSAAS_INTEGRATION: return False dnsaas_client = DNSaaS() url = dnsaas_client.build_url('records', get_params=[('ip', ip), ('type', 'A')]) return len(dnsaas_client.get_api_result(url)) > 0
def clean_dns(cls, instances, **kwargs): """ Clean DNS entries for each instance if DNSaaS integration is enabled. """ if not settings.ENABLE_DNSAAS_INTEGRATION: raise DNSaaSIntegrationNotEnabledError() dnsaas = DNSaaS() # TODO: transaction? for instance in instances: ips = list( instance.ipaddresses.exclude(is_management=True).values_list( 'address', flat=True)) if not ips: logger.info('No IPs for %s - skipping cleaning DNS entries', instance) continue records = dnsaas.get_dns_records(ips) if len(records) > settings.DEPLOYMENT_MAX_DNS_ENTRIES_TO_CLEAN: raise Exception( 'Cannot clean {} entries for {} - clean it manually'.format( len(records), instance)) for record in records: logger.warning('Deleting %s (%s / %s / %s) DNS record', record['pk'], record['type'], record['name'], record['content']) if dnsaas.delete_dns_record(record['pk']): raise Exception() # TODO
class TestGetDnsRecords(TestCase): @patch.object(DNSaaS, '_get_oauth_token') def setUp(self, mocked): mocked.return_value = 'token' self.dnsaas = DNSaaS() @patch.object(DNSaaS, 'get_api_result') def test_return_empty_when_api_returns_empty(self, mocked): mocked.return_value = [] found_dns = self.dnsaas.get_dns_records(['192.168.0.1']) self.assertEqual(found_dns, []) def test_return_empty_when_no_ipaddress(self): found_dns = self.dnsaas.get_dns_records([]) self.assertEqual(found_dns, []) @patch.object(DNSaaS, 'get_api_result') def test_return_dns_records_when_api_returns_records(self, mocked): data = { 'content': '127.0.0.3', 'name': '1.test.pl', 'type': 'A', 'id': 1 } mocked.return_value = [data] found_dns = self.dnsaas.get_dns_records(['192.168.0.1']) self.assertEqual(len(found_dns), 1) self.assertEqual(found_dns[0]['content'], data['content']) self.assertEqual(found_dns[0]['name'], data['name']) self.assertEqual(found_dns[0]['type'], RecordType.a) @override_settings(DNSAAS_URL='http://dnsaas.com/') def test_build_url(self): self.assertEqual(self.dnsaas.build_url('domains'), 'http://dnsaas.com/api/domains/') @override_settings(DNSAAS_URL='http://dnsaas.com/') def test_build_url_with_version(self): self.assertEqual(self.dnsaas.build_url('domains'), 'http://dnsaas.com/api/domains/') @override_settings(DNSAAS_URL='http://dnsaas.com/') def test_build_url_with_id(self): self.assertEqual(self.dnsaas.build_url('domains', id=1), 'http://dnsaas.com/api/domains/1/') @override_settings(DNSAAS_URL='http://dnsaas.com/') def test_build_url_with_get_params(self): self.assertEqual( self.dnsaas.build_url('domains', get_params=[('name', 'ralph')]), 'http://dnsaas.com/api/domains/?name=ralph') @override_settings(DNSAAS_URL='http://dnsaas.com/') def test_build_url_with_id_and_get_params(self): self.assertEqual( self.dnsaas.build_url('domains', id=1, get_params=[('name', 'ralph')]), 'http://dnsaas.com/api/domains/1/?name=ralph')
def create_dns_entries(cls, instances, **kwargs): if not settings.ENABLE_DNSAAS_INTEGRATION: raise DNSaaSIntegrationNotEnabledError() dnsaas = DNSaaS() # TODO: transaction? for instance in instances: # TODO: use dedicated param instead of history_kwargs ip = kwargs['history_kwargs'][instance.pk]['ip'] dnsaas.create_dns_record(record={ 'name': instance.hostname, 'type': RecordType.a.id, 'content': ip, 'ptr': True, }, service=instance.service)
def test_user_get_info_when_dnsaas_user_has_no_perm(self): class RequestStub(): status_code = 202 request = RequestStub() dns = DNSaaS() result = dns._response2result(request) self.assertEqual( result, { 'non_field_errors': [_("Your request couldn't be handled, try later.")] }, )
def delete_dns_record(instance, *args, **kwargs): if not _should_send_dnsaas_request(instance): return DNSaaS().send_ipaddress_data({ 'address': instance.address, 'hostname': instance.hostname, 'action': 'delete' })
def update_dns_record(instance, created, *args, **kwargs): if not _should_send_dnsaas_request(instance): return keys = ['address', 'hostname'] data_to_send = { 'old': {key: instance._previous_state[key] for key in keys}, 'new': {key: instance.__dict__[key] for key in keys}, 'service_uid': _get_connected_service_uid(instance) } data_to_send['action'] = 'add' if created else 'update' if data_to_send['old']['hostname'] is not None: DNSaaS().send_ipaddress_data(data_to_send)
def update_dns_record(instance, created, *args, **kwargs): if not _should_send_dnsaas_request(instance): return keys = ['address', 'hostname'] old = {key: instance._previous_state[key] for key in keys} new = {key: instance.__dict__[key] for key in keys} if old != new and old['hostname'] is not None: data_to_send = { 'old': old, 'new': new, 'service_uid': _get_connected_service_uid(instance), 'action': 'add' if created else 'update' } DNSaaS().send_ipaddress_data(data_to_send)
class TestGetDnsRecords(TestCase): def setUp(self): self.dnsaas = DNSaaS() @patch.object(DNSaaS, 'get_api_result') def test_return_empty_when_api_returns_empty(self, mocked): mocked.return_value = [] found_dns = self.dnsaas.get_dns_records(['192.168.0.1']) self.assertEqual(found_dns, []) @patch.object(DNSaaS, 'get_api_result') def test_return_dns_records_when_api_returns_records(self, mocked): data = { 'content': '127.0.0.3', 'name': '1.test.pl', 'type': 'A', 'id': 1 } mocked.return_value = [data] found_dns = self.dnsaas.get_dns_records(['192.168.0.1']) self.assertEqual(len(found_dns), 1) self.assertEqual(found_dns[0]['content'], data['content']) self.assertEqual(found_dns[0]['name'], data['name']) self.assertEqual(found_dns[0]['type'], RecordType.a)
def __init__(self, *args, **kwargs): if not settings.ENABLE_DNSAAS_INTEGRATION: raise DNSaaSIntegrationNotEnabledError() self.dnsaas = DNSaaS() return super().__init__(*args, **kwargs)
class DNSView(RalphDetailView): icon = 'chain-broken' name = 'dns_edit' label = 'DNS' url_name = 'dns_edit' template_name = 'dns/dns_edit.html' def __init__(self, *args, **kwargs): if not settings.ENABLE_DNSAAS_INTEGRATION: raise DNSaaSIntegrationNotEnabledError() self.dnsaas = DNSaaS() return super().__init__(*args, **kwargs) def get_forms(self): forms = [] ipaddresses = self.object.ipaddress_set.all().values_list('address', flat=True) if not ipaddresses: # If ipaddresses is empty return empty form list because we can not # identify the records do not have any IP address return forms initial = self.dnsaas.get_dns_records(ipaddresses) for item in initial: forms.append(DNSRecordForm(item)) if initial and initial[0]['type'] == RecordType.a.id: # from API "A" record is always first empty_form = DNSRecordForm(initial={'name': initial[0]['name']}) else: empty_form = DNSRecordForm() forms.append(empty_form) return forms def get(self, request, *args, **kwargs): if 'forms' not in kwargs: kwargs['forms'] = self.get_forms() return super().get(request, *kwargs, **kwargs) def post(self, request, *args, **kwargs): forms = self.get_forms() posted_form = DNSRecordForm(request.POST) # Find form which request's data belongs to for i, form in enumerate(forms): if (str(form.data.get('pk', '')) == str(posted_form.data.get('pk', ''))): forms[i] = posted_form break if posted_form.is_valid(): if posted_form.data.get('delete'): errors = self.dnsaas.delete_dns_record(form.data['pk']) elif posted_form.cleaned_data.get('pk'): errors = self.dnsaas.update_dns_record( posted_form.cleaned_data) else: errors = self.dnsaas.create_dns_record( posted_form.cleaned_data) if errors: for field_name, field_errors in errors.items(): for field_error in field_errors: if field_name == 'non_field_errors': field_name = None posted_form.add_error(field_name, field_error) else: return HttpResponseRedirect('.') kwargs['forms'] = forms return self.get(request, *args, **kwargs)
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.dns = DNSaaS()
class Command(BaseCommand): help = 'Compare DNS records in DNSaaS with state of IP-hostname in Ralph' # TODO (mkurek): add possibility to exclude some domains def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.dns = DNSaaS() def _fetch_dns_records(self): """ Fetch DNS Records (A and PTR) from DNSaaS. Returns two nested dicts, where key on the first level is record type, and key-values on the second level are name-content and content-name respectively. Example output: ( # regular order: name -> [content] { 'A': { 'myserver.mydc.net': ['1.2.3.4', '5.6.7.8'], 'myserver2.mydc.net': ['10.20.30.40'] }, 'PTR': { '4.3.2.1.in-addr.arpa': ['myserver.mydc.net'], '8.7.5.6.in-addr.arpa': ['myserver.mydc.net'], '40.30.20.10.in-addr.arpa': ['myserver2.mydc.net'] } }, # reversed order: content -> [name] { 'A': { '1.2.3.4': ['myserver.mydc.net'], '5.6.7.8': ['myserver.mydc.net'], '10.20.30.40': ['myserver2.mydc.net'] }, 'PTR': { 'myserver.mydc.net': [ '4.3.2.1.in-addr.arpa', '8.7.5.6.in-addr.arpa' ], 'myserver2.mydc.net': ['40.30.20.10.in-addr.arpa'] } } ) """ url = self.dns.build_url('records', get_params=[ ('limit', 1000), ('offset', 0), ] + [('type', t) for t in {'A', 'PTR'}]) api_results = self.dns.get_api_result(url) records_by_types = defaultdict(lambda: defaultdict(list)) records_by_types_rev = defaultdict(lambda: defaultdict(list)) for record in api_results: records_by_types[record['type']][record['name']].append( record['content']) records_by_types_rev[record['type']][record['content']].append( record['name']) return records_by_types, records_by_types_rev def _get_ips(self): """ Return dict with IP-hostname from Ralph. """ ips = IPAddress.objects.filter( ethernet__base_object__cloudhost__isnull=True, hostname__isnull=False).values_list('address', 'hostname') return dict(ips) def get_missing_a_records_in_dnsaas(self, ips, dns, dns_rev): """ Return pairs of (ip, hostname) which are present in Ralph, but not in DNSaaS """ for ip, hostname in ips.items(): if ip not in dns_rev['A']: yield (ip, hostname) def get_wrong_a_records_in_dnsaas(self, ips, dns, dns_rev): """ Return triplets of (ip, ralph_hostname, dns_hostname) for Records which are both in Ralph and DNSaaS, but are inconsistent """ for ip, hostname in ips.items(): if ip in dns_rev['A']: dns_hostnames = dns_rev['A'][ip] if hostname not in dns_hostnames or len(dns_hostnames) != 1: yield (ip, hostname, dns_hostnames) def get_missing_a_records_in_ralph(self, ips, dns, dns_rev): """ Return pairs of (ip, list of hostnames) for Records (ips) which are present in DNSaaS, but they are not in Ralph. """ for ip, hostnames in dns_rev['A'].items(): if ip not in ips: yield (ip, hostnames) def check_ralph_ptrs(self, ips, dns, dns_rev): """ Return pairs of (ip, hostname) which has not properly configured PTR records. """ for ip, hostname in ips.items(): if ip in dns_rev['A'] and hostname in dns_rev['A'][ip]: ptr = get_ptr(ip) if ptr not in dns['PTR'] or hostname not in dns['PTR'][ptr]: yield (ip, hostname, dns['PTR'].get(ptr)) def get_zombie_ptrs(self, ips, dns, dns_rev): """ Return pairs of (ip, hostname) which has not properly configured PTR records. """ for hostname, ptrs in dns_rev['PTR'].items(): for ptr in ptrs: ip = '.'.join(ptr.split('.')[3::-1]) if hostname not in dns['A'] or ip not in dns['A'][hostname]: yield ptr, hostname def get_duplicated_ptrs(self, ips, dns, dns_rev): """ Return pairs of (ip, hostname) which has not properly configured PTR records. """ for ptr, hostnames in dns['PTR'].items(): if len(hostnames) > 1: yield ptr, hostnames def handle(self, **options): dns, dns_rev = self._fetch_dns_records() ips = self._get_ips() for func, headers, description in [ (self.get_missing_a_records_in_dnsaas, ['IP', 'hostname'], 'A records missing in DNSaaS'), (self.get_missing_a_records_in_ralph, ['IP', 'hostname'], 'A records missing in Ralph'), (self.get_wrong_a_records_in_dnsaas, ['IP', 'ralph hostname', 'dnsaas hostnames'], 'Inconsistent A records'), (self.check_ralph_ptrs, ['IP', 'ralph hostname', 'PTR content'], 'Missing or wrong PTR records'), (self.get_zombie_ptrs, ['PTR', 'hostname (content)'], 'Zombie PTR records'), (self.get_duplicated_ptrs, ['PTR', 'hostnames'], 'Duplicated PTR records'), ]: result = func(ips, dns, dns_rev) self.stdout.write( TEMPLATE.format( description=description, headers='\t'.join(headers), content='\n'.join( ['\t'.join(map(str, line)) for line in result]), ))
def setUp(self): self.dnsaas = DNSaaS()
def setUp(self, mocked): mocked.return_value = 'token' self.dnsaas = DNSaaS()
def handle(self, *args, **options): dnsaas_client = DNSaaS() domains = self.get_domains(dnsaas_client) self.update_from_domains(domains) self.get_records(dnsaas_client)