Fix the amphora image support for RH Linux flavors
Not all Linux flavors accept the same type of configuration to manage NICs. The amphora-agent must be able to distinguish between different Linux flavors and choose the appropriate type of jinja2 NIC configuration template for each one, respectively. Up until now, The amphora-agent had no notion of the operating system it is running on, therefore it used NIC configuration templates that only match Debian based Linux flavors (mostly Ubuntu). Making it unusable for flavors such as RHEL, Fedora and CentOS. This fix enhances how the amphora-agent is handling NIC hot plugs. It will use the appropriate jinja2 template by checking the Amphora distribution name when needed. Co-Authored-By: Michael Johnson <johnsomor@gmail.com> Closes-Bug #1548070 Change-Id: Id99948aec64656a0532afc68e146f0610bff1378
This commit is contained in:
parent
67866ea193
commit
c00488143d
@ -333,21 +333,21 @@ if [ "$AMP_BASEOS" = "ubuntu" ]; then
|
||||
export UBUNTU_MIRROR="$BASE_OS_MIRROR"
|
||||
fi
|
||||
elif [ "$AMP_BASEOS" = "fedora" ]; then
|
||||
AMP_element_sequence=${AMP_element_sequence:-"base vm fedora"}
|
||||
AMP_element_sequence=${AMP_element_sequence:-"base vm fedora selinux-permissive"}
|
||||
AMP_element_sequence="$AMP_element_sequence $AMP_BACKEND"
|
||||
if [ "$BASE_OS_MIRROR" ]; then
|
||||
AMP_element_sequence="$AMP_element_sequence fedora-mirror"
|
||||
export FEDORA_MIRROR="$BASE_OS_MIRROR"
|
||||
fi
|
||||
elif [ "$AMP_BASEOS" = "centos" ]; then
|
||||
AMP_element_sequence=${AMP_element_sequence:-"base vm centos7 epel"}
|
||||
AMP_element_sequence=${AMP_element_sequence:-"base vm centos7 epel selinux-permissive"}
|
||||
AMP_element_sequence="$AMP_element_sequence $AMP_BACKEND"
|
||||
if [ "$BASE_OS_MIRROR" ]; then
|
||||
AMP_element_sequence="$AMP_element_sequence centos-mirror"
|
||||
export CENTOS_MIRROR="$BASE_OS_MIRROR"
|
||||
fi
|
||||
elif [ "$AMP_BASEOS" = "rhel" ]; then
|
||||
AMP_element_sequence=${AMP_element_sequence:-"base vm rhel7"}
|
||||
AMP_element_sequence=${AMP_element_sequence:-"base vm rhel7 selinux-permissive"}
|
||||
AMP_element_sequence="$AMP_element_sequence $AMP_BACKEND"
|
||||
fi
|
||||
|
||||
|
@ -8,6 +8,9 @@ set -o pipefail
|
||||
|
||||
pip install -U -c /opt/upper-constraints.txt /opt/amphora-agent
|
||||
|
||||
# Accommodate centos default install location
|
||||
ln -s /bin/amphora-agent /usr/local/bin/amphora-agent || true
|
||||
|
||||
mkdir /etc/octavia
|
||||
# we assume certs, etc will come in through the config drive
|
||||
mkdir /etc/octavia/certs
|
||||
|
@ -1,9 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -eux
|
||||
set -o pipefail
|
||||
|
||||
# Allow haproxy to proxy any port if SELinux is in enforcing mode
|
||||
# https://bugs.launchpad.net/tripleo/+bug/1339938
|
||||
if [[ -x /usr/sbin/semanage ]]; then
|
||||
setsebool -P haproxy_connect_any 1
|
||||
fi
|
12
elements/haproxy-octavia-ubuntu/post-install.d/20-setup-haproxy-log
Executable file
12
elements/haproxy-octavia-ubuntu/post-install.d/20-setup-haproxy-log
Executable file
@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -eu
|
||||
set -o pipefail
|
||||
|
||||
if [[ -d /etc/rsyslog.d ]] && [[ ! -e /etc/rsyslog.d/49-haproxy.conf ]]; then
|
||||
cat >> /etc/rsyslog.d/49-haproxy.conf <<EOF
|
||||
# Send HAProxy messages to a dedicated logfile
|
||||
if \$programname startswith 'haproxy' then /var/log/haproxy.log
|
||||
&~
|
||||
EOF
|
||||
fi
|
@ -1,9 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -eux
|
||||
set -o pipefail
|
||||
|
||||
# Allow haproxy to proxy any port if SELinux is in enforcing mode
|
||||
# https://bugs.launchpad.net/tripleo/+bug/1339938
|
||||
if [[ -x /usr/sbin/semanage ]]; then
|
||||
setsebool -P haproxy_connect_any 1
|
||||
fi
|
0
elements/haproxy-octavia/post-install.d/20-disable-default-haproxy
Normal file → Executable file
0
elements/haproxy-octavia/post-install.d/20-disable-default-haproxy
Normal file → Executable file
12
elements/haproxy-octavia/post-install.d/20-setup-haproxy-log
Executable file
12
elements/haproxy-octavia/post-install.d/20-setup-haproxy-log
Executable file
@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -eu
|
||||
set -o pipefail
|
||||
|
||||
if [[ -d /etc/rsyslog.d ]] && [[ ! -e /etc/rsyslog.d/49-haproxy.conf ]]; then
|
||||
cat >> /etc/rsyslog.d/49-haproxy.conf <<EOF
|
||||
# Send HAProxy messages to a dedicated logfile
|
||||
if \$programname startswith 'haproxy' then /var/log/haproxy.log
|
||||
&~
|
||||
EOF
|
||||
fi
|
@ -1,6 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -eu
|
||||
set -o pipefail
|
||||
|
||||
chkconfig keepalived on
|
@ -3,6 +3,8 @@ it with data from DHCP. This means that DNS resolution will not work from the
|
||||
amphora. This is OK because all outbound connections from the amphora will
|
||||
be based using raw IP addresses.
|
||||
|
||||
In addition we remove dns from the nsswitch.conf hosts setting.
|
||||
|
||||
This has the real benefit of speeding up host boot and configutation times.
|
||||
This is especially helpful when running tempest tests in a devstack environment
|
||||
where DNS resolution from the amphora usually doesn't work anyway: This means
|
||||
|
@ -14,3 +14,7 @@ else
|
||||
make_resolv_conf() { : ; }" > /etc/dhclient-enter-hooks
|
||||
chmod +x /etc/dhclient-enter-hooks
|
||||
fi
|
||||
|
||||
if [ -e /etc/nsswitch.conf ]; then
|
||||
sed -i -e "/hosts:/ s/dns//g" /etc/nsswitch.conf
|
||||
fi
|
||||
|
@ -224,7 +224,13 @@
|
||||
[amphora_agent]
|
||||
# agent_server_ca = /etc/octavia/certs/client_ca.pem
|
||||
# agent_server_cert = /etc/octavia/certs/server.pem
|
||||
# agent_server_network_dir = /etc/netns/amphora-haproxy/network/interfaces.d/
|
||||
|
||||
# Defaults for agent_server_network_dir when not specified here are:
|
||||
# Ubuntu: /etc/netns/amphora-haproxy/network/interfaces.d/
|
||||
# Centos/fedora/rhel: /etc/netns/amphora-haproxy/sysconfig/network-scripts/
|
||||
#
|
||||
# agent_server_network_dir =
|
||||
|
||||
# agent_server_network_file =
|
||||
# agent_request_read_timeout = 120
|
||||
|
||||
|
@ -31,25 +31,30 @@ from octavia.common import constants as consts
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def compile_amphora_info():
|
||||
class AmphoraInfo(object):
|
||||
def __init__(self, osutils):
|
||||
self._osutils = osutils
|
||||
|
||||
def compile_amphora_info(self):
|
||||
return flask.jsonify(
|
||||
{'hostname': socket.gethostname(),
|
||||
'haproxy_version': _get_version_of_installed_package('haproxy'),
|
||||
'haproxy_version':
|
||||
self._get_version_of_installed_package('haproxy'),
|
||||
'api_version': api_server.VERSION})
|
||||
|
||||
|
||||
def compile_amphora_details():
|
||||
def compile_amphora_details(self):
|
||||
listener_list = util.get_listeners()
|
||||
meminfo = _get_meminfo()
|
||||
cpu = _cpu()
|
||||
meminfo = self._get_meminfo()
|
||||
cpu = self._cpu()
|
||||
st = os.statvfs('/')
|
||||
return flask.jsonify(
|
||||
{'hostname': socket.gethostname(),
|
||||
'haproxy_version': _get_version_of_installed_package('haproxy'),
|
||||
'haproxy_version':
|
||||
self._get_version_of_installed_package('haproxy'),
|
||||
'api_version': api_server.VERSION,
|
||||
'networks': _get_networks(),
|
||||
'networks': self._get_networks(),
|
||||
'active': True,
|
||||
'haproxy_count': _count_haproxy_processes(listener_list),
|
||||
'haproxy_count': self._count_haproxy_processes(listener_list),
|
||||
'cpu': {
|
||||
'total': cpu['total'],
|
||||
'user': cpu['user'],
|
||||
@ -66,22 +71,20 @@ def compile_amphora_details():
|
||||
'disk': {
|
||||
'used': (st.f_blocks - st.f_bfree) * st.f_frsize,
|
||||
'available': st.f_bavail * st.f_frsize},
|
||||
'load': [
|
||||
_load()],
|
||||
'load': self._load(),
|
||||
'topology': consts.TOPOLOGY_SINGLE,
|
||||
'topology_status': consts.TOPOLOGY_STATUS_OK,
|
||||
'listeners': listener_list,
|
||||
'packages': {}})
|
||||
|
||||
def _get_version_of_installed_package(self, name):
|
||||
|
||||
def _get_version_of_installed_package(name):
|
||||
cmd = "dpkg --status {name}".format(name=name)
|
||||
cmd = self._osutils.cmd_get_version_of_installed_package(name)
|
||||
out = subprocess.check_output(cmd.split())
|
||||
m = re.search(b'Version: .*', out)
|
||||
return m.group(0)[len('Version: '):]
|
||||
|
||||
|
||||
def _count_haproxy_processes(listener_list):
|
||||
def _count_haproxy_processes(self, listener_list):
|
||||
num = 0
|
||||
for listener_id in listener_list:
|
||||
if util.is_listener_running(listener_id):
|
||||
@ -89,11 +92,11 @@ def _count_haproxy_processes(listener_list):
|
||||
num += 1
|
||||
return num
|
||||
|
||||
|
||||
def _get_meminfo():
|
||||
def _get_meminfo(self):
|
||||
re_parser = re.compile(r'^(?P<key>\S*):\s*(?P<value>\d*)\s*kB')
|
||||
result = dict()
|
||||
for line in open('/proc/meminfo'):
|
||||
with open('/proc/meminfo', 'r') as meminfo:
|
||||
for line in meminfo:
|
||||
match = re_parser.match(line)
|
||||
if not match:
|
||||
continue # skip lines that don't parse
|
||||
@ -101,8 +104,7 @@ def _get_meminfo():
|
||||
result[key] = int(value)
|
||||
return result
|
||||
|
||||
|
||||
def _cpu():
|
||||
def _cpu(self):
|
||||
with open('/proc/stat') as f:
|
||||
cpu = f.readline()
|
||||
vals = cpu.split(' ')
|
||||
@ -117,21 +119,20 @@ def _cpu():
|
||||
'total': sum([int(i) for i in vals[2:]])
|
||||
}
|
||||
|
||||
|
||||
def _load():
|
||||
def _load(self):
|
||||
with open('/proc/loadavg') as f:
|
||||
load = f.readline()
|
||||
vals = load.split(' ')
|
||||
return vals[:3]
|
||||
|
||||
|
||||
def _get_networks():
|
||||
def _get_networks(self):
|
||||
networks = dict()
|
||||
with pyroute2.NetNS(consts.AMPHORA_NAMESPACE) as netns:
|
||||
for interface in netns.get_links():
|
||||
interface_name = None
|
||||
for item in interface['attrs']:
|
||||
if item[0] == 'IFLA_IFNAME' and not item[1].startswith('eth'):
|
||||
if (item[0] == 'IFLA_IFNAME'
|
||||
and not item[1].startswith('eth')):
|
||||
break
|
||||
elif item[0] == 'IFLA_IFNAME':
|
||||
interface_name = item[1]
|
||||
@ -141,8 +142,7 @@ def _get_networks():
|
||||
network_rx=item[1]['rx_bytes'])
|
||||
return networks
|
||||
|
||||
|
||||
def get_interface(ip_addr):
|
||||
def get_interface(self, ip_addr):
|
||||
|
||||
try:
|
||||
ip_version = ipaddress.ip_address(six.text_type(ip_addr)).version
|
||||
|
@ -26,6 +26,7 @@ import jinja2
|
||||
import six
|
||||
from werkzeug import exceptions
|
||||
|
||||
from octavia.amphorae.backends.agent.api_server import osutils
|
||||
from octavia.amphorae.backends.agent.api_server import util
|
||||
from octavia.amphorae.backends.utils import haproxy_query as query
|
||||
from octavia.common import constants as consts
|
||||
@ -74,6 +75,9 @@ class Wrapped(object):
|
||||
|
||||
class Listener(object):
|
||||
|
||||
def __init__(self):
|
||||
self._osutils = osutils.BaseOS.get_os_util()
|
||||
|
||||
def get_haproxy_config(self, listener_id):
|
||||
"""Gets the haproxy config
|
||||
|
||||
@ -171,7 +175,8 @@ class Listener(object):
|
||||
respawn_count=util.CONF.haproxy_amphora.respawn_count,
|
||||
respawn_interval=(util.CONF.haproxy_amphora.
|
||||
respawn_interval),
|
||||
amphora_nsname=consts.AMPHORA_NAMESPACE
|
||||
amphora_nsname=consts.AMPHORA_NAMESPACE,
|
||||
HasIFUPAll=self._osutils.has_ifup_all()
|
||||
)
|
||||
text_file.write(text)
|
||||
|
||||
|
444
octavia/amphorae/backends/agent/api_server/osutils.py
Normal file
444
octavia/amphorae/backends/agent/api_server/osutils.py
Normal file
@ -0,0 +1,444 @@
|
||||
# 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.
|
||||
|
||||
import logging
|
||||
import os
|
||||
import platform
|
||||
import shutil
|
||||
import stat
|
||||
import subprocess
|
||||
|
||||
import flask
|
||||
import ipaddress
|
||||
import jinja2
|
||||
from oslo_config import cfg
|
||||
import six
|
||||
from werkzeug import exceptions
|
||||
|
||||
from octavia.common import constants as consts
|
||||
from octavia.common import exceptions as octavia_exceptions
|
||||
from octavia.common import utils
|
||||
from octavia.i18n import _LE
|
||||
|
||||
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):
|
||||
|
||||
def __init__(self, os_name):
|
||||
self.os_name = os_name
|
||||
|
||||
@classmethod
|
||||
def get_os_util(cls):
|
||||
os_name = platform.linux_distribution(full_distribution_name=False)[0]
|
||||
for subclass in BaseOS.__subclasses__():
|
||||
if subclass.is_os_name(os_name):
|
||||
return subclass(os_name)
|
||||
raise octavia_exceptions.InvalidAmphoraOperatingSystem(os_name=os_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 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.
|
||||
os.makedirs('/etc/netns/' + consts.AMPHORA_NAMESPACE)
|
||||
shutil.copytree(
|
||||
network_dir,
|
||||
'/etc/netns/{netns}/{net_dir}'.format(
|
||||
netns=consts.AMPHORA_NAMESPACE,
|
||||
net_dir=netns_network_dir),
|
||||
symlinks=True,
|
||||
ignore=ignore)
|
||||
|
||||
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(
|
||||
interface=primary_interface,
|
||||
vip=vip,
|
||||
vip_ipv6=ip.version is 6,
|
||||
prefix=utils.netmask_to_prefix(netmask),
|
||||
broadcast=broadcast,
|
||||
netmask=netmask,
|
||||
gateway=gateway,
|
||||
mtu=mtu,
|
||||
vrrp_ip=vrrp_ip,
|
||||
vrrp_ipv6=vrrp_version is 6,
|
||||
host_routes=render_host_routes,
|
||||
)
|
||||
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 _generate_network_file_text(self, 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):
|
||||
if index == -1:
|
||||
netns_ip_interface = netns_interface
|
||||
else:
|
||||
netns_ip_interface = "{int}:{ip}".format(
|
||||
int=netns_interface, ip=index)
|
||||
try:
|
||||
ip_addr = fixed_ip['ip_address']
|
||||
cidr = fixed_ip['subnet_cidr']
|
||||
ip = ipaddress.ip_address(
|
||||
ip_addr if six.text_type == type(
|
||||
ip_addr) else six.u(ip_addr))
|
||||
network = ipaddress.ip_network(
|
||||
cidr if six.text_type == type(
|
||||
cidr) else six.u(cidr))
|
||||
broadcast = network.broadcast_address.exploded
|
||||
netmask = (network.prefixlen if ip.version is 6
|
||||
else network.netmask.exploded)
|
||||
host_routes = self.get_host_routes(fixed_ip)
|
||||
|
||||
except ValueError:
|
||||
return flask.make_response(flask.jsonify(dict(
|
||||
message="Invalid network IP")), 400)
|
||||
new_text = template_port.render(interface=netns_ip_interface,
|
||||
ipv6=ip.version is 6,
|
||||
ip_address=ip.exploded,
|
||||
broadcast=broadcast,
|
||||
netmask=netmask,
|
||||
mtu=mtu,
|
||||
host_routes=host_routes)
|
||||
text = '\n'.join([text, new_text])
|
||||
return text
|
||||
|
||||
def get_host_routes(self, fixed_ip):
|
||||
host_routes = []
|
||||
for hr in fixed_ip.get('host_routes', []):
|
||||
network = ipaddress.ip_network(
|
||||
hr['destination'] if isinstance(
|
||||
hr['destination'], six.text_type) else
|
||||
six.u(hr['destination']))
|
||||
host_routes.append({'network': network, 'gw': hr['nexthop']})
|
||||
return host_routes
|
||||
|
||||
def _bring_if_up(self, interface, what):
|
||||
# Note, we are not using pyroute2 for this as it is not /etc/netns
|
||||
# aware.
|
||||
cmd = ("ip netns exec {ns} ifup {params}".format(
|
||||
ns=consts.AMPHORA_NAMESPACE, params=interface))
|
||||
try:
|
||||
subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
|
||||
except subprocess.CalledProcessError as e:
|
||||
LOG.error(_LE('Failed to if up {0} due to '
|
||||
'error: {1}').format(interface, str(e)))
|
||||
raise exceptions.HTTPException(
|
||||
response=flask.make_response(flask.jsonify(dict(
|
||||
message='Error plugging {0}'.format(what),
|
||||
details=e.output)), 500))
|
||||
|
||||
def _bring_if_down(self, interface):
|
||||
# Note, we are not using pyroute2 for this as it is not /etc/netns
|
||||
# aware.
|
||||
cmd = ("ip netns exec {ns} ifdown {params}".format(
|
||||
ns=consts.AMPHORA_NAMESPACE, params=interface))
|
||||
try:
|
||||
subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
|
||||
except subprocess.CalledProcessError:
|
||||
pass
|
||||
|
||||
def bring_interfaces_up(self, ip, primary_interface, secondary_interface):
|
||||
self._bring_if_down(primary_interface)
|
||||
self._bring_if_down(secondary_interface)
|
||||
self._bring_if_up(primary_interface, 'VIP')
|
||||
self._bring_if_up(secondary_interface, 'VIP')
|
||||
|
||||
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']
|
||||
|
||||
def cmd_get_version_of_installed_package(self, package_name):
|
||||
return "dpkg --status {name}".format(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 + '.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(Ubuntu, self).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(Ubuntu, self).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(Ubuntu, self).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'
|
||||
|
||||
@classmethod
|
||||
def is_os_name(cls, os_name):
|
||||
return os_name in ['fedora', 'redhat', 'centos']
|
||||
|
||||
def cmd_get_version_of_installed_package(self, package_name):
|
||||
return "rpm -qi {name}".format(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,
|
||||
'ifcfg-' + interface)
|
||||
network_dir = consts.RH_AMP_NET_DIR_TEMPLATE.format(
|
||||
netns=consts.AMPHORA_NAMESPACE)
|
||||
return os.path.join(network_dir, '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):
|
||||
return self.get_network_interface_file('route-' + 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*', 'ifcfg-lo*')
|
||||
super(RH, self).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(RH, self).write_vip_interface_file(
|
||||
interface_file_path, primary_interface, vip, ip, broadcast,
|
||||
netmask, gateway, mtu, vrrp_ip, vrrp_version, render_host_routes,
|
||||
template_vip)
|
||||
|
||||
if ip.version == 4:
|
||||
# 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(RH, self).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)
|
||||
|
||||
if render_host_routes:
|
||||
routes_interface_file_path = (
|
||||
self.get_static_routes_interface_file(primary_interface))
|
||||
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)
|
||||
|
||||
def write_static_routes_interface_file(self, interface_file_path,
|
||||
interface, host_routes,
|
||||
template_routes):
|
||||
# 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(
|
||||
interface=interface,
|
||||
host_routes=host_routes,
|
||||
)
|
||||
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(RH, self).write_port_interface_file(
|
||||
netns_interface, fixed_ips, mtu, interface_file_path,
|
||||
template_port)
|
||||
|
||||
if fixed_ips:
|
||||
host_routes = []
|
||||
for fixed_ip in fixed_ips:
|
||||
host_routes.extend(self.get_host_routes(fixed_ip))
|
||||
|
||||
routes_interface_file_path = (
|
||||
self.get_static_routes_interface_file(netns_interface))
|
||||
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)
|
||||
|
||||
def bring_interfaces_up(self, ip, primary_interface, secondary_interface):
|
||||
if ip.version == 4:
|
||||
super(RH, self).bring_interfaces_up(
|
||||
ip, primary_interface, secondary_interface)
|
||||
else:
|
||||
# Secondary interface is not present in IPv6 configuration
|
||||
self._bring_if_down(primary_interface)
|
||||
self._bring_if_up(primary_interface, 'VIP')
|
||||
|
||||
def has_ifup_all(self):
|
||||
return False
|
@ -15,7 +15,6 @@
|
||||
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import socket
|
||||
import stat
|
||||
import subprocess
|
||||
@ -29,9 +28,8 @@ import pyroute2
|
||||
import six
|
||||
from werkzeug import exceptions
|
||||
|
||||
from octavia.amphorae.backends.agent.api_server import util
|
||||
from octavia.common import constants as consts
|
||||
from octavia.i18n import _LE, _LI
|
||||
from octavia.i18n import _LI
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
@ -48,6 +46,8 @@ template_vip = j2_env.get_template(ETH_X_VIP_CONF)
|
||||
|
||||
|
||||
class Plug(object):
|
||||
def __init__(self, osutils):
|
||||
self._osutils = osutils
|
||||
|
||||
def plug_vip(self, vip, subnet_cidr, gateway,
|
||||
mac_address, mtu=None, vrrp_ip=None, host_routes=None):
|
||||
@ -97,57 +97,24 @@ class Plug(object):
|
||||
secondary_interface = "{interface}:0".format(
|
||||
interface=primary_interface)
|
||||
|
||||
# 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.
|
||||
interface_file_path = util.get_network_interface_file(
|
||||
interface_file_path = self._osutils.get_network_interface_file(
|
||||
primary_interface)
|
||||
os.makedirs('/etc/netns/' + consts.AMPHORA_NAMESPACE)
|
||||
shutil.copytree(
|
||||
'/etc/network',
|
||||
'/etc/netns/{}/network'.format(consts.AMPHORA_NAMESPACE),
|
||||
symlinks=True,
|
||||
ignore=shutil.ignore_patterns('eth0*', 'openssh*'))
|
||||
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))
|
||||
|
||||
# write interface file
|
||||
self._osutils.create_netns_dir()
|
||||
|
||||
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(
|
||||
interface=primary_interface,
|
||||
self._osutils.write_interfaces_file()
|
||||
self._osutils.write_vip_interface_file(
|
||||
interface_file_path=interface_file_path,
|
||||
primary_interface=primary_interface,
|
||||
vip=vip,
|
||||
vip_ipv6=ip.version is 6,
|
||||
ip=ip,
|
||||
broadcast=broadcast,
|
||||
netmask=netmask,
|
||||
gateway=gateway,
|
||||
mtu=mtu,
|
||||
vrrp_ip=vrrp_ip,
|
||||
vrrp_ipv6=vrrp_version is 6,
|
||||
host_routes=render_host_routes,
|
||||
)
|
||||
text_file.write(text)
|
||||
vrrp_version=vrrp_version,
|
||||
render_host_routes=render_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
|
||||
@ -172,60 +139,14 @@ class Plug(object):
|
||||
IFLA_IFNAME=primary_interface)
|
||||
|
||||
# bring interfaces up
|
||||
self._bring_if_down(primary_interface)
|
||||
self._bring_if_down(secondary_interface)
|
||||
self._bring_if_up(primary_interface, 'VIP')
|
||||
self._bring_if_up(secondary_interface, 'VIP')
|
||||
self._osutils.bring_interfaces_up(
|
||||
ip, primary_interface, secondary_interface)
|
||||
|
||||
return flask.make_response(flask.jsonify(dict(
|
||||
message="OK",
|
||||
details="VIP {vip} plugged on interface {interface}".format(
|
||||
vip=vip, interface=primary_interface))), 202)
|
||||
|
||||
def _generate_network_file_text(self, netns_interface, fixed_ips, mtu):
|
||||
text = ''
|
||||
if fixed_ips is None:
|
||||
text = template_port.render(interface=netns_interface)
|
||||
else:
|
||||
for index, fixed_ip in enumerate(fixed_ips, -1):
|
||||
if index == -1:
|
||||
netns_ip_interface = netns_interface
|
||||
else:
|
||||
netns_ip_interface = "{int}:{ip}".format(
|
||||
int=netns_interface, ip=index)
|
||||
try:
|
||||
ip_addr = fixed_ip['ip_address']
|
||||
cidr = fixed_ip['subnet_cidr']
|
||||
ip = ipaddress.ip_address(
|
||||
ip_addr if six.text_type == type(
|
||||
ip_addr) else six.u(ip_addr))
|
||||
network = ipaddress.ip_network(
|
||||
cidr if six.text_type == type(
|
||||
cidr) else six.u(cidr))
|
||||
broadcast = network.broadcast_address.exploded
|
||||
netmask = (network.prefixlen if ip.version is 6
|
||||
else network.netmask.exploded)
|
||||
host_routes = []
|
||||
for hr in fixed_ip.get('host_routes', []):
|
||||
network = ipaddress.ip_network(
|
||||
hr['destination'] if isinstance(
|
||||
hr['destination'], six.text_type) else
|
||||
six.u(hr['destination']))
|
||||
host_routes.append({'network': network,
|
||||
'gw': hr['nexthop']})
|
||||
except ValueError:
|
||||
return flask.make_response(flask.jsonify(dict(
|
||||
message="Invalid network IP")), 400)
|
||||
new_text = template_port.render(interface=netns_ip_interface,
|
||||
ipv6=ip.version is 6,
|
||||
ip_address=ip.exploded,
|
||||
broadcast=broadcast,
|
||||
netmask=netmask,
|
||||
mtu=mtu,
|
||||
host_routes=host_routes)
|
||||
text = '\n'.join([text, new_text])
|
||||
return text
|
||||
|
||||
def _check_ip_addresses(self, fixed_ips):
|
||||
if fixed_ips:
|
||||
for ip in fixed_ips:
|
||||
@ -267,27 +188,13 @@ class Plug(object):
|
||||
'namespace {2}').format(default_netns_interface,
|
||||
netns_interface,
|
||||
consts.AMPHORA_NAMESPACE))
|
||||
interface_file_path = util.get_network_interface_file(netns_interface)
|
||||
|
||||
# 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)
|
||||
text_file.write(text)
|
||||
interface_file_path = self._osutils.get_network_interface_file(
|
||||
netns_interface)
|
||||
self._osutils.write_port_interface_file(
|
||||
netns_interface=netns_interface,
|
||||
fixed_ips=fixed_ips,
|
||||
mtu=mtu,
|
||||
interface_file_path=interface_file_path)
|
||||
|
||||
# Update the list of interfaces to add to the namespace
|
||||
self._update_plugged_interfaces_file(netns_interface, mac_address)
|
||||
@ -299,8 +206,8 @@ class Plug(object):
|
||||
net_ns_fd=consts.AMPHORA_NAMESPACE,
|
||||
IFLA_IFNAME=netns_interface)
|
||||
|
||||
self._bring_if_down(netns_interface)
|
||||
self._bring_if_up(netns_interface, 'network')
|
||||
self._osutils._bring_if_down(netns_interface)
|
||||
self._osutils._bring_if_up(netns_interface, 'network')
|
||||
|
||||
return flask.make_response(flask.jsonify(dict(
|
||||
message="OK",
|
||||
@ -318,31 +225,6 @@ class Plug(object):
|
||||
response=flask.make_response(flask.jsonify(dict(
|
||||
details="No suitable network interface found")), 404))
|
||||
|
||||
def _bring_if_up(self, interface, what):
|
||||
# Note, we are not using pyroute2 for this as it is not /etc/netns
|
||||
# aware.
|
||||
cmd = ("ip netns exec {ns} ifup {params}".format(
|
||||
ns=consts.AMPHORA_NAMESPACE, params=interface))
|
||||
try:
|
||||
subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
|
||||
except subprocess.CalledProcessError as e:
|
||||
LOG.error(_LE('Failed to if up {0} due to '
|
||||
'error: {1}').format(interface, str(e)))
|
||||
raise exceptions.HTTPException(
|
||||
response=flask.make_response(flask.jsonify(dict(
|
||||
message='Error plugging {0}'.format(what),
|
||||
details=e.output)), 500))
|
||||
|
||||
def _bring_if_down(self, interface):
|
||||
# Note, we are not using pyroute2 for this as it is not /etc/netns
|
||||
# aware.
|
||||
cmd = ("ip netns exec {ns} ifdown {params}".format(
|
||||
ns=consts.AMPHORA_NAMESPACE, params=interface))
|
||||
try:
|
||||
subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
|
||||
except subprocess.CalledProcessError:
|
||||
pass
|
||||
|
||||
def _update_plugged_interfaces_file(self, interface, mac_address):
|
||||
# write interfaces to plugged_interfaces file and prevent duplicates
|
||||
plug_inf_file = consts.PLUGGED_INTERFACES
|
||||
|
@ -22,6 +22,7 @@ from octavia.amphorae.backends.agent.api_server import amphora_info
|
||||
from octavia.amphorae.backends.agent.api_server import certificate_update
|
||||
from octavia.amphorae.backends.agent.api_server import keepalived
|
||||
from octavia.amphorae.backends.agent.api_server import listener
|
||||
from octavia.amphorae.backends.agent.api_server import osutils
|
||||
from octavia.amphorae.backends.agent.api_server import plug
|
||||
|
||||
PATH_PREFIX = '/' + api_server.VERSION
|
||||
@ -43,9 +44,11 @@ def register_app_error_handler(app):
|
||||
class Server(object):
|
||||
def __init__(self):
|
||||
self.app = flask.Flask(__name__)
|
||||
self._osutils = osutils.BaseOS.get_os_util()
|
||||
self._keepalived = keepalived.Keepalived()
|
||||
self._listener = listener.Listener()
|
||||
self._plug = plug.Plug()
|
||||
self._plug = plug.Plug(self._osutils)
|
||||
self._amphora_info = amphora_info.AmphoraInfo(self._osutils)
|
||||
|
||||
register_app_error_handler(self.app)
|
||||
|
||||
@ -119,10 +122,10 @@ class Server(object):
|
||||
return self._listener.delete_listener(listener_id)
|
||||
|
||||
def get_details(self):
|
||||
return amphora_info.compile_amphora_details()
|
||||
return self._amphora_info.compile_amphora_details()
|
||||
|
||||
def get_info(self):
|
||||
return amphora_info.compile_amphora_info()
|
||||
return self._amphora_info.compile_amphora_info()
|
||||
|
||||
def get_all_listeners_status(self):
|
||||
return self._listener.get_all_listeners_status()
|
||||
@ -179,4 +182,4 @@ class Server(object):
|
||||
return self._keepalived.manager_keepalived_service(action)
|
||||
|
||||
def get_interface(self, ip_addr):
|
||||
return amphora_info.get_interface(ip_addr)
|
||||
return self._amphora_info.get_interface(ip_addr)
|
||||
|
@ -16,7 +16,6 @@
|
||||
#}
|
||||
# Generated by Octavia agent
|
||||
auto {{ interface }} {{ interface }}:0
|
||||
|
||||
{%- if vrrp_ip %}
|
||||
iface {{ interface }} inet{{ '6' if vrrp_ipv6 }} static
|
||||
address {{ vrrp_ip }}
|
||||
@ -31,6 +30,7 @@ up route add -net {{ hr.network }} gw {{ hr.gw }} dev {{ interface }}
|
||||
down route del -net {{ hr.network }} gw {{ hr.gw }} dev {{ interface }}
|
||||
{%- endfor %}
|
||||
{%- else %}
|
||||
|
||||
iface {{ interface }} inet{{ '6' if vip_ipv6 }} {{ 'auto' if vip_ipv6 else 'dhcp' }}
|
||||
{%- endif %}
|
||||
|
||||
|
@ -0,0 +1,47 @@
|
||||
{#
|
||||
# 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 %}
|
||||
|
@ -0,0 +1,54 @@
|
||||
{#
|
||||
# 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 }}"
|
||||
IPV6_DEFAULTGW="{{ gateway }}"
|
||||
{%- if mtu %}
|
||||
IPV6_MTU="{{ mtu }}"
|
||||
{%- endif %}
|
||||
{%- else %} {# not vrrp_ipv6 #}
|
||||
BOOTPROTO="static"
|
||||
IPADDR="{{ vrrp_ip }}"
|
||||
NETMASK="{{ netmask }}"
|
||||
GATEWAY="{{ gateway }}"
|
||||
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 vip_ipv6 %}
|
||||
IPV6ADDR_SECONDARIES="{{ vip }}/{{ prefix }}"
|
||||
{%- endif %}
|
||||
|
@ -0,0 +1,29 @@
|
||||
{#
|
||||
# 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 }}"
|
||||
|
@ -0,0 +1,20 @@
|
||||
{#
|
||||
# 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 %}
|
||||
|
@ -15,9 +15,13 @@ ExecStartPre=-/sbin/ip netns exec {{ amphora_nsname }} sysctl --system
|
||||
# We need the plugged_interfaces file sorted to join the host interfaces
|
||||
ExecStartPre=-/bin/sh -c '/usr/bin/sort -k 1 /var/lib/octavia/plugged_interfaces > /var/lib/octavia/plugged_interfaces.sorted'
|
||||
# Assign the interfaces into the namespace with the appropriate name
|
||||
ExecStartPre=-/bin/sh -c '/sbin/ip link | awk \'{getline n; print $0,n}\' | awk \'{sub(":","",$2)} {print $17 " " $2}\' | sort -k 1 | join -j 1 - /var/lib/octavia/plugged_interfaces.sorted | awk \'{system("ip link set "$2" netns {{ amphora_nsname }} name "$3"")}\''
|
||||
ExecStartPre=-/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 %}
|
||||
ExecStartPre=-/sbin/ip netns exec {{ amphora_nsname }} ifup -a
|
||||
{%- else %}
|
||||
ExecStartPre=-/bin/awk '{system("/sbin/ip netns exec amphora-haproxy ifup " $2)}' /var/lib/octavia/plugged_interfaces
|
||||
{%- endif %}
|
||||
#
|
||||
ExecStart=/sbin/ip netns exec {{ amphora_nsname }} /usr/sbin/haproxy-systemd-wrapper -f {{ haproxy_cfg }} -p {{ haproxy_pid }} -L {{ peer_name }} $EXTRAOPTS
|
||||
ExecReload=/usr/sbin/haproxy -c -f {{ haproxy_cfg }}
|
||||
|
@ -57,9 +57,13 @@ haproxy_start()
|
||||
# We need the plugged_interfaces file sorted to join the host interfaces
|
||||
sort -k 1 /var/lib/octavia/plugged_interfaces > /var/lib/octavia/plugged_interfaces.sorted || true
|
||||
# Assign the interfaces into the namespace with the appropriate name
|
||||
ip link | awk '{getline n; print $0,n}' | awk '{sub(":","",$2)} {print $17 " " $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
|
||||
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-haproxy ifup " $2)}' /var/lib/octavia/plugged_interfaces || true
|
||||
{%- endif %}
|
||||
|
||||
start-stop-daemon --start --pidfile "$PIDFILE" \
|
||||
--exec $HAPROXY -- -f "$CONFIG" -D -p "$PIDFILE" \
|
||||
|
@ -39,9 +39,14 @@ pre-start script
|
||||
# interfaces
|
||||
sort -k 1 /var/lib/octavia/plugged_interfaces > /var/lib/octavia/plugged_interfaces.sorted || true
|
||||
# Assign the interfaces into the namespace with the appropriate name
|
||||
ip link | awk '{getline n; print $0,n}' | awk '{sub(":","",$2)} {print $17 " " $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
|
||||
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-haproxy ifup " $2)}' /var/lib/octavia/plugged_interfaces || true
|
||||
{%- endif %}
|
||||
|
||||
end script
|
||||
|
||||
script
|
||||
|
@ -125,13 +125,6 @@ def is_listener_running(listener_id):
|
||||
os.path.join('/proc', get_haproxy_pid(listener_id)))
|
||||
|
||||
|
||||
def get_network_interface_file(interface):
|
||||
if CONF.amphora_agent.agent_server_network_file:
|
||||
return CONF.amphora_agent.agent_server_network_file
|
||||
return os.path.join(CONF.amphora_agent.agent_server_network_dir,
|
||||
interface + '.cfg')
|
||||
|
||||
|
||||
def get_os_init_system():
|
||||
if os.path.exists(consts.INIT_PROC_COMM_PATH):
|
||||
with open(consts.INIT_PROC_COMM_PATH, 'r') as init_comm:
|
||||
|
@ -34,7 +34,9 @@ heartbeat_key = {{ heartbeat_key }}
|
||||
[amphora_agent]
|
||||
agent_server_ca = {{ agent_server_ca }}
|
||||
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 -%}
|
||||
|
@ -69,8 +69,6 @@ amphora_agent_opts = [
|
||||
help=_("The server certificate for the agent.py server "
|
||||
"to use")),
|
||||
cfg.StrOpt('agent_server_network_dir',
|
||||
default='/etc/netns/{}/network/interfaces.d/'.format(
|
||||
constants.AMPHORA_NAMESPACE),
|
||||
help=_("The directory where new network interfaces "
|
||||
"are located")),
|
||||
cfg.StrOpt('agent_server_network_file',
|
||||
|
@ -366,3 +366,8 @@ KEEPALIVED_UPSTART = 'octavia-keepalived.conf'
|
||||
# Authentication
|
||||
KEYSTONE = 'keystone'
|
||||
NOAUTH = 'noauth'
|
||||
|
||||
UBUNTU_AMP_NET_DIR_TEMPLATE = '/etc/netns/{netns}/network/interfaces.d/'
|
||||
RH_AMP_NET_DIR_TEMPLATE = '/etc/netns/{netns}/sysconfig/network-scripts/'
|
||||
UBUNTU = 'ubuntu'
|
||||
CENTOS = 'centos'
|
||||
|
@ -237,6 +237,10 @@ class ServerGroupObjectDeleteException(OctaviaException):
|
||||
message = _('Failed to delete server group object.')
|
||||
|
||||
|
||||
class InvalidAmphoraOperatingSystem(OctaviaException):
|
||||
message = _('Invalid amphora operating system: %(os_name)s')
|
||||
|
||||
|
||||
class QuotaException(APIException):
|
||||
msg = _('Quota has been met.')
|
||||
code = 403
|
||||
|
@ -74,6 +74,10 @@ def ip_port_str(ip_address, port):
|
||||
return "[{ip}]:{port}".format(ip=ip, port=port)
|
||||
|
||||
|
||||
def netmask_to_prefix(netmask):
|
||||
return netaddr.IPAddress(netmask).netmask_bits()
|
||||
|
||||
|
||||
class exception_logger(object):
|
||||
"""Wrap a function and log raised exception
|
||||
|
||||
|
@ -28,6 +28,9 @@ class OpenFixture(fixtures.Fixture):
|
||||
|
||||
def _setUp(self):
|
||||
self.mock_open = mock.mock_open(read_data=self.contents)
|
||||
# work around for https://bugs.python.org/issue21258
|
||||
self.mock_open.return_value.__iter__ = (
|
||||
lambda self: iter(self.readline, ''))
|
||||
self._orig_open = open
|
||||
|
||||
def replacement_open(name, *args, **kwargs):
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,206 @@
|
||||
# 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.
|
||||
|
||||
import random
|
||||
|
||||
import mock
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from octavia.amphorae.backends.agent import api_server
|
||||
from octavia.amphorae.backends.agent.api_server import amphora_info
|
||||
from octavia.tests.common import utils as test_utils
|
||||
import octavia.tests.unit.base as base
|
||||
|
||||
|
||||
class TestAmphoraInfo(base.TestCase):
|
||||
|
||||
API_VERSION = random.randrange(0, 10000)
|
||||
HAPROXY_VERSION = random.randrange(0, 10000)
|
||||
|
||||
def setUp(self):
|
||||
super(TestAmphoraInfo, self).setUp()
|
||||
self.osutils_mock = mock.MagicMock()
|
||||
self.amp_info = amphora_info.AmphoraInfo(self.osutils_mock)
|
||||
|
||||
@mock.patch.object(amphora_info, "flask")
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.'
|
||||
'amphora_info.AmphoraInfo._get_version_of_installed_package',
|
||||
return_value=HAPROXY_VERSION)
|
||||
@mock.patch('socket.gethostname', return_value='FAKE_HOST')
|
||||
def test_compile_amphora_info(self, mock_gethostname, mock_pkg_version,
|
||||
mock_flask):
|
||||
original_version = api_server.VERSION
|
||||
api_server.VERSION = self.API_VERSION
|
||||
expected_dict = {'api_version': self.API_VERSION,
|
||||
'hostname': 'FAKE_HOST',
|
||||
'haproxy_version': self.HAPROXY_VERSION}
|
||||
self.amp_info.compile_amphora_info()
|
||||
mock_flask.jsonify.assert_called_once_with(expected_dict)
|
||||
api_server.VERSION = original_version
|
||||
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
|
||||
'is_listener_running')
|
||||
def test__count_haproxy_process(self, mock_is_running):
|
||||
|
||||
# Test no listeners passed in
|
||||
result = self.amp_info._count_haproxy_processes([])
|
||||
self.assertEqual(0, result)
|
||||
|
||||
# Test with a listener specified
|
||||
mock_is_running.side_effect = [True, False]
|
||||
result = self.amp_info._count_haproxy_processes(
|
||||
[uuidutils.generate_uuid(), uuidutils.generate_uuid()])
|
||||
self.assertEqual(1, result)
|
||||
|
||||
def test__get_meminfo(self):
|
||||
|
||||
# Test live data from host
|
||||
|
||||
result = self.amp_info._get_meminfo()
|
||||
|
||||
# check that /proc/meminfo is giving us the fields we use
|
||||
self.assertIn('MemTotal', result)
|
||||
self.assertIn('MemFree', result)
|
||||
self.assertIn('Buffers', result)
|
||||
self.assertIn('Cached', result)
|
||||
self.assertIn('SwapCached', result)
|
||||
self.assertIn('Shmem', result)
|
||||
self.assertIn('Slab', result)
|
||||
|
||||
# Known data test
|
||||
meminfo = ('MemTotal: 21692784 kB\n'
|
||||
'MemFree: 12685624 kB\n'
|
||||
'MemAvailable: 17384072 kB\n'
|
||||
'Buffers: 344792 kB\n'
|
||||
'Cached: 4271856 kB\n'
|
||||
'SwapCached: 0 kB\n'
|
||||
'Active: 5808816 kB\n'
|
||||
'Inactive: 2445236 kB\n'
|
||||
'Active(anon): 3646184 kB\n'
|
||||
'Inactive(anon): 8672 kB\n'
|
||||
'Active(file): 2162632 kB\n'
|
||||
'Inactive(file): 2436564 kB\n'
|
||||
'Unevictable: 52664 kB\n'
|
||||
'Mlocked: 52664 kB\n'
|
||||
'SwapTotal: 20476924 kB\n'
|
||||
'SwapFree: 20476924 kB\n'
|
||||
'Dirty: 92 kB\n'
|
||||
'Writeback: 0 kB\n'
|
||||
'AnonPages: 3690088 kB\n'
|
||||
'Mapped: 108520 kB\n'
|
||||
'Shmem: 9520 kB\n'
|
||||
'Slab: 534384 kB\n'
|
||||
'SReclaimable: 458160 kB\n'
|
||||
'SUnreclaim: 76224 kB\n'
|
||||
'KernelStack: 11776 kB\n'
|
||||
'PageTables: 33088 kB\n'
|
||||
'NFS_Unstable: 0 kB\n'
|
||||
'Bounce: 0 kB\n'
|
||||
'WritebackTmp: 0 kB\n'
|
||||
'CommitLimit: 31323316 kB\n'
|
||||
'Committed_AS: 6930732 kB\n'
|
||||
'VmallocTotal: 34359738367 kB\n'
|
||||
'VmallocUsed: 0 kB\n'
|
||||
'VmallocChunk: 0 kB\n'
|
||||
'HardwareCorrupted: 0 kB\n'
|
||||
'AnonHugePages: 1400832 kB\n'
|
||||
'CmaTotal: 0 kB\n'
|
||||
'CmaFree: 0 kB\n'
|
||||
'HugePages_Total: 0\n'
|
||||
'HugePages_Free: 0\n'
|
||||
'HugePages_Rsvd: 0\n'
|
||||
'HugePages_Surp: 0\n'
|
||||
'Hugepagesize: 2048 kB\n'
|
||||
'DirectMap4k: 130880 kB\n'
|
||||
'DirectMap2M: 8376320 kB\n'
|
||||
'DirectMap1G: 14680064 kB\n')
|
||||
|
||||
self.useFixture(test_utils.OpenFixture('/proc/meminfo',
|
||||
contents=meminfo))
|
||||
|
||||
expected_result = {'SwapCached': 0, 'DirectMap2M': 8376320,
|
||||
'CmaTotal': 0, 'Inactive': 2445236,
|
||||
'KernelStack': 11776, 'SwapTotal': 20476924,
|
||||
'VmallocUsed': 0, 'Buffers': 344792,
|
||||
'MemTotal': 21692784, 'Mlocked': 52664,
|
||||
'Cached': 4271856, 'AnonPages': 3690088,
|
||||
'Unevictable': 52664, 'SUnreclaim': 76224,
|
||||
'MemFree': 12685624, 'Writeback': 0,
|
||||
'NFS_Unstable': 0, 'VmallocTotal': 34359738367,
|
||||
'MemAvailable': 17384072, 'CmaFree': 0,
|
||||
'SwapFree': 20476924, 'AnonHugePages': 1400832,
|
||||
'DirectMap1G': 14680064, 'Hugepagesize': 2048,
|
||||
'Dirty': 92, 'Bounce': 0, 'PageTables': 33088,
|
||||
'SReclaimable': 458160, 'Active': 5808816,
|
||||
'Mapped': 108520, 'Slab': 534384,
|
||||
'Active(anon)': 3646184, 'VmallocChunk': 0,
|
||||
'Inactive(file)': 2436564, 'WritebackTmp': 0,
|
||||
'Shmem': 9520, 'Inactive(anon)': 8672,
|
||||
'HardwareCorrupted': 0, 'Active(file)': 2162632,
|
||||
'DirectMap4k': 130880, 'Committed_AS': 6930732,
|
||||
'CommitLimit': 31323316}
|
||||
|
||||
result = self.amp_info._get_meminfo()
|
||||
self.assertEqual(expected_result, result)
|
||||
|
||||
def test__cpu(self):
|
||||
|
||||
sample_stat = 'cpu 252551 802 52554 7181757 7411 0 8336 0 0 0'
|
||||
|
||||
expected_result = {'user': '252551', 'iowait': '7411', 'nice': '802',
|
||||
'softirq': '8336', 'idle': '7181757',
|
||||
'system': '52554', 'total': 7503411, 'irq': '0'}
|
||||
|
||||
self.useFixture(test_utils.OpenFixture('/proc/stat',
|
||||
contents=sample_stat))
|
||||
|
||||
result = self.amp_info._cpu()
|
||||
|
||||
self.assertEqual(expected_result, result)
|
||||
|
||||
def test__load(self):
|
||||
|
||||
sample_loadavg = '0.09 0.11 0.10 2/630 15346'
|
||||
|
||||
expected_result = ['0.09', '0.11', '0.10']
|
||||
|
||||
self.useFixture(test_utils.OpenFixture('/proc/loadavg',
|
||||
contents=sample_loadavg))
|
||||
|
||||
result = self.amp_info._load()
|
||||
|
||||
self.assertEqual(expected_result, result)
|
||||
|
||||
@mock.patch('pyroute2.NetNS')
|
||||
def test__get_networks(self, mock_netns):
|
||||
|
||||
# The output of get_links is huge, just pulling out the parts we
|
||||
# care about for this test.
|
||||
sample_get_links_minimal = [
|
||||
{'attrs': [('IFLA_IFNAME', 'lo')]},
|
||||
{'attrs': [('IFLA_IFNAME', 'eth1'),
|
||||
('IFLA_STATS64', {'tx_bytes': 418, 'rx_bytes': 996})]},
|
||||
{'attrs': [('IFLA_IFNAME', 'eth2'),
|
||||
('IFLA_STATS64', {'tx_bytes': 578, 'rx_bytes': 848})]},
|
||||
{'attrs': [('IFLA_IFNAME', 'eth3')]}]
|
||||
|
||||
netns_handle = mock_netns.return_value.__enter__.return_value
|
||||
netns_handle.get_links.return_value = sample_get_links_minimal
|
||||
|
||||
expected_result = {'eth1': {'network_rx': 996, 'network_tx': 418},
|
||||
'eth2': {'network_rx': 848, 'network_tx': 578}}
|
||||
|
||||
result = self.amp_info._get_networks()
|
||||
|
||||
self.assertEqual(expected_result, result)
|
@ -0,0 +1,217 @@
|
||||
# Copyright 2017 Redhat.
|
||||
#
|
||||
# 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 os
|
||||
import shutil
|
||||
|
||||
import mock
|
||||
from oslo_config import fixture as oslo_fixture
|
||||
|
||||
from octavia.amphorae.backends.agent.api_server import osutils
|
||||
from octavia.common import config
|
||||
from octavia.common import constants as consts
|
||||
from octavia.common import exceptions as octavia_exceptions
|
||||
from octavia.tests.unit import base
|
||||
|
||||
|
||||
class TestOSUtils(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestOSUtils, self).setUp()
|
||||
|
||||
self.base_os_util = osutils.BaseOS('unknown')
|
||||
|
||||
with mock.patch('platform.linux_distribution',
|
||||
return_value=['Ubuntu', 'Foo', 'Bar']):
|
||||
self.ubuntu_os_util = osutils.BaseOS.get_os_util()
|
||||
|
||||
with mock.patch('platform.linux_distribution',
|
||||
return_value=['centos', 'Foo', 'Bar']):
|
||||
self.rh_os_util = osutils.BaseOS.get_os_util()
|
||||
|
||||
def test_get_os_util(self):
|
||||
with mock.patch('platform.linux_distribution',
|
||||
return_value=['Ubuntu', 'Foo', 'Bar']):
|
||||
returned_cls = osutils.BaseOS.get_os_util()
|
||||
self.assertIsInstance(returned_cls, osutils.Ubuntu)
|
||||
with mock.patch('platform.linux_distribution',
|
||||
return_value=['fedora', 'Foo', 'Bar']):
|
||||
returned_cls = osutils.BaseOS.get_os_util()
|
||||
self.assertIsInstance(returned_cls, osutils.RH)
|
||||
with mock.patch('platform.linux_distribution',
|
||||
return_value=['redhat', 'Foo', 'Bar']):
|
||||
returned_cls = osutils.BaseOS.get_os_util()
|
||||
self.assertIsInstance(returned_cls, osutils.RH)
|
||||
with mock.patch('platform.linux_distribution',
|
||||
return_value=['centos', 'Foo', 'Bar']):
|
||||
returned_cls = osutils.BaseOS.get_os_util()
|
||||
self.assertIsInstance(returned_cls, osutils.RH)
|
||||
with mock.patch('platform.linux_distribution',
|
||||
return_value=['FakeOS', 'Foo', 'Bar']):
|
||||
self.assertRaises(
|
||||
octavia_exceptions.InvalidAmphoraOperatingSystem,
|
||||
osutils.BaseOS.get_os_util)
|
||||
|
||||
def test_get_network_interface_file(self):
|
||||
conf = self.useFixture(oslo_fixture.Config(config.cfg.CONF))
|
||||
|
||||
fake_agent_server_network_dir = "/path/to/interface"
|
||||
fake_agent_server_network_file = "/path/to/interfaces_file"
|
||||
|
||||
base_fake_nic_path = os.path.join(fake_agent_server_network_dir,
|
||||
consts.NETNS_PRIMARY_INTERFACE)
|
||||
base_real_nic_path = os.path.join(
|
||||
consts.UBUNTU_AMP_NET_DIR_TEMPLATE.format(
|
||||
netns=consts.AMPHORA_NAMESPACE),
|
||||
consts.NETNS_PRIMARY_INTERFACE)
|
||||
|
||||
rh_interface_name = 'ifcfg-{nic}'.format(
|
||||
nic=consts.NETNS_PRIMARY_INTERFACE)
|
||||
rh_fake_nic_path = os.path.join(fake_agent_server_network_dir,
|
||||
rh_interface_name)
|
||||
rh_real_nic_path = os.path.join(
|
||||
consts.RH_AMP_NET_DIR_TEMPLATE.format(
|
||||
netns=consts.AMPHORA_NAMESPACE),
|
||||
rh_interface_name)
|
||||
|
||||
ubuntu_interface_name = '{nic}.cfg'.format(
|
||||
nic=consts.NETNS_PRIMARY_INTERFACE)
|
||||
ubuntu_fake_nic_path = os.path.join(fake_agent_server_network_dir,
|
||||
ubuntu_interface_name)
|
||||
ubuntu_real_nic_path = os.path.join(
|
||||
consts.UBUNTU_AMP_NET_DIR_TEMPLATE.format(
|
||||
netns=consts.AMPHORA_NAMESPACE),
|
||||
ubuntu_interface_name)
|
||||
|
||||
# Check that agent_server_network_file is returned, when provided
|
||||
conf.config(group="amphora_agent",
|
||||
agent_server_network_file=fake_agent_server_network_file)
|
||||
|
||||
base_interface_file = (
|
||||
self.base_os_util.
|
||||
get_network_interface_file(consts.NETNS_PRIMARY_INTERFACE))
|
||||
self.assertEqual(fake_agent_server_network_file, base_interface_file)
|
||||
|
||||
rh_interface_file = (
|
||||
self.rh_os_util.
|
||||
get_network_interface_file(consts.NETNS_PRIMARY_INTERFACE))
|
||||
self.assertEqual(fake_agent_server_network_file, rh_interface_file)
|
||||
|
||||
ubuntu_interface_file = (
|
||||
self.ubuntu_os_util.
|
||||
get_network_interface_file(consts.NETNS_PRIMARY_INTERFACE))
|
||||
self.assertEqual(fake_agent_server_network_file, ubuntu_interface_file)
|
||||
|
||||
# Check that agent_server_network_dir is used, when provided
|
||||
conf.config(group="amphora_agent", agent_server_network_file=None)
|
||||
conf.config(group="amphora_agent",
|
||||
agent_server_network_dir=fake_agent_server_network_dir)
|
||||
|
||||
base_interface_file = (
|
||||
self.base_os_util.
|
||||
get_network_interface_file(consts.NETNS_PRIMARY_INTERFACE))
|
||||
self.assertEqual(base_fake_nic_path, base_interface_file)
|
||||
|
||||
rh_interface_file = (
|
||||
self.rh_os_util.
|
||||
get_network_interface_file(consts.NETNS_PRIMARY_INTERFACE))
|
||||
self.assertEqual(rh_fake_nic_path, rh_interface_file)
|
||||
|
||||
ubuntu_interface_file = (
|
||||
self.ubuntu_os_util.
|
||||
get_network_interface_file(consts.NETNS_PRIMARY_INTERFACE))
|
||||
self.assertEqual(ubuntu_fake_nic_path, ubuntu_interface_file)
|
||||
|
||||
# Check When neither agent_server_network_dir or
|
||||
# agent_server_network_file where provided.
|
||||
conf.config(group="amphora_agent", agent_server_network_file=None)
|
||||
conf.config(group="amphora_agent", agent_server_network_dir=None)
|
||||
|
||||
base_interface_file = (
|
||||
self.base_os_util.
|
||||
get_network_interface_file(consts.NETNS_PRIMARY_INTERFACE))
|
||||
self.assertEqual(base_real_nic_path, base_interface_file)
|
||||
|
||||
rh_interface_file = (
|
||||
self.rh_os_util.
|
||||
get_network_interface_file(consts.NETNS_PRIMARY_INTERFACE))
|
||||
self.assertEqual(rh_real_nic_path, rh_interface_file)
|
||||
|
||||
ubuntu_interface_file = (
|
||||
self.ubuntu_os_util.
|
||||
get_network_interface_file(consts.NETNS_PRIMARY_INTERFACE))
|
||||
self.assertEqual(ubuntu_real_nic_path, ubuntu_interface_file)
|
||||
|
||||
def test_cmd_get_version_of_installed_package(self):
|
||||
package_name = 'foo'
|
||||
ubuntu_cmd = "dpkg --status {name}".format(name=package_name)
|
||||
rh_cmd = "rpm -qi {name}".format(name=package_name)
|
||||
|
||||
returned_ubuntu_cmd = (
|
||||
self.ubuntu_os_util.cmd_get_version_of_installed_package(
|
||||
package_name))
|
||||
self.assertEqual(ubuntu_cmd, returned_ubuntu_cmd)
|
||||
|
||||
returned_rh_cmd = (self.rh_os_util.
|
||||
cmd_get_version_of_installed_package(package_name))
|
||||
self.assertEqual(rh_cmd, returned_rh_cmd)
|
||||
|
||||
def test_has_ifup_all(self):
|
||||
self.assertTrue(self.base_os_util.has_ifup_all())
|
||||
self.assertTrue(self.ubuntu_os_util.has_ifup_all())
|
||||
self.assertFalse(self.rh_os_util.has_ifup_all())
|
||||
|
||||
@mock.patch('shutil.copy2')
|
||||
@mock.patch('os.makedirs')
|
||||
@mock.patch('shutil.copytree')
|
||||
def test_create_netns_dir(self, mock_copytree, mock_makedirs, mock_copy2):
|
||||
network_dir = 'foo'
|
||||
netns_network_dir = 'fake_netns_network'
|
||||
ignore = shutil.ignore_patterns('fake_eth*', 'fake_loopback*')
|
||||
self.rh_os_util.create_netns_dir(network_dir,
|
||||
netns_network_dir,
|
||||
ignore)
|
||||
mock_copytree.assert_any_call(
|
||||
network_dir,
|
||||
os.path.join('/etc/netns/',
|
||||
consts.AMPHORA_NAMESPACE,
|
||||
netns_network_dir),
|
||||
ignore=ignore,
|
||||
symlinks=True)
|
||||
|
||||
mock_makedirs.assert_any_call(os.path.join('/etc/netns/',
|
||||
consts.AMPHORA_NAMESPACE))
|
||||
mock_copy2.assert_any_call(
|
||||
'/etc/sysconfig/network',
|
||||
'/etc/netns/{netns}/sysconfig'.format(
|
||||
netns=consts.AMPHORA_NAMESPACE))
|
||||
|
||||
mock_copytree.reset_mock()
|
||||
mock_makedirs.reset_mock()
|
||||
mock_copy2.reset_mock()
|
||||
|
||||
self.ubuntu_os_util.create_netns_dir(network_dir,
|
||||
netns_network_dir,
|
||||
ignore)
|
||||
mock_copytree.assert_any_call(
|
||||
network_dir,
|
||||
os.path.join('/etc/netns/',
|
||||
consts.AMPHORA_NAMESPACE,
|
||||
netns_network_dir),
|
||||
ignore=ignore,
|
||||
symlinks=True)
|
||||
|
||||
mock_makedirs.assert_any_call(os.path.join('/etc/netns/',
|
||||
consts.AMPHORA_NAMESPACE))
|
||||
mock_copy2.assert_not_called()
|
@ -18,6 +18,7 @@ import subprocess
|
||||
import mock
|
||||
import netifaces
|
||||
|
||||
from octavia.amphorae.backends.agent.api_server import osutils
|
||||
from octavia.amphorae.backends.agent.api_server import plug
|
||||
import octavia.tests.unit.base as base
|
||||
|
||||
@ -36,7 +37,8 @@ class TestPlug(base.TestCase):
|
||||
def setUp(self):
|
||||
super(TestPlug, self).setUp()
|
||||
self.mock_netifaces = mock.patch.object(plug, "netifaces").start()
|
||||
self.test_plug = plug.Plug()
|
||||
self.osutil = osutils.BaseOS.get_os_util()
|
||||
self.test_plug = plug.Plug(self.osutil)
|
||||
self.addCleanup(self.mock_netifaces.stop)
|
||||
|
||||
# Set up our fake interface
|
||||
@ -48,10 +50,19 @@ class TestPlug(base.TestCase):
|
||||
]
|
||||
}
|
||||
|
||||
def test__interface_by_mac_case_insensitive(self):
|
||||
def test__interface_by_mac_case_insensitive_ubuntu(self):
|
||||
interface = self.test_plug._interface_by_mac(FAKE_MAC_ADDRESS.upper())
|
||||
self.assertEqual(FAKE_INTERFACE, interface)
|
||||
|
||||
def test__interface_by_mac_case_insensitive_rh(self):
|
||||
with mock.patch('platform.linux_distribution',
|
||||
return_value=['centos', 'Foo']):
|
||||
osutil = osutils.BaseOS.get_os_util()
|
||||
self.test_plug = plug.Plug(osutil)
|
||||
interface = self.test_plug._interface_by_mac(
|
||||
FAKE_MAC_ADDRESS.upper())
|
||||
self.assertEqual(FAKE_INTERFACE, interface)
|
||||
|
||||
@mock.patch('pyroute2.NSPopen')
|
||||
@mock.patch.object(plug, "flask")
|
||||
@mock.patch('pyroute2.IPRoute')
|
||||
@ -146,9 +157,10 @@ class TestPlug(base.TestCase):
|
||||
class TestPlugNetwork(base.TestCase):
|
||||
def setUp(self):
|
||||
super(TestPlugNetwork, self).setUp()
|
||||
self.test_plug = plug.Plug()
|
||||
self.osutil = osutils.BaseOS.get_os_util()
|
||||
self.test_plug = plug.Plug(self.osutil)
|
||||
|
||||
def test__generate_network_file_text_static_ip(self):
|
||||
def test__generate_network_file_text_static_ip_ubuntu(self):
|
||||
netns_interface = 'eth1234'
|
||||
FIXED_IP = '192.0.2.2'
|
||||
BROADCAST = '192.0.2.255'
|
||||
@ -177,8 +189,9 @@ class TestPlugNetwork(base.TestCase):
|
||||
'up route add -net {dest2} gw {nexthop} dev {netns_interface}\n'
|
||||
'down route del -net {dest2} gw {nexthop} dev {netns_interface}\n')
|
||||
|
||||
text = self.test_plug._generate_network_file_text(netns_interface,
|
||||
fixed_ips, MTU)
|
||||
template_port = osutils.j2_env.get_template('plug_port_ethX.conf.j2')
|
||||
text = self.test_plug._osutils._generate_network_file_text(
|
||||
netns_interface, fixed_ips, MTU, template_port)
|
||||
expected_text = format_text.format(netns_interface=netns_interface,
|
||||
fixed_ip=FIXED_IP,
|
||||
broadcast=BROADCAST,
|
||||
|
@ -1,47 +0,0 @@
|
||||
# Copyright 2015 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.
|
||||
|
||||
import os
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_config import fixture as oslo_fixture
|
||||
|
||||
from octavia.amphorae.backends.agent.api_server import util
|
||||
from octavia.tests.unit import base
|
||||
|
||||
|
||||
class TestUtils(base.TestCase):
|
||||
def setUp(self):
|
||||
self.dir = '/etc/network/interfaces.d'
|
||||
self.file = '/etc/network/interfaces'
|
||||
|
||||
self.conf = self.useFixture(oslo_fixture.Config(cfg.CONF))
|
||||
self.conf.config(group="amphora_agent",
|
||||
agent_server_network_dir=self.dir)
|
||||
|
||||
super(TestUtils, self).setUp()
|
||||
|
||||
def test_get_network_interface_file(self):
|
||||
interface = 'eth0'
|
||||
|
||||
self.conf.config(group="amphora_agent",
|
||||
agent_server_network_file=None)
|
||||
path = util.get_network_interface_file(interface)
|
||||
expected_path = os.path.join(self.dir, interface + '.cfg')
|
||||
self.assertEqual(expected_path, path)
|
||||
|
||||
self.conf.config(group="amphora_agent",
|
||||
agent_server_network_file=self.file)
|
||||
path = util.get_network_interface_file(interface)
|
||||
self.assertEqual(self.file, path)
|
@ -42,3 +42,9 @@ class TestConfig(base.TestCase):
|
||||
utils.ip_port_str('127.0.0.1', 8080))
|
||||
self.assertEqual("[::1]:8080",
|
||||
utils.ip_port_str('::1', 8080))
|
||||
|
||||
def test_netmask_to_prefix(self):
|
||||
self.assertEqual(utils.netmask_to_prefix('255.0.0.0'), 8)
|
||||
self.assertEqual(utils.netmask_to_prefix('255.255.0.0'), 16)
|
||||
self.assertEqual(utils.netmask_to_prefix('255.255.255.0'), 24)
|
||||
self.assertEqual(utils.netmask_to_prefix('255.255.255.128'), 25)
|
||||
|
@ -0,0 +1,18 @@
|
||||
---
|
||||
prelude: >
|
||||
Amphora image support for RH Linux flavors.
|
||||
features:
|
||||
- The diskimage-create script supports different operating system flavors
|
||||
such as Ubuntu (the default option), CentOS, Fedora and RHEL. Adaptations
|
||||
were made to several elements to ensure all images are operational.
|
||||
- The amphora-agent is now able to distinguish between operating systems and
|
||||
choose the right course of action to manage files and networking on each
|
||||
Linux flavor.
|
||||
issues:
|
||||
- To use CentOS, Fedora, or RHEL in your amphora image you must set
|
||||
the user_group option, located in the [haproxy_amphora] section of the
|
||||
octavia.conf file to "haproxy". This will be made automatic in a
|
||||
future version.
|
||||
upgrade:
|
||||
- agent_server_network_dir is now auto-detected for Ubuntu, CentOS, Fedora
|
||||
and RHEL if one is not specified in the configuration file.
|
Loading…
Reference in New Issue
Block a user