Allow choice of upstart or sysvinit
upstart was hardcoded as the means of start, stopping and reloading of haproxy. Allow for sysvinit scripts and paths to handle haproxy. This patch provides a configuration option to switch between the defaultl upstart init scripts or sysvinit. Change-Id: I9efe51c5a08d8e2268150d69ac25725c708dfb8e
This commit is contained in:
parent
9d06f43a1a
commit
8e31a1d044
@ -112,6 +112,7 @@
|
|||||||
# respawn_interval = 2
|
# respawn_interval = 2
|
||||||
# Change for production to a ram drive
|
# Change for production to a ram drive
|
||||||
# haproxy_cert_dir = /tmp
|
# haproxy_cert_dir = /tmp
|
||||||
|
# use_upstart = True
|
||||||
|
|
||||||
# Maximum number of entries that can fit in the stick table.
|
# Maximum number of entries that can fit in the stick table.
|
||||||
# The size supports "k", "m", "g" suffixes.
|
# The size supports "k", "m", "g" suffixes.
|
||||||
|
@ -55,5 +55,6 @@ class AgentJinjaTemplater(object):
|
|||||||
'haproxy_cmd': CONF.haproxy_amphora.haproxy_cmd,
|
'haproxy_cmd': CONF.haproxy_amphora.haproxy_cmd,
|
||||||
'heartbeat_interval': CONF.health_manager.heartbeat_interval,
|
'heartbeat_interval': CONF.health_manager.heartbeat_interval,
|
||||||
'heartbeat_key': CONF.health_manager.heartbeat_key,
|
'heartbeat_key': CONF.health_manager.heartbeat_key,
|
||||||
|
'use_upstart': CONF.haproxy_amphora.use_upstart,
|
||||||
'respawn_count': CONF.haproxy_amphora.respawn_count,
|
'respawn_count': CONF.haproxy_amphora.respawn_count,
|
||||||
'respawn_interval': CONF.haproxy_amphora.respawn_interval})
|
'respawn_interval': CONF.haproxy_amphora.respawn_interval})
|
||||||
|
@ -32,11 +32,16 @@ from octavia.common import utils as octavia_utils
|
|||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
BUFFER = 100
|
BUFFER = 100
|
||||||
HAPROXY_CONF = 'haproxy.conf.j2'
|
|
||||||
|
|
||||||
j2_env = jinja2.Environment(loader=jinja2.FileSystemLoader(
|
UPSTART_CONF = 'upstart.conf.j2'
|
||||||
os.path.dirname(os.path.realpath(__file__)) + consts.AGENT_API_TEMPLATES))
|
SYSVINIT_CONF = 'sysvinit.conf.j2'
|
||||||
template = j2_env.get_template(HAPROXY_CONF)
|
|
||||||
|
JINJA_ENV = jinja2.Environment(
|
||||||
|
loader=jinja2.FileSystemLoader(os.path.dirname(
|
||||||
|
os.path.realpath(__file__)
|
||||||
|
) + consts.AGENT_API_TEMPLATES))
|
||||||
|
UPSTART_TEMPLATE = JINJA_ENV.get_template(UPSTART_CONF)
|
||||||
|
SYSVINIT_TEMPLATE = JINJA_ENV.get_template(SYSVINIT_CONF)
|
||||||
|
|
||||||
|
|
||||||
class ParsingError(Exception):
|
class ParsingError(Exception):
|
||||||
@ -115,8 +120,10 @@ def upload_haproxy_config(amphora_id, listener_id):
|
|||||||
# file ok - move it
|
# file ok - move it
|
||||||
os.rename(name, util.config_path(listener_id))
|
os.rename(name, util.config_path(listener_id))
|
||||||
|
|
||||||
if not os.path.exists(util.upstart_path(listener_id)):
|
use_upstart = util.CONF.haproxy_amphora.use_upstart
|
||||||
with open(util.upstart_path(listener_id), 'w') as text_file:
|
if not os.path.exists(util.init_path(listener_id)):
|
||||||
|
with open(util.init_path(listener_id), 'w') as text_file:
|
||||||
|
template = UPSTART_TEMPLATE if use_upstart else SYSVINIT_TEMPLATE
|
||||||
text = template.render(
|
text = template.render(
|
||||||
peer_name=peer_name,
|
peer_name=peer_name,
|
||||||
haproxy_pid=util.pid_path(listener_id),
|
haproxy_pid=util.pid_path(listener_id),
|
||||||
@ -127,6 +134,22 @@ def upload_haproxy_config(amphora_id, listener_id):
|
|||||||
)
|
)
|
||||||
text_file.write(text)
|
text_file.write(text)
|
||||||
|
|
||||||
|
if not use_upstart:
|
||||||
|
# make init.d script executable
|
||||||
|
file = util.init_path(listener_id)
|
||||||
|
permcmd = ("chmod 755 {file}".format(file=file))
|
||||||
|
insrvcmd = ("insserv {file}".format(file=file))
|
||||||
|
|
||||||
|
try:
|
||||||
|
subprocess.check_output(permcmd.split(), stderr=subprocess.STDOUT)
|
||||||
|
subprocess.check_output(insrvcmd.split(), stderr=subprocess.STDOUT)
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
LOG.debug("Failed to make %(file)s executable: %(err)s",
|
||||||
|
{'file': file, 'err': e})
|
||||||
|
return flask.make_response(flask.jsonify(dict(
|
||||||
|
message="Error making file {0} executable".format(file),
|
||||||
|
details=e.output)), 500)
|
||||||
|
|
||||||
res = flask.make_response(flask.jsonify({
|
res = flask.make_response(flask.jsonify({
|
||||||
'message': 'OK'}), 202)
|
'message': 'OK'}), 202)
|
||||||
res.headers['ETag'] = stream.get_md5()
|
res.headers['ETag'] = stream.get_md5()
|
||||||
@ -203,10 +226,10 @@ def delete_listener(listener_id):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# delete the directory + upstart script for that listener
|
# delete the directory + init script for that listener
|
||||||
shutil.rmtree(util.haproxy_dir(listener_id))
|
shutil.rmtree(util.haproxy_dir(listener_id))
|
||||||
if os.path.exists(util.upstart_path(listener_id)):
|
if os.path.exists(util.init_path(listener_id)):
|
||||||
os.remove(util.upstart_path(listener_id))
|
os.remove(util.init_path(listener_id))
|
||||||
|
|
||||||
return flask.jsonify({'message': 'OK'})
|
return flask.jsonify({'message': 'OK'})
|
||||||
|
|
||||||
|
@ -0,0 +1,217 @@
|
|||||||
|
{#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
# Inspired by https://gist.github.com/gfrey/8472007
|
||||||
|
#}
|
||||||
|
#!/bin/sh
|
||||||
|
### BEGIN INIT INFO
|
||||||
|
# Provides: octavia-amp-{{ haproxy_pid }}
|
||||||
|
# Required-Start: $local_fs $network
|
||||||
|
# Required-Stop: $local_fs
|
||||||
|
# Default-Start: 2 3 4 5
|
||||||
|
# Default-Stop: 0 1 6
|
||||||
|
# Short-Description: fast and reliable load balancing reverse proxy
|
||||||
|
# Description: This file should be used to start and stop haproxy.
|
||||||
|
### END INIT INFO
|
||||||
|
|
||||||
|
|
||||||
|
PATH=/sbin:/usr/sbin:/bin:/usr/bin
|
||||||
|
PIDFILE={{ haproxy_pid }}
|
||||||
|
CONFIG={{ haproxy_cfg }}
|
||||||
|
HAPROXY={{ haproxy_cmd }}
|
||||||
|
EXTRAOPTS=
|
||||||
|
ENABLED=1
|
||||||
|
|
||||||
|
test -x $HAPROXY || exit 0
|
||||||
|
test -f "$CONFIG" || exit 0
|
||||||
|
|
||||||
|
if [ -e /etc/default/haproxy ]; then
|
||||||
|
. /etc/default/haproxy
|
||||||
|
fi
|
||||||
|
|
||||||
|
test "$ENABLED" != "0" || exit 0
|
||||||
|
|
||||||
|
[ -f /etc/default/rcS ] && . /etc/default/rcS
|
||||||
|
. /lib/lsb/init-functions
|
||||||
|
|
||||||
|
|
||||||
|
haproxy_start()
|
||||||
|
{
|
||||||
|
start-stop-daemon --start --pidfile "$PIDFILE" \
|
||||||
|
--exec $HAPROXY -- -f "$CONFIG" -D -p "$PIDFILE" \
|
||||||
|
$EXTRAOPTS || return 2
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
haproxy_stop()
|
||||||
|
{
|
||||||
|
if [ ! -f $PIDFILE ] ; then
|
||||||
|
# This is a success according to LSB
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
for pid in $(cat $PIDFILE) ; do
|
||||||
|
/bin/kill $pid || return 4
|
||||||
|
done
|
||||||
|
rm -f $PIDFILE
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
haproxy_reload()
|
||||||
|
{
|
||||||
|
$HAPROXY -f "$CONFIG" -p $PIDFILE -D $EXTRAOPTS -sf $(cat $PIDFILE) \
|
||||||
|
|| return 2
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
haproxy_checkconf()
|
||||||
|
{
|
||||||
|
rcode=0
|
||||||
|
|
||||||
|
$HAPROXY -c -f "$CONFIG"
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
rcode=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return $rcode
|
||||||
|
}
|
||||||
|
|
||||||
|
haproxy_status()
|
||||||
|
{
|
||||||
|
if [ ! -f $PIDFILE ] ; then
|
||||||
|
# program not running
|
||||||
|
return 3
|
||||||
|
fi
|
||||||
|
|
||||||
|
for pid in $(cat $PIDFILE) ; do
|
||||||
|
if ! ps --no-headers p "$pid" | grep haproxy > /dev/null ; then
|
||||||
|
# program running, bogus pidfile
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
checkconf)
|
||||||
|
haproxy_checkconf
|
||||||
|
exit $?
|
||||||
|
;;
|
||||||
|
start)
|
||||||
|
log_daemon_msg "Starting haproxy" "haproxy"
|
||||||
|
haproxy_start
|
||||||
|
ret=$?
|
||||||
|
case "$ret" in
|
||||||
|
0)
|
||||||
|
log_end_msg 0
|
||||||
|
;;
|
||||||
|
1)
|
||||||
|
log_end_msg 1
|
||||||
|
echo "pid file '$PIDFILE' found, haproxy not started."
|
||||||
|
;;
|
||||||
|
2)
|
||||||
|
log_end_msg 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
exit $ret
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
log_daemon_msg "Stopping haproxy" "haproxy"
|
||||||
|
haproxy_stop
|
||||||
|
ret=$?
|
||||||
|
case "$ret" in
|
||||||
|
0|1)
|
||||||
|
log_end_msg 0
|
||||||
|
;;
|
||||||
|
2)
|
||||||
|
log_end_msg 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
exit $ret
|
||||||
|
;;
|
||||||
|
reload|force-reload)
|
||||||
|
echo "Checking HAProxy configuration first"
|
||||||
|
haproxy_checkconf
|
||||||
|
case "$?" in
|
||||||
|
0)
|
||||||
|
echo "Everything looks fine"
|
||||||
|
;;
|
||||||
|
1)
|
||||||
|
echo "Errors..."
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
log_daemon_msg "Reloading haproxy" "haproxy"
|
||||||
|
haproxy_reload
|
||||||
|
case "$?" in
|
||||||
|
0|1)
|
||||||
|
log_end_msg 0
|
||||||
|
;;
|
||||||
|
2)
|
||||||
|
log_end_msg 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
restart)
|
||||||
|
echo "Checking HAProxy configuration first"
|
||||||
|
haproxy_checkconf
|
||||||
|
case "$?" in
|
||||||
|
0)
|
||||||
|
echo "Everything looks fine"
|
||||||
|
;;
|
||||||
|
1)
|
||||||
|
echo "Errors..."
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
log_daemon_msg "Restarting haproxy" "haproxy"
|
||||||
|
haproxy_stop
|
||||||
|
haproxy_start
|
||||||
|
case "$?" in
|
||||||
|
0)
|
||||||
|
log_end_msg 0
|
||||||
|
;;
|
||||||
|
1)
|
||||||
|
log_end_msg 1
|
||||||
|
;;
|
||||||
|
2)
|
||||||
|
log_end_msg 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
status)
|
||||||
|
haproxy_status
|
||||||
|
ret=$?
|
||||||
|
case "$ret" in
|
||||||
|
0)
|
||||||
|
echo "haproxy is running."
|
||||||
|
;;
|
||||||
|
1)
|
||||||
|
echo "haproxy dead, but $PIDFILE exists."
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "haproxy not running."
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
exit $ret
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: /etc/init.d/haproxy {start|stop|reload|restart|status|checkconf}"
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
:
|
@ -20,12 +20,17 @@ from oslo_config import cfg
|
|||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
CONF.import_group('amphora_agent', 'octavia.common.config')
|
CONF.import_group('amphora_agent', 'octavia.common.config')
|
||||||
CONF.import_group('haproxy_amphora', 'octavia.common.config')
|
CONF.import_group('haproxy_amphora', 'octavia.common.config')
|
||||||
|
|
||||||
UPSTART_DIR = '/etc/init'
|
UPSTART_DIR = '/etc/init'
|
||||||
KEEPALIVED_INIT_DIR = '/etc/init.d'
|
KEEPALIVED_INIT_DIR = '/etc/init.d'
|
||||||
|
SYSVINIT_DIR = '/etc/init.d'
|
||||||
|
|
||||||
|
|
||||||
def upstart_path(listener_id):
|
def init_path(listener_id):
|
||||||
return os.path.join(UPSTART_DIR, ('haproxy-{0}.conf'.format(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 haproxy_dir(listener_id):
|
def haproxy_dir(listener_id):
|
||||||
|
@ -23,6 +23,7 @@ bind_port = {{ bind_port }}
|
|||||||
haproxy_cmd = {{ haproxy_cmd }}
|
haproxy_cmd = {{ haproxy_cmd }}
|
||||||
respawn_count = {{ respawn_count }}
|
respawn_count = {{ respawn_count }}
|
||||||
respawn_interval = {{ respawn_interval }}
|
respawn_interval = {{ respawn_interval }}
|
||||||
|
use_upstart = {{ use_upstart }}
|
||||||
|
|
||||||
[health_manager]
|
[health_manager]
|
||||||
controller_ip_port_list = {{ controller_list|join(', ') }}
|
controller_ip_port_list = {{ controller_list|join(', ') }}
|
||||||
|
@ -200,6 +200,8 @@ haproxy_amphora_opts = [
|
|||||||
help=_("The client certificate to talk to the agent")),
|
help=_("The client certificate to talk to the agent")),
|
||||||
cfg.StrOpt('server_ca', default='/etc/octavia/certs/server_ca.pem',
|
cfg.StrOpt('server_ca', default='/etc/octavia/certs/server_ca.pem',
|
||||||
help=_("The ca which signed the server certificates")),
|
help=_("The ca which signed the server certificates")),
|
||||||
|
cfg.BoolOpt('use_upstart', default=True,
|
||||||
|
help=_("If False, use sysvinit.")),
|
||||||
]
|
]
|
||||||
|
|
||||||
controller_worker_opts = [
|
controller_worker_opts = [
|
||||||
|
@ -0,0 +1,713 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
import mock
|
||||||
|
import netifaces
|
||||||
|
from oslo_config import cfg
|
||||||
|
import six
|
||||||
|
|
||||||
|
from octavia.amphorae.backends.agent import api_server
|
||||||
|
from octavia.amphorae.backends.agent.api_server import certificate_update
|
||||||
|
from octavia.amphorae.backends.agent.api_server import server
|
||||||
|
from octavia.amphorae.backends.agent.api_server import util
|
||||||
|
from octavia.common import constants as consts
|
||||||
|
from octavia.common import utils as octavia_utils
|
||||||
|
import octavia.tests.unit.base as base
|
||||||
|
|
||||||
|
RANDOM_ERROR = 'random error'
|
||||||
|
OK = dict(message='OK')
|
||||||
|
|
||||||
|
if six.PY2:
|
||||||
|
import __builtin__ as builtins
|
||||||
|
else:
|
||||||
|
import builtins
|
||||||
|
|
||||||
|
|
||||||
|
class ServerTestCase(base.TestCase):
|
||||||
|
app = None
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
cfg.CONF.set_override('use_upstart', False, group='haproxy_amphora')
|
||||||
|
self.app = server.app.test_client()
|
||||||
|
super(ServerTestCase, self).setUp()
|
||||||
|
|
||||||
|
@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):
|
||||||
|
mock_exists.return_value = True
|
||||||
|
m = mock.mock_open()
|
||||||
|
|
||||||
|
# happy case init file exists
|
||||||
|
with mock.patch.object(builtins, 'open', m):
|
||||||
|
rv = self.app.put('/' + api_server.VERSION +
|
||||||
|
'/listeners/amp_123/123/haproxy',
|
||||||
|
data='test')
|
||||||
|
self.assertEqual(202, rv.status_code)
|
||||||
|
m.assert_called_once_with(
|
||||||
|
'/var/lib/octavia/123/haproxy.cfg.new', 'w')
|
||||||
|
handle = m()
|
||||||
|
handle.write.assert_called_once_with(six.b('test'))
|
||||||
|
calls = [
|
||||||
|
mock.call("haproxy -c -L {peer} -f {config_file}".format(
|
||||||
|
config_file='/var/lib/octavia/123/haproxy.cfg.new',
|
||||||
|
peer=(octavia_utils.base64_sha1_string('amp_123')
|
||||||
|
.rstrip('='))).split(), stderr=-2),
|
||||||
|
mock.call(['chmod', '755', '/etc/init.d/haproxy-123'],
|
||||||
|
stderr=-2),
|
||||||
|
mock.call(['insserv', '/etc/init.d/haproxy-123'], stderr=-2)
|
||||||
|
]
|
||||||
|
mock_subprocess.assert_has_calls(calls)
|
||||||
|
mock_rename.assert_called_once_with(
|
||||||
|
'/var/lib/octavia/123/haproxy.cfg.new',
|
||||||
|
'/var/lib/octavia/123/haproxy.cfg')
|
||||||
|
|
||||||
|
# exception writing
|
||||||
|
m = mock.mock_open()
|
||||||
|
m.side_effect = IOError() # open crashes
|
||||||
|
with mock.patch.object(builtins, 'open', m):
|
||||||
|
rv = self.app.put('/' + api_server.VERSION +
|
||||||
|
'/listeners/amp_123/123/haproxy',
|
||||||
|
data='test')
|
||||||
|
self.assertEqual(500, rv.status_code)
|
||||||
|
|
||||||
|
# check if files get created
|
||||||
|
mock_exists.return_value = False
|
||||||
|
m = mock.mock_open()
|
||||||
|
|
||||||
|
# happy case init file exists
|
||||||
|
with mock.patch.object(builtins, 'open', m):
|
||||||
|
rv = self.app.put('/' + api_server.VERSION +
|
||||||
|
'/listeners/amp_123/123/haproxy',
|
||||||
|
data='test')
|
||||||
|
self.assertEqual(202, rv.status_code)
|
||||||
|
m.assert_any_call('/var/lib/octavia/123/haproxy.cfg.new', 'w')
|
||||||
|
m.assert_any_call(util.SYSVINIT_DIR + '/haproxy-123', 'w')
|
||||||
|
handle = m()
|
||||||
|
handle.write.assert_any_call(six.b('test'))
|
||||||
|
# skip the template stuff
|
||||||
|
mock_makedirs.assert_called_with('/var/lib/octavia/123')
|
||||||
|
|
||||||
|
# unhappy case haproxy check fails
|
||||||
|
mock_exists.return_value = True
|
||||||
|
mock_subprocess.side_effect = [subprocess.CalledProcessError(
|
||||||
|
7, 'test', RANDOM_ERROR)]
|
||||||
|
with mock.patch.object(builtins, 'open', m):
|
||||||
|
rv = self.app.put('/' + api_server.VERSION +
|
||||||
|
'/listeners/amp_123/123/haproxy',
|
||||||
|
data='test')
|
||||||
|
self.assertEqual(400, rv.status_code)
|
||||||
|
self.assertEqual(
|
||||||
|
{'message': 'Invalid request', u'details': u'random error'},
|
||||||
|
json.loads(rv.data.decode('utf-8')))
|
||||||
|
m.assert_called_with('/var/lib/octavia/123/haproxy.cfg.new', 'w')
|
||||||
|
handle = m()
|
||||||
|
handle.write.assert_called_with(six.b('test'))
|
||||||
|
mock_subprocess.assert_called_with(
|
||||||
|
"haproxy -c -L {peer} -f {config_file}".format(
|
||||||
|
config_file='/var/lib/octavia/123/haproxy.cfg.new',
|
||||||
|
peer=(octavia_utils.
|
||||||
|
base64_sha1_string('amp_123').rstrip('='))).split(),
|
||||||
|
stderr=-2)
|
||||||
|
mock_remove.assert_called_once_with(
|
||||||
|
'/var/lib/octavia/123/haproxy.cfg.new')
|
||||||
|
|
||||||
|
@mock.patch('os.path.exists')
|
||||||
|
@mock.patch('octavia.amphorae.backends.agent.api_server.listener.'
|
||||||
|
'vrrp_check_script_update')
|
||||||
|
@mock.patch('subprocess.check_output')
|
||||||
|
def test_start(self, mock_subprocess, mock_vrrp, mock_exists):
|
||||||
|
rv = self.app.put('/' + api_server.VERSION + '/listeners/123/error')
|
||||||
|
self.assertEqual(400, rv.status_code)
|
||||||
|
self.assertEqual(
|
||||||
|
{'message': 'Invalid Request',
|
||||||
|
'details': 'Unknown action: error', },
|
||||||
|
json.loads(rv.data.decode('utf-8')))
|
||||||
|
|
||||||
|
mock_exists.return_value = False
|
||||||
|
rv = self.app.put('/' + api_server.VERSION + '/listeners/123/start')
|
||||||
|
self.assertEqual(404, rv.status_code)
|
||||||
|
self.assertEqual(
|
||||||
|
{'message': 'Listener Not Found',
|
||||||
|
'details': 'No listener with UUID: 123'},
|
||||||
|
json.loads(rv.data.decode('utf-8')))
|
||||||
|
mock_exists.assert_called_with('/var/lib/octavia/123/haproxy.cfg')
|
||||||
|
|
||||||
|
mock_exists.return_value = True
|
||||||
|
rv = self.app.put('/' + api_server.VERSION + '/listeners/123/start')
|
||||||
|
self.assertEqual(202, rv.status_code)
|
||||||
|
self.assertEqual(
|
||||||
|
{'message': 'OK',
|
||||||
|
'details': 'Configuration file is valid\nhaproxy daemon for'
|
||||||
|
+ ' 123 started'},
|
||||||
|
json.loads(rv.data.decode('utf-8')))
|
||||||
|
mock_subprocess.assert_called_with(
|
||||||
|
['/usr/sbin/service', 'haproxy-123', 'start'], stderr=-2)
|
||||||
|
|
||||||
|
mock_exists.return_value = True
|
||||||
|
mock_subprocess.side_effect = subprocess.CalledProcessError(
|
||||||
|
7, 'test', RANDOM_ERROR)
|
||||||
|
rv = self.app.put('/' + api_server.VERSION + '/listeners/123/start')
|
||||||
|
self.assertEqual(500, rv.status_code)
|
||||||
|
self.assertEqual(
|
||||||
|
{
|
||||||
|
'message': 'Error starting haproxy',
|
||||||
|
'details': RANDOM_ERROR,
|
||||||
|
}, json.loads(rv.data.decode('utf-8')))
|
||||||
|
mock_subprocess.assert_called_with(
|
||||||
|
['/usr/sbin/service', 'haproxy-123', 'start'], stderr=-2)
|
||||||
|
|
||||||
|
@mock.patch('socket.gethostname')
|
||||||
|
@mock.patch('subprocess.check_output')
|
||||||
|
def test_info(self, mock_subbprocess, mock_hostname):
|
||||||
|
mock_hostname.side_effect = ['test-host']
|
||||||
|
mock_subbprocess.side_effect = [
|
||||||
|
"""Package: haproxy
|
||||||
|
Status: install ok installed
|
||||||
|
Priority: optional
|
||||||
|
Section: net
|
||||||
|
Installed-Size: 803
|
||||||
|
Maintainer: Ubuntu Developers
|
||||||
|
Architecture: amd64
|
||||||
|
Version: 1.4.24-2
|
||||||
|
"""]
|
||||||
|
rv = self.app.get('/' + api_server.VERSION + '/info')
|
||||||
|
|
||||||
|
self.assertEqual(200, rv.status_code)
|
||||||
|
self.assertEqual(dict(
|
||||||
|
api_version='0.5',
|
||||||
|
haproxy_version='1.4.24-2',
|
||||||
|
hostname='test-host'),
|
||||||
|
json.loads(rv.data.decode('utf-8')))
|
||||||
|
|
||||||
|
@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):
|
||||||
|
mock_exists.return_value = False
|
||||||
|
rv = self.app.delete('/' + api_server.VERSION + '/listeners/123')
|
||||||
|
self.assertEqual(404, rv.status_code)
|
||||||
|
self.assertEqual(
|
||||||
|
{'message': 'Listener Not Found',
|
||||||
|
'details': 'No listener with UUID: 123'},
|
||||||
|
json.loads(rv.data.decode('utf-8')))
|
||||||
|
mock_exists.assert_called_with('/var/lib/octavia/123/haproxy.cfg')
|
||||||
|
|
||||||
|
# service is stopped + no init script
|
||||||
|
mock_exists.side_effect = [True, False, False]
|
||||||
|
rv = self.app.delete('/' + api_server.VERSION + '/listeners/123')
|
||||||
|
self.assertEqual(200, rv.status_code)
|
||||||
|
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.d/haproxy-123')
|
||||||
|
mock_exists.assert_any_call('/var/lib/octavia/123/123.pid')
|
||||||
|
|
||||||
|
# service is stopped + init script
|
||||||
|
mock_exists.side_effect = [True, False, True]
|
||||||
|
rv = self.app.delete('/' + api_server.VERSION + '/listeners/123')
|
||||||
|
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.d/haproxy-123')
|
||||||
|
|
||||||
|
# service is running + init script
|
||||||
|
mock_exists.side_effect = [True, True, True, True]
|
||||||
|
mock_pid.return_value = '456'
|
||||||
|
rv = self.app.delete('/' + api_server.VERSION + '/listeners/123')
|
||||||
|
self.assertEqual(200, rv.status_code)
|
||||||
|
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(
|
||||||
|
['/usr/sbin/service', 'haproxy-123', 'stop'], stderr=-2)
|
||||||
|
|
||||||
|
# service is running + stopping fails
|
||||||
|
mock_exists.side_effect = [True, True, True]
|
||||||
|
mock_check_output.side_effect = subprocess.CalledProcessError(
|
||||||
|
7, 'test', RANDOM_ERROR)
|
||||||
|
rv = self.app.delete('/' + api_server.VERSION + '/listeners/123')
|
||||||
|
self.assertEqual(500, rv.status_code)
|
||||||
|
self.assertEqual(
|
||||||
|
{'details': 'random error', 'message': 'Error stopping haproxy'},
|
||||||
|
json.loads(rv.data.decode('utf-8')))
|
||||||
|
# that's the last call before exception
|
||||||
|
mock_exists.assert_called_with('/proc/456')
|
||||||
|
|
||||||
|
@mock.patch('os.path.exists')
|
||||||
|
def test_get_haproxy(self, mock_exists):
|
||||||
|
CONTENT = "bibble\nbibble"
|
||||||
|
mock_exists.side_effect = [False]
|
||||||
|
rv = self.app.get('/' + api_server.VERSION + '/listeners/123/haproxy')
|
||||||
|
self.assertEqual(404, rv.status_code)
|
||||||
|
|
||||||
|
mock_exists.side_effect = [True]
|
||||||
|
m = mock.mock_open(read_data=CONTENT)
|
||||||
|
|
||||||
|
with mock.patch.object(builtins, 'open', m):
|
||||||
|
rv = self.app.get('/' + api_server.VERSION +
|
||||||
|
'/listeners/123/haproxy')
|
||||||
|
self.assertEqual(200, rv.status_code)
|
||||||
|
self.assertEqual(six.b(CONTENT), rv.data)
|
||||||
|
self.assertEqual('text/plain; charset=utf-8',
|
||||||
|
rv.headers['Content-Type'])
|
||||||
|
|
||||||
|
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
|
||||||
|
+ 'get_listeners')
|
||||||
|
@mock.patch('octavia.amphorae.backends.agent.api_server.listener.'
|
||||||
|
+ '_check_listener_status')
|
||||||
|
@mock.patch('octavia.amphorae.backends.agent.api_server.listener.'
|
||||||
|
+ '_parse_haproxy_file')
|
||||||
|
def test_get_all_listeners(self, mock_parse, mock_status, mock_listener):
|
||||||
|
# no listeners
|
||||||
|
mock_listener.side_effect = [[]]
|
||||||
|
rv = self.app.get('/' + api_server.VERSION + '/listeners')
|
||||||
|
|
||||||
|
self.assertEqual(200, rv.status_code)
|
||||||
|
self.assertFalse(json.loads(rv.data.decode('utf-8')))
|
||||||
|
|
||||||
|
# one listener ACTIVE
|
||||||
|
mock_listener.side_effect = [['123']]
|
||||||
|
mock_parse.side_effect = [{'mode': 'test'}]
|
||||||
|
mock_status.side_effect = [consts.ACTIVE]
|
||||||
|
rv = self.app.get('/' + api_server.VERSION + '/listeners')
|
||||||
|
|
||||||
|
self.assertEqual(200, rv.status_code)
|
||||||
|
self.assertEqual(
|
||||||
|
[{'status': consts.ACTIVE, 'type': 'test', 'uuid': '123'}],
|
||||||
|
json.loads(rv.data.decode('utf-8')))
|
||||||
|
|
||||||
|
# two listener one ACTIVE, one ERROR
|
||||||
|
mock_listener.side_effect = [['123', '456']]
|
||||||
|
mock_parse.side_effect = [{'mode': 'test'}, {'mode': 'http'}]
|
||||||
|
mock_status.side_effect = [consts.ACTIVE, consts.ERROR]
|
||||||
|
rv = self.app.get('/' + api_server.VERSION + '/listeners')
|
||||||
|
|
||||||
|
self.assertEqual(200, rv.status_code)
|
||||||
|
self.assertEqual(
|
||||||
|
[{'status': consts.ACTIVE, 'type': 'test', 'uuid': '123'},
|
||||||
|
{'status': consts.ERROR, 'type': 'http', 'uuid': '456'}],
|
||||||
|
json.loads(rv.data.decode('utf-8')))
|
||||||
|
|
||||||
|
@mock.patch('octavia.amphorae.backends.agent.api_server.listener.'
|
||||||
|
+ '_check_listener_status')
|
||||||
|
@mock.patch('octavia.amphorae.backends.agent.api_server.listener.'
|
||||||
|
+ '_parse_haproxy_file')
|
||||||
|
@mock.patch('octavia.amphorae.backends.utils.haproxy_query.HAProxyQuery')
|
||||||
|
@mock.patch('os.path.exists')
|
||||||
|
def test_get_listener(self, mock_exists, mock_query, mock_parse,
|
||||||
|
mock_status):
|
||||||
|
# Listener not found
|
||||||
|
mock_exists.side_effect = [False]
|
||||||
|
rv = self.app.get('/' + api_server.VERSION + '/listeners/123')
|
||||||
|
self.assertEqual(404, rv.status_code)
|
||||||
|
self.assertEqual(
|
||||||
|
{'message': 'Listener Not Found',
|
||||||
|
'details': 'No listener with UUID: 123'},
|
||||||
|
json.loads(rv.data.decode('utf-8')))
|
||||||
|
|
||||||
|
# Listener not ACTIVE
|
||||||
|
mock_parse.side_effect = [dict(mode='test')]
|
||||||
|
mock_status.side_effect = [consts.ERROR]
|
||||||
|
mock_exists.side_effect = [True]
|
||||||
|
rv = self.app.get('/' + api_server.VERSION + '/listeners/123')
|
||||||
|
self.assertEqual(200, rv.status_code)
|
||||||
|
self.assertEqual(dict(
|
||||||
|
status=consts.ERROR,
|
||||||
|
type='test',
|
||||||
|
uuid='123'), json.loads(rv.data.decode('utf-8')))
|
||||||
|
|
||||||
|
# Listener ACTIVE
|
||||||
|
mock_parse.side_effect = [dict(mode='test', stats_socket='blah')]
|
||||||
|
mock_status.side_effect = [consts.ACTIVE]
|
||||||
|
mock_exists.side_effect = [True]
|
||||||
|
mock_pool = mock.Mock()
|
||||||
|
mock_query.side_effect = [mock_pool]
|
||||||
|
mock_pool.get_pool_status.side_effect = [
|
||||||
|
{'tcp-servers': {
|
||||||
|
'status': 'DOWN',
|
||||||
|
'uuid': 'tcp-servers',
|
||||||
|
'members': [
|
||||||
|
{'id-34833': 'DOWN'},
|
||||||
|
{'id-34836': 'DOWN'}]}}]
|
||||||
|
rv = self.app.get('/' + api_server.VERSION + '/listeners/123')
|
||||||
|
self.assertEqual(200, rv.status_code)
|
||||||
|
self.assertEqual(dict(
|
||||||
|
status=consts.ACTIVE,
|
||||||
|
type='test',
|
||||||
|
uuid='123',
|
||||||
|
pools=[dict(
|
||||||
|
status=consts.DOWN,
|
||||||
|
uuid='tcp-servers',
|
||||||
|
members=[
|
||||||
|
{u'id-34833': u'DOWN'},
|
||||||
|
{u'id-34836': u'DOWN'}])]),
|
||||||
|
json.loads(rv.data.decode('utf-8')))
|
||||||
|
|
||||||
|
@mock.patch('os.path.exists')
|
||||||
|
@mock.patch('os.remove')
|
||||||
|
def test_delete_cert(self, mock_remove, mock_exists):
|
||||||
|
mock_exists.side_effect = [False]
|
||||||
|
rv = self.app.delete('/' + api_server.VERSION +
|
||||||
|
'/listeners/123/certificates/test.pem')
|
||||||
|
self.assertEqual(404, rv.status_code)
|
||||||
|
self.assertEqual(dict(
|
||||||
|
details='No certificate with filename: test.pem',
|
||||||
|
message='Certificate Not Found'),
|
||||||
|
json.loads(rv.data.decode('utf-8')))
|
||||||
|
mock_exists.assert_called_once_with(
|
||||||
|
'/var/lib/octavia/certs/123/test.pem')
|
||||||
|
|
||||||
|
# wrong file name
|
||||||
|
mock_exists.side_effect = [True]
|
||||||
|
rv = self.app.put('/' + api_server.VERSION +
|
||||||
|
'/listeners/123/certificates/test.bla',
|
||||||
|
data='TestTest')
|
||||||
|
self.assertEqual(400, rv.status_code)
|
||||||
|
|
||||||
|
mock_exists.side_effect = [True]
|
||||||
|
rv = self.app.delete('/' + api_server.VERSION +
|
||||||
|
'/listeners/123/certificates/test.pem')
|
||||||
|
self.assertEqual(200, rv.status_code)
|
||||||
|
self.assertEqual(OK, json.loads(rv.data.decode('utf-8')))
|
||||||
|
mock_remove.assert_called_once_with(
|
||||||
|
'/var/lib/octavia/certs/123/test.pem')
|
||||||
|
|
||||||
|
@mock.patch('os.path.exists')
|
||||||
|
def test_get_certificate_md5(self, mock_exists):
|
||||||
|
CONTENT = "TestTest"
|
||||||
|
|
||||||
|
mock_exists.side_effect = [False]
|
||||||
|
rv = self.app.get('/' + api_server.VERSION +
|
||||||
|
'/listeners/123/certificates/test.pem')
|
||||||
|
self.assertEqual(404, rv.status_code)
|
||||||
|
self.assertEqual(dict(
|
||||||
|
details='No certificate with filename: test.pem',
|
||||||
|
message='Certificate Not Found'),
|
||||||
|
json.loads(rv.data.decode('utf-8')))
|
||||||
|
mock_exists.assert_called_with('/var/lib/octavia/certs/123/test.pem')
|
||||||
|
|
||||||
|
# wrong file name
|
||||||
|
mock_exists.side_effect = [True]
|
||||||
|
rv = self.app.put('/' + api_server.VERSION +
|
||||||
|
'/listeners/123/certificates/test.bla',
|
||||||
|
data='TestTest')
|
||||||
|
self.assertEqual(400, rv.status_code)
|
||||||
|
|
||||||
|
m = mock.mock_open(read_data=CONTENT)
|
||||||
|
mock_exists.return_value = True
|
||||||
|
mock_exists.side_effect = None
|
||||||
|
with mock.patch.object(builtins, 'open', m):
|
||||||
|
rv = self.app.get('/' + api_server.VERSION +
|
||||||
|
'/listeners/123/certificates/test.pem')
|
||||||
|
self.assertEqual(200, rv.status_code)
|
||||||
|
self.assertEqual(dict(md5sum=hashlib.md5(six.b(CONTENT)).hexdigest()),
|
||||||
|
json.loads(rv.data.decode('utf-8')))
|
||||||
|
|
||||||
|
@mock.patch('os.path.exists')
|
||||||
|
@mock.patch('os.fchmod')
|
||||||
|
@mock.patch('os.makedirs')
|
||||||
|
def test_upload_certificate_md5(self, mock_makedir, mock_chmod,
|
||||||
|
mock_exists):
|
||||||
|
# wrong file name
|
||||||
|
mock_exists.side_effect = [True]
|
||||||
|
rv = self.app.put('/' + api_server.VERSION +
|
||||||
|
'/listeners/123/certificates/test.bla',
|
||||||
|
data='TestTest')
|
||||||
|
self.assertEqual(400, rv.status_code)
|
||||||
|
|
||||||
|
mock_exists.side_effect = [True, True, True]
|
||||||
|
m = mock.mock_open()
|
||||||
|
|
||||||
|
with mock.patch.object(builtins, 'open', m):
|
||||||
|
rv = self.app.put('/' + api_server.VERSION +
|
||||||
|
'/listeners/123/certificates/test.pem',
|
||||||
|
data='TestTest')
|
||||||
|
self.assertEqual(200, rv.status_code)
|
||||||
|
self.assertEqual(OK, json.loads(rv.data.decode('utf-8')))
|
||||||
|
handle = m()
|
||||||
|
handle.write.assert_called_once_with(six.b('TestTest'))
|
||||||
|
mock_chmod.assert_called_once_with(handle.fileno(), 0o600)
|
||||||
|
|
||||||
|
mock_exists.side_effect = [True, False]
|
||||||
|
m = mock.mock_open()
|
||||||
|
|
||||||
|
with mock.patch.object(builtins, 'open', m):
|
||||||
|
rv = self.app.put('/' + api_server.VERSION +
|
||||||
|
'/listeners/123/certificates/test.pem',
|
||||||
|
data='TestTest')
|
||||||
|
self.assertEqual(200, rv.status_code)
|
||||||
|
self.assertEqual(OK, json.loads(rv.data.decode('utf-8')))
|
||||||
|
handle = m()
|
||||||
|
mock_makedir.called_once_with('/var/lib/octavia/123')
|
||||||
|
|
||||||
|
@mock.patch('os.fchmod')
|
||||||
|
def test_upload_server_certificate(self, mock_chmod):
|
||||||
|
certificate_update.BUFFER = 5 # test the while loop
|
||||||
|
m = mock.mock_open()
|
||||||
|
|
||||||
|
with mock.patch.object(builtins, 'open', m):
|
||||||
|
rv = self.app.put('/' + api_server.VERSION +
|
||||||
|
'/certificate',
|
||||||
|
data='TestTest')
|
||||||
|
self.assertEqual(202, rv.status_code)
|
||||||
|
self.assertEqual(OK, json.loads(rv.data.decode('utf-8')))
|
||||||
|
handle = m()
|
||||||
|
handle.write.assert_any_call(six.b('TestT'))
|
||||||
|
handle.write.assert_any_call(six.b('est'))
|
||||||
|
mock_chmod.assert_called_once_with(handle.fileno(), 0o600)
|
||||||
|
|
||||||
|
@mock.patch('netifaces.interfaces')
|
||||||
|
@mock.patch('netifaces.ifaddresses')
|
||||||
|
@mock.patch('subprocess.check_output')
|
||||||
|
def test_plug_network(self, mock_check_output, mock_ifaddress,
|
||||||
|
mock_interfaces):
|
||||||
|
port_info = {'mac_address': '123'}
|
||||||
|
|
||||||
|
# No interface at all
|
||||||
|
mock_interfaces.side_effect = [[]]
|
||||||
|
rv = self.app.post('/' + api_server.VERSION + "/plug/network",
|
||||||
|
content_type='application/json',
|
||||||
|
data=json.dumps(port_info))
|
||||||
|
self.assertEqual(404, rv.status_code)
|
||||||
|
self.assertEqual(dict(details="No suitable network interface found"),
|
||||||
|
json.loads(rv.data.decode('utf-8')))
|
||||||
|
|
||||||
|
# No interface down
|
||||||
|
mock_interfaces.side_effect = [['blah']]
|
||||||
|
mock_ifaddress.side_effect = [[netifaces.AF_INET]]
|
||||||
|
rv = self.app.post('/' + api_server.VERSION + "/plug/network",
|
||||||
|
content_type='application/json',
|
||||||
|
data=json.dumps(port_info))
|
||||||
|
self.assertEqual(404, rv.status_code)
|
||||||
|
self.assertEqual(dict(details="No suitable network interface found"),
|
||||||
|
json.loads(rv.data.decode('utf-8')))
|
||||||
|
mock_ifaddress.assert_called_once_with('blah')
|
||||||
|
|
||||||
|
# One Interface down, Happy Path
|
||||||
|
mock_interfaces.side_effect = [['blah']]
|
||||||
|
mock_ifaddress.side_effect = [[netifaces.AF_LINK],
|
||||||
|
{netifaces.AF_LINK: [{'addr': '123'}]}]
|
||||||
|
m = mock.mock_open()
|
||||||
|
with mock.patch.object(builtins, 'open', m):
|
||||||
|
rv = self.app.post('/' + api_server.VERSION + "/plug/network",
|
||||||
|
content_type='application/json',
|
||||||
|
data=json.dumps(port_info))
|
||||||
|
self.assertEqual(202, rv.status_code)
|
||||||
|
m.assert_called_once_with(
|
||||||
|
'/etc/network/interfaces.d/blah.cfg', 'w')
|
||||||
|
handle = m()
|
||||||
|
handle.write.assert_called_once_with(
|
||||||
|
'\n# Generated by Octavia agent\n'
|
||||||
|
'auto blah blah:0\n'
|
||||||
|
'iface blah inet dhcp')
|
||||||
|
mock_check_output.assert_called_with(
|
||||||
|
['ifup', 'blah'], stderr=-2)
|
||||||
|
|
||||||
|
# same as above but ifup fails
|
||||||
|
mock_interfaces.side_effect = [['blah']]
|
||||||
|
mock_ifaddress.side_effect = [[netifaces.AF_LINK],
|
||||||
|
{netifaces.AF_LINK: [{'addr': '123'}]}]
|
||||||
|
mock_check_output.side_effect = [subprocess.CalledProcessError(
|
||||||
|
7, 'test', RANDOM_ERROR), subprocess.CalledProcessError(
|
||||||
|
7, 'test', RANDOM_ERROR)]
|
||||||
|
m = mock.mock_open()
|
||||||
|
with mock.patch.object(builtins, 'open', m):
|
||||||
|
rv = self.app.post('/' + api_server.VERSION + "/plug/network",
|
||||||
|
content_type='application/json',
|
||||||
|
data=json.dumps(port_info))
|
||||||
|
self.assertEqual(500, rv.status_code)
|
||||||
|
self.assertEqual(
|
||||||
|
{'details': RANDOM_ERROR,
|
||||||
|
'message': 'Error plugging network'},
|
||||||
|
json.loads(rv.data.decode('utf-8')))
|
||||||
|
|
||||||
|
@mock.patch('netifaces.interfaces')
|
||||||
|
@mock.patch('netifaces.ifaddresses')
|
||||||
|
@mock.patch('subprocess.check_output')
|
||||||
|
@mock.patch('pyroute2.IPRoute')
|
||||||
|
def test_plug_VIP(self, mock_pyroute2, mock_check_output, mock_ifaddress,
|
||||||
|
mock_interfaces):
|
||||||
|
|
||||||
|
subnet_info = {'subnet_cidr': '10.0.0.0/24',
|
||||||
|
'gateway': '10.0.0.1',
|
||||||
|
'mac_address': '123'}
|
||||||
|
|
||||||
|
# malformated ip
|
||||||
|
rv = self.app.post('/' + api_server.VERSION + '/plug/vip/error',
|
||||||
|
data=json.dumps(subnet_info),
|
||||||
|
content_type='application/json')
|
||||||
|
self.assertEqual(400, rv.status_code)
|
||||||
|
|
||||||
|
# No subnet info
|
||||||
|
rv = self.app.post('/' + api_server.VERSION + '/plug/vip/error')
|
||||||
|
self.assertEqual(400, rv.status_code)
|
||||||
|
|
||||||
|
# No interface at all
|
||||||
|
mock_interfaces.side_effect = [[]]
|
||||||
|
rv = self.app.post('/' + api_server.VERSION + "/plug/vip/203.0.113.2",
|
||||||
|
content_type='application/json',
|
||||||
|
data=json.dumps(subnet_info))
|
||||||
|
self.assertEqual(404, rv.status_code)
|
||||||
|
self.assertEqual(dict(details="No suitable network interface found"),
|
||||||
|
json.loads(rv.data.decode('utf-8')))
|
||||||
|
|
||||||
|
# Two interfaces down
|
||||||
|
mock_interfaces.side_effect = [['blah', 'blah2']]
|
||||||
|
mock_ifaddress.side_effect = [['blabla'], ['blabla']]
|
||||||
|
rv = self.app.post('/' + api_server.VERSION + "/plug/vip/203.0.113.2",
|
||||||
|
content_type='application/json',
|
||||||
|
data=json.dumps(subnet_info))
|
||||||
|
self.assertEqual(404, rv.status_code)
|
||||||
|
self.assertEqual(dict(details="No suitable network interface found"),
|
||||||
|
json.loads(rv.data.decode('utf-8')))
|
||||||
|
|
||||||
|
# One Interface down, Happy Path
|
||||||
|
mock_interfaces.side_effect = [['blah']]
|
||||||
|
mock_ifaddress.side_effect = [[netifaces.AF_LINK],
|
||||||
|
{netifaces.AF_LINK: [{'addr': '123'}]}]
|
||||||
|
m = mock.mock_open()
|
||||||
|
with mock.patch.object(builtins, 'open', m):
|
||||||
|
rv = self.app.post('/' + api_server.VERSION +
|
||||||
|
"/plug/vip/203.0.113.2",
|
||||||
|
content_type='application/json',
|
||||||
|
data=json.dumps(subnet_info))
|
||||||
|
self.assertEqual(202, rv.status_code)
|
||||||
|
m.assert_called_once_with(
|
||||||
|
'/etc/network/interfaces.d/blah.cfg', 'w')
|
||||||
|
handle = m()
|
||||||
|
handle.write.assert_called_once_with(
|
||||||
|
'\n# Generated by Octavia agent\n'
|
||||||
|
'auto blah blah:0\n'
|
||||||
|
'iface blah inet dhcp\n'
|
||||||
|
'iface blah:0 inet static\n'
|
||||||
|
'address 203.0.113.2\n'
|
||||||
|
'broadcast 203.0.113.255\n'
|
||||||
|
'netmask 255.255.255.0')
|
||||||
|
mock_check_output.assert_called_with(
|
||||||
|
['ifup', 'blah:0'], stderr=-2)
|
||||||
|
|
||||||
|
mock_interfaces.side_effect = [['blah']]
|
||||||
|
mock_ifaddress.side_effect = [[netifaces.AF_LINK],
|
||||||
|
{netifaces.AF_LINK: [{'addr': '123'}]}]
|
||||||
|
mock_check_output.side_effect = [
|
||||||
|
'unplug1',
|
||||||
|
subprocess.CalledProcessError(
|
||||||
|
7, 'test', RANDOM_ERROR), subprocess.CalledProcessError(
|
||||||
|
7, 'test', RANDOM_ERROR)]
|
||||||
|
m = mock.mock_open()
|
||||||
|
with mock.patch.object(builtins, 'open', m):
|
||||||
|
rv = self.app.post('/' + api_server.VERSION +
|
||||||
|
"/plug/vip/203.0.113.2",
|
||||||
|
content_type='application/json',
|
||||||
|
data=json.dumps(subnet_info))
|
||||||
|
self.assertEqual(500, rv.status_code)
|
||||||
|
self.assertEqual(
|
||||||
|
{'details': RANDOM_ERROR,
|
||||||
|
'message': 'Error plugging VIP'},
|
||||||
|
json.loads(rv.data.decode('utf-8')))
|
||||||
|
|
||||||
|
@mock.patch('netifaces.ifaddresses')
|
||||||
|
@mock.patch('netifaces.interfaces')
|
||||||
|
def test_get_interface(self, mock_interfaces, mock_ifaddresses):
|
||||||
|
interface_res = {'interface': 'eth0'}
|
||||||
|
mock_interfaces.return_value = ['lo', 'eth0']
|
||||||
|
mock_ifaddresses.return_value = {
|
||||||
|
17: [{'addr': '00:00:00:00:00:00'}],
|
||||||
|
2: [{'addr': '203.0.113.2'}],
|
||||||
|
10: [{'addr': '::1'}]}
|
||||||
|
rv = self.app.get('/' + api_server.VERSION + '/interface/203.0.113.2',
|
||||||
|
data=json.dumps(interface_res),
|
||||||
|
content_type='application/json')
|
||||||
|
self.assertEqual(200, rv.status_code)
|
||||||
|
|
||||||
|
rv = self.app.get('/' + api_server.VERSION + '/interface/::1',
|
||||||
|
data=json.dumps(interface_res),
|
||||||
|
content_type='application/json')
|
||||||
|
self.assertEqual(200, rv.status_code)
|
||||||
|
|
||||||
|
rv = self.app.get('/' + api_server.VERSION + '/interface/10.0.0.1',
|
||||||
|
data=json.dumps(interface_res),
|
||||||
|
content_type='application/json')
|
||||||
|
self.assertEqual(404, rv.status_code)
|
||||||
|
|
||||||
|
rv = self.app.get('/' + api_server.VERSION +
|
||||||
|
'/interface/00:00:00:00:00:00',
|
||||||
|
data=json.dumps(interface_res),
|
||||||
|
content_type='application/json')
|
||||||
|
self.assertEqual(500, rv.status_code)
|
||||||
|
|
||||||
|
@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_subprocess,
|
||||||
|
mock_rename, mock_makedirs, mock_exists):
|
||||||
|
|
||||||
|
mock_exists.return_value = True
|
||||||
|
m = mock.mock_open()
|
||||||
|
with mock.patch.object(builtins, 'open', m):
|
||||||
|
rv = self.app.put('/' + api_server.VERSION + '/vrrp/upload',
|
||||||
|
data='test')
|
||||||
|
self.assertEqual(200, rv.status_code)
|
||||||
|
|
||||||
|
mock_exists.return_value = False
|
||||||
|
m = mock.mock_open()
|
||||||
|
with mock.patch.object(builtins, 'open', m):
|
||||||
|
rv = self.app.put('/' + api_server.VERSION + '/vrrp/upload',
|
||||||
|
data='test')
|
||||||
|
self.assertEqual(200, rv.status_code)
|
||||||
|
|
||||||
|
mock_subprocess.side_effect = subprocess.CalledProcessError(1,
|
||||||
|
'blah!')
|
||||||
|
|
||||||
|
with mock.patch.object(builtins, 'open', m):
|
||||||
|
rv = self.app.put('/' + api_server.VERSION + '/vrrp/upload',
|
||||||
|
data='test')
|
||||||
|
self.assertEqual(500, rv.status_code)
|
||||||
|
|
||||||
|
mock_subprocess.side_effect = [True,
|
||||||
|
subprocess.CalledProcessError(1,
|
||||||
|
'blah!')]
|
||||||
|
|
||||||
|
with mock.patch.object(builtins, 'open', m):
|
||||||
|
rv = self.app.put('/' + api_server.VERSION + '/vrrp/upload',
|
||||||
|
data='test')
|
||||||
|
self.assertEqual(500, rv.status_code)
|
||||||
|
|
||||||
|
@mock.patch('subprocess.check_output')
|
||||||
|
def test_manage_service_vrrp(self, mock_check_output):
|
||||||
|
rv = self.app.put('/' + api_server.VERSION + '/vrrp/start')
|
||||||
|
self.assertEqual(202, rv.status_code)
|
||||||
|
|
||||||
|
rv = self.app.put('/' + api_server.VERSION + '/vrrp/restart')
|
||||||
|
self.assertEqual(400, rv.status_code)
|
||||||
|
|
||||||
|
mock_check_output.side_effect = subprocess.CalledProcessError(1,
|
||||||
|
'blah!')
|
||||||
|
|
||||||
|
rv = self.app.put('/' + api_server.VERSION + '/vrrp/start')
|
||||||
|
self.assertEqual(500, rv.status_code)
|
@ -36,6 +36,7 @@ class AgentJinjaTestCase(base.TestCase):
|
|||||||
agent_server_network_dir='/etc/network/interfaces.d/')
|
agent_server_network_dir='/etc/network/interfaces.d/')
|
||||||
self.conf.config(group="haproxy_amphora",
|
self.conf.config(group="haproxy_amphora",
|
||||||
base_cert_dir='/var/lib/octavia/certs')
|
base_cert_dir='/var/lib/octavia/certs')
|
||||||
|
self.conf.config(group="haproxy_amphora", use_upstart='True')
|
||||||
self.conf.config(group="haproxy_amphora", base_path='/var/lib/octavia')
|
self.conf.config(group="haproxy_amphora", base_path='/var/lib/octavia')
|
||||||
self.conf.config(group="haproxy_amphora", bind_host='0.0.0.0')
|
self.conf.config(group="haproxy_amphora", bind_host='0.0.0.0')
|
||||||
self.conf.config(group="haproxy_amphora", bind_port=9443)
|
self.conf.config(group="haproxy_amphora", bind_port=9443)
|
||||||
@ -62,7 +63,8 @@ class AgentJinjaTestCase(base.TestCase):
|
|||||||
'bind_port = 9443\n'
|
'bind_port = 9443\n'
|
||||||
'haproxy_cmd = /usr/sbin/haproxy\n'
|
'haproxy_cmd = /usr/sbin/haproxy\n'
|
||||||
'respawn_count = 2\n'
|
'respawn_count = 2\n'
|
||||||
'respawn_interval = 2\n\n'
|
'respawn_interval = 2\n'
|
||||||
|
'use_upstart = True\n\n'
|
||||||
'[health_manager]\n'
|
'[health_manager]\n'
|
||||||
'controller_ip_port_list = 192.0.2.10:5555\n'
|
'controller_ip_port_list = 192.0.2.10:5555\n'
|
||||||
'heartbeat_interval = 10\n'
|
'heartbeat_interval = 10\n'
|
||||||
@ -82,6 +84,7 @@ class AgentJinjaTestCase(base.TestCase):
|
|||||||
ajc = agent_jinja_cfg.AgentJinjaTemplater()
|
ajc = agent_jinja_cfg.AgentJinjaTemplater()
|
||||||
self.conf.config(group="amphora_agent",
|
self.conf.config(group="amphora_agent",
|
||||||
agent_server_network_file='/etc/network/interfaces')
|
agent_server_network_file='/etc/network/interfaces')
|
||||||
|
self.conf.config(group="haproxy_amphora", use_upstart='False')
|
||||||
expected_config = ('\n[DEFAULT]\n'
|
expected_config = ('\n[DEFAULT]\n'
|
||||||
'debug = False\n\n'
|
'debug = False\n\n'
|
||||||
'[haproxy_amphora]\n'
|
'[haproxy_amphora]\n'
|
||||||
@ -91,7 +94,8 @@ class AgentJinjaTestCase(base.TestCase):
|
|||||||
'bind_port = 9443\n'
|
'bind_port = 9443\n'
|
||||||
'haproxy_cmd = /usr/sbin/haproxy\n'
|
'haproxy_cmd = /usr/sbin/haproxy\n'
|
||||||
'respawn_count = 2\n'
|
'respawn_count = 2\n'
|
||||||
'respawn_interval = 2\n\n'
|
'respawn_interval = 2\n'
|
||||||
|
'use_upstart = False\n\n'
|
||||||
'[health_manager]\n'
|
'[health_manager]\n'
|
||||||
'controller_ip_port_list = 192.0.2.10:5555\n'
|
'controller_ip_port_list = 192.0.2.10:5555\n'
|
||||||
'heartbeat_interval = 10\n'
|
'heartbeat_interval = 10\n'
|
||||||
|
Loading…
Reference in New Issue
Block a user