Agent: swap flask responses to webob, handle 404 retries better
Change-Id: I7c8f7da8db104be54cea1fe6f411dcab9d134e2a
This commit is contained in:
parent
c8615b7ec4
commit
43199884d5
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.'
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in New Issue