Agent: swap flask responses to webob, handle 404 retries better

Change-Id: I7c8f7da8db104be54cea1fe6f411dcab9d134e2a
This commit is contained in:
Adam Harwell 2017-06-15 12:34:57 -07:00
parent c8615b7ec4
commit 43199884d5
12 changed files with 187 additions and 155 deletions

View File

@ -17,11 +17,11 @@ import re
import socket
import subprocess
import flask
import ipaddress
import netifaces
import pyroute2
import six
import webob
from octavia.amphorae.backends.agent import api_server
from octavia.amphorae.backends.agent.api_server import util
@ -33,46 +33,47 @@ class AmphoraInfo(object):
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})
return webob.Response(
json={'hostname': socket.gethostname(),
'haproxy_version':
self._get_version_of_installed_package('haproxy'),
'api_version': api_server.VERSION})
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': {}})
return webob.Response(
json={'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):
@ -144,16 +145,16 @@ class AmphoraInfo(object):
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)
return webob.Response(
json=dict(message="Invalid IP address"), status=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)
return webob.Response(
json=dict(message="Bad IP address version"), status=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
@ -194,11 +195,11 @@ class AmphoraInfo(object):
# 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 webob.Response(
json=dict(message='OK',
interface=int_attr[1]),
status=200)
return flask.make_response(
flask.jsonify(dict(message="Error interface not found "
"for IP address")), 404)
return webob.Response(
json=dict(message="Error interface not found for IP address"),
status=404)

View File

@ -17,6 +17,7 @@ import stat
import flask
from oslo_config import cfg
import webob
BUFFER = 1024
@ -35,5 +36,4 @@ def upload_server_cert():
crt_file.write(b)
b = stream.read(BUFFER)
return flask.make_response(flask.jsonify({
'message': 'OK'}), 202)
return webob.Response(json={'message': 'OK'}, status=202)

View File

@ -19,6 +19,7 @@ import subprocess
import flask
import jinja2
import webob
from octavia.amphorae.backends.agent.api_server import listener
from octavia.amphorae.backends.agent.api_server import util
@ -109,12 +110,11 @@ class Keepalived(object):
except subprocess.CalledProcessError as e:
LOG.debug('Failed to enable octavia-keepalived service: '
'%(err)s', {'err': e})
return flask.make_response(flask.jsonify(dict(
return webob.Response(json=dict(
message="Error enabling octavia-keepalived service",
details=e.output)), 500)
details=e.output), status=500)
res = flask.make_response(flask.jsonify({
'message': 'OK'}), 200)
res = webob.Response(json={'message': 'OK'}, status=200)
res.headers['ETag'] = stream.get_md5()
return res
@ -124,9 +124,9 @@ class Keepalived(object):
if action not in [consts.AMP_ACTION_START,
consts.AMP_ACTION_STOP,
consts.AMP_ACTION_RELOAD]:
return flask.make_response(flask.jsonify(dict(
return webob.Response(json=dict(
message='Invalid Request',
details="Unknown action: {0}".format(action))), 400)
details="Unknown action: {0}".format(action)), status=400)
cmd = ("/usr/sbin/service octavia-keepalived {action}".format(
action=action))
@ -135,10 +135,11 @@ class Keepalived(object):
subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
LOG.debug('Failed to %s keepalived service: %s', action, e)
return flask.make_response(flask.jsonify(dict(
return webob.Response(json=dict(
message="Failed to {0} keepalived service".format(action),
details=e.output)), 500)
details=e.output), status=500)
return flask.make_response(flask.jsonify(
dict(message='OK',
details='keepalived {action}ed'.format(action=action))), 202)
return webob.Response(
json=dict(message='OK',
details='keepalived {action}ed'.format(action=action)),
status=202)

View File

@ -14,7 +14,6 @@
import hashlib
import io
import json
import logging
import os
import re
@ -26,6 +25,7 @@ import flask
import jinja2
from oslo_config import cfg
import six
import webob
from werkzeug import exceptions
from octavia.amphorae.backends.agent.api_server import osutils
@ -89,7 +89,7 @@ class Listener(object):
self._check_listener_exists(listener_id)
with open(util.config_path(listener_id), 'r') as file:
cfg = file.read()
resp = flask.Response(cfg, mimetype='text/plain', )
resp = webob.Response(cfg, content_type='text/plain')
resp.headers['ETag'] = hashlib.md5(six.b(cfg)).hexdigest() # nosec
return resp
@ -137,9 +137,9 @@ class Listener(object):
except subprocess.CalledProcessError as e:
LOG.error("Failed to verify haproxy file: %s", e)
os.remove(name) # delete file
return flask.make_response(flask.jsonify(dict(
message="Invalid request",
details=e.output)), 400)
return webob.Response(
json=dict(message="Invalid request", details=e.output),
status=400)
# file ok - move it
os.rename(name, util.config_path(listener_id))
@ -166,11 +166,11 @@ class Listener(object):
except util.UnknownInitError:
LOG.error("Unknown init system found.")
return flask.make_response(flask.jsonify(dict(
return webob.Response(json=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)
"file for the load balancing process."), status=500)
if init_system == consts.INIT_SYSTEMD:
# mode 00644
@ -204,13 +204,13 @@ class Listener(object):
except subprocess.CalledProcessError as e:
LOG.error("Failed to enable haproxy-%(list)s service: %(err)s",
{'list': listener_id, 'err': e})
return flask.make_response(flask.jsonify(dict(
return webob.Response(json=dict(
message="Error enabling haproxy-{0} service".format(
listener_id), details=e.output)), 500)
listener_id), details=e.output), status=500)
res = flask.make_response(flask.jsonify({
'message': 'OK'}), 202)
res = webob.Response(json={'message': 'OK'}, status=202)
res.headers['ETag'] = stream.get_md5()
return res
def start_stop_listener(self, listener_id, action):
@ -218,9 +218,9 @@ class Listener(object):
if action not in [consts.AMP_ACTION_START,
consts.AMP_ACTION_STOP,
consts.AMP_ACTION_RELOAD]:
return flask.make_response(flask.jsonify(dict(
return webob.Response(json=dict(
message='Invalid Request',
details="Unknown action: {0}".format(action))), 400)
details="Unknown action: {0}".format(action)), status=400)
self._check_listener_exists(listener_id)
@ -245,24 +245,23 @@ class Listener(object):
if 'Job is already running' not in e.output:
LOG.debug("Failed to %(action)s HAProxy service: %(err)s",
{'action': action, 'err': e})
return flask.make_response(flask.jsonify(dict(
return webob.Response(json=dict(
message="Error {0}ing haproxy".format(action),
details=e.output)), 500)
details=e.output), status=500)
if action in [consts.AMP_ACTION_STOP,
consts.AMP_ACTION_RELOAD]:
return flask.make_response(flask.jsonify(
dict(message='OK',
details='Listener {listener_id} {action}ed'.format(
listener_id=listener_id, action=action))), 202)
return webob.Response(json=dict(
message='OK',
details='Listener {listener_id} {action}ed'.format(
listener_id=listener_id, action=action)), status=202)
details = (
'Configuration file is valid\n'
'haproxy daemon for {0} started'.format(listener_id)
)
return flask.make_response(flask.jsonify(
dict(message='OK',
details=details)), 202)
return webob.Response(json=dict(message='OK', details=details),
status=202)
def delete_listener(self, listener_id):
self._check_listener_exists(listener_id)
@ -275,9 +274,9 @@ class Listener(object):
subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
LOG.error("Failed to stop HAProxy service: %s", e)
return flask.make_response(flask.jsonify(dict(
return webob.Response(json=dict(
message="Error stopping haproxy",
details=e.output)), 500)
details=e.output), status=500)
# parse config and delete stats socket
try:
@ -311,16 +310,16 @@ class Listener(object):
except subprocess.CalledProcessError as e:
LOG.error("Failed to disable haproxy-%(list)s service: "
"%(err)s", {'list': listener_id, 'err': e})
return flask.make_response(flask.jsonify(dict(
return webob.Response(json=dict(
message="Error disabling haproxy-{0} service".format(
listener_id), details=e.output)), 500)
listener_id), details=e.output), status=500)
# delete the directory + init script for that listener
shutil.rmtree(util.haproxy_dir(listener_id))
if os.path.exists(init_path):
os.remove(init_path)
return flask.jsonify({'message': 'OK'})
return webob.Response(json={'message': 'OK'})
def get_all_listeners_status(self):
"""Gets the status of all listeners
@ -346,11 +345,7 @@ class Listener(object):
'type': listener_type,
})
# Can't use jsonify since lists are not supported
# for security reason: http://stackoverflow.com/
# questions/12435297/how-do-i-jsonify-a-list-in-flask
return flask.Response(json.dumps(listeners),
mimetype='application/json')
return webob.Response(json=listeners, content_type='application/json')
def get_listener_status(self, listener_id):
"""Gets the status of a listener
@ -373,7 +368,7 @@ class Listener(object):
uuid=listener_id,
type=''
)
return flask.jsonify(stats)
return webob.Response(json=stats)
cfg = self._parse_haproxy_file(listener_id)
stats = dict(
@ -386,7 +381,7 @@ class Listener(object):
q = query.HAProxyQuery(cfg['stats_socket'])
servers = q.get_pool_status()
stats['pools'] = list(servers.values())
return flask.jsonify(stats)
return webob.Response(json=stats)
def upload_certificate(self, listener_id, filename):
self._check_ssl_filename_format(filename)
@ -406,7 +401,7 @@ class Listener(object):
crt_file.write(b)
b = stream.read(BUFFER)
resp = flask.jsonify(dict(message='OK'))
resp = webob.Response(json=dict(message='OK'))
resp.headers['ETag'] = stream.get_md5()
return resp
@ -416,28 +411,28 @@ class Listener(object):
cert_path = self._cert_file_path(listener_id, filename)
path_exists = os.path.exists(cert_path)
if not path_exists:
return flask.make_response(flask.jsonify(dict(
return webob.Response(json=dict(
message='Certificate Not Found',
details="No certificate with filename: {f}".format(
f=filename))), 404)
f=filename)), status=404)
with open(cert_path, 'r') as crt_file:
cert = crt_file.read()
md5 = hashlib.md5(six.b(cert)).hexdigest() # nosec
resp = flask.jsonify(dict(md5sum=md5))
resp = webob.Response(json=dict(md5sum=md5))
resp.headers['ETag'] = md5
return resp
def delete_certificate(self, listener_id, filename):
self._check_ssl_filename_format(filename)
if not os.path.exists(self._cert_file_path(listener_id, filename)):
return flask.make_response(flask.jsonify(dict(
return webob.Response(json=dict(
message='Certificate Not Found',
details="No certificate with filename: {f}".format(
f=filename))), 404)
f=filename)), status=404)
os.remove(self._cert_file_path(listener_id, filename))
return flask.jsonify(dict(message='OK'))
return webob.Response(json=dict(message='OK'))
def _check_listener_status(self, listener_id):
if os.path.exists(util.pid_path(listener_id)):
@ -484,17 +479,17 @@ class Listener(object):
# check if we know about that listener
if not os.path.exists(util.config_path(listener_id)):
raise exceptions.HTTPException(
response=flask.make_response(flask.jsonify(dict(
response=webob.Response(json=dict(
message='Listener Not Found',
details="No listener with UUID: {0}".format(
listener_id))), 404))
listener_id)), status=404))
def _check_ssl_filename_format(self, filename):
# check if the format is (xxx.)*xxx.pem
if not re.search('(\w.)+pem', filename):
raise exceptions.HTTPException(
response=flask.make_response(flask.jsonify(dict(
message='Filename has wrong format')), 400))
response=webob.Response(json=dict(
message='Filename has wrong format'), status=400))
def _cert_dir(self, listener_id):
return os.path.join(util.CONF.haproxy_amphora.base_cert_dir,

View File

@ -19,11 +19,11 @@ import shutil
import stat
import subprocess
import flask
import ipaddress
import jinja2
from oslo_config import cfg
import six
import webob
from werkzeug import exceptions
from octavia.common import constants as consts
@ -154,8 +154,8 @@ class BaseOS(object):
host_routes = self.get_host_routes(fixed_ip)
except ValueError:
return flask.make_response(flask.jsonify(dict(
message="Invalid network IP")), 400)
return webob.Response(
json=dict(message="Invalid network IP"), status=400)
new_text = template_port.render(interface=netns_ip_interface,
ipv6=ip.version is 6,
ip_address=ip.exploded,
@ -186,9 +186,9 @@ class BaseOS(object):
except subprocess.CalledProcessError as e:
LOG.error('Failed to if up %s due to error: %s', interface, e)
raise exceptions.HTTPException(
response=flask.make_response(flask.jsonify(dict(
response=webob.Response(json=dict(
message='Error plugging {0}'.format(what),
details=e.output)), 500))
details=e.output), status=500))
def _bring_if_down(self, interface):
# Note, we are not using pyroute2 for this as it is not /etc/netns

View File

@ -19,13 +19,13 @@ import socket
import stat
import subprocess
import flask
import ipaddress
import jinja2
import netifaces
from oslo_config import cfg
import pyroute2
import six
import webob
from werkzeug import exceptions
from octavia.common import constants as consts
@ -78,15 +78,15 @@ class Plug(object):
render_host_routes.append({'network': network,
'gw': hr['nexthop']})
except ValueError:
return flask.make_response(flask.jsonify(dict(
message="Invalid VIP")), 400)
return webob.Response(json=dict(message="Invalid VIP"),
status=400)
# Check if the interface is already in the network namespace
# Do not attempt to re-plug the VIP if it is already in the
# network namespace
if self._netns_interface_exists(mac_address):
return flask.make_response(flask.jsonify(dict(
message="Interface already exists")), 409)
return webob.Response(
json=dict(message="Interface already exists"), status=409)
# This is the interface prior to moving into the netns
default_netns_interface = self._interface_by_mac(mac_address)
@ -141,10 +141,10 @@ class Plug(object):
self._osutils.bring_interfaces_up(
ip, primary_interface, secondary_interface)
return flask.make_response(flask.jsonify(dict(
return webob.Response(json=dict(
message="OK",
details="VIP {vip} plugged on interface {interface}".format(
vip=vip, interface=primary_interface))), 202)
vip=vip, interface=primary_interface)), status=202)
def _check_ip_addresses(self, fixed_ips):
if fixed_ips:
@ -159,8 +159,8 @@ class Plug(object):
# Do not attempt to re-plug the network if it is already in the
# network namespace
if self._netns_interface_exists(mac_address):
return flask.make_response(flask.jsonify(dict(
message="Interface already exists")), 409)
return webob.Response(json=dict(
message="Interface already exists"), status=409)
# This is the interface as it was initially plugged into the
# default network namespace, this will likely always be eth1
@ -168,8 +168,8 @@ class Plug(object):
try:
self._check_ip_addresses(fixed_ips=fixed_ips)
except socket.error:
return flask.make_response(flask.jsonify(dict(
message="Invalid network port")), 400)
return webob.Response(json=dict(
message="Invalid network port"), status=400)
default_netns_interface = self._interface_by_mac(mac_address)
@ -207,10 +207,10 @@ class Plug(object):
self._osutils._bring_if_down(netns_interface)
self._osutils._bring_if_up(netns_interface, 'network')
return flask.make_response(flask.jsonify(dict(
return webob.Response(json=dict(
message="OK",
details="Plugged on interface {interface}".format(
interface=netns_interface))), 202)
interface=netns_interface)), status=202)
def _interface_by_mac(self, mac):
for interface in netifaces.interfaces():
@ -220,8 +220,8 @@ class Plug(object):
if link.get('addr', '').lower() == mac.lower():
return interface
raise exceptions.HTTPException(
response=flask.make_response(flask.jsonify(dict(
details="No suitable network interface found")), 404))
response=webob.Response(json=dict(
details="No suitable network interface found"), status=404))
def _update_plugged_interfaces_file(self, interface, mac_address):
# write interfaces to plugged_interfaces file and prevent duplicates

