Adds support for systemd amphora images

This patch enables auto-detection of the init system used in the
amphora image and adds support for systemd amphora.
This patch allows Ubuntu xenial amphora images to work.
It also merges two functional test files into one file to reduce
code duplication.

This is a scenario gate fix.

Change-Id: I5fec1680bd47719ae9f2fcb6abaaba8a78e2ae8b
Closes-Bug: #1640866
This commit is contained in:
Michael Johnson 2016-11-10 23:35:24 +00:00
parent 5e76bab500
commit c4408c4c78
25 changed files with 630 additions and 1256 deletions

View File

@ -0,0 +1,12 @@
[Unit]
Description=OpenStack Octavia Amphora Agent
After=network.target syslog.service
Wants=syslog.service
[Service]
ExecStart=/usr/local/bin/amphora-agent --config-file /etc/octavia/amphora-agent.conf
KillMode=mixed
Restart=always
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,11 @@
#!/bin/bash
if [ ${DIB_DEBUG_TRACE:-0} -gt 0 ]; then
set -x
fi
set -eu
set -o pipefail
if [[ -f /bin/systemctl ]]; then
/bin/systemctl enable amphora-agent
fi

View File

@ -1,3 +1,4 @@
os-svc-install
package-installs
pkg-map
sysctl

View File

@ -1,3 +1,3 @@
{
"haproxy/trusty-backports": null
"haproxy": null
}

View File

@ -0,0 +1,22 @@
{
"release": {
"ubuntu": {
"trusty": {
"haproxy": "haproxy/trusty-backports"
}
}
},
"distro": {
"ubuntu": {
"haproxy": "haproxy"
}
},
"family": {
"debian": {
"haproxy": "haproxy"
}
},
"default": {
"haproxy": "haproxy"
}
}

View File

@ -3,4 +3,6 @@
set -eu
set -o pipefail
update-rc.d -f haproxy remove
# Doing both here as just remove doesn't seem to work on xenial
update-rc.d haproxy disable || true
update-rc.d -f haproxy remove || true

View File

@ -4,4 +4,6 @@
set -eu
set -o xtrace
echo deb http://archive.ubuntu.com/ubuntu trusty-backports main restricted universe multiverse > /etc/apt/sources.list.d/backports.list
if [ "$DISTRO_NAME" == "ubuntu" ] && [ "$DIB_RELEASE" == "trusty" ]; then
echo deb http://archive.ubuntu.com/ubuntu trusty-backports main restricted universe multiverse > /etc/apt/sources.list.d/backports.list
fi

View File

@ -1,2 +1,3 @@
os-svc-install
package-installs
pkg-map

View File

@ -1,3 +1,3 @@
{
"keepalived/trusty-backports": null
"keepalived": null
}

View File

@ -0,0 +1,23 @@
{
"release": {
"ubuntu": {
"trusty": {
"keepalived": "keepalived/trusty-backports"
}
}
},
"distro": {
"ubuntu": {
"keepalived": "keepalived"
}
},
"family": {
"debian": {
"keepalived": "keepalived"
}
},
"default": {
"keepalived": "keepalived"
}
}

View File

@ -4,4 +4,6 @@
set -eu
set -o xtrace
echo deb http://archive.ubuntu.com/ubuntu trusty-backports main restricted universe multiverse > /etc/apt/sources.list.d/backports.list
if [ "$DISTRO_NAME" == "ubuntu" ] && [ "$DIB_RELEASE" == "trusty" ]; then
echo deb http://archive.ubuntu.com/ubuntu trusty-backports main restricted universe multiverse > /etc/apt/sources.list.d/backports.list
fi

View File

@ -124,7 +124,10 @@
# respawn_interval = 2
# client_cert = /etc/octavia/certs/client.pem
# server_ca = /etc/octavia/certs/server_ca.pem
#
# This setting is deprecated. It is now automatically discovered.
# use_upstart = True
#
# rest_request_conn_timeout = 10
# rest_request_read_timeout = 60

View File

