Merge "Fix the amphora image support for RH Linux flavors"
This commit is contained in:
commit
e555d175c2
@ -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,177 +31,177 @@ from octavia.common import constants as consts
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def compile_amphora_info():
|
||||
return flask.jsonify(
|
||||
{'hostname': socket.gethostname(),
|
||||
'haproxy_version': _get_version_of_installed_package('haproxy'),
|
||||
'api_version': api_server.VERSION})
|
||||
class AmphoraInfo(object):
|
||||
def __init__(self, osutils):
|
||||
self._osutils = osutils
|
||||
|
||||
def compile_amphora_info(self):
|
||||
return flask.jsonify(
|
||||
{'hostname': socket.gethostname(),
|
||||
'haproxy_version':
|
||||
self._get_version_of_installed_package('haproxy'),
|
||||
'api_version': api_server.VERSION})
|
||||
|
||||
def compile_amphora_details():
|
||||
listener_list = util.get_listeners()
|
||||
meminfo = _get_meminfo()
|
||||
cpu = _cpu()
|
||||
st = os.statvfs('/')
|
||||
return flask.jsonify(
|
||||
{'hostname': socket.gethostname(),
|
||||
'haproxy_version': _get_version_of_installed_package('haproxy'),
|
||||
'api_version': api_server.VERSION,
|
||||
'networks': _get_networks(),
|
||||
'active': True,
|
||||
'haproxy_count': _count_haproxy_processes(listener_list),
|
||||
'cpu': {
|
||||
'total': cpu['total'],
|
||||
'user': cpu['user'],
|
||||
'system': cpu['system'],
|
||||
'soft_irq': cpu['softirq'], },
|
||||
'memory': {
|
||||
'total': meminfo['MemTotal'],
|
||||
'free': meminfo['MemFree'],
|
||||
'buffers': meminfo['Buffers'],
|
||||
'cached': meminfo['Cached'],
|
||||
'swap_used': meminfo['SwapCached'],
|
||||
'shared': meminfo['Shmem'],
|
||||
'slab': meminfo['Slab'], },
|
||||
'disk': {
|
||||
'used': (st.f_blocks - st.f_bfree) * st.f_frsize,
|
||||
'available': st.f_bavail * st.f_frsize},
|
||||
'load': [
|
||||
_load()],
|
||||
'topology': consts.TOPOLOGY_SINGLE,
|
||||
'topology_status': consts.TOPOLOGY_STATUS_OK,
|
||||
'listeners': listener_list,
|
||||
'packages': {}})
|
||||
def compile_amphora_details(self):
|
||||
listener_list = util.get_listeners()
|
||||
meminfo = self._get_meminfo()
|
||||
cpu = self._cpu()
|
||||
st = os.statvfs('/')
|
||||
return flask.jsonify(
|
||||
{'hostname': socket.gethostname(),
|
||||
'haproxy_version':
|
||||
self._get_version_of_installed_package('haproxy'),
|
||||
'api_version': api_server.VERSION,
|
||||
'networks': self._get_networks(),
|
||||
'active': True,
|
||||
'haproxy_count': self._count_haproxy_processes(listener_list),
|
||||
'cpu': {
|
||||
'total': cpu['total'],
|
||||
'user': cpu['user'],
|
||||
'system': cpu['system'],
|
||||
'soft_irq': cpu['softirq'], },
|
||||
'memory': {
|
||||
'total': meminfo['MemTotal'],
|
||||
'free': meminfo['MemFree'],
|
||||
'buffers': meminfo['Buffers'],
|
||||
'cached': meminfo['Cached'],
|
||||
'swap_used': meminfo['SwapCached'],
|
||||
'shared': meminfo['Shmem'],
|
||||
'slab': meminfo['Slab'], },
|
||||
'disk': {
|
||||
'used': (st.f_blocks - st.f_bfree) * st.f_frsize,
|
||||
'available': st.f_bavail * st.f_frsize},
|
||||
'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)
|
||||
out = subprocess.check_output(cmd.split())
|
||||
m = re.search(b'Version: .*', out)
|
||||
return m.group(0)[len('Version: '):]
|
||||
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(self, listener_list):
|
||||
num = 0
|
||||
for listener_id in listener_list:
|
||||
if util.is_listener_running(listener_id):
|
||||
# optional check if it's still running
|
||||
num += 1
|
||||
return num
|
||||
|
||||
def _count_haproxy_processes(listener_list):
|
||||
num = 0
|
||||
for listener_id in listener_list:
|
||||
if util.is_listener_running(listener_id):
|
||||
# optional check if it's still running
|
||||
num += 1
|
||||
return num
|
||||
def _get_meminfo(self):
|
||||
re_parser = re.compile(r'^(?P<key>\S*):\s*(?P<value>\d*)\s*kB')
|
||||
result = dict()
|
||||
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
|
||||
key, value = match.groups(['key', 'value'])
|
||||
result[key] = int(value)
|
||||
return result
|
||||
|
||||
def _cpu(self):
|
||||
with open('/proc/stat') as f:
|
||||
cpu = f.readline()
|
||||
vals = cpu.split(' ')
|
||||
return {
|
||||
'user': vals[2],
|
||||
'nice': vals[3],
|
||||
'system': vals[4],
|
||||
'idle': vals[5],
|
||||
'iowait': vals[6],
|
||||
'irq': vals[7],
|
||||
'softirq': vals[8],
|
||||
'total': sum([int(i) for i in vals[2:]])
|
||||
}
|
||||
|
||||
def _get_meminfo():
|
||||
re_parser = re.compile(r'^(?P<key>\S*):\s*(?P<value>\d*)\s*kB')
|
||||
result = dict()
|
||||
for line in open('/proc/meminfo'):
|
||||
match = re_parser.match(line)
|
||||
if not match:
|
||||
continue # skip lines that don't parse
|
||||
key, value = match.groups(['key', 'value'])
|
||||
result[key] = int(value)
|
||||
return result
|
||||
def _load(self):
|
||||
with open('/proc/loadavg') as f:
|
||||
load = f.readline()
|
||||
vals = load.split(' ')
|
||||
return vals[:3]
|
||||
|
||||
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')):
|
||||
break
|
||||
elif item[0] == 'IFLA_IFNAME':
|
||||
interface_name = item[1]
|
||||
if item[0] == 'IFLA_STATS64':
|
||||
networks[interface_name] = dict(
|
||||
network_tx=item[1]['tx_bytes'],
|
||||
network_rx=item[1]['rx_bytes'])
|
||||
return networks
|
||||
|
||||
def _cpu():
|
||||
with open('/proc/stat') as f:
|
||||
cpu = f.readline()
|
||||
vals = cpu.split(' ')
|
||||
return {
|
||||
'user': vals[2],
|
||||
'nice': vals[3],
|
||||
'system': vals[4],
|
||||
'idle': vals[5],
|
||||
'iowait': vals[6],
|
||||
'irq': vals[7],
|
||||
'softirq': vals[8],
|
||||
'total': sum([int(i) for i in vals[2:]])
|
||||
}
|
||||
def get_interface(self, ip_addr):
|
||||
|
||||
try:
|
||||
ip_version = ipaddress.ip_address(six.text_type(ip_addr)).version
|
||||
except Exception:
|
||||
return flask.make_response(
|
||||
flask.jsonify(dict(message="Invalid IP address")), 400)
|
||||
|
||||
def _load():
|
||||
with open('/proc/loadavg') as f:
|
||||
load = f.readline()
|
||||
vals = load.split(' ')
|
||||
return vals[:3]
|
||||
if ip_version == 4:
|
||||
address_format = netifaces.AF_INET
|
||||
elif ip_version == 6:
|
||||
address_format = netifaces.AF_INET6
|
||||
else:
|
||||
return flask.make_response(
|
||||
flask.jsonify(dict(message="Bad IP address version")), 400)
|
||||
|
||||
# We need to normalize the address as IPv6 has multiple representations
|
||||
# fe80:0000:0000:0000:f816:3eff:fef2:2058 == fe80::f816:3eff:fef2:2058
|
||||
normalized_addr = socket.inet_ntop(address_format,
|
||||
socket.inet_pton(address_format,
|
||||
ip_addr))
|
||||
|
||||
def _get_networks():
|
||||
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'):
|
||||
break
|
||||
elif item[0] == 'IFLA_IFNAME':
|
||||
interface_name = item[1]
|
||||
if item[0] == 'IFLA_STATS64':
|
||||
networks[interface_name] = dict(
|
||||
network_tx=item[1]['tx_bytes'],
|
||||
network_rx=item[1]['rx_bytes'])
|
||||
return networks
|
||||
with pyroute2.NetNS(consts.AMPHORA_NAMESPACE) as netns:
|
||||
for addr in netns.get_addr():
|
||||
# Save the interface index as IPv6 records don't list a
|
||||
# textual interface
|
||||
interface_idx = addr['index']
|
||||
# Save the address family (IPv4/IPv6) for use normalizing
|
||||
# the IP address for comparison
|
||||
interface_af = addr['family']
|
||||
# Search through the attributes of each address record
|
||||
for attr in addr['attrs']:
|
||||
# Look for the attribute name/value pair for the address
|
||||
if attr[0] == 'IFA_ADDRESS':
|
||||
# Compare the normalized address with the address we
|
||||
# we are looking for. Since we have matched the name
|
||||
# above, attr[1] is the address value
|
||||
if normalized_addr == socket.inet_ntop(
|
||||
interface_af,
|
||||
socket.inet_pton(interface_af, attr[1])):
|
||||
|
||||
# Lookup the matching interface name by
|
||||
# getting the interface with the index we found
|
||||
# in the above address search
|
||||
lookup_int = netns.get_links(interface_idx)
|
||||
# Search through the attributes of the matching
|
||||
# interface record
|
||||
for int_attr in lookup_int[0]['attrs']:
|
||||
# Look for the attribute name/value pair
|
||||
# that includes the interface name
|
||||
if int_attr[0] == 'IFLA_IFNAME':
|
||||
# Return the response with the matching
|
||||
# interface name that is in int_attr[1]
|
||||
# for the matching interface attribute
|
||||
# name
|
||||
return flask.make_response(
|
||||
flask.jsonify(
|
||||
dict(message='OK',
|
||||
interface=int_attr[1])), 200)
|
||||
|
||||
def get_interface(ip_addr):
|
||||
|
||||
try:
|
||||
ip_version = ipaddress.ip_address(six.text_type(ip_addr)).version
|
||||
except Exception:
|
||||
return flask.make_response(
|
||||
flask.jsonify(dict(message="Invalid IP address")), 400)
|
||||
|
||||
if ip_version == 4:
|
||||
address_format = netifaces.AF_INET
|
||||
elif ip_version == 6:
|
||||
address_format = netifaces.AF_INET6
|
||||
else:
|
||||
return flask.make_response(
|
||||
flask.jsonify(dict(message="Bad IP address version")), 400)
|
||||
|
||||
# We need to normalize the address as IPv6 has multiple representations
|
||||
# fe80:0000:0000:0000:f816:3eff:fef2:2058 == fe80::f816:3eff:fef2:2058
|
||||
normalized_addr = socket.inet_ntop(address_format,
|
||||
socket.inet_pton(address_format,
|
||||
ip_addr))
|
||||
|
||||
with pyroute2.NetNS(consts.AMPHORA_NAMESPACE) as netns:
|
||||
for addr in netns.get_addr():
|
||||
# Save the interface index as IPv6 records don't list a
|
||||
# textual interface
|
||||
interface_idx = addr['index']
|
||||
# Save the address family (IPv4/IPv6) for use normalizing
|
||||
# the IP address for comparison
|
||||
interface_af = addr['family']
|
||||
# Search through the attributes of each address record
|
||||
for attr in addr['attrs']:
|
||||
# Look for the attribute name/value pair for the address
|
||||
if attr[0] == 'IFA_ADDRESS':
|
||||
# Compare the normalized address with the address we
|
||||
# we are looking for. Since we have matched the name
|
||||
# above, attr[1] is the address value
|
||||
if normalized_addr == socket.inet_ntop(
|
||||
interface_af,
|
||||
socket.inet_pton(interface_af, attr[1])):
|
||||
|
||||
# Lookup the matching interface name by
|
||||
# getting the interface with the index we found
|
||||
# in the above address search
|
||||
lookup_int = netns.get_links(interface_idx)
|
||||
# Search through the attributes of the matching
|
||||
# interface record
|
||||
for int_attr in lookup_int[0]['attrs']:
|
||||
# Look for the attribute name/value pair
|
||||
# that includes the interface name
|
||||
if int_attr[0] == 'IFLA_IFNAME':
|
||||
# Return the response with the matching
|
||||
# interface name that is in int_attr[1]
|
||||
# for the matching interface attribute
|
||||
# name
|
||||
return flask.make_response(
|
||||
flask.jsonify(
|
||||
dict(message='OK',
|
||||
interface=int_attr[1])), 200)
|
||||
|
||||
return flask.make_response(
|
||||
flask.jsonify(dict(message="Error interface not found "
|
||||
"for IP address")), 404)
|
||||
flask.jsonify(dict(message="Error interface not found "
|
||||
"for IP address")), 404)
|
||||
|
@ -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,
|
||||
vip=vip,
|
||||
vip_ipv6=ip.version is 6,
|
||||
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)
|
||||
self._osutils.write_interfaces_file()
|
||||
self._osutils.write_vip_interface_file(
|
||||
interface_file_path=interface_file_path,
|
||||
primary_interface=primary_interface,
|
||||
vip=vip,
|
||||
ip=ip,
|
||||
broadcast=broadcast,
|
||||
netmask=netmask,
|
||||
gateway=gateway,
|
||||
mtu=mtu,
|
||||
vrrp_ip=vrrp_ip,
|
||||
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