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
This commit is contained in:
Miguel Lavalle 2017-05-27 18:27:34 -05:00 committed by Miguel Lavalle
parent 37d33b2fcd
commit 4a77533259
3 changed files with 157 additions and 85 deletions

View File

@ -80,113 +80,167 @@ class DNSExtensionDriver(api.ExtensionDriver):
db_data[dns.DNSDOMAIN] = new_value db_data[dns.DNSDOMAIN] = new_value
def process_create_port(self, plugin_context, request_data, db_data): 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 return
dns_name, is_dns_domain_default = self._get_request_dns_name( dns_name, is_dns_domain_default = self._get_request_dns_name(
request_data) request_data)
if is_dns_domain_default: if is_dns_domain_default:
return return
network = self._get_network(plugin_context, db_data['network_id']) network = self._get_network(plugin_context, db_data['network_id'])
if self.external_dns_not_needed( self._create_port_dns_record(plugin_context, request_data, db_data,
plugin_context, network) or not network[dns.DNSDOMAIN]: network, dns_name)
current_dns_name = ''
current_dns_domain = ''
else:
current_dns_name = dns_name
current_dns_domain = network[dns.DNSDOMAIN]
port_obj.PortDNS(plugin_context, def _create_port_dns_record(self, plugin_context, request_data, db_data,
port_id=db_data['id'], network, dns_name):
current_dns_name=current_dns_name, external_dns_domain = (request_data.get(dns.DNSDOMAIN) or
current_dns_domain=current_dns_domain, network.get(dns.DNSDOMAIN))
previous_dns_name='', current_dns_name, current_dns_domain = (
previous_dns_domain='', self._calculate_current_dns_name_and_domain(
dns_name=dns_name, dns_name, external_dns_domain,
dns_domain='').create() self.external_dns_not_needed(plugin_context, network)))
def _update_dns_db(self, dns_name, dns_domain, db_data, dns_data_obj = port_obj.PortDNS(
plugin_context, has_fixed_ips): 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( dns_data_db = port_obj.PortDNS.get_object(
plugin_context, plugin_context,
port_id=db_data['id']) port_id=db_data['id'])
if dns_data_db: if dns_data_db:
is_dns_name_changed = (dns_name is not None and is_dns_name_changed = (dns_name is not None and
dns_data_db['current_dns_name'] != dns_name) dns_data_db[dns.DNSNAME] != dns_name)
is_dns_domain_changed = (dns_domain is not None and
if is_dns_name_changed or (has_fixed_ips and dns_data_db[dns.DNSDOMAIN] != dns_domain)
dns_data_db['current_dns_name']): if (is_dns_name_changed or is_dns_domain_changed or
dns_data_db['previous_dns_name'] = ( (has_fixed_ips and dns_data_db['current_dns_name'])):
dns_data_db['current_dns_name']) dns_data_db = self._populate_previous_external_dns_data(
dns_data_db['previous_dns_domain'] = ( dns_data_db)
dns_data_db['current_dns_domain']) dns_data_db = self._populate_current_external_dns_data(
if is_dns_name_changed: request_data, network, dns_data_db, dns_name, dns_domain,
dns_data_db[dns.DNSNAME] = dns_name is_dns_name_changed, is_dns_domain_changed)
dns_data_db['current_dns_name'] = dns_name elif not dns_data_db['current_dns_name']:
if dns_name: # If port was removed from external DNS service in previous
dns_data_db['current_dns_domain'] = dns_domain # update, make sure we don't attempt removal again
else: dns_data_db['previous_dns_name'] = ''
dns_data_db['current_dns_domain'] = '' dns_data_db['previous_dns_domain'] = ''
dns_data_db.update() dns_data_db.update()
return dns_data_db return dns_data_db
if dns_name: if dns_name or dns_domain:
dns_data_db = port_obj.PortDNS(plugin_context, dns_data_db = self._create_port_dns_record(plugin_context,
port_id=db_data['id'], request_data, db_data, network, dns_name or '')
current_dns_name=dns_name, return dns_data_db
current_dns_domain=dns_domain,
previous_dns_name='', def _populate_previous_external_dns_data(self, dns_data_db):
previous_dns_domain='', dns_data_db['previous_dns_name'] = (
dns_name=dns_name, dns_data_db['current_dns_name'])
dns_domain='') dns_data_db['previous_dns_domain'] = (
dns_data_db.create() 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 return dns_data_db
def process_update_port(self, plugin_context, request_data, db_data): 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 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 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']) network = self._get_network(plugin_context, db_data['network_id'])
dns_domain = network[dns.DNSDOMAIN]
dns_data_db = None dns_data_db = None
if not dns_domain or self.external_dns_not_needed(plugin_context, if self.external_dns_not_needed(plugin_context, network):
network):
# No need to update external DNS service. Only process the port's # No need to update external DNS service. Only process the port's
# dns_name attribute if necessary # dns_name or dns_domain attributes if necessary
if dns_name is not None: if has_dns_name or has_dns_domain:
dns_data_db = self._process_only_dns_name_update( dns_data_db = self._process_only_port_update(
plugin_context, db_data, dns_name) plugin_context, request_data, db_data)
else: else:
dns_data_db = self._update_dns_db(dns_name, dns_domain, db_data, dns_data_db = self._update_dns_db(plugin_context, request_data,
plugin_context, has_fixed_ips) db_data, network)
self._extend_port_dict(plugin_context.session, db_data, db_data, self._extend_port_dict(plugin_context.session, db_data, db_data,
dns_data_db) 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( dns_data_db = port_obj.PortDNS.get_object(
plugin_context, plugin_context,
port_id=db_data['id']) port_id=db_data['id'])
if dns_data_db: 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() dns_data_db.update()
return dns_data_db return dns_data_db
if dns_name: dns_data_db = port_obj.PortDNS(plugin_context,
dns_data_db = port_obj.PortDNS(plugin_context, port_id=db_data['id'],
port_id=db_data['id'], current_dns_name='',
current_dns_name='', current_dns_domain='',
current_dns_domain='', previous_dns_name='',
previous_dns_name='', previous_dns_domain='',
previous_dns_domain='', dns_name=dns_name or '',
dns_name=dns_name, dns_domain=dns_domain or '')
dns_domain='') dns_data_db.create()
dns_data_db.create()
return dns_data_db return dns_data_db
def external_dns_not_needed(self, context, network): def external_dns_not_needed(self, context, network):
@ -324,15 +378,24 @@ class DNSExtensionDriverML2(DNSExtensionDriver):
class DNSDomainPortsExtensionDriver(DNSExtensionDriverML2): class DNSDomainPortsExtensionDriver(DNSExtensionDriverML2):
_supported_extension_alias = 'dns-domain-ports' _supported_extension_aliases = ['dns-integration', 'dns-domain-ports']
@property @property
def extension_alias(self): def extension_aliases(self):
return self._supported_extension_alias return self._supported_extension_aliases
def initialize(self): def initialize(self):
LOG.info(_LI("DNSDomainPortsExtensionDriver initialization complete")) 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 DNS_DRIVER = None
@ -411,11 +474,13 @@ def _update_port_in_external_dns_service(resource, event, trigger, **kwargs):
return return
original_ips = [ip['ip_address'] for ip in original_port['fixed_ips']] original_ips = [ip['ip_address'] for ip in original_port['fixed_ips']]
updated_ips = [ip['ip_address'] for ip in updated_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 is_dns_name_changed = (updated_port[dns.DNSNAME] !=
set(original_ips) == set(updated_ips)): original_port[dns.DNSNAME])
return is_dns_domain_changed = (dns.DNSDOMAIN in updated_port and
if (updated_port[dns.DNSNAME] == original_port[dns.DNSNAME] and updated_port[dns.DNSDOMAIN] !=
not original_port[dns.DNSNAME]): 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 return
dns_data_db = port_obj.PortDNS.get_object( dns_data_db = port_obj.PortDNS.get_object(
context, port_id=updated_port['id']) context, port_id=updated_port['id'])

View File

@ -438,10 +438,12 @@ class DNSIntegrationTestCase(test_plugin.Ml2PluginV2TestCase):
dns_data_db_2['current_dns_name']) dns_data_db_2['current_dns_name'])
self.assertEqual(dns_data_db_1['current_dns_domain'], self.assertEqual(dns_data_db_1['current_dns_domain'],
dns_data_db_2['current_dns_domain']) dns_data_db_2['current_dns_domain'])
self.assertEqual(dns_data_db_1['previous_dns_name'], # The first update cleared this port's data from the external DNS
dns_data_db_2['previous_dns_name']) # service. Therefore, the second update should have cleared
self.assertEqual(dns_data_db_1['previous_dns_domain'], # previous_dns_name and previous_dns_domain, which record what has
dns_data_db_2['previous_dns_domain']) # 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_client.recordsets.create.call_args_list)
self.assertFalse( self.assertFalse(
mock_admin_client.recordsets.create.call_args_list) mock_admin_client.recordsets.create.call_args_list)

View File

@ -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.