View File

@ -15,6 +15,7 @@
import flask
import six
import webob
from werkzeug import exceptions
from octavia.amphorae.backends.agent import api_server
@ -31,7 +32,7 @@ PATH_PREFIX = '/' + api_server.VERSION
# make the error pages all json
def make_json_error(ex):
code = ex.code if isinstance(ex, exceptions.HTTPException) else 500
response = flask.jsonify({'error': str(ex), 'http_code': code})
response = webob.Response(json={'error': str(ex), 'http_code': code})
response.status_code = code
return response

View File

@ -15,6 +15,7 @@
import functools
import hashlib
import simplejson
import time
import warnings
@ -265,7 +266,6 @@ class AmphoraAPIClient(object):
headers['User-Agent'] = OCTAVIA_API_CLIENT
self.ssl_adapter.uuid = amp.id
retry_attempt = False
exception = None
# Keep retrying
for a in six.moves.xrange(CONF.haproxy_amphora.connection_max_retries):
@ -278,11 +278,24 @@ class AmphoraAPIClient(object):
r = _request(**reqargs)
LOG.debug('Connected to amphora. Response: %(resp)s',
{'resp': r})
# Give a 404 response one retry. Flask/werkzeug is
# returning 404 on startup.
if r.status_code == 404 and retry_attempt is False:
retry_attempt = True
raise requests.ConnectionError
content_type = r.headers.get('content-type', '')
# Check the 404 to see if it is just that the network in the
# amphora is not yet up, in which case retry.
# Otherwise return the response quickly.
if r.status_code == 404:
LOG.debug('Got a 404 (content-type: %s) -- connection '
'data: %s' % (content_type, r.content))
if content_type.find("application/json") == -1:
LOG.debug("Amphora agent not ready.")
raise requests.ConnectionError
try:
json_data = r.json().get('details', '')
if 'No suitable network interface found' in json_data:
LOG.debug("Amphora network interface not found.")
raise requests.ConnectionError
except simplejson.JSONDecodeError: # if r.json() fails
pass # TODO(rm_work) Should we do something?
return r
except (requests.ConnectionError, requests.Timeout) as e:
exception = e

