Source code for octavia.amphorae.backends.utils.interface

# Copyright 2020 Red Hat, Inc. 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.

import errno
import ipaddress
import os
import socket
import subprocess
import time

from oslo_config import cfg
from oslo_log import log as logging
import pyroute2

from octavia.amphorae.backends.utils import interface_file
from octavia.common import constants as consts
from octavia.common import exceptions

CONF = cfg.CONF

LOG = logging.getLogger(__name__)


[docs]class InterfaceController(object): ADD = 'add' DELETE = 'delete' SET = 'set'
[docs] def interface_file_list(self): net_dir = interface_file.InterfaceFile.get_directory() for f in os.listdir(net_dir): for ext in interface_file.InterfaceFile.get_extensions(): if f.endswith(ext): yield os.path.join(net_dir, f)
[docs] def list(self): interfaces = {} for f in self.interface_file_list(): iface = interface_file.InterfaceFile.from_file(f) interfaces[iface.name] = iface return interfaces
def _family(self, address): return (socket.AF_INET6 if ipaddress.ip_network(address, strict=False).version == 6 else socket.AF_INET) def _ipr_command(self, method, command, retry_on_invalid_argument=False, retry_interval=.2, raise_on_error=True, max_retries=20, **kwargs): for dummy in range(max_retries + 1): try: method(command, **kwargs) break except pyroute2.NetlinkError as e: if e.code == errno.EINVAL and retry_on_invalid_argument: LOG.debug("Retrying after %d sec.", retry_interval) time.sleep(retry_interval) continue if command == self.ADD and e.code != errno.EEXIST: msg = "Cannot call {} {} (with {}): {}".format( method.__name__, command, kwargs, e) if raise_on_error: raise exceptions.AmphoraNetworkConfigException(msg) LOG.error(msg) return else: msg = "Cannot call {} {} (with {}) after {} retries.".format( method.__name__, command, kwargs, max_retries) if raise_on_error: raise exceptions.AmphoraNetworkConfigException(msg) LOG.error(msg) def _dhclient_up(self, interface_name): cmd = ["/sbin/dhclient", "-lf", "/var/lib/dhclient/dhclient-{}.leases".format( interface_name), "-pf", "/run/dhclient-{}.pid".format(interface_name), interface_name] LOG.debug("Running '%s'", cmd) subprocess.check_output(cmd, stderr=subprocess.STDOUT) def _dhclient_down(self, interface_name): cmd = ["/sbin/dhclient", "-r", "-lf", "/var/lib/dhclient/dhclient-{}.leases".format( interface_name), "-pf", "/run/dhclient-{}.pid".format(interface_name), interface_name] LOG.debug("Running '%s'", cmd) subprocess.check_output(cmd, stderr=subprocess.STDOUT) def _ipv6auto_up(self, interface_name): # Set values to enable SLAAC on interface_name # accept_ra is set to 2 to accept router advertisements if forwarding # is enabled on the interface for key, value in (('accept_ra', 2), ('autoconf', 1)): cmd = ["/sbin/sysctl", "-w", "net.ipv6.conf.{}.{}={}".format(interface_name, key, value)] LOG.debug("Running '%s'", cmd) subprocess.check_output(cmd, stderr=subprocess.STDOUT) def _ipv6auto_down(self, interface_name): for key, value in (('accept_ra', 0), ('autoconf', 0)): cmd = ["/sbin/sysctl", "-w", "net.ipv6.conf.{}.{}={}".format(interface_name, key, value)] LOG.debug("Running '%s'", cmd) subprocess.check_output(cmd, stderr=subprocess.STDOUT)
[docs] def up(self, interface): LOG.info("Setting interface %s up", interface.name) for address in interface.addresses: if address.get(consts.DHCP): self._dhclient_up(interface.name) if address.get(consts.IPV6AUTO): self._ipv6auto_up(interface.name) with pyroute2.IPRoute() as ipr: idx = ipr.link_lookup(ifname=interface.name)[0] self._ipr_command(ipr.link, self.SET, index=idx, state=consts.IFACE_UP, mtu=interface.mtu) for address in interface.addresses: if (consts.ADDRESS not in address or address.get(consts.DHCP) or address.get(consts.IPV6AUTO)): continue address[consts.FAMILY] = self._family(address[consts.ADDRESS]) LOG.debug("%s: Adding address %s", interface.name, address) self._ipr_command(ipr.addr, self.ADD, index=idx, **address) for route in interface.routes: route[consts.FAMILY] = self._family(route[consts.DST]) LOG.debug("%s: Adding route %s", interface.name, route) # Set retry_on_invalid_argument=True because the interface # might not be ready after setting its addresses # Note: can we use 'replace' instead of 'add' here? # Set raise_on_error to False, possible invalid (user-defined) # routes from the subnet's host_routes will not break the # script. self._ipr_command(ipr.route, self.ADD, retry_on_invalid_argument=True, raise_on_error=False, oif=idx, **route) for rule in interface.rules: rule[consts.FAMILY] = self._family(rule[consts.SRC]) LOG.debug("%s: Adding rule %s", interface.name, rule) self._ipr_command(ipr.rule, self.ADD, retry_on_invalid_argument=True, **rule) for script in interface.scripts[consts.IFACE_UP]: LOG.debug("%s: Running command '%s'", interface.name, script[consts.COMMAND]) subprocess.check_output(script[consts.COMMAND].split())
[docs] def down(self, interface): LOG.info("Setting interface %s down", interface.name) for address in interface.addresses: if address.get(consts.DHCP): self._dhclient_down(interface.name) if address.get(consts.IPV6AUTO): self._ipv6auto_down(interface.name) with pyroute2.IPRoute() as ipr: idx = ipr.link_lookup(ifname=interface.name)[0] link = ipr.get_links(idx)[0] current_state = link.get(consts.STATE) if current_state == consts.IFACE_UP: for rule in interface.rules: rule[consts.FAMILY] = self._family(rule[consts.SRC]) LOG.debug("%s: Deleting rule %s", interface.name, rule) self._ipr_command(ipr.rule, self.DELETE, raise_on_error=False, **rule) for route in interface.routes: route[consts.FAMILY] = self._family(route[consts.DST]) LOG.debug("%s: Deleting route %s", interface.name, route) self._ipr_command(ipr.route, self.DELETE, raise_on_error=False, oif=idx, **route) for address in interface.addresses: if consts.ADDRESS not in address: continue address[consts.FAMILY] = self._family( address[consts.ADDRESS]) LOG.debug("%s: Deleting address %s", interface.name, address) self._ipr_command(ipr.addr, self.DELETE, raise_on_error=False, index=idx, **address) self._ipr_command(ipr.link, self.SET, raise_on_error=False, index=idx, state=consts.IFACE_DOWN) if current_state == consts.IFACE_UP: for script in interface.scripts[consts.IFACE_DOWN]: LOG.debug("%s: Running command '%s'", interface.name, script[consts.COMMAND]) try: subprocess.check_output(script[consts.COMMAND].split()) except Exception as e: LOG.error("Error while running command '%s' on %s: %s", script[consts.COMMAND], interface.name, e)