@ -31,7 +31,9 @@ LOG = logging.getLogger(__name__)
j2_env = jinja2.Environment(autoescape=True, loader=jinja2.FileSystemLoader(
os.path.dirname(os.path.realpath(__file__)) + consts.AGENT_API_TEMPLATES))
template = j2_env.get_template(consts.KEEPALIVED_CONF)
UPSTART_TEMPLATE = j2_env.get_template(consts.KEEPALIVED_JINJA2_UPSTART)
SYSVINIT_TEMPLATE = j2_env.get_template(consts.KEEPALIVED_JINJA2_SYSVINIT)
SYSTEMD_TEMPLATE = j2_env.get_template(consts.KEEPALIVED_JINJA2_SYSTEMD)
check_script_template = j2_env.get_template(consts.CHECK_SCRIPT_CONF)
@ -54,10 +56,28 @@ class Keepalived(object):
f.write(b)
b = stream.read(BUFFER)
file_path = util.keepalived_init_path()
# mode 00755
mode = (stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP |
stat.S_IROTH | stat.S_IXOTH)
init_system = util.get_os_init_system()
file_path = util.keepalived_init_path(init_system)
if init_system == consts.INIT_SYSTEMD:
template = SYSTEMD_TEMPLATE
init_enable_cmd = "systemctl enable octavia-keepalived"
elif init_system == consts.INIT_UPSTART:
template = UPSTART_TEMPLATE
elif init_system == consts.INIT_SYSVINIT:
template = SYSVINIT_TEMPLATE
init_enable_cmd = "insserv {file}".format(file=file_path)
else:
raise util.UnknownInitError()
if init_system == consts.INIT_SYSTEMD:
# mode 00644
mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
else:
# mode 00755
mode = (stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP |
stat.S_IROTH | stat.S_IXOTH)
if not os.path.exists(file_path):
with os.fdopen(os.open(file_path, flags, mode), 'w') as text_file:
text = template.render(
@ -71,6 +91,9 @@ class Keepalived(object):
# Renders the Keepalived check script
keepalived_path = util.keepalived_check_script_path()
# mode 00755
mode = (stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP |
stat.S_IROTH | stat.S_IXOTH)
open_obj = os.open(keepalived_path, flags, mode)
with os.fdopen(open_obj, 'w') as text_file:
text = check_script_template.render(
@ -78,6 +101,18 @@ class Keepalived(object):
)
text_file.write(text)
# Make sure the new service is enabled on boot
if init_system != consts.INIT_UPSTART:
try:
subprocess.check_output(init_enable_cmd.split(),
stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
LOG.debug("Failed to enable octavia-keepalived service: "
"%(err)s", {'err': e})
return flask.make_response(flask.jsonify(dict(
message="Error enabling octavia-keepalived service",
details=e.output)), 500)
res = flask.make_response(flask.jsonify({
'message': 'OK'}), 200)
res.headers['ETag'] = stream.get_md5()

View File

@ -30,12 +30,14 @@ 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
from octavia.common import utils as octavia_utils
from octavia.i18n import _LE
LOG = logging.getLogger(__name__)
BUFFER = 100
UPSTART_CONF = 'upstart.conf.j2'
SYSVINIT_CONF = 'sysvinit.conf.j2'
SYSTEMD_CONF = 'systemd.conf.j2'
JINJA_ENV = jinja2.Environment(
autoescape=True,
@ -44,6 +46,7 @@ JINJA_ENV = jinja2.Environment(
) + consts.AGENT_API_TEMPLATES))
UPSTART_TEMPLATE = JINJA_ENV.get_template(UPSTART_CONF)
SYSVINIT_TEMPLATE = JINJA_ENV.get_template(SYSVINIT_CONF)
SYSTEMD_TEMPLATE = JINJA_ENV.get_template(SYSTEMD_CONF)
class ParsingError(Exception):
@ -113,7 +116,7 @@ class Listener(object):
try:
subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
LOG.debug("Failed to verify haproxy file: %s", e)
LOG.error(_LE("Failed to verify haproxy file: %s"), e)
os.remove(name) # delete file
return flask.make_response(flask.jsonify(dict(
message="Invalid request",
@ -122,15 +125,44 @@ class Listener(object):
# file ok - move it
os.rename(name, util.config_path(listener_id))
use_upstart = util.CONF.haproxy_amphora.use_upstart
file = util.init_path(listener_id)
# mode 00755
mode = (stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP |
stat.S_IROTH | stat.S_IXOTH)
if not os.path.exists(file):
with os.fdopen(os.open(file, flags, mode), 'w') as text_file:
template = (UPSTART_TEMPLATE if use_upstart
else SYSVINIT_TEMPLATE)
try:
init_system = util.get_os_init_system()
LOG.debug('Found init system: {0}'.format(init_system))
init_path = util.init_path(listener_id, init_system)
if init_system == consts.INIT_SYSTEMD:
template = SYSTEMD_TEMPLATE
init_enable_cmd = "systemctl enable haproxy-{list}".format(
list=listener_id)
elif init_system == consts.INIT_UPSTART:
template = UPSTART_TEMPLATE
elif init_system == consts.INIT_SYSVINIT:
template = SYSVINIT_TEMPLATE
init_enable_cmd = "insserv {file}".format(file=init_path)
else:
raise util.UnknownInitError()
except util.UnknownInitError:
LOG.error(_LE("Unknown init system found."))
return flask.make_response(flask.jsonify(dict(
message="Unknown init system in amphora",
details="The amphora image is running an unknown init "
"system. We can't create the init configuration "
"file for the load balancing process.")), 500)
if init_system == consts.INIT_SYSTEMD:
# mode 00644
mode = (stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH)
else:
# mode 00755
mode = (stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP |
stat.S_IROTH | stat.S_IXOTH)
if not os.path.exists(init_path):
with os.fdopen(os.open(init_path, flags, mode), 'w') as text_file:
text = template.render(
peer_name=peer_name,
haproxy_pid=util.pid_path(listener_id),
@ -143,18 +175,18 @@ class Listener(object):
)
text_file.write(text)
if not use_upstart:
insrvcmd = ("insserv {file}".format(file=file))
# Make sure the new service is enabled on boot
if init_system != consts.INIT_UPSTART:
try:
subprocess.check_output(insrvcmd.split(),
subprocess.check_output(init_enable_cmd.split(),
stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
LOG.debug("Failed to make %(file)s executable: %(err)s",
{'file': file, 'err': e})
LOG.error(_LE("Failed to enable haproxy-%(list)s "
"service: %(err)s"),
{'list': listener_id, 'err': e})
return flask.make_response(flask.jsonify(dict(
message="Error making file {0} executable".format(file),
details=e.output)), 500)
message="Error enabling haproxy-{0} service".format(
listener_id), details=e.output)), 500)
res = flask.make_response(flask.jsonify({
'message': 'OK'}), 202)
@ -221,7 +253,7 @@ class Listener(object):
try:
subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
LOG.debug("Failed to stop HAProxy service: %s", e)
LOG.error(_LE("Failed to stop HAProxy service: %s"), e)
return flask.make_response(flask.jsonify(dict(
message="Error stopping haproxy",
details=e.output)), 500)
@ -239,10 +271,34 @@ class Listener(object):
except Exception:
pass
# disable the service
init_system = util.get_os_init_system()
init_path = util.init_path(listener_id, init_system)
if init_system == consts.INIT_SYSTEMD:
init_disable_cmd = "systemctl disable haproxy-{list}".format(
list=listener_id)
elif init_system == consts.INIT_SYSVINIT:
init_disable_cmd = "insserv -r {file}".format(file=init_path)
elif init_system != consts.INIT_UPSTART:
raise util.UnknownInitError()
if init_system != consts.INIT_UPSTART:
try:
subprocess.check_output(init_disable_cmd.split(),
stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
LOG.error(_LE("Failed to disable haproxy-%(list)s "
"service: %(err)s"),
{'list': listener_id, 'err': e})
return flask.make_response(flask.jsonify(dict(
message="Error disabling haproxy-{0} service".format(
listener_id), details=e.output)), 500)
# delete the directory + init script for that listener
shutil.rmtree(util.haproxy_dir(listener_id))
if os.path.exists(util.init_path(listener_id)):
os.remove(util.init_path(listener_id))
if os.path.exists(init_path):
os.remove(init_path)
return flask.jsonify({'message': 'OK'})

View File

@ -1,67 +0,0 @@
{#
# Copyright 2015 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
#}
#!/bin/sh
RETVAL=0
prog="octavia-keepalived"
start() {
echo -n $"Starting $prog"
ip netns exec {{ amphora_nsname }} {{ keepalived_cmd }} -D -d -f {{ keepalived_cfg }}
RETVAL=$?
echo
[ $RETVAL -eq 0 ] && touch {{ keepalived_pid }}
}
stop() {
echo -n $"Stopping $prog"
kill -9 `pidof keepalived`
RETVAL=$?
echo
[ $RETVAL -eq 0 ] && rm -f {{ keepalived_pid }}
}
status() {
kill -0 `pidof keepalived`
RETVAL=$?
[ $RETVAL -eq 0 ] && echo -n $"$prog is running"
[ $RETVAL -eq 1 ] && echo -n $"$prog is not found"
echo
}
# See how we were called.
case "$1" in
start)
start
;;
stop)
stop
;;
reload)
stop
start
;;
status)
status
;;
*)
echo "Usage: $0 {start|stop|reload|status}"
RETVAL=1
esac
exit $RETVAL

