"Fossies" - the Fresh Open Source Software Archive

Member "neutron-14.0.3/neutron/agent/l3/ha_router.py" (22 Oct 2019, 20492 Bytes) of package /linux/misc/openstack/neutron-14.0.3.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "ha_router.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 14.0.2_vs_14.0.3.

    1 # Copyright (c) 2015 OpenStack Foundation
    2 #
    3 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
    4 #    not use this file except in compliance with the License. You may obtain
    5 #    a copy of the License at
    6 #
    7 #         http://www.apache.org/licenses/LICENSE-2.0
    8 #
    9 #    Unless required by applicable law or agreed to in writing, software
   10 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   11 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   12 #    License for the specific language governing permissions and limitations
   13 #    under the License.
   14 
   15 import os
   16 import shutil
   17 import signal
   18 
   19 import netaddr
   20 from neutron_lib.api.definitions import portbindings
   21 from neutron_lib import constants as n_consts
   22 from neutron_lib.utils import runtime
   23 from oslo_log import log as logging
   24 
   25 from neutron.agent.l3 import namespaces
   26 from neutron.agent.l3 import router_info as router
   27 from neutron.agent.linux import external_process
   28 from neutron.agent.linux import ip_lib
   29 from neutron.agent.linux import keepalived
   30 from neutron.common import constants as const
   31 from neutron.common import utils as common_utils
   32 from neutron.extensions import revisions
   33 from neutron.extensions import timestamp
   34 
   35 LOG = logging.getLogger(__name__)
   36 HA_DEV_PREFIX = 'ha-'
   37 IP_MONITOR_PROCESS_SERVICE = 'ip_monitor'
   38 SIGTERM_TIMEOUT = 10
   39 KEEPALIVED_STATE_CHANGE_MONITOR_SERVICE_NAME = (
   40     "neutron-keepalived-state-change-monitor")
   41 
   42 # The multiplier is used to compensate execution time of function sending
   43 # SIGHUP to keepalived process. The constant multiplies ha_vrrp_advert_int
   44 # config option and the result is the throttle delay.
   45 THROTTLER_MULTIPLIER = 1.5
   46 
   47 
   48 class HaRouterNamespace(namespaces.RouterNamespace):
   49     """Namespace for HA router.
   50 
   51     This namespace sets the ip_nonlocal_bind to 0 for HA router namespaces.
   52     It does so to prevent sending gratuitous ARPs for interfaces that got VIP
   53     removed in the middle of processing.
   54     It also disables ipv6 forwarding by default. Forwarding will be
   55     enabled during router configuration processing only for the master node.
   56     It has to be disabled on all other nodes to avoid sending MLD packets
   57     which cause lost connectivity to Floating IPs.
   58     """
   59     def create(self):
   60         super(HaRouterNamespace, self).create(ipv6_forwarding=False)
   61         # HA router namespaces should not have ip_nonlocal_bind enabled
   62         ip_lib.set_ip_nonlocal_bind_for_namespace(self.name, 0)
   63 
   64 
   65 class HaRouter(router.RouterInfo):
   66     def __init__(self, state_change_callback, *args, **kwargs):
   67         super(HaRouter, self).__init__(*args, **kwargs)
   68 
   69         self.ha_port = None
   70         self.keepalived_manager = None
   71         self.state_change_callback = state_change_callback
   72         self._ha_state = None
   73         self._ha_state_path = None
   74 
   75     def create_router_namespace_object(
   76             self, router_id, agent_conf, iface_driver, use_ipv6):
   77         return HaRouterNamespace(
   78             router_id, agent_conf, iface_driver, use_ipv6)
   79 
   80     @property
   81     def ha_state_path(self):
   82         if not self._ha_state_path and self.keepalived_manager:
   83             self._ha_state_path = (self.keepalived_manager.
   84                                    get_full_config_file_path('state'))
   85         return self._ha_state_path
   86 
   87     @property
   88     def ha_priority(self):
   89         return self.router.get('priority', keepalived.HA_DEFAULT_PRIORITY)
   90 
   91     @property
   92     def ha_vr_id(self):
   93         return self.router.get('ha_vr_id')
   94 
   95     @property
   96     def ha_state(self):
   97         if self._ha_state:
   98             return self._ha_state
   99         try:
  100             with open(self.ha_state_path, 'r') as f:
  101                 self._ha_state = f.read()
  102         except (OSError, IOError):
  103             LOG.debug('Error while reading HA state for %s', self.router_id)
  104         return self._ha_state or 'unknown'
  105 
  106     @ha_state.setter
  107     def ha_state(self, new_state):
  108         self._ha_state = new_state
  109         try:
  110             with open(self.ha_state_path, 'w') as f:
  111                 f.write(new_state)
  112         except (OSError, IOError):
  113             LOG.error('Error while writing HA state for %s',
  114                       self.router_id)
  115 
  116     @property
  117     def ha_namespace(self):
  118         return self.ns_name
  119 
  120     def is_router_master(self):
  121         """this method is normally called before the ha_router object is fully
  122         initialized
  123         """
  124         if self.router.get('_ha_state') == 'active':
  125             return True
  126         else:
  127             return False
  128 
  129     def initialize(self, process_monitor):
  130         ha_port = self.router.get(n_consts.HA_INTERFACE_KEY)
  131         if not ha_port:
  132             msg = ("Unable to process HA router %s without HA port" %
  133                    self.router_id)
  134             LOG.exception(msg)
  135             raise Exception(msg)
  136         super(HaRouter, self).initialize(process_monitor)
  137 
  138         self.set_ha_port()
  139         self._init_keepalived_manager(process_monitor)
  140         self.ha_network_added()
  141         self.update_initial_state(self.state_change_callback)
  142         self.spawn_state_change_monitor(process_monitor)
  143 
  144     def _init_keepalived_manager(self, process_monitor):
  145         self.keepalived_manager = keepalived.KeepalivedManager(
  146             self.router['id'],
  147             keepalived.KeepalivedConf(),
  148             process_monitor,
  149             conf_path=self.agent_conf.ha_confs_path,
  150             namespace=self.ha_namespace,
  151             throttle_restart_value=(
  152                 self.agent_conf.ha_vrrp_advert_int * THROTTLER_MULTIPLIER))
  153 
  154         config = self.keepalived_manager.config
  155 
  156         interface_name = self.get_ha_device_name()
  157         subnets = self.ha_port.get('subnets', [])
  158         ha_port_cidrs = [subnet['cidr'] for subnet in subnets]
  159         instance = keepalived.KeepalivedInstance(
  160             'BACKUP',
  161             interface_name,
  162             self.ha_vr_id,
  163             ha_port_cidrs,
  164             nopreempt=True,
  165             advert_int=self.agent_conf.ha_vrrp_advert_int,
  166             priority=self.ha_priority,
  167             vrrp_health_check_interval=(
  168                 self.agent_conf.ha_vrrp_health_check_interval),
  169             ha_conf_dir=self.keepalived_manager.get_conf_dir())
  170         instance.track_interfaces.append(interface_name)
  171 
  172         if self.agent_conf.ha_vrrp_auth_password:
  173             # TODO(safchain): use oslo.config types when it will be available
  174             # in order to check the validity of ha_vrrp_auth_type
  175             instance.set_authentication(self.agent_conf.ha_vrrp_auth_type,
  176                                         self.agent_conf.ha_vrrp_auth_password)
  177 
  178         config.add_instance(instance)
  179 
  180     def enable_keepalived(self):
  181         self.keepalived_manager.spawn()
  182 
  183     def disable_keepalived(self):
  184         if not self.keepalived_manager:
  185             LOG.debug('Error while disabling keepalived for %s - no manager',
  186                       self.router_id)
  187             return
  188         self.keepalived_manager.disable()
  189         conf_dir = self.keepalived_manager.get_conf_dir()
  190         shutil.rmtree(conf_dir)
  191 
  192     def _get_keepalived_instance(self):
  193         return self.keepalived_manager.config.get_instance(self.ha_vr_id)
  194 
  195     def _get_primary_vip(self):
  196         return self._get_keepalived_instance().get_primary_vip()
  197 
  198     def get_ha_device_name(self):
  199         return (HA_DEV_PREFIX + self.ha_port['id'])[:self.driver.DEV_NAME_LEN]
  200 
  201     def ha_network_added(self):
  202         interface_name = self.get_ha_device_name()
  203 
  204         self.driver.plug(self.ha_port['network_id'],
  205                          self.ha_port['id'],
  206                          interface_name,
  207                          self.ha_port['mac_address'],
  208                          namespace=self.ha_namespace,
  209                          prefix=HA_DEV_PREFIX,
  210                          mtu=self.ha_port.get('mtu'))
  211         ip_cidrs = common_utils.fixed_ip_cidrs(self.ha_port['fixed_ips'])
  212         self.driver.init_l3(interface_name, ip_cidrs,
  213                             namespace=self.ha_namespace,
  214                             preserve_ips=[self._get_primary_vip()])
  215 
  216     def ha_network_removed(self):
  217         if not self.ha_port:
  218             LOG.debug('Error while removing HA network for %s - no port',
  219                       self.router_id)
  220             return
  221         self.driver.unplug(self.get_ha_device_name(),
  222                            namespace=self.ha_namespace,
  223                            prefix=HA_DEV_PREFIX)
  224         self.ha_port = None
  225 
  226     def _add_vips(self, port, interface_name):
  227         for ip_cidr in common_utils.fixed_ip_cidrs(port['fixed_ips']):
  228             self._add_vip(ip_cidr, interface_name)
  229 
  230     def _add_vip(self, ip_cidr, interface, scope=None):
  231         instance = self._get_keepalived_instance()
  232         instance.add_vip(ip_cidr, interface, scope)
  233 
  234     def _remove_vip(self, ip_cidr):
  235         instance = self._get_keepalived_instance()
  236         instance.remove_vip_by_ip_address(ip_cidr)
  237 
  238     def _clear_vips(self, interface):
  239         instance = self._get_keepalived_instance()
  240         instance.remove_vips_vroutes_by_interface(interface)
  241 
  242     def _get_cidrs_from_keepalived(self, interface_name):
  243         instance = self._get_keepalived_instance()
  244         return instance.get_existing_vip_ip_addresses(interface_name)
  245 
  246     def get_router_cidrs(self, device):
  247         return set(self._get_cidrs_from_keepalived(device.name))
  248 
  249     def routes_updated(self, old_routes, new_routes):
  250         instance = self._get_keepalived_instance()
  251         instance.virtual_routes.extra_routes = [
  252             keepalived.KeepalivedVirtualRoute(
  253                 route['destination'], route['nexthop'])
  254             for route in new_routes]
  255         super(HaRouter, self).routes_updated(old_routes, new_routes)
  256 
  257     def _add_default_gw_virtual_route(self, ex_gw_port, interface_name):
  258         gateway_ips = self._get_external_gw_ips(ex_gw_port)
  259 
  260         default_gw_rts = []
  261         instance = self._get_keepalived_instance()
  262         for gw_ip in gateway_ips:
  263             # TODO(Carl) This is repeated everywhere.  A method would
  264             # be nice.
  265             default_gw = n_consts.IP_ANY[netaddr.IPAddress(gw_ip).version]
  266             default_gw_rts.append(keepalived.KeepalivedVirtualRoute(
  267                 default_gw, gw_ip, interface_name))
  268         instance.virtual_routes.gateway_routes = default_gw_rts
  269 
  270     def _add_extra_subnet_onlink_routes(self, ex_gw_port, interface_name):
  271         extra_subnets = ex_gw_port.get('extra_subnets', [])
  272         instance = self._get_keepalived_instance()
  273         onlink_route_cidrs = set(s['cidr'] for s in extra_subnets)
  274         instance.virtual_routes.extra_subnets = [
  275             keepalived.KeepalivedVirtualRoute(
  276                 onlink_route_cidr, None, interface_name, scope='link') for
  277             onlink_route_cidr in onlink_route_cidrs]
  278 
  279     def _should_delete_ipv6_lladdr(self, ipv6_lladdr):
  280         """Only the master should have any IP addresses configured.
  281         Let keepalived manage IPv6 link local addresses, the same way we let
  282         it manage IPv4 addresses. If the router is not in the master state,
  283         we must delete the address first as it is autoconfigured by the kernel.
  284         """
  285         manager = self.keepalived_manager
  286         if manager.get_process().active:
  287             if self.ha_state != 'master':
  288                 conf = manager.get_conf_on_disk()
  289                 managed_by_keepalived = conf and ipv6_lladdr in conf
  290                 if managed_by_keepalived:
  291                     return False
  292             else:
  293                 return False
  294         return True
  295 
  296     def _disable_ipv6_addressing_on_interface(self, interface_name):
  297         """Disable IPv6 link local addressing on the device and add it as
  298         a VIP to keepalived. This means that the IPv6 link local address
  299         will only be present on the master.
  300         """
  301         device = ip_lib.IPDevice(interface_name, namespace=self.ha_namespace)
  302         ipv6_lladdr = ip_lib.get_ipv6_lladdr(device.link.address)
  303 
  304         if self._should_delete_ipv6_lladdr(ipv6_lladdr):
  305             self.driver.configure_ipv6_ra(self.ha_namespace, interface_name,
  306                                           const.ACCEPT_RA_DISABLED)
  307             device.addr.flush(n_consts.IP_VERSION_6)
  308         else:
  309             self.driver.configure_ipv6_ra(self.ha_namespace, interface_name,
  310                                           const.ACCEPT_RA_WITHOUT_FORWARDING)
  311 
  312         self._remove_vip(ipv6_lladdr)
  313         self._add_vip(ipv6_lladdr, interface_name, scope='link')
  314 
  315     def _add_gateway_vip(self, ex_gw_port, interface_name):
  316         self._add_vips(ex_gw_port, interface_name)
  317         self._add_default_gw_virtual_route(ex_gw_port, interface_name)
  318         self._add_extra_subnet_onlink_routes(ex_gw_port, interface_name)
  319 
  320     def add_floating_ip(self, fip, interface_name, device):
  321         fip_ip = fip['floating_ip_address']
  322         ip_cidr = common_utils.ip_to_cidr(fip_ip)
  323         self._add_vip(ip_cidr, interface_name)
  324         return n_consts.FLOATINGIP_STATUS_ACTIVE
  325 
  326     def remove_floating_ip(self, device, ip_cidr):
  327         self._remove_vip(ip_cidr)
  328         to = common_utils.cidr_to_ip(ip_cidr)
  329         if device.addr.list(to=to):
  330             super(HaRouter, self).remove_floating_ip(device, ip_cidr)
  331 
  332     def internal_network_updated(self, interface_name, ip_cidrs, mtu):
  333         self.driver.set_mtu(interface_name, mtu, namespace=self.ns_name,
  334                             prefix=router.INTERNAL_DEV_PREFIX)
  335         self._clear_vips(interface_name)
  336         self._disable_ipv6_addressing_on_interface(interface_name)
  337         for ip_cidr in ip_cidrs:
  338             self._add_vip(ip_cidr, interface_name)
  339 
  340     def _plug_ha_router_port(self, port, name_getter, prefix):
  341         port_id = port['id']
  342         interface_name = name_getter(port_id)
  343         self.driver.plug(port['network_id'],
  344                          port_id,
  345                          interface_name,
  346                          port['mac_address'],
  347                          namespace=self.ha_namespace,
  348                          prefix=prefix,
  349                          mtu=port.get('mtu'))
  350 
  351         self._disable_ipv6_addressing_on_interface(interface_name)
  352         self._add_vips(port, interface_name)
  353 
  354     def internal_network_added(self, port):
  355         self._plug_ha_router_port(
  356             port, self.get_internal_device_name, router.INTERNAL_DEV_PREFIX)
  357 
  358     def internal_network_removed(self, port):
  359         super(HaRouter, self).internal_network_removed(port)
  360 
  361         interface_name = self.get_internal_device_name(port['id'])
  362         self._clear_vips(interface_name)
  363 
  364     def _get_state_change_monitor_process_manager(self):
  365         return external_process.ProcessManager(
  366             self.agent_conf,
  367             '%s.monitor' % self.router_id,
  368             self.ha_namespace,
  369             service=KEEPALIVED_STATE_CHANGE_MONITOR_SERVICE_NAME,
  370             default_cmd_callback=self._get_state_change_monitor_callback())
  371 
  372     def _get_state_change_monitor_callback(self):
  373         ha_device = self.get_ha_device_name()
  374         ha_cidr = self._get_primary_vip()
  375 
  376         def callback(pid_file):
  377             cmd = [
  378                 'neutron-keepalived-state-change',
  379                 '--router_id=%s' % self.router_id,
  380                 '--namespace=%s' % self.ha_namespace,
  381                 '--conf_dir=%s' % self.keepalived_manager.get_conf_dir(),
  382                 '--monitor_interface=%s' % ha_device,
  383                 '--monitor_cidr=%s' % ha_cidr,
  384                 '--pid_file=%s' % pid_file,
  385                 '--state_path=%s' % self.agent_conf.state_path,
  386                 '--user=%s' % os.geteuid(),
  387                 '--group=%s' % os.getegid()]
  388             return cmd
  389 
  390         return callback
  391 
  392     def spawn_state_change_monitor(self, process_monitor):
  393         pm = self._get_state_change_monitor_process_manager()
  394         pm.enable()
  395         process_monitor.register(
  396             self.router_id, IP_MONITOR_PROCESS_SERVICE, pm)
  397 
  398     def destroy_state_change_monitor(self, process_monitor):
  399         if not self.ha_port:
  400             LOG.debug('Error while destroying state change monitor for %s - '
  401                       'no port', self.router_id)
  402             return
  403         pm = self._get_state_change_monitor_process_manager()
  404         process_monitor.unregister(
  405             self.router_id, IP_MONITOR_PROCESS_SERVICE)
  406         pm.disable(sig=str(int(signal.SIGTERM)))
  407         try:
  408             common_utils.wait_until_true(lambda: not pm.active,
  409                                          timeout=SIGTERM_TIMEOUT)
  410         except common_utils.WaitTimeout:
  411             pm.disable(sig=str(int(signal.SIGKILL)))
  412 
  413     def update_initial_state(self, callback):
  414         addresses = ip_lib.get_devices_with_ip(self.ha_namespace,
  415                                                name=self.get_ha_device_name())
  416         cidrs = (address['cidr'] for address in addresses)
  417         ha_cidr = self._get_primary_vip()
  418         state = 'master' if ha_cidr in cidrs else 'backup'
  419         self.ha_state = state
  420         callback(self.router_id, state)
  421 
  422     @staticmethod
  423     def _gateway_ports_equal(port1, port2):
  424         def _get_filtered_dict(d, ignore):
  425             return {k: v for k, v in d.items() if k not in ignore}
  426 
  427         keys_to_ignore = set([portbindings.HOST_ID, timestamp.UPDATED,
  428                               revisions.REVISION])
  429         port1_filtered = _get_filtered_dict(port1, keys_to_ignore)
  430         port2_filtered = _get_filtered_dict(port2, keys_to_ignore)
  431         return port1_filtered == port2_filtered
  432 
  433     def external_gateway_added(self, ex_gw_port, interface_name):
  434         self._plug_external_gateway(ex_gw_port, interface_name, self.ns_name)
  435         self._add_gateway_vip(ex_gw_port, interface_name)
  436         self._disable_ipv6_addressing_on_interface(interface_name)
  437 
  438         # Enable RA and IPv6 forwarding only for master instances. This will
  439         # prevent backup routers from sending packets to the upstream switch
  440         # and disrupt connections.
  441         enable = self.ha_state == 'master'
  442         self._configure_ipv6_params_on_gw(ex_gw_port, self.ns_name,
  443                                           interface_name, enable)
  444 
  445     def external_gateway_updated(self, ex_gw_port, interface_name):
  446         self._plug_external_gateway(
  447             ex_gw_port, interface_name, self.ha_namespace)
  448         ip_cidrs = common_utils.fixed_ip_cidrs(self.ex_gw_port['fixed_ips'])
  449         for old_gateway_cidr in ip_cidrs:
  450             self._remove_vip(old_gateway_cidr)
  451         self._add_gateway_vip(ex_gw_port, interface_name)
  452 
  453     def external_gateway_removed(self, ex_gw_port, interface_name):
  454         self._clear_vips(interface_name)
  455 
  456         if self.ha_state == 'master':
  457             super(HaRouter, self).external_gateway_removed(ex_gw_port,
  458                                                            interface_name)
  459         else:
  460             # We are not the master node, so no need to delete ip addresses.
  461             self.driver.unplug(interface_name,
  462                                namespace=self.ns_name,
  463                                prefix=router.EXTERNAL_DEV_PREFIX)
  464 
  465     def delete(self):
  466         if self.process_monitor:
  467             self.destroy_state_change_monitor(self.process_monitor)
  468         self.disable_keepalived()
  469         self.ha_network_removed()
  470         super(HaRouter, self).delete()
  471 
  472     def set_ha_port(self):
  473         ha_port = self.router.get(n_consts.HA_INTERFACE_KEY)
  474         if not ha_port:
  475             return
  476         # NOTE: once HA port is set, it MUST remain this value no matter what
  477         # the server return. Because there is race condition between l3-agent
  478         # side sync router info for processing and server side router deleting.
  479         # TODO(liuyulong): make sure router HA ports never change.
  480         if not self.ha_port or (self.ha_port and
  481                                 self.ha_port['status'] != ha_port['status']):
  482             self.ha_port = ha_port
  483 
  484     def process(self):
  485         super(HaRouter, self).process()
  486 
  487         self.set_ha_port()
  488         LOG.debug("Processing HA router with HA port: %s", self.ha_port)
  489         if (self.ha_port and
  490                 self.ha_port['status'] == n_consts.PORT_STATUS_ACTIVE):
  491             self.enable_keepalived()
  492 
  493     @runtime.synchronized('enable_radvd')
  494     def enable_radvd(self, internal_ports=None):
  495         if (self.keepalived_manager.get_process().active and
  496                 self.ha_state == 'master'):
  497             super(HaRouter, self).enable_radvd(internal_ports)