From 4a7753325999ef1e5c77f6131cfe03b2cfa364a7 Mon Sep 17 00:00:00 2001 From: Miguel Lavalle Date: Sat, 27 May 2017 18:27:34 -0500 Subject: [PATCH] Add port dns_domain processing logic This patchset adds logic to the ML2 DNS integration extension to process a new dns_domain attribute associated to ports. This patchset belongs to a series that adds dns_domain attribute functionality to ports. DocImpact: Ports have a new dns_domain attribute, that takes precedence over networks dns_domain when published to an external DNS service. APIImpact: Users can now specify a dns_domain attribute in port POST and PUT operations. Change-Id: I02d8587d3a1f9f3f6b8cbc79dbe8df4b4b99a893 Partial-Bug: #1650678 --- .../plugins/ml2/extensions/dns_integration.py | 227 +++++++++++------- .../ml2/extensions/test_dns_integration.py | 10 +- ...-dns-domain-to-ports-f71359d75909a2d5.yaml | 5 + 3 files changed, 157 insertions(+), 85 deletions(-) create mode 100644 releasenotes/notes/add-dns-domain-to-ports-f71359d75909a2d5.yaml diff --git a/neutron/plugins/ml2/extensions/dns_integration.py b/neutron/plugins/ml2/extensions/dns_integration.py index a13a292b381..2d4e3e56add 100644 --- a/neutron/plugins/ml2/extensions/dns_integration.py +++ b/neutron/plugins/ml2/extensions/dns_integration.py @@ -80,113 +80,167 @@ class DNSExtensionDriver(api.ExtensionDriver): db_data[dns.DNSDOMAIN] = new_value def process_create_port(self, plugin_context, request_data, db_data): - if not request_data.get(dns.DNSNAME): + if not (request_data.get(dns.DNSNAME) or + request_data.get(dns.DNSDOMAIN)): return dns_name, is_dns_domain_default = self._get_request_dns_name( request_data) if is_dns_domain_default: return network = self._get_network(plugin_context, db_data['network_id']) - if self.external_dns_not_needed( - plugin_context, network) or not network[dns.DNSDOMAIN]: - current_dns_name = '' - current_dns_domain = '' - else: - current_dns_name = dns_name - current_dns_domain = network[dns.DNSDOMAIN] + self._create_port_dns_record(plugin_context, request_data, db_data, + network, dns_name) - port_obj.PortDNS(plugin_context, - port_id=db_data['id'], - current_dns_name=current_dns_name, - current_dns_domain=current_dns_domain, - previous_dns_name='', - previous_dns_domain='', - dns_name=dns_name, - dns_domain='').create() + def _create_port_dns_record(self, plugin_context, request_data, db_data, + network, dns_name): + external_dns_domain = (request_data.get(dns.DNSDOMAIN) or + network.get(dns.DNSDOMAIN)) + current_dns_name, current_dns_domain = ( + self._calculate_current_dns_name_and_domain( + dns_name, external_dns_domain, + self.external_dns_not_needed(plugin_context, network))) - def _update_dns_db(self, dns_name, dns_domain, db_data, - plugin_context, has_fixed_ips): + dns_data_obj = port_obj.PortDNS( + plugin_context, + port_id=db_data['id'], + current_dns_name=current_dns_name, + current_dns_domain=current_dns_domain, + previous_dns_name='', + previous_dns_domain='', + dns_name=dns_name, + dns_domain=request_data.get(dns.DNSDOMAIN, '')) + dns_data_obj.create() + return dns_data_obj + + def _calculate_current_dns_name_and_domain(self, dns_name, + external_dns_domain, + no_external_dns_service): + # When creating a new PortDNS object, the current_dns_name and + # current_dns_domain fields hold the data that the integration driver + # will send to the external DNS service. They are set to non-blank + # values only if all the following conditions are met: + # 1) There is an external DNS integration driver configured + # 2) The user request contains a valid non-blank value for the port's + # dns_name + # 3) The user request contains a valid non-blank value for the port's + # dns_domain or the port's network has a non-blank value in its + # dns_domain attribute + are_both_dns_attributes_set = dns_name and external_dns_domain + if no_external_dns_service or not are_both_dns_attributes_set: + return '', '' + return dns_name, external_dns_domain + + def _update_dns_db(self, plugin_context, request_data, db_data, network): + dns_name = request_data.get(dns.DNSNAME) + dns_domain = request_data.get(dns.DNSDOMAIN) + has_fixed_ips = 'fixed_ips' in request_data dns_data_db = port_obj.PortDNS.get_object( plugin_context, port_id=db_data['id']) if dns_data_db: is_dns_name_changed = (dns_name is not None and - dns_data_db['current_dns_name'] != dns_name) - - if is_dns_name_changed or (has_fixed_ips and - dns_data_db['current_dns_name']): - dns_data_db['previous_dns_name'] = ( - dns_data_db['current_dns_name']) - dns_data_db['previous_dns_domain'] = ( - dns_data_db['current_dns_domain']) - if is_dns_name_changed: - dns_data_db[dns.DNSNAME] = dns_name - dns_data_db['current_dns_name'] = dns_name - if dns_name: - dns_data_db['current_dns_domain'] = dns_domain - else: - dns_data_db['current_dns_domain'] = '' + dns_data_db[dns.DNSNAME] != dns_name) + is_dns_domain_changed = (dns_domain is not None and + dns_data_db[dns.DNSDOMAIN] != dns_domain) + if (is_dns_name_changed or is_dns_domain_changed or + (has_fixed_ips and dns_data_db['current_dns_name'])): + dns_data_db = self._populate_previous_external_dns_data( + dns_data_db) + dns_data_db = self._populate_current_external_dns_data( + request_data, network, dns_data_db, dns_name, dns_domain, + is_dns_name_changed, is_dns_domain_changed) + elif not dns_data_db['current_dns_name']: + # If port was removed from external DNS service in previous + # update, make sure we don't attempt removal again + dns_data_db['previous_dns_name'] = '' + dns_data_db['previous_dns_domain'] = '' dns_data_db.update() return dns_data_db - if dns_name: - dns_data_db = port_obj.PortDNS(plugin_context, - port_id=db_data['id'], - current_dns_name=dns_name, - current_dns_domain=dns_domain, - previous_dns_name='', - previous_dns_domain='', - dns_name=dns_name, - dns_domain='') - dns_data_db.create() + if dns_name or dns_domain: + dns_data_db = self._create_port_dns_record(plugin_context, + request_data, db_data, network, dns_name or '') + return dns_data_db + + def _populate_previous_external_dns_data(self, dns_data_db): + dns_data_db['previous_dns_name'] = ( + dns_data_db['current_dns_name']) + dns_data_db['previous_dns_domain'] = ( + dns_data_db['current_dns_domain']) + return dns_data_db + + def _populate_current_external_dns_data(self, request_data, network, + dns_data_db, dns_name, dns_domain, + is_dns_name_changed, + is_dns_domain_changed): + if is_dns_name_changed or is_dns_domain_changed: + if is_dns_name_changed: + dns_data_db[dns.DNSNAME] = dns_name + external_dns_domain = (dns_data_db[dns.DNSDOMAIN] or + network.get(dns.DNSDOMAIN)) + if is_dns_domain_changed: + dns_data_db[dns.DNSDOMAIN] = dns_domain + external_dns_domain = request_data[dns.DNSDOMAIN] + if not external_dns_domain: + external_dns_domain = network.get(dns.DNSDOMAIN) + dns_data_db['current_dns_name'] = dns_data_db[dns.DNSNAME] + dns_data_db['current_dns_domain'] = external_dns_domain + if not (dns_data_db['current_dns_name'] and + dns_data_db['current_dns_domain']): + dns_data_db['current_dns_name'] = '' + dns_data_db['current_dns_domain'] = '' return dns_data_db def process_update_port(self, plugin_context, request_data, db_data): - dns_name = request_data.get(dns.DNSNAME) + has_dns_name = dns.DNSNAME in request_data has_fixed_ips = 'fixed_ips' in request_data - if dns_name is None and not has_fixed_ips: + has_dns_domain = dns.DNSDOMAIN in request_data + if not any((has_dns_name, has_fixed_ips, has_dns_domain)): + return + is_dns_domain_default = self._get_request_dns_name( + request_data)[1] + if is_dns_domain_default: + self._extend_port_dict(plugin_context.session, db_data, + db_data, None) return - if dns_name is not None: - dns_name, is_dns_domain_default = self._get_request_dns_name( - request_data) - if is_dns_domain_default: - self._extend_port_dict(plugin_context.session, db_data, - db_data, None) - return network = self._get_network(plugin_context, db_data['network_id']) - dns_domain = network[dns.DNSDOMAIN] dns_data_db = None - if not dns_domain or self.external_dns_not_needed(plugin_context, - network): + if self.external_dns_not_needed(plugin_context, network): # No need to update external DNS service. Only process the port's - # dns_name attribute if necessary - if dns_name is not None: - dns_data_db = self._process_only_dns_name_update( - plugin_context, db_data, dns_name) + # dns_name or dns_domain attributes if necessary + if has_dns_name or has_dns_domain: + dns_data_db = self._process_only_port_update( + plugin_context, request_data, db_data) else: - dns_data_db = self._update_dns_db(dns_name, dns_domain, db_data, - plugin_context, has_fixed_ips) + dns_data_db = self._update_dns_db(plugin_context, request_data, + db_data, network) self._extend_port_dict(plugin_context.session, db_data, db_data, dns_data_db) - def _process_only_dns_name_update(self, plugin_context, db_data, dns_name): + def _process_only_port_update(self, plugin_context, request_data, + db_data): + dns_name = request_data.get(dns.DNSNAME) + dns_domain = request_data.get(dns.DNSDOMAIN) dns_data_db = port_obj.PortDNS.get_object( plugin_context, port_id=db_data['id']) if dns_data_db: - dns_data_db['dns_name'] = dns_name + if dns_name is not None and dns_data_db[dns.DNSNAME] != dns_name: + dns_data_db[dns.DNSNAME] = dns_name + if (dns_domain is not None and + dns_data_db[dns.DNSDOMAIN] != dns_domain): + dns_data_db[dns.DNSDOMAIN] = dns_domain dns_data_db.update() return dns_data_db - if dns_name: - dns_data_db = port_obj.PortDNS(plugin_context, - port_id=db_data['id'], - current_dns_name='', - current_dns_domain='', - previous_dns_name='', - previous_dns_domain='', - dns_name=dns_name, - dns_domain='') - dns_data_db.create() + dns_data_db = port_obj.PortDNS(plugin_context, + port_id=db_data['id'], + current_dns_name='', + current_dns_domain='', + previous_dns_name='', + previous_dns_domain='', + dns_name=dns_name or '', + dns_domain=dns_domain or '') + dns_data_db.create() return dns_data_db def external_dns_not_needed(self, context, network): @@ -324,15 +378,24 @@ class DNSExtensionDriverML2(DNSExtensionDriver): class DNSDomainPortsExtensionDriver(DNSExtensionDriverML2): - _supported_extension_alias = 'dns-domain-ports' + _supported_extension_aliases = ['dns-integration', 'dns-domain-ports'] @property - def extension_alias(self): - return self._supported_extension_alias + def extension_aliases(self): + return self._supported_extension_aliases def initialize(self): LOG.info(_LI("DNSDomainPortsExtensionDriver initialization complete")) + def extend_port_dict(self, session, db_data, response_data): + response_data = ( + super(DNSDomainPortsExtensionDriver, self).extend_port_dict( + session, db_data, response_data)) + dns_data_db = db_data.dns + response_data[dns.DNSDOMAIN] = '' + if dns_data_db: + response_data[dns.DNSDOMAIN] = dns_data_db[dns.DNSDOMAIN] + DNS_DRIVER = None @@ -411,11 +474,13 @@ def _update_port_in_external_dns_service(resource, event, trigger, **kwargs): return original_ips = [ip['ip_address'] for ip in original_port['fixed_ips']] updated_ips = [ip['ip_address'] for ip in updated_port['fixed_ips']] - if (updated_port[dns.DNSNAME] == original_port[dns.DNSNAME] and - set(original_ips) == set(updated_ips)): - return - if (updated_port[dns.DNSNAME] == original_port[dns.DNSNAME] and - not original_port[dns.DNSNAME]): + is_dns_name_changed = (updated_port[dns.DNSNAME] != + original_port[dns.DNSNAME]) + is_dns_domain_changed = (dns.DNSDOMAIN in updated_port and + updated_port[dns.DNSDOMAIN] != + original_port[dns.DNSDOMAIN]) + ips_changed = set(original_ips) != set(updated_ips) + if not any((is_dns_name_changed, is_dns_domain_changed, ips_changed)): return dns_data_db = port_obj.PortDNS.get_object( context, port_id=updated_port['id']) diff --git a/neutron/tests/unit/plugins/ml2/extensions/test_dns_integration.py b/neutron/tests/unit/plugins/ml2/extensions/test_dns_integration.py index e87853abe29..ac816ec8470 100644 --- a/neutron/tests/unit/plugins/ml2/extensions/test_dns_integration.py +++ b/neutron/tests/unit/plugins/ml2/extensions/test_dns_integration.py @@ -438,10 +438,12 @@ class DNSIntegrationTestCase(test_plugin.Ml2PluginV2TestCase): dns_data_db_2['current_dns_name']) self.assertEqual(dns_data_db_1['current_dns_domain'], dns_data_db_2['current_dns_domain']) - self.assertEqual(dns_data_db_1['previous_dns_name'], - dns_data_db_2['previous_dns_name']) - self.assertEqual(dns_data_db_1['previous_dns_domain'], - dns_data_db_2['previous_dns_domain']) + # The first update cleared this port's data from the external DNS + # service. Therefore, the second update should have cleared + # previous_dns_name and previous_dns_domain, which record what has + # to be cleared from the external DNS service + self.assertFalse(dns_data_db_2['previous_dns_name']) + self.assertFalse(dns_data_db_2['previous_dns_domain']) self.assertFalse(mock_client.recordsets.create.call_args_list) self.assertFalse( mock_admin_client.recordsets.create.call_args_list) diff --git a/releasenotes/notes/add-dns-domain-to-ports-f71359d75909a2d5.yaml b/releasenotes/notes/add-dns-domain-to-ports-f71359d75909a2d5.yaml new file mode 100644 index 00000000000..53650fc9b47 --- /dev/null +++ b/releasenotes/notes/add-dns-domain-to-ports-f71359d75909a2d5.yaml @@ -0,0 +1,5 @@ +--- +features: + - Ports have now a ``dns_domain`` attribute. A port's ``dns_domain`` + attribute has precedence over the network's ``dns_domain`` + from the point of view of publishing it to the external DNS service.