View File

@ -0,0 +1,13 @@
[Unit]
Description=Keepalive Daemon (LVS and VRRP)
After=network-online.target
Wants=network-online.target
[Service]
Type=forking
KillMode=process
ExecStart=/sbin/ip netns exec {{ amphora_nsname }} {{ keepalived_cmd }} -D -d -f {{ keepalived_cfg }}
ExecReload=/bin/kill -HUP $MAINPID
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,83 @@
#!/bin/sh
#
# keepalived LVS cluster monitor daemon.
#
# Written by Andres Salomon <dilinger@voxel.net>
#
### BEGIN INIT INFO
# Provides: keepalived
# Required-Start: $syslog $network $remote_fs
# Required-Stop: $syslog $network $remote_fs
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Starts keepalived
# Description: Starts keepalived lvs loadbalancer
### END INIT INFO
PATH=/sbin:/bin:/usr/sbin:/usr/bin
DAEMON="ip netns exec {{ amphora_nsname }} {{ keepalived_cmd }}"
NAME=octavia-keepalived
DESC=octavia-keepalived
TMPFILES="/tmp/.vrrp /tmp/.healthcheckers"
DAEMON_ARGS="-D -d -f {{ keepalived_cfg }}"
#includes lsb functions
. /lib/lsb/init-functions
test -f $DAEMON || exit 0
# Read configuration variable file if it is present
[ -r /etc/default/$NAME ] && . /etc/default/$NAME
case "$1" in
start)
log_daemon_msg "Starting $DESC" "$NAME"
for file in $TMPFILES
do
test -e $file && test ! -L $file && rm $file
done
if start-stop-daemon --start --quiet --pidfile /var/run/$NAME.pid \
--exec $DAEMON -- $DAEMON_ARGS; then
log_end_msg 0
else
log_end_msg 1
fi
;;
stop)
log_daemon_msg "Stopping $DESC" "$NAME"
if start-stop-daemon --oknodo --stop --quiet --pidfile /var/run/$NAME.pid \
--exec $DAEMON; then
log_end_msg 0
else
log_end_msg 1
fi
;;
reload|force-reload)
log_action_begin_msg "Reloading $DESC configuration..."
if start-stop-daemon --stop --quiet --signal 1 --pidfile \
/var/run/$NAME.pid --exec $DAEMON; then
log_end_msg 0
else
log_action_end_msg 1
fi
;;
restart)
log_action_begin_msg "Restarting $DESC" "$NAME"
start-stop-daemon --stop --quiet --pidfile \
/var/run/$NAME.pid --exec $DAEMON || true
sleep 1
if start-stop-daemon --start --quiet --pidfile \
/var/run/$NAME.pid --exec $DAEMON -- $DAEMON_ARGS; then
log_end_msg 0
else
log_end_msg 1
fi
;;
*)
echo "Usage: /etc/init.d/$NAME {start|stop|restart|reload|force-reload}" >&2
exit 1
;;
esac
exit 0

