# Copyright (c) 2016 IBM # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_config import cfg from oslo_log import log as logging from neutron._i18n import _LE, _LI from neutron.api.v2 import attributes from neutron.callbacks import events from neutron.callbacks import registry from neutron.callbacks import resources from neutron.db import dns_db from neutron.db import models_v2 from neutron.extensions import dns from neutron import manager from neutron.plugins.common import utils as plugin_utils from neutron.plugins.ml2 import db from neutron.plugins.ml2 import driver_api as api from neutron.services.externaldns import driver LOG = logging.getLogger(__name__) class DNSExtensionDriver(api.ExtensionDriver): _supported_extension_alias = 'dns-integration' @property def extension_alias(self): return self._supported_extension_alias def process_create_network(self, plugin_context, request_data, db_data): dns_domain = request_data.get(dns.DNSDOMAIN) if not attributes.is_attr_set(dns_domain): return if dns_domain: plugin_context.session.add(dns_db.NetworkDNSDomain( network_id=db_data['id'], dns_domain=dns_domain)) db_data[dns.DNSDOMAIN] = dns_domain def process_update_network(self, plugin_context, request_data, db_data): new_value = request_data.get(dns.DNSDOMAIN) if not attributes.is_attr_set(new_value): return current_dns_domain = db_data.get(dns.DNSDOMAIN) if current_dns_domain == new_value: return net_id = db_data['id'] if current_dns_domain: net_dns_domain = plugin_context.session.query( dns_db.NetworkDNSDomain).filter_by(network_id=net_id).one() if new_value: net_dns_domain['dns_domain'] = new_value db_data[dns.DNSDOMAIN] = new_value else: plugin_context.session.delete(net_dns_domain) db_data[dns.DNSDOMAIN] = '' elif new_value: plugin_context.session.add(dns_db.NetworkDNSDomain( network_id=net_id, dns_domain=new_value)) db_data[dns.DNSDOMAIN] = new_value def process_create_port(self, plugin_context, request_data, db_data): if not request_data.get(dns.DNSNAME): return network = self._get_network(plugin_context, db_data['network_id']) if not network[dns.DNSDOMAIN]: return if self.external_dns_not_needed(plugin_context, network): return plugin_context.session.add(dns_db.PortDNS( port_id=db_data['id'], current_dns_name=request_data[dns.DNSNAME], current_dns_domain=network[dns.DNSDOMAIN], previous_dns_name='', previous_dns_domain='')) def process_update_port(self, plugin_context, request_data, db_data): dns_name = request_data.get(dns.DNSNAME) has_fixed_ips = 'fixed_ips' in request_data if dns_name is None and not has_fixed_ips: return network = self._get_network(plugin_context, db_data['network_id']) if not network[dns.DNSDOMAIN]: return if self.external_dns_not_needed(plugin_context, network): return dns_domain = network[dns.DNSDOMAIN] dns_data_db = plugin_context.session.query(dns_db.PortDNS).filter_by( port_id=db_data['id']).one_or_none() 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['current_dns_name'] = dns_name if dns_name: dns_data_db['current_dns_domain'] = dns_domain else: dns_data_db['current_dns_domain'] = '' return if dns_name: plugin_context.session.add(dns_db.PortDNS( port_id=db_data['id'], current_dns_name=dns_name, current_dns_domain=dns_domain, previous_dns_name='', previous_dns_domain='')) def external_dns_not_needed(self, context, network): """Decide if ports in network need to be sent to the DNS service. :param context: plugin request context :param network: network dictionary :return True or False """ pass def extend_network_dict(self, session, db_data, response_data): response_data[dns.DNSDOMAIN] = '' if db_data.dns_domain: response_data[dns.DNSDOMAIN] = db_data.dns_domain[dns.DNSDOMAIN] return response_data def extend_port_dict(self, session, db_data, response_data): response_data[dns.DNSNAME] = db_data[dns.DNSNAME] return response_data def _get_network(self, context, network_id): plugin = manager.NeutronManager.get_plugin() return plugin.get_network(context, network_id) class DNSExtensionDriverML2(DNSExtensionDriver): def initialize(self): LOG.info(_LI("DNSExtensionDriverML2 initialization complete")) def _is_tunnel_tenant_network(self, provider_net): if provider_net['network_type'] == 'geneve': tunnel_ranges = cfg.CONF.ml2_type_geneve.vni_ranges elif provider_net['network_type'] == 'vxlan': tunnel_ranges = cfg.CONF.ml2_type_vxlan.vni_ranges else: tunnel_ranges = cfg.CONF.ml2_type_gre.tunnel_id_ranges segmentation_id = int(provider_net['segmentation_id']) for entry in tunnel_ranges: entry = entry.strip() tun_min, tun_max = entry.split(':') tun_min = tun_min.strip() tun_max = tun_max.strip() return int(tun_min) <= segmentation_id <= int(tun_max) def _is_vlan_tenant_network(self, provider_net): network_vlan_ranges = plugin_utils.parse_network_vlan_ranges( cfg.CONF.ml2_type_vlan.network_vlan_ranges) vlan_ranges = network_vlan_ranges[provider_net['physical_network']] if not vlan_ranges: return False segmentation_id = int(provider_net['segmentation_id']) for vlan_range in vlan_ranges: if vlan_range[0] <= segmentation_id <= vlan_range[1]: return True def external_dns_not_needed(self, context, network): dns_driver = _get_dns_driver() if not dns_driver: return True if network['router:external']: return True segments = db.get_network_segments(context.session, network['id']) if len(segments) > 1: return False provider_net = segments[0] if provider_net['network_type'] == 'local': return True if provider_net['network_type'] == 'flat': return False if provider_net['network_type'] == 'vlan': return self._is_vlan_tenant_network(provider_net) if provider_net['network_type'] in ['gre', 'vxlan', 'geneve']: return self._is_tunnel_tenant_network(provider_net) return True DNS_DRIVER = None def _get_dns_driver(): global DNS_DRIVER if DNS_DRIVER: return DNS_DRIVER if not cfg.CONF.external_dns_driver: return try: DNS_DRIVER = driver.ExternalDNSService.get_instance() LOG.debug("External DNS driver loaded: %s", cfg.CONF.external_dns_driver) return DNS_DRIVER except ImportError: LOG.exception(_LE("ImportError exception occurred while loading " "the external DNS service driver")) raise dns.ExternalDNSDriverNotFound( driver=cfg.CONF.external_dns_driver) def _create_port_in_external_dns_service(resource, event, trigger, **kwargs): dns_driver = _get_dns_driver() if not dns_driver: return context = kwargs['context'] port = kwargs['port'] dns_data_db = context.session.query(dns_db.PortDNS).filter_by( port_id=port['id']).one_or_none() if not dns_data_db: return records = [ip['ip_address'] for ip in port['fixed_ips']] _send_data_to_external_dns_service(context, dns_driver, dns_data_db['current_dns_domain'], dns_data_db['current_dns_name'], records) def _send_data_to_external_dns_service(context, dns_driver, dns_domain, dns_name, records): try: dns_driver.create_record_set(context, dns_domain, dns_name, records) except (dns.DNSDomainNotFound, dns.DuplicateRecordSet) as e: LOG.exception(_LE("Error publishing port data in external DNS " "service. Name: '%(name)s'. Domain: '%(domain)s'. " "DNS service driver message '%(message)s'") % {"name": dns_name, "domain": dns_domain, "message": e.msg}) def _remove_data_from_external_dns_service(context, dns_driver, dns_domain, dns_name, records): try: dns_driver.delete_record_set(context, dns_domain, dns_name, records) except (dns.DNSDomainNotFound, dns.DuplicateRecordSet) as e: LOG.exception(_LE("Error deleting port data from external DNS " "service. Name: '%(name)s'. Domain: '%(domain)s'. " "IP addresses '%(ips)s'. DNS service driver message " "'%(message)s'") % {"name": dns_name, "domain": dns_domain, "message": e.msg, "ips": ', '.join(records)}) def _update_port_in_external_dns_service(resource, event, trigger, **kwargs): dns_driver = _get_dns_driver() if not dns_driver: return context = kwargs['context'] updated_port = kwargs['port'] original_port = kwargs.get('original_port') if not original_port: 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]): return dns_data_db = context.session.query(dns_db.PortDNS).filter_by( port_id=updated_port['id']).one_or_none() if not dns_data_db: return if dns_data_db['previous_dns_name']: _remove_data_from_external_dns_service( context, dns_driver, dns_data_db['previous_dns_domain'], dns_data_db['previous_dns_name'], original_ips) if dns_data_db['current_dns_name']: _send_data_to_external_dns_service(context, dns_driver, dns_data_db['current_dns_domain'], dns_data_db['current_dns_name'], updated_ips) def _delete_port_in_external_dns_service(resource, event, trigger, **kwargs): dns_driver = _get_dns_driver() if not dns_driver: return context = kwargs['context'] port_id = kwargs['port_id'] dns_data_db = context.session.query(dns_db.PortDNS).filter_by( port_id=port_id).one_or_none() if not dns_data_db: return if dns_data_db['current_dns_name']: ip_allocations = context.session.query( models_v2.IPAllocation).filter_by(port_id=port_id).all() records = [alloc['ip_address'] for alloc in ip_allocations] _remove_data_from_external_dns_service( context, dns_driver, dns_data_db['current_dns_domain'], dns_data_db['current_dns_name'], records) def subscribe(): registry.subscribe( _create_port_in_external_dns_service, resources.PORT, events.AFTER_CREATE) registry.subscribe( _update_port_in_external_dns_service, resources.PORT, events.AFTER_UPDATE) registry.subscribe( _delete_port_in_external_dns_service, resources.PORT, events.BEFORE_DELETE) subscribe()