View File

@ -578,7 +578,7 @@ class TestServerTestCase(base.TestCase):
self.assertEqual(200, rv.status_code)
self.assertEqual(six.b(CONTENT), rv.data)
self.assertEqual('text/plain; charset=utf-8',
rv.headers['Content-Type'])
rv.headers['Content-Type'].lower())
def test_ubuntu_get_all_listeners(self):
self._test_get_all_listeners(consts.UBUNTU)

View File

@ -33,20 +33,20 @@ class TestAmphoraInfo(base.TestCase):
self.osutils_mock = mock.MagicMock()
self.amp_info = amphora_info.AmphoraInfo(self.osutils_mock)
@mock.patch.object(amphora_info, "flask")
@mock.patch.object(amphora_info, "webob")
@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):
mock_webob):
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)
mock_webob.Response.assert_called_once_with(json=expected_dict)
api_server.VERSION = original_version
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'

View File

@ -66,7 +66,7 @@ class TestPlug(base.TestCase):
self.assertEqual(FAKE_INTERFACE, interface)
@mock.patch('pyroute2.NSPopen')
@mock.patch.object(plug, "flask")
@mock.patch.object(plug, "webob")
@mock.patch('pyroute2.IPRoute')
@mock.patch('pyroute2.netns.create')
@mock.patch('pyroute2.NetNS')
@ -75,7 +75,7 @@ class TestPlug(base.TestCase):
@mock.patch('os.makedirs')
def test_plug_vip_ipv4(self, mock_makedirs, mock_copytree,
mock_check_output, mock_netns, mock_netns_create,
mock_pyroute2, mock_flask, mock_nspopen):
mock_pyroute2, mock_webob, mock_nspopen):
m = mock.mock_open()
with mock.patch('os.open'), mock.patch.object(os, 'fdopen', m):
self.test_plug.plug_vip(
@ -84,17 +84,17 @@ class TestPlug(base.TestCase):
gateway=FAKE_GATEWAY_IPV4,
mac_address=FAKE_MAC_ADDRESS
)
mock_flask.jsonify.assert_any_call({
mock_webob.Response.assert_any_call(json={
'message': 'OK',
'details': 'VIP {vip} plugged on interface {interface}'.format(
vip=FAKE_IP_IPV4, interface='eth1')
})
}, status=202)
mock_nspopen.assert_called_once_with(
'amphora-haproxy', ['/sbin/sysctl', '--system'],
stdout=subprocess.PIPE)
@mock.patch('pyroute2.NSPopen')
@mock.patch.object(plug, "flask")
@mock.patch.object(plug, "webob")
@mock.patch('pyroute2.IPRoute')
@mock.patch('pyroute2.netns.create')
@mock.patch('pyroute2.NetNS')
@ -103,7 +103,7 @@ class TestPlug(base.TestCase):
@mock.patch('os.makedirs')
def test_plug_vip_ipv6(self, mock_makedirs, mock_copytree,
mock_check_output, mock_netns, mock_netns_create,
mock_pyroute2, mock_flask, mock_nspopen):
mock_pyroute2, mock_webob, mock_nspopen):
m = mock.mock_open()
with mock.patch('os.open'), mock.patch.object(os, 'fdopen', m):
self.test_plug.plug_vip(
@ -112,16 +112,16 @@ class TestPlug(base.TestCase):
gateway=FAKE_GATEWAY_IPV6,
mac_address=FAKE_MAC_ADDRESS
)
mock_flask.jsonify.assert_any_call({
mock_webob.Response.assert_any_call(json={
'message': 'OK',
'details': 'VIP {vip} plugged on interface {interface}'.format(
vip=FAKE_IP_IPV6_EXPANDED, interface='eth1')
})
}, status=202)
mock_nspopen.assert_called_once_with(
'amphora-haproxy', ['/sbin/sysctl', '--system'],
stdout=subprocess.PIPE)
@mock.patch.object(plug, "flask")
@mock.patch.object(plug, "webob")
@mock.patch('pyroute2.IPRoute')
@mock.patch('pyroute2.netns.create')
@mock.patch('pyroute2.NetNS')
@ -130,7 +130,7 @@ class TestPlug(base.TestCase):
@mock.patch('os.makedirs')
def test_plug_vip_bad_ip(self, mock_makedirs, mock_copytree,
mock_check_output, mock_netns, mock_netns_create,
mock_pyroute2, mock_flask):
mock_pyroute2, mock_webob):
m = mock.mock_open()
with mock.patch('os.open'), mock.patch.object(os, 'fdopen', m):
self.test_plug.plug_vip(
@ -139,7 +139,8 @@ class TestPlug(base.TestCase):
gateway=FAKE_GATEWAY_IPV4,
mac_address=FAKE_MAC_ADDRESS
)
mock_flask.jsonify.assert_any_call({'message': 'Invalid VIP'})
mock_webob.Response.assert_any_call(json={'message': 'Invalid VIP'},
status=400)
@mock.patch('pyroute2.NetNS')
def test__netns_interface_exists(self, mock_netns):