View File

@ -0,0 +1,25 @@
{#
# Copyright 2015 Hewlett-Packard Development Company, L.P.
# Copyright 2016 Rackspace
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
#}
description "Octavia keepalived"
start on runlevel [2345]
stop on runlevel [!2345]
respawn
exec /sbin/ip netns exec {{ amphora_nsname }} {{ keepalived_cmd }} -n -D -d -f {{ keepalived_cfg }}

View File

@ -0,0 +1,29 @@
[Unit]
Description=HAProxy Load Balancer
Documentation=man:haproxy(1)
Documentation=file:/usr/share/doc/haproxy/configuration.txt.gz
After=network.target syslog.service
Wants=syslog.service
[Service]
EnvironmentFile=-/etc/default/haproxy
ExecStartPre=/usr/sbin/haproxy -f {{ haproxy_cfg }} -c -q
# Re-add the namespace
ExecStartPre=-/sbin/ip netns add {{ amphora_nsname }}
# Load the system sysctl into the new namespace
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"")}\''
# Bring up all of the namespace interfaces
ExecStartPre=-/sbin/ip netns exec {{ amphora_nsname }} ifup -a
#
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 }}
ExecReload=/bin/kill -USR2 $MAINPID
KillMode=mixed
Restart=always
[Install]
WantedBy=multi-user.target

View File

