Add generic network interface management in the amphora
Handle network configuration using Octavia tools. amphora-interface configures network interfaces inside the amphora using pyroute2 and a set of json files for persistent configuration in the /etc/octavia/interfaces/ directory. Story: 2005235 Task: 30019 Depends-On: https://review.opendev.org/806558 Change-Id: I5360c8246cd39f90eb7104a883f87c0042d146c4
This commit is contained in:
parent
39735ebf10
commit
5dd7ad9ad8
@ -27,6 +27,9 @@ ln -s $AMP_VENV/bin/haproxy-vrrp-* /usr/local/bin/ || true
|
||||
# Link heath checker script
|
||||
ln -s $AMP_VENV/bin/amphora-health-checker /usr/local/bin/amphora-health-checker || true
|
||||
|
||||
# Link amphora interface script
|
||||
ln -s $AMP_VENV/bin/amphora-interface /usr/local/bin/amphora-interface || true
|
||||
|
||||
mkdir /etc/octavia
|
||||
# we assume certs, etc will come in through the config drive
|
||||
mkdir /etc/octavia/certs
|
||||
|
@ -457,7 +457,6 @@
|
||||
#
|
||||
# agent_server_network_dir =
|
||||
|
||||
# agent_server_network_file =
|
||||
# agent_request_read_timeout = 180
|
||||
|
||||
# Minimum TLS protocol, eg: TLS, TLSv1.1, TLSv1.2, TLSv1.3 (if available)
|
||||
|
@ -40,8 +40,6 @@ class AgentJinjaTemplater(object):
|
||||
'agent_server_cert': CONF.amphora_agent.agent_server_cert,
|
||||
'agent_server_network_dir':
|
||||
CONF.amphora_agent.agent_server_network_dir,
|
||||
'agent_server_network_file':
|
||||
CONF.amphora_agent.agent_server_network_file,
|
||||
'agent_request_read_timeout':
|
||||
CONF.amphora_agent.agent_request_read_timeout,
|
||||
'amphora_id': amphora_id,
|
||||
|
@ -28,7 +28,6 @@ import webob
|
||||
from werkzeug import exceptions
|
||||
|
||||
from octavia.amphorae.backends.agent.api_server import haproxy_compatibility
|
||||
from octavia.amphorae.backends.agent.api_server import osutils
|
||||
from octavia.amphorae.backends.agent.api_server import util
|
||||
from octavia.common import constants as consts
|
||||
from octavia.common import utils as octavia_utils
|
||||
@ -73,9 +72,6 @@ class Wrapped(object):
|
||||
|
||||
class Loadbalancer(object):
|
||||
|
||||
def __init__(self):
|
||||
self._osutils = osutils.BaseOS.get_os_util()
|
||||
|
||||
def get_haproxy_config(self, lb_id):
|
||||
"""Gets the haproxy config
|
||||
|
||||
@ -199,7 +195,6 @@ class Loadbalancer(object):
|
||||
respawn_interval),
|
||||
amphora_netns=consts.AMP_NETNS_SVC_PREFIX,
|
||||
amphora_nsname=consts.AMPHORA_NAMESPACE,
|
||||
HasIFUPAll=self._osutils.has_ifup_all(),
|
||||
haproxy_major_version=hap_major,
|
||||
haproxy_minor_version=hap_minor
|
||||
)
|
||||
|
@ -12,31 +12,22 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import errno
|
||||
import ipaddress
|
||||
import os
|
||||
import shutil
|
||||
import stat
|
||||
import subprocess
|
||||
|
||||
import distro
|
||||
import jinja2
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
import webob
|
||||
from werkzeug import exceptions
|
||||
|
||||
from octavia.amphorae.backends.utils import interface_file
|
||||
from octavia.common import constants as consts
|
||||
from octavia.common import exceptions as octavia_exceptions
|
||||
from octavia.common import utils
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
j2_env = jinja2.Environment(autoescape=True, loader=jinja2.FileSystemLoader(
|
||||
os.path.dirname(os.path.realpath(__file__)) + consts.AGENT_API_TEMPLATES))
|
||||
|
||||
|
||||
class BaseOS(object):
|
||||
|
||||
@ -62,160 +53,51 @@ class BaseOS(object):
|
||||
def _map_package_name(self, package_name):
|
||||
return self.package_name_map.get(package_name, package_name)
|
||||
|
||||
def get_network_interface_file(self, interface):
|
||||
if CONF.amphora_agent.agent_server_network_file:
|
||||
return CONF.amphora_agent.agent_server_network_file
|
||||
if CONF.amphora_agent.agent_server_network_dir:
|
||||
return os.path.join(CONF.amphora_agent.agent_server_network_dir,
|
||||
interface)
|
||||
network_dir = consts.UBUNTU_AMP_NET_DIR_TEMPLATE.format(
|
||||
netns=consts.AMPHORA_NAMESPACE)
|
||||
return os.path.join(network_dir, interface)
|
||||
def write_interface_file(self, interface, ip_address, prefixlen):
|
||||
interface = interface_file.InterfaceFile(
|
||||
name=interface,
|
||||
addresses=[{
|
||||
"address": ip_address,
|
||||
"prefixlen": prefixlen
|
||||
}]
|
||||
)
|
||||
interface.write()
|
||||
|
||||
def create_netns_dir(self, network_dir, netns_network_dir, ignore=None):
|
||||
# We need to setup the netns network directory so that the ifup
|
||||
# commands used here and in the startup scripts "sees" the right
|
||||
# interfaces and scripts.
|
||||
try:
|
||||
os.makedirs('/etc/netns/' + consts.AMPHORA_NAMESPACE)
|
||||
def write_vip_interface_file(self, interface, vip, ip_version,
|
||||
prefixlen, gateway,
|
||||
mtu, vrrp_ip,
|
||||
host_routes):
|
||||
vip_interface = interface_file.VIPInterfaceFile(
|
||||
name=interface,
|
||||
mtu=mtu,
|
||||
vip=vip,
|
||||
ip_version=ip_version,
|
||||
prefixlen=prefixlen,
|
||||
gateway=gateway,
|
||||
vrrp_ip=vrrp_ip,
|
||||
host_routes=host_routes,
|
||||
topology=CONF.controller_worker.loadbalancer_topology)
|
||||
vip_interface.write()
|
||||
|
||||
shutil.copytree(
|
||||
network_dir,
|
||||
'/etc/netns/{netns}/{net_dir}'.format(
|
||||
netns=consts.AMPHORA_NAMESPACE,
|
||||
net_dir=netns_network_dir),
|
||||
symlinks=True,
|
||||
ignore=ignore)
|
||||
except OSError as e:
|
||||
# Raise the error if it's not "File exists" otherwise pass
|
||||
if e.errno != errno.EEXIST:
|
||||
raise
|
||||
|
||||
def write_vip_interface_file(self, interface_file_path,
|
||||
primary_interface, vip, ip, broadcast,
|
||||
netmask, gateway, mtu, vrrp_ip, vrrp_version,
|
||||
render_host_routes, template_vip):
|
||||
# write interface file
|
||||
|
||||
mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
|
||||
|
||||
# If we are using a consolidated interfaces file, just append
|
||||
# otherwise clear the per interface file as we are rewriting it
|
||||
# TODO(johnsom): We need a way to clean out old interfaces records
|
||||
if CONF.amphora_agent.agent_server_network_file:
|
||||
flags = os.O_WRONLY | os.O_CREAT | os.O_APPEND
|
||||
else:
|
||||
flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
|
||||
|
||||
with os.fdopen(os.open(interface_file_path, flags, mode),
|
||||
'w') as text_file:
|
||||
text = template_vip.render(
|
||||
consts=consts,
|
||||
interface=primary_interface,
|
||||
vip=vip,
|
||||
vip_ipv6=ip.version == 6,
|
||||
# For ipv6 the netmask is already the prefix
|
||||
prefix=(netmask if ip.version == 6
|
||||
else utils.netmask_to_prefix(netmask)),
|
||||
broadcast=broadcast,
|
||||
netmask=netmask,
|
||||
gateway=gateway,
|
||||
network=utils.ip_netmask_to_cidr(vip, netmask),
|
||||
mtu=mtu,
|
||||
vrrp_ip=vrrp_ip,
|
||||
vrrp_ipv6=vrrp_version == 6,
|
||||
host_routes=render_host_routes,
|
||||
topology=CONF.controller_worker.loadbalancer_topology,
|
||||
)
|
||||
text_file.write(text)
|
||||
|
||||
def write_port_interface_file(self, netns_interface, fixed_ips, mtu,
|
||||
interface_file_path, template_port):
|
||||
# write interface file
|
||||
|
||||
# If we are using a consolidated interfaces file, just append
|
||||
# otherwise clear the per interface file as we are rewriting it
|
||||
# TODO(johnsom): We need a way to clean out old interfaces records
|
||||
if CONF.amphora_agent.agent_server_network_file:
|
||||
flags = os.O_WRONLY | os.O_CREAT | os.O_APPEND
|
||||
else:
|
||||
flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
|
||||
|
||||
# mode 00644
|
||||
mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
|
||||
|
||||
with os.fdopen(os.open(interface_file_path, flags, mode),
|
||||
'w') as text_file:
|
||||
text = self._generate_network_file_text(
|
||||
netns_interface, fixed_ips, mtu, template_port)
|
||||
text_file.write(text)
|
||||
def write_port_interface_file(self, interface, fixed_ips, mtu):
|
||||
port_interface = interface_file.PortInterfaceFile(
|
||||
name=interface,
|
||||
mtu=mtu,
|
||||
fixed_ips=fixed_ips)
|
||||
port_interface.write()
|
||||
|
||||
@classmethod
|
||||
def _generate_network_file_text(cls, netns_interface, fixed_ips, mtu,
|
||||
template_port):
|
||||
text = ''
|
||||
if fixed_ips is None:
|
||||
text = template_port.render(interface=netns_interface)
|
||||
else:
|
||||
for index, fixed_ip in enumerate(fixed_ips, -1):
|
||||
try:
|
||||
ip_addr = fixed_ip['ip_address']
|
||||
cidr = fixed_ip['subnet_cidr']
|
||||
ip = ipaddress.ip_address(ip_addr)
|
||||
network = ipaddress.ip_network(cidr)
|
||||
broadcast = network.broadcast_address.exploded
|
||||
netmask = (network.prefixlen if ip.version == 6
|
||||
else network.netmask.exploded)
|
||||
host_routes = cls.get_host_routes(fixed_ip)
|
||||
|
||||
except ValueError:
|
||||
return webob.Response(
|
||||
json=dict(message="Invalid network IP"), status=400)
|
||||
new_text = template_port.render(interface=netns_interface,
|
||||
ipv6=ip.version == 6,
|
||||
ip_address=ip.exploded,
|
||||
broadcast=broadcast,
|
||||
netmask=netmask,
|
||||
mtu=mtu,
|
||||
host_routes=host_routes)
|
||||
text = '\n'.join([text, new_text])
|
||||
return text
|
||||
|
||||
@classmethod
|
||||
def get_host_routes(cls, fixed_ip):
|
||||
host_routes = []
|
||||
for hr in fixed_ip.get('host_routes', []):
|
||||
network = ipaddress.ip_network(hr['destination'])
|
||||
host_routes.append({'network': network, 'gw': hr['nexthop']})
|
||||
return host_routes
|
||||
|
||||
@classmethod
|
||||
def _bring_if_up(cls, interface, what, flush=True):
|
||||
# Note, we are not using pyroute2 for this as it is not /etc/netns
|
||||
# aware.
|
||||
# Work around for bug:
|
||||
# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=845121
|
||||
int_up = "ip netns exec {ns} ip link set {int} up".format(
|
||||
ns=consts.AMPHORA_NAMESPACE, int=interface)
|
||||
addr_flush = "ip netns exec {ns} ip addr flush {int}".format(
|
||||
ns=consts.AMPHORA_NAMESPACE, int=interface)
|
||||
|
||||
cmd = ("ip netns exec {ns} ifup {params}".format(
|
||||
def _bring_if_up(cls, interface, what):
|
||||
cmd = ("ip netns exec {ns} amphora-interface up {params}".format(
|
||||
ns=consts.AMPHORA_NAMESPACE, params=interface))
|
||||
LOG.debug("Executing: %s", cmd)
|
||||
try:
|
||||
out = subprocess.check_output(int_up.split(),
|
||||
stderr=subprocess.STDOUT)
|
||||
LOG.debug(out)
|
||||
if flush:
|
||||
out = subprocess.check_output(addr_flush.split(),
|
||||
stderr=subprocess.STDOUT)
|
||||
LOG.debug(out)
|
||||
out = subprocess.check_output(cmd.split(),
|
||||
stderr=subprocess.STDOUT)
|
||||
LOG.debug(out)
|
||||
except subprocess.CalledProcessError as e:
|
||||
LOG.error('Failed to ifup %s due to error: %s %s', interface, e,
|
||||
e.output)
|
||||
LOG.error('Failed to set up %s due to error: %s %s', interface,
|
||||
e, e.output)
|
||||
raise exceptions.HTTPException(
|
||||
response=webob.Response(json=dict(
|
||||
message='Error plugging {0}'.format(what),
|
||||
@ -223,34 +105,23 @@ class BaseOS(object):
|
||||
|
||||
@classmethod
|
||||
def _bring_if_down(cls, interface):
|
||||
# Note, we are not using pyroute2 for this as it is not /etc/netns
|
||||
# aware.
|
||||
cmd = ("ip netns exec {ns} ifdown {params}".format(
|
||||
cmd = ("ip netns exec {ns} amphora-interface down {params}".format(
|
||||
ns=consts.AMPHORA_NAMESPACE, params=interface))
|
||||
LOG.debug("Executing: %s", cmd)
|
||||
try:
|
||||
subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
|
||||
except subprocess.CalledProcessError as e:
|
||||
LOG.info('Ignoring failure to ifdown %s due to error: %s %s',
|
||||
LOG.info('Ignoring failure to set %s down due to error: %s %s',
|
||||
interface, e, e.output)
|
||||
|
||||
@classmethod
|
||||
def bring_interfaces_up(cls, ip, primary_interface, secondary_interface):
|
||||
def bring_interfaces_up(cls, ip, primary_interface):
|
||||
cls._bring_if_down(primary_interface)
|
||||
if secondary_interface:
|
||||
cls._bring_if_down(secondary_interface)
|
||||
cls._bring_if_up(primary_interface, 'VIP')
|
||||
if secondary_interface:
|
||||
cls._bring_if_up(secondary_interface, 'VIP', flush=False)
|
||||
|
||||
def has_ifup_all(self):
|
||||
return True
|
||||
|
||||
|
||||
class Ubuntu(BaseOS):
|
||||
|
||||
ETH_X_PORT_CONF = 'plug_port_ethX.conf.j2'
|
||||
ETH_X_VIP_CONF = 'plug_vip_ethX.conf.j2'
|
||||
|
||||
@classmethod
|
||||
def is_os_name(cls, os_name):
|
||||
return os_name in ['ubuntu']
|
||||
@ -259,87 +130,9 @@ class Ubuntu(BaseOS):
|
||||
name = self._map_package_name(package_name)
|
||||
return "dpkg-query -W -f=${{Version}} {name}".format(name=name)
|
||||
|
||||
def get_network_interface_file(self, interface):
|
||||
if CONF.amphora_agent.agent_server_network_file:
|
||||
return CONF.amphora_agent.agent_server_network_file
|
||||
if CONF.amphora_agent.agent_server_network_dir:
|
||||
return os.path.join(CONF.amphora_agent.agent_server_network_dir,
|
||||
interface + '.cfg')
|
||||
network_dir = consts.UBUNTU_AMP_NET_DIR_TEMPLATE.format(
|
||||
netns=consts.AMPHORA_NAMESPACE)
|
||||
return os.path.join(network_dir, interface + '.cfg')
|
||||
|
||||
def get_network_path(self):
|
||||
return '/etc/network'
|
||||
|
||||
def get_netns_network_dir(self):
|
||||
network_dir = self.get_network_path()
|
||||
return os.path.basename(network_dir)
|
||||
|
||||
def create_netns_dir(
|
||||
self, network_dir=None, netns_network_dir=None, ignore=None):
|
||||
if not netns_network_dir:
|
||||
netns_network_dir = self.get_netns_network_dir()
|
||||
if not network_dir:
|
||||
network_dir = self.get_network_path()
|
||||
if not ignore:
|
||||
ignore = shutil.ignore_patterns('eth0*', 'openssh*')
|
||||
super().create_netns_dir(
|
||||
network_dir, netns_network_dir, ignore)
|
||||
|
||||
def write_interfaces_file(self):
|
||||
name = '/etc/netns/{}/network/interfaces'.format(
|
||||
consts.AMPHORA_NAMESPACE)
|
||||
flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
|
||||
# mode 00644
|
||||
mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
|
||||
with os.fdopen(os.open(name, flags, mode), 'w') as int_file:
|
||||
int_file.write('auto lo\n')
|
||||
int_file.write('iface lo inet loopback\n')
|
||||
if not CONF.amphora_agent.agent_server_network_file:
|
||||
int_file.write('source /etc/netns/{}/network/'
|
||||
'interfaces.d/*.cfg\n'.format(
|
||||
consts.AMPHORA_NAMESPACE))
|
||||
|
||||
def write_vip_interface_file(self, interface_file_path,
|
||||
primary_interface, vip, ip, broadcast,
|
||||
netmask, gateway, mtu, vrrp_ip, vrrp_version,
|
||||
render_host_routes, template_vip=None):
|
||||
if not template_vip:
|
||||
template_vip = j2_env.get_template(self.ETH_X_VIP_CONF)
|
||||
super().write_vip_interface_file(
|
||||
interface_file_path, primary_interface, vip, ip, broadcast,
|
||||
netmask, gateway, mtu, vrrp_ip, vrrp_version, render_host_routes,
|
||||
template_vip)
|
||||
|
||||
def write_port_interface_file(self, netns_interface, fixed_ips, mtu,
|
||||
interface_file_path=None,
|
||||
template_port=None):
|
||||
if not interface_file_path:
|
||||
interface_file_path = self.get_network_interface_file(
|
||||
netns_interface)
|
||||
if not template_port:
|
||||
template_port = j2_env.get_template(self.ETH_X_PORT_CONF)
|
||||
super().write_port_interface_file(
|
||||
netns_interface, fixed_ips, mtu, interface_file_path,
|
||||
template_port)
|
||||
|
||||
def has_ifup_all(self):
|
||||
return True
|
||||
|
||||
|
||||
class RH(BaseOS):
|
||||
|
||||
ETH_X_PORT_CONF = 'rh_plug_port_ethX.conf.j2'
|
||||
ETH_X_VIP_CONF = 'rh_plug_vip_ethX.conf.j2'
|
||||
ETH_X_ALIAS_VIP_CONF = 'rh_plug_vip_ethX_alias.conf.j2'
|
||||
ROUTE_ETH_X_CONF = 'rh_route_ethX.conf.j2'
|
||||
RULE_ETH_X_CONF = 'rh_rule_ethX.conf.j2'
|
||||
# The reason of make them as jinja templates is the current scripts force
|
||||
# to add the iptables, so leave it now for future extending if possible.
|
||||
ETH_IFUP_LOCAL_SCRIPT = 'rh_plug_port_eth_ifup_local.conf.j2'
|
||||
ETH_IFDOWN_LOCAL_SCRIPT = 'rh_plug_port_eth_ifdown_local.conf.j2'
|
||||
|
||||
@classmethod
|
||||
def is_os_name(cls, os_name):
|
||||
return os_name in ['fedora', 'rhel']
|
||||
@ -348,227 +141,6 @@ class RH(BaseOS):
|
||||
name = self._map_package_name(package_name)
|
||||
return "rpm -q --queryformat %{{VERSION}} {name}".format(name=name)
|
||||
|
||||
@staticmethod
|
||||
def _get_network_interface_file(prefix, interface):
|
||||
if CONF.amphora_agent.agent_server_network_file:
|
||||
return CONF.amphora_agent.agent_server_network_file
|
||||
if CONF.amphora_agent.agent_server_network_dir:
|
||||
network_dir = CONF.amphora_agent.agent_server_network_dir
|
||||
else:
|
||||
network_dir = consts.RH_AMP_NET_DIR_TEMPLATE.format(
|
||||
netns=consts.AMPHORA_NAMESPACE)
|
||||
return os.path.join(network_dir, prefix + interface)
|
||||
|
||||
def get_network_interface_file(self, interface):
|
||||
return self._get_network_interface_file('ifcfg-', interface)
|
||||
|
||||
def get_alias_network_interface_file(self, interface):
|
||||
return self.get_network_interface_file(interface + ':0')
|
||||
|
||||
def get_static_routes_interface_file(self, interface, version):
|
||||
route = 'route6-' if version == 6 else 'route-'
|
||||
return self._get_network_interface_file(route, interface)
|
||||
|
||||
def get_route_rules_interface_file(self, interface, version):
|
||||
rule = 'rule6-' if version == 6 else 'rule-'
|
||||
return self._get_network_interface_file(rule, interface)
|
||||
|
||||
def get_network_path(self):
|
||||
return '/etc/sysconfig/network-scripts'
|
||||
|
||||
def get_netns_network_dir(self):
|
||||
network_full_path = self.get_network_path()
|
||||
network_basename = os.path.basename(network_full_path)
|
||||
network_dirname = os.path.dirname(network_full_path)
|
||||
network_prefixdir = os.path.basename(network_dirname)
|
||||
return os.path.join(network_prefixdir, network_basename)
|
||||
|
||||
def create_netns_dir(
|
||||
self, network_dir=None, netns_network_dir=None, ignore=None):
|
||||
if not netns_network_dir:
|
||||
netns_network_dir = self.get_netns_network_dir()
|
||||
if not network_dir:
|
||||
network_dir = self.get_network_path()
|
||||
if not ignore:
|
||||
ignore = shutil.ignore_patterns('ifcfg-eth0*')
|
||||
super().create_netns_dir(
|
||||
network_dir, netns_network_dir, ignore)
|
||||
|
||||
# Copy /etc/sysconfig/network file
|
||||
src = '/etc/sysconfig/network'
|
||||
dst = '/etc/netns/{netns}/sysconfig'.format(
|
||||
netns=consts.AMPHORA_NAMESPACE)
|
||||
shutil.copy2(src, dst)
|
||||
|
||||
def write_interfaces_file(self):
|
||||
# No interfaces file in RH based flavors
|
||||
return
|
||||
|
||||
def write_vip_interface_file(self, interface_file_path,
|
||||
primary_interface, vip, ip, broadcast,
|
||||
netmask, gateway, mtu, vrrp_ip, vrrp_version,
|
||||
render_host_routes, template_vip=None):
|
||||
if not template_vip:
|
||||
template_vip = j2_env.get_template(self.ETH_X_VIP_CONF)
|
||||
super().write_vip_interface_file(
|
||||
interface_file_path, primary_interface, vip, ip, broadcast,
|
||||
netmask, gateway, mtu, vrrp_ip, vrrp_version, render_host_routes,
|
||||
template_vip)
|
||||
|
||||
# keepalived will handle the VIP if we are on active/standby
|
||||
if (ip.version == 4 and
|
||||
CONF.controller_worker.loadbalancer_topology ==
|
||||
consts.TOPOLOGY_SINGLE):
|
||||
# Create an IPv4 alias interface, needed in RH based flavors
|
||||
alias_interface_file_path = self.get_alias_network_interface_file(
|
||||
primary_interface)
|
||||
template_vip_alias = j2_env.get_template(self.ETH_X_ALIAS_VIP_CONF)
|
||||
super().write_vip_interface_file(
|
||||
alias_interface_file_path, primary_interface, vip, ip,
|
||||
broadcast, netmask, gateway, mtu, vrrp_ip, vrrp_version,
|
||||
render_host_routes, template_vip_alias)
|
||||
|
||||
routes_interface_file_path = (
|
||||
self.get_static_routes_interface_file(primary_interface,
|
||||
ip.version))
|
||||
template_routes = j2_env.get_template(self.ROUTE_ETH_X_CONF)
|
||||
|
||||
self.write_static_routes_interface_file(
|
||||
routes_interface_file_path, primary_interface,
|
||||
render_host_routes, template_routes, gateway, vip, netmask)
|
||||
|
||||
# keepalived will handle the rule(s) if we are on actvie/standby
|
||||
if (CONF.controller_worker.loadbalancer_topology ==
|
||||
consts.TOPOLOGY_SINGLE):
|
||||
route_rules_interface_file_path = (
|
||||
self.get_route_rules_interface_file(primary_interface,
|
||||
ip.version))
|
||||
template_rules = j2_env.get_template(self.RULE_ETH_X_CONF)
|
||||
|
||||
self.write_static_routes_interface_file(
|
||||
route_rules_interface_file_path, primary_interface,
|
||||
render_host_routes, template_rules, gateway, vip, netmask)
|
||||
|
||||
self._write_ifup_ifdown_local_scripts_if_possible()
|
||||
|
||||
def write_static_routes_interface_file(self, interface_file_path,
|
||||
interface, host_routes,
|
||||
template_routes, gateway,
|
||||
vip, netmask):
|
||||
# write static routes interface file
|
||||
|
||||
mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
|
||||
|
||||
# TODO(johnsom): We need a way to clean out old interfaces records
|
||||
if CONF.amphora_agent.agent_server_network_file:
|
||||
flags = os.O_WRONLY | os.O_CREAT | os.O_APPEND
|
||||
else:
|
||||
flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
|
||||
|
||||
with os.fdopen(os.open(interface_file_path, flags, mode),
|
||||
'w') as text_file:
|
||||
text = template_routes.render(
|
||||
consts=consts,
|
||||
interface=interface,
|
||||
host_routes=host_routes,
|
||||
gateway=gateway,
|
||||
network=utils.ip_netmask_to_cidr(vip, netmask),
|
||||
vip=vip,
|
||||
topology=CONF.controller_worker.loadbalancer_topology,
|
||||
)
|
||||
text_file.write(text)
|
||||
|
||||
def write_port_interface_file(self, netns_interface, fixed_ips, mtu,
|
||||
interface_file_path=None,
|
||||
template_port=None):
|
||||
if not interface_file_path:
|
||||
interface_file_path = self.get_network_interface_file(
|
||||
netns_interface)
|
||||
if not template_port:
|
||||
template_port = j2_env.get_template(self.ETH_X_PORT_CONF)
|
||||
super().write_port_interface_file(
|
||||
netns_interface, fixed_ips, mtu, interface_file_path,
|
||||
template_port)
|
||||
|
||||
if fixed_ips:
|
||||
host_routes = []
|
||||
host_routes_ipv6 = []
|
||||
for fixed_ip in fixed_ips:
|
||||
ip_addr = fixed_ip['ip_address']
|
||||
ip = ipaddress.ip_address(ip_addr)
|
||||
if ip.version == 6:
|
||||
host_routes_ipv6.extend(self.get_host_routes(fixed_ip))
|
||||
else:
|
||||
host_routes.extend(self.get_host_routes(fixed_ip))
|
||||
|
||||
routes_interface_file_path = (
|
||||
self.get_static_routes_interface_file(netns_interface, 4))
|
||||
template_routes = j2_env.get_template(self.ROUTE_ETH_X_CONF)
|
||||
|
||||
self.write_static_routes_interface_file(
|
||||
routes_interface_file_path, netns_interface,
|
||||
host_routes, template_routes, None, None, None)
|
||||
|
||||
routes_interface_file_path_ipv6 = (
|
||||
self.get_static_routes_interface_file(netns_interface, 6))
|
||||
template_routes = j2_env.get_template(self.ROUTE_ETH_X_CONF)
|
||||
|
||||
self.write_static_routes_interface_file(
|
||||
routes_interface_file_path_ipv6, netns_interface,
|
||||
host_routes_ipv6, template_routes, None, None, None)
|
||||
|
||||
self._write_ifup_ifdown_local_scripts_if_possible()
|
||||
|
||||
@classmethod
|
||||
def bring_interfaces_up(cls, ip, primary_interface, secondary_interface):
|
||||
if ip.version == 4:
|
||||
super(RH, cls).bring_interfaces_up(
|
||||
ip, primary_interface, secondary_interface)
|
||||
else:
|
||||
# Secondary interface is not present in IPv6 configuration
|
||||
cls._bring_if_down(primary_interface)
|
||||
cls._bring_if_up(primary_interface, 'VIP')
|
||||
|
||||
def has_ifup_all(self):
|
||||
return False
|
||||
|
||||
def _write_ifup_ifdown_local_scripts_if_possible(self):
|
||||
if self._check_ifup_ifdown_local_scripts_exists():
|
||||
template_ifup_local = j2_env.get_template(
|
||||
self.ETH_IFUP_LOCAL_SCRIPT)
|
||||
self.write_port_interface_if_local_scripts(template_ifup_local)
|
||||
template_ifdown_local = j2_env.get_template(
|
||||
self.ETH_IFDOWN_LOCAL_SCRIPT)
|
||||
self.write_port_interface_if_local_scripts(template_ifdown_local,
|
||||
ifup=False)
|
||||
|
||||
def _check_ifup_ifdown_local_scripts_exists(self):
|
||||
file_names = ['ifup-local', 'ifdown-local']
|
||||
target_dir = '/sbin/'
|
||||
res = []
|
||||
for file_name in file_names:
|
||||
if os.path.exists(os.path.join(target_dir, file_name)):
|
||||
res.append(True)
|
||||
else:
|
||||
res.append(False)
|
||||
|
||||
# This means we only add the scripts when both of them are non-exists
|
||||
return not any(res)
|
||||
|
||||
def write_port_interface_if_local_scripts(
|
||||
self, template_script, ifup=True):
|
||||
file_name = 'ifup' + '-local'
|
||||
mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
|
||||
flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
|
||||
if not ifup:
|
||||
file_name = 'ifdown' + '-local'
|
||||
with os.fdopen(
|
||||
os.open(os.path.join(
|
||||
'/sbin/', file_name), flags, mode), 'w') as text_file:
|
||||
text = template_script.render()
|
||||
text_file.write(text)
|
||||
os.chmod(os.path.join('/sbin/', file_name), stat.S_IEXEC)
|
||||
|
||||
|
||||
class CentOS(RH):
|
||||
|
||||
|
@ -30,9 +30,6 @@ from octavia.common import constants as consts
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
ETH_X_VIP_CONF = 'plug_vip_ethX.conf.j2'
|
||||
ETH_X_PORT_CONF = 'plug_port_ethX.conf.j2'
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -40,26 +37,20 @@ class Plug(object):
|
||||
def __init__(self, osutils):
|
||||
self._osutils = osutils
|
||||
|
||||
def plug_lo(self):
|
||||
self._osutils.write_interface_file(
|
||||
interface="lo",
|
||||
ip_address="127.0.0.1",
|
||||
prefixlen=8)
|
||||
|
||||
def plug_vip(self, vip, subnet_cidr, gateway,
|
||||
mac_address, mtu=None, vrrp_ip=None, host_routes=None):
|
||||
# Validate vip and subnet_cidr, calculate broadcast address and netmask
|
||||
try:
|
||||
render_host_routes = []
|
||||
ip = ipaddress.ip_address(vip)
|
||||
network = ipaddress.ip_network(subnet_cidr)
|
||||
vip = ip.exploded
|
||||
broadcast = network.broadcast_address.exploded
|
||||
netmask = (network.prefixlen if ip.version == 6
|
||||
else network.netmask.exploded)
|
||||
vrrp_version = None
|
||||
if vrrp_ip:
|
||||
vrrp_ip_obj = ipaddress.ip_address(vrrp_ip)
|
||||
vrrp_version = vrrp_ip_obj.version
|
||||
if host_routes:
|
||||
for hr in host_routes:
|
||||
network = ipaddress.ip_network(hr['destination'])
|
||||
render_host_routes.append({'network': network,
|
||||
'gw': hr['nexthop']})
|
||||
prefixlen = network.prefixlen
|
||||
except ValueError:
|
||||
return webob.Response(json=dict(message="Invalid VIP"),
|
||||
status=400)
|
||||
@ -76,27 +67,16 @@ class Plug(object):
|
||||
|
||||
# Always put the VIP interface as eth1
|
||||
primary_interface = consts.NETNS_PRIMARY_INTERFACE
|
||||
secondary_interface = "{interface}:0".format(
|
||||
interface=primary_interface)
|
||||
|
||||
interface_file_path = self._osutils.get_network_interface_file(
|
||||
primary_interface)
|
||||
|
||||
self._osutils.create_netns_dir()
|
||||
|
||||
self._osutils.write_interfaces_file()
|
||||
self._osutils.write_vip_interface_file(
|
||||
interface_file_path=interface_file_path,
|
||||
primary_interface=primary_interface,
|
||||
interface=primary_interface,
|
||||
vip=vip,
|
||||
ip=ip,
|
||||
broadcast=broadcast,
|
||||
netmask=netmask,
|
||||
ip_version=ip.version,
|
||||
prefixlen=prefixlen,
|
||||
gateway=gateway,
|
||||
mtu=mtu,
|
||||
vrrp_ip=vrrp_ip,
|
||||
vrrp_version=vrrp_version,
|
||||
render_host_routes=render_host_routes)
|
||||
host_routes=host_routes)
|
||||
|
||||
# Update the list of interfaces to add to the namespace
|
||||
# This is used in the amphora reboot case to re-establish the namespace
|
||||
@ -136,13 +116,8 @@ class Plug(object):
|
||||
ipr.link('set', index=idx, net_ns_fd=consts.AMPHORA_NAMESPACE,
|
||||
IFLA_IFNAME=primary_interface)
|
||||
|
||||
# In an ha amphora, keepalived should bring the VIP interface up
|
||||
if (CONF.controller_worker.loadbalancer_topology ==
|
||||
consts.TOPOLOGY_ACTIVE_STANDBY):
|
||||
secondary_interface = None
|
||||
# bring interfaces up
|
||||
self._osutils.bring_interfaces_up(
|
||||
ip, primary_interface, secondary_interface)
|
||||
self._osutils.bring_interfaces_up(ip, primary_interface)
|
||||
|
||||
return webob.Response(json=dict(
|
||||
message="OK",
|
||||
@ -189,13 +164,10 @@ class Plug(object):
|
||||
LOG.info('Plugged interface %s will become %s in the namespace %s',
|
||||
default_netns_interface, netns_interface,
|
||||
consts.AMPHORA_NAMESPACE)
|
||||
interface_file_path = self._osutils.get_network_interface_file(
|
||||
netns_interface)
|
||||
self._osutils.write_port_interface_file(
|
||||
netns_interface=netns_interface,
|
||||
interface=netns_interface,
|
||||
fixed_ips=fixed_ips,
|
||||
mtu=mtu,
|
||||
interface_file_path=interface_file_path)
|
||||
mtu=mtu)
|
||||
|
||||
# Update the list of interfaces to add to the namespace
|
||||
self._update_plugged_interfaces_file(netns_interface, mac_address)
|
||||
|
@ -64,6 +64,8 @@ class Server(object):
|
||||
|
||||
register_app_error_handler(self.app)
|
||||
|
||||
self._plug.plug_lo()
|
||||
|
||||
self.app.add_url_rule(rule='/', view_func=self.version_discovery,
|
||||
methods=['GET'])
|
||||
self.app.add_url_rule(rule=PATH_PREFIX +
|
||||
|
@ -25,12 +25,4 @@ ExecStart=-/bin/sh -c '/usr/bin/sort -k 1 /var/lib/octavia/plugged_interfaces >
|
||||
# Assign the interfaces into the namespace with the appropriate name
|
||||
ExecStart=-/bin/sh -c '/sbin/ip link | awk \'{getline n; print $0,n}\' | awk \'{sub(":","",$2)} { for(i=1;i<=NF;i++) if ($i == "link/ether") {print $(i+1) " " $2} }\' | sort -k 1 | join -j 1 - /var/lib/octavia/plugged_interfaces.sorted | awk \'{system("ip link set "$2" netns {{ amphora_nsname }} name "$3"")}\''
|
||||
# Bring up all of the namespace interfaces
|
||||
{%- if HasIFUPAll %}
|
||||
# Ubuntu seems to not correctly set up the lo iface when calling ifup -a
|
||||
# Disable it first, before setting it up.
|
||||
ExecStart=-/sbin/ip netns exec {{ amphora_nsname }} ifdown lo
|
||||
ExecStart=-/sbin/ip netns exec {{ amphora_nsname }} ifup -a
|
||||
{%- else %}
|
||||
ExecStart=-/sbin/ip netns exec {{ amphora_nsname }} ifup lo
|
||||
ExecStart=-/bin/awk '{system("/sbin/ip netns exec {{ amphora_nsname }} ifup " $2)}' /var/lib/octavia/plugged_interfaces
|
||||
{%- endif %}
|
||||
ExecStart=-/sbin/ip netns exec {{ amphora_nsname }} amphora-interface up all
|
||||
|
@ -1,43 +0,0 @@
|
||||
{#
|
||||
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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.
|
||||
#}
|
||||
# Generated by Octavia agent
|
||||
auto {{ interface }}
|
||||
{%- if ip_address %}
|
||||
iface {{ interface }} inet{{ '6' if ipv6 }} static
|
||||
address {{ ip_address }}
|
||||
broadcast {{ broadcast }}
|
||||
netmask {{ netmask }}
|
||||
{%- if mtu %}
|
||||
mtu {{ mtu }}
|
||||
{%- endif %}
|
||||
{%- for hr in host_routes %}
|
||||
{%- if ((hr.network.version == 4 and hr.network.prefixlen == 32) or
|
||||
(hr.network.version == 6 and hr.network.prefixlen == 128)) %}
|
||||
up route add -host {{ hr.network }} gw {{ hr.gw }} dev {{ interface }}
|
||||
down route del -host {{ hr.network }} gw {{ hr.gw }} dev {{ interface }}
|
||||
{%- else %}
|
||||
up route add -net {{ hr.network }} gw {{ hr.gw }} dev {{ interface }}
|
||||
down route del -net {{ hr.network }} gw {{ hr.gw }} dev {{ interface }}
|
||||
{%- endif %}
|
||||
{%- endfor %}
|
||||
post-up /usr/local/bin/lvs-masquerade.sh add {{ 'ipv6' if ipv6 else 'ipv4' }} {{ interface }}
|
||||
post-down /usr/local/bin/lvs-masquerade.sh delete {{ 'ipv6' if ipv6 else 'ipv4' }} {{ interface }}
|
||||
{%- else %}
|
||||
iface {{ interface }} inet dhcp
|
||||
auto {{ interface }}:0
|
||||
iface {{ interface }}:0 inet6 auto
|
||||
{%- endif %}
|
||||
|
@ -1,82 +0,0 @@
|
||||
{#
|
||||
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||
# Copyright 2016 Rackspace
|
||||
#
|
||||
# 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.
|
||||
#}
|
||||
# Generated by Octavia agent
|
||||
{%- if topology == consts.TOPOLOGY_SINGLE %}
|
||||
auto {{ interface }} {{ interface }}:0
|
||||
{%- else %}
|
||||
auto {{ interface }}
|
||||
{%- endif %}
|
||||
{%- if vrrp_ip %}
|
||||
iface {{ interface }} inet{{ '6' if vrrp_ipv6 }} static
|
||||
address {{ vrrp_ip }}
|
||||
broadcast {{ broadcast }}
|
||||
netmask {{ netmask }}
|
||||
{%- if gateway %}
|
||||
gateway {{ gateway }}
|
||||
{%- endif %}
|
||||
{%- if mtu %}
|
||||
mtu {{ mtu }}
|
||||
{%- endif %}
|
||||
{%- for hr in host_routes %}
|
||||
{%- if ((hr.network.version == 4 and hr.network.prefixlen == 32) or
|
||||
(hr.network.version == 6 and hr.network.prefixlen == 128)) %}
|
||||
up route add -host {{ hr.network }} gw {{ hr.gw }} dev {{ interface }}
|
||||
down route del -host {{ hr.network }} gw {{ hr.gw }} dev {{ interface }}
|
||||
{%- else %}
|
||||
up route add -net {{ hr.network }} gw {{ hr.gw }} dev {{ interface }}
|
||||
down route del -net {{ hr.network }} gw {{ hr.gw }} dev {{ interface }}
|
||||
{%- endif %}
|
||||
{%- endfor %}
|
||||
{%- else %}
|
||||
|
||||
iface {{ interface }} inet{{ '6' if vip_ipv6 }} {{ 'auto' if vip_ipv6 else 'dhcp' }}
|
||||
{%- endif %}
|
||||
|
||||
{%- if topology == consts.TOPOLOGY_SINGLE %}
|
||||
iface {{ interface }}:0 inet{{ '6' if vip_ipv6 }} static
|
||||
address {{ vip }}
|
||||
broadcast {{ broadcast }}
|
||||
netmask {{ netmask }}
|
||||
{%- endif %}
|
||||
|
||||
# Add a source routing table to allow members to access the VIP
|
||||
{%- if gateway %}
|
||||
|
||||
post-up /sbin/ip {{ '-6 ' if vip_ipv6 }}route add default via {{ gateway }} dev {{ interface }} onlink table 1
|
||||
post-down /sbin/ip {{ '-6 ' if vip_ipv6 }}route del default via {{ gateway }} dev {{ interface }} onlink table 1
|
||||
|
||||
{# Keepalived will insert and remove this route in active/standby #}
|
||||
{%- if topology == consts.TOPOLOGY_SINGLE %}
|
||||
post-up /sbin/ip {{ '-6 ' if vip_ipv6 }}route add {{ network }} dev {{ interface }} src {{ vip }} scope link table 1
|
||||
post-down /sbin/ip {{ '-6 ' if vip_ipv6 }}route del {{ network }} dev {{ interface }} src {{ vip }} scope link table 1
|
||||
{%- endif %}
|
||||
|
||||
{%- endif %}
|
||||
|
||||
{%- for hr in host_routes %}
|
||||
post-up /sbin/ip {{ '-6 ' if vip_ipv6 }}route add {{ hr.network }} via {{ hr.gw }} dev {{ interface }} onlink table 1
|
||||
post-down /sbin/ip {{ '-6 ' if vip_ipv6 }}route del {{ hr.network }} via {{ hr.gw }} dev {{ interface }} onlink table 1
|
||||
{%- endfor %}
|
||||
|
||||
{# Keepalived will insert and remove this rule in active/standby #}
|
||||
{%- if topology == consts.TOPOLOGY_SINGLE %}
|
||||
post-up /sbin/ip {{ '-6 ' if vip_ipv6 }}rule add from {{ vip }}/{{ '128' if vip_ipv6 else '32' }} table 1 priority 100
|
||||
post-down /sbin/ip {{ '-6 ' if vip_ipv6 }}rule del from {{ vip }}/{{ '128' if vip_ipv6 else '32' }} table 1 priority 100
|
||||
{%- endif %}
|
||||
|
||||
post-up /usr/local/bin/lvs-masquerade.sh add {{ 'ipv6' if vip_ipv6 else 'ipv4' }} {{ interface }}
|
||||
post-down /usr/local/bin/lvs-masquerade.sh delete {{ 'ipv6' if vip_ipv6 else 'ipv4' }} {{ interface }}
|
@ -1,47 +0,0 @@
|
||||
{#
|
||||
# Copyright 2017 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.
|
||||
#}
|
||||
# Generated by Octavia agent
|
||||
NM_CONTROLLED="no"
|
||||
DEVICE="{{ interface }}"
|
||||
ONBOOT="yes"
|
||||
TYPE="Ethernet"
|
||||
USERCTL="yes"
|
||||
{%- if ipv6 %}
|
||||
IPV6INIT="yes"
|
||||
{%- if mtu %}
|
||||
IPV6_MTU="{{ mtu }}"
|
||||
{%- endif %}
|
||||
{%- if ip_address %}
|
||||
IPV6_AUTOCONF="no"
|
||||
IPV6ADDR="{{ ip_address }}"
|
||||
{%- else %}
|
||||
IPV6_AUTOCONF="yes"
|
||||
{%- endif %}
|
||||
{%- else %}
|
||||
IPV6INIT="no"
|
||||
{%- if mtu %}
|
||||
MTU="{{ mtu }}"
|
||||
{%- endif %}
|
||||
{%- if ip_address %}
|
||||
BOOTPROTO="static"
|
||||
IPADDR="{{ ip_address }}"
|
||||
NETMASK="{{ netmask }}"
|
||||
{%- else %}
|
||||
BOOTPROTO="dhcp"
|
||||
PERSISTENT_DHCLIENT="1"
|
||||
{%- endif %}
|
||||
{%- endif %}
|
||||
|
@ -1,19 +0,0 @@
|
||||
{# 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.
|
||||
#}
|
||||
# Generated by Octavia agent
|
||||
#!/bin/bash
|
||||
if [[ "$1" != "lo" ]]
|
||||
then
|
||||
/usr/local/bin/lvs-masquerade.sh delete ipv4 $1
|
||||
/usr/local/bin/lvs-masquerade.sh delete ipv6 $1
|
||||
fi
|
@ -1,19 +0,0 @@
|
||||
{# 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.
|
||||
#}
|
||||
# Generated by Octavia agent
|
||||
#!/bin/bash
|
||||
if [[ "$1" != "lo" ]]
|
||||
then
|
||||
/usr/local/bin/lvs-masquerade.sh add ipv4 $1
|
||||
/usr/local/bin/lvs-masquerade.sh add ipv6 $1
|
||||
fi
|
@ -1,60 +0,0 @@
|
||||
{#
|
||||
# Copyright 2017 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.
|
||||
#}
|
||||
# Generated by Octavia agent
|
||||
NM_CONTROLLED="no"
|
||||
DEVICE="{{ interface }}"
|
||||
ONBOOT="yes"
|
||||
TYPE="Ethernet"
|
||||
USERCTL="yes"
|
||||
|
||||
{%- if vrrp_ip %}
|
||||
{%- if vrrp_ipv6 %}
|
||||
IPV6INIT="yes"
|
||||
IPV6_DEFROUTE="yes"
|
||||
IPV6_AUTOCONF="no"
|
||||
IPV6ADDR="{{ vrrp_ip }}/{{ prefix }}"
|
||||
{%- if gateway %}
|
||||
IPV6_DEFAULTGW="{{ gateway }}"
|
||||
{%- endif %}
|
||||
{%- if mtu %}
|
||||
IPV6_MTU="{{ mtu }}"
|
||||
{%- endif %}
|
||||
{%- else %} {# not vrrp_ipv6 #}
|
||||
BOOTPROTO="static"
|
||||
IPADDR="{{ vrrp_ip }}"
|
||||
NETMASK="{{ netmask }}"
|
||||
{%- if gateway %}
|
||||
GATEWAY="{{ gateway }}"
|
||||
{%- endif %}
|
||||
MTU="{{ mtu }}"
|
||||
{%- endif %} {# end if vrrp_ipv6 #}
|
||||
{%- else %} {# not vrrp_ip #}
|
||||
{%- if vip_ipv6 %}
|
||||
IPV6INIT="yes"
|
||||
IPV6_DEFROUTE="yes"
|
||||
IPV6_AUTOCONF="yes"
|
||||
{%- else %}
|
||||
BOOTPROTO="dhcp"
|
||||
PERSISTENT_DHCLIENT="1"
|
||||
{%- endif %} {# end if vip_ipv6 #}
|
||||
{%- endif %} {# end if vrrp_ip #}
|
||||
|
||||
{%- if topology == consts.TOPOLOGY_SINGLE -%}
|
||||
{%- if vip_ipv6 %}
|
||||
IPV6ADDR_SECONDARIES="{{ vip }}/{{ prefix }}"
|
||||
{%- endif %}
|
||||
{%- endif %}
|
||||
|
@ -1,29 +0,0 @@
|
||||
{#
|
||||
# Copyright 2017 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.
|
||||
#}
|
||||
# Generated by Octavia agent
|
||||
NM_CONTROLLED="no"
|
||||
DEVICE="{{ interface }}:0"
|
||||
NAME="{{ interface }}:0"
|
||||
ONBOOT="yes"
|
||||
ARPCHECK="no"
|
||||
IPV6INIT="no"
|
||||
{%- if mtu %}
|
||||
MTU="{{ mtu }}"
|
||||
{%- endif %}
|
||||
BOOTPROTO="static"
|
||||
IPADDR="{{ vip }}"
|
||||
NETMASK="{{ netmask }}"
|
||||
|
@ -1,29 +0,0 @@
|
||||
{#
|
||||
# Copyright 2017 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.
|
||||
#}
|
||||
# Generated by Octavia agent
|
||||
{%- for hr in host_routes %}
|
||||
{{ hr.network }} via {{ hr.gw }} dev {{ interface }}
|
||||
{%- endfor %}
|
||||
# Add a source routing table to allow members to access the VIP
|
||||
{%- if gateway %}
|
||||
{%- if topology == consts.TOPOLOGY_SINGLE %}
|
||||
{{ network }} dev {{ interface }} src {{ vip }} scope link table 1
|
||||
{%- endif %}
|
||||
default table 1 via {{ gateway }} dev {{ interface }}
|
||||
{%- endif %}
|
||||
{%- for hr in host_routes %}
|
||||
{{ hr.network }} table 1 via {{ hr.gw }} dev {{ interface }}
|
||||
{%- endfor %}
|
@ -1,17 +0,0 @@
|
||||
{#
|
||||
# Copyright 2017 Rackspace, US Inc.
|
||||
#
|
||||
# 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.
|
||||
#}
|
||||
# Generated by Octavia agent
|
||||
from {{ vip }} table 1
|
@ -62,11 +62,7 @@ haproxy_start()
|
||||
# Assign the interfaces into the namespace with the appropriate name
|
||||
ip link | awk '{getline n; print $0,n}' | awk '{sub(":","",$2)} { for(i=1;i<=NF;i++) if ($i == "link/ether") {print $(i+1) " " $2} }' | sort -k 1 | join -j 1 - /var/lib/octavia/plugged_interfaces.sorted | awk '{system("ip link set "$2" netns {{ amphora_nsname }} name "$3"")}' || true
|
||||
# Bring up all of the namespace interfaces
|
||||
{%- if HasIFUPAll %}
|
||||
ip netns exec {{ amphora_nsname }} ifup -a || true
|
||||
{%- else %}
|
||||
awk '{system("/sbin/ip netns exec {{ amphora_nsname }} ifup " $2)}' /var/lib/octavia/plugged_interfaces || true
|
||||
{%- endif %}
|
||||
ip netns exec {{ amphora_nsname }} amphora-interface up all || true
|
||||
|
||||
start-stop-daemon --start --pidfile "$PIDFILE" \
|
||||
--exec $HAPROXY -- -f "$CONFIG" -f "$USER_GROUP_CONF_PATH" -L "$PEER_NAME" -D -p "$PIDFILE" \
|
||||
|
@ -42,11 +42,7 @@ pre-start script
|
||||
# Assign the interfaces into the namespace with the appropriate name
|
||||
ip link | awk '{getline n; print $0,n}' | awk '{sub(":","",$2)} { for(i=1;i<=NF;i++) if ($i == "link/ether") {print $(i+1) " " $2} }' | sort -k 1 | join -j 1 - /var/lib/octavia/plugged_interfaces.sorted | awk '{system("ip link set "$2" netns {{ amphora_nsname }} name "$3"")}' || true
|
||||
# Bring up all of the namespace interfaces
|
||||
{%- if HasIFUPAll %}
|
||||
ip netns exec {{ amphora_nsname }} ifup -a || true
|
||||
{%- else %}
|
||||
awk '{system("/sbin/ip netns exec {{ amphora_nsname }} ifup " $2)}' /var/lib/octavia/plugged_interfaces || true
|
||||
{%- endif %}
|
||||
ip netns exec {{ amphora_nsname }} amphora-interface up all || true
|
||||
|
||||
end script
|
||||
|
||||
|
@ -22,7 +22,6 @@ import jinja2
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
||||
from octavia.amphorae.backends.agent.api_server import osutils
|
||||
from octavia.amphorae.backends.utils import ip_advertisement
|
||||
from octavia.amphorae.backends.utils import network_utils
|
||||
from octavia.common import constants as consts
|
||||
@ -241,8 +240,6 @@ def get_os_init_system():
|
||||
|
||||
|
||||
def install_netns_systemd_service():
|
||||
os_utils = osutils.BaseOS.get_os_util()
|
||||
|
||||
flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
|
||||
# mode 00644
|
||||
mode = (stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH)
|
||||
@ -261,8 +258,7 @@ def install_netns_systemd_service():
|
||||
with os.fdopen(os.open(netns_path, flags, mode), 'w') as text_file:
|
||||
text = jinja_env.get_template(
|
||||
consts.AMP_NETNS_SVC_PREFIX + '.systemd.j2').render(
|
||||
amphora_nsname=consts.AMPHORA_NAMESPACE,
|
||||
HasIFUPAll=os_utils.has_ifup_all())
|
||||
amphora_nsname=consts.AMPHORA_NAMESPACE)
|
||||
text_file.write(text)
|
||||
|
||||
|
||||
|
@ -40,9 +40,6 @@ agent_server_cert = {{ agent_server_cert }}
|
||||
{% if agent_server_network_dir -%}
|
||||
agent_server_network_dir = {{ agent_server_network_dir }}
|
||||
{% endif -%}
|
||||
{% if agent_server_network_file -%}
|
||||
agent_server_network_file = {{ agent_server_network_file }}
|
||||
{% endif -%}
|
||||
agent_request_read_timeout = {{ agent_request_read_timeout }}
|
||||
amphora_id = {{ amphora_id }}
|
||||
amphora_udp_driver = {{ amphora_udp_driver }}
|
||||
|
237
octavia/amphorae/backends/utils/interface.py
Normal file
237
octavia/amphorae/backends/utils/interface.py
Normal file
@ -0,0 +1,237 @@
|
||||
# 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__)
|
||||
|
||||
|
||||
class InterfaceController(object):
|
||||
ADD = 'add'
|
||||
DELETE = 'delete'
|
||||
SET = 'set'
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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())
|
||||
|
||||
def down(self, interface):
|
||||