View File

@ -295,7 +295,8 @@ class TestAmphoraAPIClientTest(base.TestCase):
@requests_mock.mock()
def test_get_info_missing(self, m):
m.get("{base}/info".format(base=self.base_url),
status_code=404)
status_code=404,
headers={'content-type': 'application/json'})
self.assertRaises(exc.NotFound, self.driver.get_info, self.amp)
@requests_mock.mock()
@ -332,7 +333,8 @@ class TestAmphoraAPIClientTest(base.TestCase):
@requests_mock.mock()
def test_get_details_missing(self, m):
m.get("{base}/details".format(base=self.base_url),
status_code=404)
status_code=404,
headers={'content-type': 'application/json'})
self.assertRaises(exc.NotFound, self.driver.get_details, self.amp)
@requests_mock.mock()
@ -368,7 +370,8 @@ class TestAmphoraAPIClientTest(base.TestCase):
@requests_mock.mock()
def test_get_all_listeners_missing(self, m):
m.get("{base}/listeners".format(base=self.base_url),
status_code=404)
status_code=404,
headers={'content-type': 'application/json'})
self.assertRaises(exc.NotFound, self.driver.get_all_listeners,
self.amp)
@ -409,7 +412,8 @@ class TestAmphoraAPIClientTest(base.TestCase):
def test_get_listener_status_missing(self, m):
m.get("{base}/listeners/{listener_id}".format(
base=self.base_url, listener_id=FAKE_UUID_1),
status_code=404)
status_code=404,
headers={'content-type': 'application/json'})
self.assertRaises(exc.NotFound,
self.driver.get_listener_status, self.amp,
FAKE_UUID_1)
@ -443,7 +447,8 @@ class TestAmphoraAPIClientTest(base.TestCase):
def test_start_listener_missing(self, m):
m.put("{base}/listeners/{listener_id}/start".format(
base=self.base_url, listener_id=FAKE_UUID_1),
status_code=404)
status_code=404,
headers={'content-type': 'application/json'})
self.assertRaises(exc.NotFound, self.driver.start_listener,
self.amp, FAKE_UUID_1)
@ -482,7 +487,8 @@ class TestAmphoraAPIClientTest(base.TestCase):
def test_stop_listener_missing(self, m):
m.put("{base}/listeners/{listener_id}/stop".format(
base=self.base_url, listener_id=FAKE_UUID_1),
status_code=404)
status_code=404,
headers={'content-type': 'application/json'})
self.assertRaises(exc.NotFound, self.driver.stop_listener,
self.amp, FAKE_UUID_1)
@ -521,7 +527,8 @@ class TestAmphoraAPIClientTest(base.TestCase):
def test_delete_listener_missing(self, m):
m.delete("{base}/listeners/{listener_id}".format(
base=self.base_url, listener_id=FAKE_UUID_1),
status_code=404)
status_code=404,
headers={'content-type': 'application/json'})
self.assertRaises(exc.NotFound, self.driver.delete_listener,
self.amp, FAKE_UUID_1)
@ -644,7 +651,8 @@ class TestAmphoraAPIClientTest(base.TestCase):
def test_get_cert_5sum_missing(self, m):
m.get("{base}/listeners/{listener_id}/certificates/{filename}".format(
base=self.base_url, listener_id=FAKE_UUID_1,
filename=FAKE_PEM_FILENAME), status_code=404)
filename=FAKE_PEM_FILENAME), status_code=404,
headers={'content-type': 'application/json'})
self.assertRaises(exc.NotFound, self.driver.get_cert_md5sum,
self.amp, FAKE_UUID_1, FAKE_PEM_FILENAME)
@ -687,7 +695,8 @@ class TestAmphoraAPIClientTest(base.TestCase):
m.delete(
"{base}/listeners/{listener_id}/certificates/{filename}".format(
base=self.base_url, listener_id=FAKE_UUID_1,
filename=FAKE_PEM_FILENAME), status_code=404)
filename=FAKE_PEM_FILENAME), status_code=404,
headers={'content-type': 'application/json'})
self.assertRaises(exc.NotFound, self.driver.delete_cert_pem, self.amp,
FAKE_UUID_1, FAKE_PEM_FILENAME)
@ -787,6 +796,17 @@ class TestAmphoraAPIClientTest(base.TestCase):
self.driver.plug_vip(self.amp, FAKE_IP, self.subnet_info)
self.assertTrue(m.called)
@requests_mock.mock()
def test_plug_vip_api_not_ready(self, m):
m.post("{base}/plug/vip/{vip}".format(
base=self.base_url, vip=FAKE_IP),
status_code=404, headers={'content-type': 'text/html'}
)
self.assertRaises(driver_except.TimeOutException,
self.driver.plug_vip,
self.amp, FAKE_IP, self.subnet_info)
self.assertTrue(m.called)
@requests_mock.mock()
def test_plug_network(self, m):
m.post("{base}/plug/network".format(