@ -14,23 +14,33 @@
import os
import subprocess
from oslo_config import cfg
from octavia.common import constants as consts
CONF = cfg.CONF
CONF.import_group('amphora_agent', 'octavia.common.config')
CONF.import_group('haproxy_amphora', 'octavia.common.config')
UPSTART_DIR = '/etc/init'
KEEPALIVED_INIT_DIR = '/etc/init.d'
SYSVINIT_DIR = '/etc/init.d'
class UnknownInitError(Exception):
pass
def init_path(listener_id):
use_upstart = CONF.haproxy_amphora.use_upstart
hconf = 'haproxy-{0}.conf' if use_upstart else 'haproxy-{0}'
idir = UPSTART_DIR if use_upstart else SYSVINIT_DIR
return os.path.join(idir, hconf.format(listener_id))
def init_path(listener_id, init_system):
if init_system == consts.INIT_SYSTEMD:
return os.path.join(consts.SYSTEMD_DIR,
'haproxy-{0}.service'.format(listener_id))
elif init_system == consts.INIT_UPSTART:
return os.path.join(consts.UPSTART_DIR,
'haproxy-{0}.conf'.format(listener_id))
elif init_system == consts.INIT_SYSVINIT:
return os.path.join(consts.SYSVINIT_DIR,
'haproxy-{0}'.format(listener_id))
else:
raise UnknownInitError()
def haproxy_dir(listener_id):
@ -63,8 +73,15 @@ def keepalived_dir():
return os.path.join(CONF.haproxy_amphora.base_path, 'vrrp')
def keepalived_init_path():
return os.path.join(KEEPALIVED_INIT_DIR, 'octavia-keepalived')
def keepalived_init_path(init_system):
if init_system == consts.INIT_SYSTEMD:
return os.path.join(consts.SYSTEMD_DIR, consts.KEEPALIVED_SYSTEMD)
elif init_system == consts.INIT_UPSTART:
return os.path.join(consts.UPSTART_DIR, consts.KEEPALIVED_UPSTART)
elif init_system == consts.INIT_SYSVINIT:
return os.path.join(consts.SYSVINIT_DIR, consts.KEEPALIVED_SYSVINIT)
else:
raise UnknownInitError()
def keepalived_pid_path():
@ -115,3 +132,21 @@ def get_network_interface_file(interface):
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:
init_proc_name = init_comm.read().rstrip('\n')
if init_proc_name == consts.INIT_SYSTEMD:
return consts.INIT_SYSTEMD
if init_proc_name == 'init':
init_path = consts.INIT_PATH
if os.path.exists(init_path):
args = [init_path, '--version']
init_version = subprocess.check_output(args, shell=False)
if consts.INIT_UPSTART in init_version:
return consts.INIT_UPSTART
else:
return consts.INIT_SYSVINIT
return consts.INIT_UNKOWN

View File

@ -191,6 +191,9 @@ haproxy_amphora_opts = [
cfg.StrOpt('server_ca', default='/etc/octavia/certs/server_ca.pem',
help=_("The ca which signed the server certificates")),
cfg.BoolOpt('use_upstart', default=True,
deprecated_for_removal=True,
deprecated_reason='This is now automatically discovered '
' and configured.',
help=_("If False, use sysvinit.")),
]

View File

@ -310,7 +310,9 @@ HAPROXY_MEMBER_STATUSES = (UP, DOWN, NO_CHECK)
API_VERSION = '0.5'
HAPROXY_BASE_PEER_PORT = 1025
KEEPALIVED_CONF = 'keepalived.conf.j2'
KEEPALIVED_JINJA2_UPSTART = 'keepalived.upstart.j2'
KEEPALIVED_JINJA2_SYSTEMD = 'keepalived.systemd.j2'
KEEPALIVED_JINJA2_SYSVINIT = 'keepalived.sysvinit.j2'
CHECK_SCRIPT_CONF = 'keepalived_check_script.conf.j2'
PLUGGED_INTERFACES = '/var/lib/octavia/plugged_interfaces'
@ -336,3 +338,20 @@ AMP_ACTION_START = 'start'
AMP_ACTION_STOP = 'stop'
AMP_ACTION_RELOAD = 'reload'
GLANCE_IMAGE_ACTIVE = 'active'
INIT_SYSTEMD = 'systemd'
INIT_UPSTART = 'upstart'
INIT_SYSVINIT = 'sysvinit'
INIT_UNKOWN = 'unknown'
VALID_INIT_SYSTEMS = (INIT_SYSTEMD, INIT_SYSVINIT, INIT_UPSTART)
INIT_PATH = '/sbin/init'
SYSTEMD_DIR = '/usr/lib/systemd/system'
SYSVINIT_DIR = '/etc/init.d'
UPSTART_DIR = '/etc/init'
INIT_PROC_COMM_PATH = '/proc/1/comm'
KEEPALIVED_SYSTEMD = 'octavia-keepalived.service'
KEEPALIVED_SYSVINIT = 'octavia-keepalived'
KEEPALIVED_UPSTART = 'octavia-keepalived.conf'

View File

@ -51,13 +51,48 @@ class TestServerTestCase(base.TestCase):
conf = self.useFixture(oslo_fixture.Config(cfg.CONF))
conf.config(group="haproxy_amphora", base_path='/var/lib/octavia')
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
'get_os_init_system', return_value=consts.INIT_SYSTEMD)
@mock.patch('os.path.exists')
@mock.patch('os.makedirs')
@mock.patch('os.rename')
@mock.patch('subprocess.check_output')
@mock.patch('os.remove')
def test_haproxy(self, mock_remove, mock_subprocess, mock_rename,
mock_makedirs, mock_exists):
def test_haproxy_systemd(self, mock_remove, mock_subprocess, mock_rename,
mock_makedirs, mock_exists, mock_init_system):
self._test_haproxy(mock_remove, mock_subprocess, mock_rename,
mock_makedirs, mock_exists, mock_init_system,
consts.INIT_SYSTEMD)
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
'get_os_init_system', return_value=consts.INIT_SYSVINIT)
@mock.patch('os.path.exists')
@mock.patch('os.makedirs')
@mock.patch('os.rename')
@mock.patch('subprocess.check_output')
@mock.patch('os.remove')
def test_haproxy_sysvinit(self, mock_remove, mock_subprocess, mock_rename,
mock_makedirs, mock_exists, mock_init_system):
self._test_haproxy(mock_remove, mock_subprocess, mock_rename,
mock_makedirs, mock_exists, mock_init_system,
consts.INIT_SYSVINIT)
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
'get_os_init_system', return_value=consts.INIT_UPSTART)
@mock.patch('os.path.exists')
@mock.patch('os.makedirs')
@mock.patch('os.rename')
@mock.patch('subprocess.check_output')
@mock.patch('os.remove')
def test_haproxy_upstart(self, mock_remove, mock_subprocess, mock_rename,
mock_makedirs, mock_exists, mock_init_system):
self._test_haproxy(mock_remove, mock_subprocess, mock_rename,
mock_makedirs, mock_exists, mock_init_system,
consts.INIT_UPSTART)
def _test_haproxy(self, mock_remove, mock_subprocess, mock_rename,
mock_makedirs, mock_exists, mock_init_system,
init_system):
flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
mock_exists.return_value = True
@ -77,7 +112,7 @@ class TestServerTestCase(base.TestCase):
self.assertEqual(202, rv.status_code)
handle = m()
handle.write.assert_called_once_with(six.b('test'))
mock_subprocess.assert_called_once_with(
mock_subprocess.assert_any_call(
"haproxy -c -L {peer} -f {config_file}".format(
config_file=file_name,
peer=(octavia_utils.
@ -87,6 +122,17 @@ class TestServerTestCase(base.TestCase):
'/var/lib/octavia/123/haproxy.cfg.new',
'/var/lib/octavia/123/haproxy.cfg')
if init_system == consts.INIT_SYSTEMD:
mock_subprocess.assert_any_call(
"systemctl enable haproxy-123".split(),
stderr=subprocess.STDOUT)
elif init_system == consts.INIT_SYSVINIT:
mock_subprocess.assert_any_call(
"insserv /etc/init.d/haproxy-123".split(),
stderr=subprocess.STDOUT)
else:
self.assertIn(init_system, consts.VALID_INIT_SYSTEMS)
# exception writing
m = self.useFixture(test_utils.OpenFixture(file_name)).mock_open
m.side_effect = IOError() # open crashes
@ -98,7 +144,15 @@ class TestServerTestCase(base.TestCase):
# check if files get created
mock_exists.return_value = False
init_path = '/etc/init/haproxy-123.conf'
if init_system == consts.INIT_SYSTEMD:
init_path = consts.SYSTEMD_DIR + '/haproxy-123.service'
elif init_system == consts.INIT_UPSTART:
init_path = consts.UPSTART_DIR + '/haproxy-123.conf'
elif init_system == consts.INIT_SYSVINIT:
init_path = consts.SYSVINIT_DIR + '/haproxy-123'
else:
self.assertIn(init_system, consts.VALID_INIT_SYSTEMS)
m = self.useFixture(test_utils.OpenFixture(init_path)).mock_open
# happy case upstart file exists
with mock.patch('os.open') as mock_open, mock.patch.object(
@ -109,8 +163,12 @@ class TestServerTestCase(base.TestCase):
data='test')
self.assertEqual(202, rv.status_code)
mode = (stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP |
stat.S_IROTH | stat.S_IXOTH)
if init_system == consts.INIT_SYSTEMD:
mode = (stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP |
stat.S_IROTH)
else:
mode = (stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP |
stat.S_IROTH | stat.S_IXOTH)
mock_open.assert_called_with(init_path, flags, mode)
mock_fdopen.assert_called_with(123, 'w')
handle = mock_fdopen()
@ -145,6 +203,16 @@ class TestServerTestCase(base.TestCase):
stderr=-2)
mock_remove.assert_called_once_with(file_name)
# unhappy path with bogus init system
mock_init_system.return_value = 'bogus'
with mock.patch('os.open') as mock_open, mock.patch.object(
os, 'fdopen', m) as mock_fdopen:
mock_open.return_value = 123
rv = self.app.put('/' + api_server.VERSION +
'/listeners/amp_123/123/haproxy',
data='test')
self.assertEqual(500, rv.status_code)
@mock.patch('os.path.exists')
@mock.patch('octavia.amphorae.backends.agent.api_server.listener.Listener.'
'vrrp_check_script_update')
@ -247,14 +315,53 @@ class TestServerTestCase(base.TestCase):
hostname='test-host'),
json.loads(rv.data.decode('utf-8')))
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
'get_os_init_system', return_value=consts.INIT_SYSTEMD)
@mock.patch('os.path.exists')
@mock.patch('subprocess.check_output')
@mock.patch('octavia.amphorae.backends.agent.api_server.util.' +
'get_haproxy_pid')
@mock.patch('shutil.rmtree')
@mock.patch('os.remove')
def test_delete_listener(self, mock_remove, mock_rmtree, mock_pid,
mock_check_output, mock_exists):
def test_delete_listener_systemd(self, mock_remove, mock_rmtree, mock_pid,
mock_check_output, mock_exists,
mock_init_system):
self._test_delete_listener(mock_remove, mock_rmtree, mock_pid,
mock_check_output, mock_exists,
consts.INIT_SYSTEMD)
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
'get_os_init_system', return_value=consts.INIT_SYSVINIT)
@mock.patch('os.path.exists')
@mock.patch('subprocess.check_output')
@mock.patch('octavia.amphorae.backends.agent.api_server.util.' +
'get_haproxy_pid')
@mock.patch('shutil.rmtree')
@mock.patch('os.remove')
def test_delete_listener_sysvinit(self, mock_remove, mock_rmtree, mock_pid,
mock_check_output, mock_exists,
mock_init_system):
self._test_delete_listener(mock_remove, mock_rmtree, mock_pid,
mock_check_output, mock_exists,
consts.INIT_SYSVINIT)
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
'get_os_init_system', return_value=consts.INIT_UPSTART)
@mock.patch('os.path.exists')
@mock.patch('subprocess.check_output')
@mock.patch('octavia.amphorae.backends.agent.api_server.util.' +
'get_haproxy_pid')
@mock.patch('shutil.rmtree')
@mock.patch('os.remove')
def test_delete_listener_upstart(self, mock_remove, mock_rmtree, mock_pid,
mock_check_output, mock_exists,
mock_init_system):
self._test_delete_listener(mock_remove, mock_rmtree, mock_pid,
mock_check_output, mock_exists,
consts.INIT_UPSTART)
def _test_delete_listener(self, mock_remove, mock_rmtree, mock_pid,
mock_check_output, mock_exists, init_system):
mock_exists.return_value = False
rv = self.app.delete('/' + api_server.VERSION + '/listeners/123')
self.assertEqual(404, rv.status_code)
@ -271,7 +378,19 @@ class TestServerTestCase(base.TestCase):
self.assertEqual({u'message': u'OK'},
json.loads(rv.data.decode('utf-8')))
mock_rmtree.assert_called_with('/var/lib/octavia/123')
mock_exists.assert_called_with('/etc/init/haproxy-123.conf')
if init_system == consts.INIT_SYSTEMD:
mock_exists.assert_called_with(consts.SYSTEMD_DIR +
'/haproxy-123.service')
elif init_system == consts.INIT_UPSTART:
mock_exists.assert_called_with(consts.UPSTART_DIR +
'/haproxy-123.conf')
elif init_system == consts.INIT_SYSVINIT:
mock_exists.assert_called_with(consts.SYSVINIT_DIR +
'/haproxy-123')
else:
self.assertIn(init_system, consts.VALID_INIT_SYSTEMS)
mock_exists.assert_any_call('/var/lib/octavia/123/123.pid')
# service is stopped + upstart script
@ -280,7 +399,18 @@ class TestServerTestCase(base.TestCase):
self.assertEqual(200, rv.status_code)
self.assertEqual({u'message': u'OK'},
json.loads(rv.data.decode('utf-8')))
mock_remove.assert_called_once_with('/etc/init/haproxy-123.conf')
if init_system == consts.INIT_SYSTEMD:
mock_remove.assert_called_with(consts.SYSTEMD_DIR +
'/haproxy-123.service')
elif init_system == consts.INIT_UPSTART:
mock_remove.assert_called_with(consts.UPSTART_DIR +
'/haproxy-123.conf')
elif init_system == consts.INIT_SYSVINIT:
mock_remove.assert_called_with(consts.SYSVINIT_DIR +
'/haproxy-123')
else:
self.assertIn(init_system, consts.VALID_INIT_SYSTEMS)
# service is running + upstart script
mock_exists.side_effect = [True, True, True, True]
@ -290,9 +420,23 @@ class TestServerTestCase(base.TestCase):
self.assertEqual({u'message': u'OK'},
json.loads(rv.data.decode('utf-8')))
mock_pid.assert_called_once_with('123')
mock_check_output.assert_called_once_with(
mock_check_output.assert_any_call(
['/usr/sbin/service', 'haproxy-123', 'stop'], stderr=-2)
if init_system == consts.INIT_SYSTEMD:
mock_check_output.assert_any_call(
"systemctl disable haproxy-123".split(),
stderr=subprocess.STDOUT)
elif init_system == consts.INIT_UPSTART:
mock_remove.assert_any_call(consts.UPSTART_DIR +
'/haproxy-123.conf')
elif init_system == consts.INIT_SYSVINIT:
mock_check_output.assert_any_call(
"insserv -r /etc/init.d/haproxy-123".split(),
stderr=subprocess.STDOUT)
else:
self.assertIn(init_system, consts.VALID_INIT_SYSTEMS)
# service is running + stopping fails
mock_exists.side_effect = [True, True, True]
mock_check_output.side_effect = subprocess.CalledProcessError(
@ -1239,12 +1383,58 @@ class TestServerTestCase(base.TestCase):
content_type='application/json')
self.assertEqual(400, rv.status_code)
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
'get_os_init_system', return_value=consts.INIT_SYSTEMD)
@mock.patch('os.path.exists')
@mock.patch('os.makedirs')
@mock.patch('os.rename')
@mock.patch('subprocess.check_output')
@mock.patch('os.remove')
def test_upload_keepalived_config(self, mock_remove,
mock_rename, mock_makedirs, mock_exists):
def test_upload_keepalived_config_systemd(self, mock_remove,
mock_subprocess, mock_rename,
mock_makedirs, mock_exists,
mock_init_system):
self._test_upload_keepalived_config(mock_remove, mock_subprocess,
mock_rename, mock_makedirs,
mock_exists, mock_init_system,
consts.INIT_SYSTEMD)
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
'get_os_init_system', return_value=consts.INIT_UPSTART)
@mock.patch('os.path.exists')
@mock.patch('os.makedirs')
@mock.patch('os.rename')
@mock.patch('subprocess.check_output')
@mock.patch('os.remove')
def test_upload_keepalived_config_upstart(self, mock_remove,
mock_subprocess, mock_rename,
mock_makedirs, mock_exists,
mock_init_system):
self._test_upload_keepalived_config(mock_remove, mock_subprocess,
mock_rename, mock_makedirs,
mock_exists, mock_init_system,
consts.INIT_UPSTART)
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
'get_os_init_system', return_value=consts.INIT_SYSVINIT)
@mock.patch('os.path.exists')
@mock.patch('os.makedirs')
@mock.patch('os.rename')
@mock.patch('subprocess.check_output')
@mock.patch('os.remove')
def test_upload_keepalived_config_sysvinit(self, mock_remove,
mock_subprocess, mock_rename,
mock_makedirs, mock_exists,
mock_init_system):
self._test_upload_keepalived_config(mock_remove, mock_subprocess,
mock_rename, mock_makedirs,
mock_exists, mock_init_system,
consts.INIT_SYSVINIT)
def _test_upload_keepalived_config(self, mock_remove, mock_subprocess,
mock_rename, mock_makedirs,
mock_exists, mock_init_system,
init_system):
flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC

View File

@ -0,0 +1,8 @@
---
features:
- Adds support for amphora images that use systemd.
- Add support for Ubuntu Xenial amphora images.
deprecations:
- The "use_upstart" configuration option is now deprecated
because the amphora agent can now automatically discover
the init system in use in the amphora image.