UDP for [2]
These files will split with the current Octavia repo, before other parts are ok. Patch List: [1] Finish keepalived LVS jinja template for UDP support [2] Extend the ability of amp agent for upload/refresh the keepalived process [3] Extend the db model and db table with necessary fields for met the new udp backend [4] Add logic/workflow elements process in UDP cases [5] Extend the existing API to access udp parameters in Listener API [6] Extend the existing pool API to access the new option in session_persistence fields Change-Id: Ib4924e602d450b1feadb29e830d715ae77f5bbfe
This commit is contained in:
parent
8004fe23bd
commit
a890f2ba35
@ -310,6 +310,10 @@
|
||||
# agent_server_network_file =
|
||||
# agent_request_read_timeout = 120
|
||||
|
||||
# Amphora default UDP driver is keepalived_lvs
|
||||
#
|
||||
# amphora_udp_driver = keepalived_lvs
|
||||
|
||||
[keepalived_vrrp]
|
||||
# Amphora Role/Priority advertisement interval in seconds
|
||||
# vrrp_advert_int = 1
|
||||
|
@ -59,4 +59,5 @@ class AgentJinjaTemplater(object):
|
||||
'heartbeat_key': CONF.health_manager.heartbeat_key,
|
||||
'use_upstart': CONF.haproxy_amphora.use_upstart,
|
||||
'respawn_count': CONF.haproxy_amphora.respawn_count,
|
||||
'respawn_interval': CONF.haproxy_amphora.respawn_interval})
|
||||
'respawn_interval': CONF.haproxy_amphora.respawn_interval,
|
||||
'amphora_udp_driver': CONF.amphora_agent.amphora_udp_driver})
|
||||
|
@ -32,48 +32,68 @@ class AmphoraInfo(object):
|
||||
def __init__(self, osutils):
|
||||
self._osutils = osutils
|
||||
|
||||
def compile_amphora_info(self):
|
||||
return webob.Response(
|
||||
json={'hostname': socket.gethostname(),
|
||||
'haproxy_version':
|
||||
self._get_version_of_installed_package('haproxy'),
|
||||
'api_version': api_server.VERSION})
|
||||
def compile_amphora_info(self, extend_udp_driver=None):
|
||||
extend_body = {}
|
||||
if extend_udp_driver:
|
||||
extend_body = self._get_extend_body_from_udp_driver(
|
||||
extend_udp_driver)
|
||||
body = {'hostname': socket.gethostname(),
|
||||
'haproxy_version':
|
||||
self._get_version_of_installed_package('haproxy'),
|
||||
'api_version': api_server.VERSION}
|
||||
if extend_body:
|
||||
body.update(extend_body)
|
||||
return webob.Response(json=body)
|
||||
|
||||
def compile_amphora_details(self):
|
||||
listener_list = util.get_listeners()
|
||||
def compile_amphora_details(self, extend_udp_driver=None):
|
||||
haproxy_listener_list = util.get_listeners()
|
||||
extend_body = {}
|
||||
udp_listener_list = []
|
||||
if extend_udp_driver:
|
||||
udp_listener_list = util.get_udp_listeners()
|
||||
extend_data = self._get_extend_body_from_udp_driver(
|
||||
extend_udp_driver)
|
||||
udp_count = self._count_udp_listener_processes(extend_udp_driver,
|
||||
udp_listener_list)
|
||||
extend_body['udp_listener_process_count'] = udp_count
|
||||
extend_body.update(extend_data)
|
||||
meminfo = self._get_meminfo()
|
||||
cpu = self._cpu()
|
||||
st = os.statvfs('/')
|
||||
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': {}})
|
||||
body = {'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(haproxy_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': list(
|
||||
set(haproxy_listener_list + udp_listener_list))
|
||||
if udp_listener_list else haproxy_listener_list,
|
||||
'packages': {}}
|
||||
if extend_body:
|
||||
body.update(extend_body)
|
||||
return webob.Response(json=body)
|
||||
|
||||
def _get_version_of_installed_package(self, name):
|
||||
|
||||
@ -89,6 +109,22 @@ class AmphoraInfo(object):
|
||||
num += 1
|
||||
return num
|
||||
|
||||
def _count_udp_listener_processes(self, udp_driver, listener_list):
|
||||
num = 0
|
||||
for listener_id in listener_list:
|
||||
if udp_driver.is_listener_running(listener_id):
|
||||
# optional check if it's still running
|
||||
num += 1
|
||||
return num
|
||||
|
||||
def _get_extend_body_from_udp_driver(self, extend_udp_driver):
|
||||
extend_info = extend_udp_driver.get_subscribed_amp_compile_info()
|
||||
extend_data = {}
|
||||
for extend in extend_info:
|
||||
package_version = self._get_version_of_installed_package(extend)
|
||||
extend_data['%s_version' % extend] = package_version
|
||||
return extend_data
|
||||
|
||||
def _get_meminfo(self):
|
||||
re_parser = re.compile(r'^(?P<key>\S*):\s*(?P<value>\d*)\s*kB')
|
||||
result = dict()
|
||||
|
352
octavia/amphorae/backends/agent/api_server/keepalivedlvs.py
Normal file
352
octavia/amphorae/backends/agent/api_server/keepalivedlvs.py
Normal file
@ -0,0 +1,352 @@
|
||||
# Copyright 2011-2014 OpenStack Foundation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import stat
|
||||
import subprocess
|
||||
|
||||
import flask
|
||||
import jinja2
|
||||
from oslo_log import log as logging
|
||||
import webob
|
||||
from werkzeug import exceptions
|
||||
|
||||
from octavia.amphorae.backends.agent.api_server import listener
|
||||
from octavia.amphorae.backends.agent.api_server import udp_listener_base
|
||||
from octavia.amphorae.backends.agent.api_server import util
|
||||
from octavia.amphorae.backends.utils import keepalivedlvs_query
|
||||
from octavia.common import constants as consts
|
||||
|
||||
BUFFER = 100
|
||||
CHECK_SCRIPT_NAME = 'udp_check.sh'
|
||||
KEEPALIVED_CHECK_SCRIPT_NAME = 'lvs_udp_check.sh'
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
j2_env = jinja2.Environment(autoescape=True, loader=jinja2.FileSystemLoader(
|
||||
os.path.dirname(os.path.realpath(__file__)) + consts.AGENT_API_TEMPLATES))
|
||||
UPSTART_TEMPLATE = j2_env.get_template(consts.KEEPALIVED_JINJA2_UPSTART)
|
||||
SYSVINIT_TEMPLATE = j2_env.get_template(consts.KEEPALIVED_JINJA2_SYSVINIT)
|
||||
SYSTEMD_TEMPLATE = j2_env.get_template(consts.KEEPALIVED_JINJA2_SYSTEMD)
|
||||
check_script_file_template = j2_env.get_template(
|
||||
consts.KEEPALIVED_CHECK_SCRIPT)
|
||||
|
||||
|
||||
class KeepalivedLvs(udp_listener_base.UdpListenerApiServerBase):
|
||||
|
||||
_SUBSCRIBED_AMP_COMPILE = ['keepalived', 'ipvsadm']
|
||||
|
||||
def upload_udp_listener_config(self, listener_id):
|
||||
stream = listener.Wrapped(flask.request.stream)
|
||||
NEED_CHECK = True
|
||||
|
||||
if not os.path.exists(util.keepalived_lvs_dir()):
|
||||
os.makedirs(util.keepalived_lvs_dir())
|
||||
if not os.path.exists(util.keepalived_backend_check_script_dir()):
|
||||
current_file_dir, _ = os.path.split(os.path.abspath(__file__))
|
||||
|
||||
try:
|
||||
script_dir = os.path.join(os.path.abspath(
|
||||
os.path.join(current_file_dir, '../..')), 'utils')
|
||||
assert True is os.path.exists(script_dir)
|
||||
assert True is os.path.exists(os.path.join(
|
||||
script_dir, CHECK_SCRIPT_NAME))
|
||||
except Exception:
|
||||
raise exceptions.Conflict(
|
||||
description='%(file_name)s not Found for '
|
||||
'UDP Listener %(listener_id)s' %
|
||||
{'file_name': CHECK_SCRIPT_NAME,
|
||||
'listener_id': listener_id})
|
||||
os.makedirs(util.keepalived_backend_check_script_dir())
|
||||
shutil.copy2(os.path.join(script_dir, CHECK_SCRIPT_NAME),
|
||||
util.keepalived_backend_check_script_path())
|
||||
os.chmod(util.keepalived_backend_check_script_path(), stat.S_IEXEC)
|
||||
# Based on current topology setting, only the amphora instances in
|
||||
# Active-Standby topology will create the directory below. So for
|
||||
# Single topology, it should not create the directory and the check
|
||||
# scripts for status change.
|
||||
if not os.path.exists(util.keepalived_check_scripts_dir()):
|
||||
NEED_CHECK = False
|
||||
|
||||
conf_file = util.keepalived_lvs_cfg_path(listener_id)
|
||||
flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
|
||||
# mode 00644
|
||||
mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
|
||||
with os.fdopen(os.open(conf_file, flags, mode), 'wb') as f:
|
||||
b = stream.read(BUFFER)
|
||||
while b:
|
||||
f.write(b)
|
||||
b = stream.read(BUFFER)
|
||||
|
||||
init_system = util.get_os_init_system()
|
||||
|
||||
file_path = util.keepalived_lvs_init_path(init_system, listener_id)
|
||||
|
||||
if init_system == consts.INIT_SYSTEMD:
|
||||
template = SYSTEMD_TEMPLATE
|
||||
init_enable_cmd = ("systemctl enable "
|
||||
"octavia-keepalivedlvs-%s"
|
||||
% str(listener_id))
|
||||
elif init_system == consts.INIT_UPSTART:
|
||||
template = UPSTART_TEMPLATE
|
||||
elif init_system == consts.INIT_SYSVINIT:
|
||||
template = SYSVINIT_TEMPLATE
|
||||
init_enable_cmd = "insserv {file}".format(file=file_path)
|
||||
else:
|
||||
raise util.UnknownInitError()
|
||||
|
||||
if init_system == consts.INIT_SYSTEMD:
|
||||
# mode 00644
|
||||
mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
|
||||
else:
|
||||
# mode 00755
|
||||
mode = (stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP |
|
||||
stat.S_IROTH | stat.S_IXOTH)
|
||||
keepalived_pid, vrrp_pid, check_pid = util.keepalived_lvs_pids_path(
|
||||
listener_id)
|
||||
if not os.path.exists(file_path):
|
||||
with os.fdopen(os.open(file_path, flags, mode), 'w') as text_file:
|
||||
text = template.render(
|
||||
keepalived_pid=keepalived_pid,
|
||||
vrrp_pid=vrrp_pid,
|
||||
check_pid=check_pid,
|
||||
keepalived_cmd=consts.KEEPALIVED_CMD,
|
||||
keepalived_cfg=util.keepalived_lvs_cfg_path(listener_id),
|
||||
amphora_nsname=consts.AMPHORA_NAMESPACE
|
||||
)
|
||||
text_file.write(text)
|
||||
|
||||
# Make sure the new service is enabled on boot
|
||||
if init_system != consts.INIT_UPSTART:
|
||||
try:
|
||||
subprocess.check_output(init_enable_cmd.split(),
|
||||
stderr=subprocess.STDOUT)
|
||||
except subprocess.CalledProcessError as e:
|
||||
LOG.debug('Failed to enable '
|
||||
'octavia-keepalivedlvs service: '
|
||||
'%(err)s', {'err': e})
|
||||
return webob.Response(json=dict(
|
||||
message="Error enabling "
|
||||
"octavia-keepalivedlvs service",
|
||||
details=e.output), status=500)
|
||||
|
||||
if NEED_CHECK:
|
||||
# inject the check script for keepalived process
|
||||
script_path = os.path.join(util.keepalived_check_scripts_dir(),
|
||||
KEEPALIVED_CHECK_SCRIPT_NAME)
|
||||
if not os.path.exists(script_path):
|
||||
with os.fdopen(os.open(script_path, flags, stat.S_IEXEC),
|
||||
'w') as script_file:
|
||||
text = check_script_file_template.render(
|
||||
consts=consts,
|
||||
init_system=init_system,
|
||||
keepalived_lvs_pid_dir=util.keepalived_lvs_dir()
|
||||
)
|
||||
script_file.write(text)
|
||||
|
||||
res = webob.Response(json={'message': 'OK'}, status=200)
|
||||
res.headers['ETag'] = stream.get_md5()
|
||||
return res
|
||||
|
||||
def _check_udp_listener_exists(self, listener_id):
|
||||
if not os.path.exists(util.keepalived_lvs_cfg_path(listener_id)):
|
||||
raise exceptions.HTTPException(
|
||||
response=webob.Response(json=dict(
|
||||
message='UDP Listener Not Found',
|
||||
details="No UDP listener with UUID: {0}".format(
|
||||
listener_id)), status=404))
|
||||
|
||||
def get_udp_listener_config(self, listener_id):
|
||||
"""Gets the keepalivedlvs config
|
||||
|
||||
:param listener_id: the id of the listener
|
||||
"""
|
||||
self._check_udp_listener_exists(listener_id)
|
||||
with open(util.keepalived_lvs_cfg_path(listener_id), 'r') as file:
|
||||
cfg = file.read()
|
||||
resp = webob.Response(cfg, content_type='text/plain')
|
||||
return resp
|
||||
|
||||
def manage_udp_listener(self, listener_id, action):
|
||||
action = action.lower()
|
||||
if action not in [consts.AMP_ACTION_START,
|
||||
consts.AMP_ACTION_STOP,
|
||||
consts.AMP_ACTION_RELOAD]:
|
||||
return webob.Response(json=dict(
|
||||
message='Invalid Request',
|
||||
details="Unknown action: {0}".format(action)), status=400)
|
||||
|
||||
self._check_udp_listener_exists(listener_id)
|
||||
if action == consts.AMP_ACTION_RELOAD:
|
||||
if consts.OFFLINE == self._check_udp_listener_status(listener_id):
|
||||
action = consts.AMP_ACTION_START
|
||||
|
||||
cmd = ("/usr/sbin/service "
|
||||
"octavia-keepalivedlvs-{listener_id} "
|
||||
"{action}".format(listener_id=listener_id, action=action))
|
||||
|
||||
try:
|
||||
subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
|
||||
except subprocess.CalledProcessError as e:
|
||||
LOG.debug('Failed to %s keepalivedlvs listener %s',
|
||||
listener_id + ' : ' + action, e)
|
||||
return webob.Response(json=dict(
|
||||
message=("Failed to {0} keepalivedlvs listener {1}"
|
||||
.format(action, listener_id)),
|
||||
details=e.output), status=500)
|
||||
|
||||
return webob.Response(
|
||||
json=dict(message='OK',
|
||||
details='keepalivedlvs listener {listener_id}'
|
||||
'{action}ed'.format(listener_id=listener_id,
|
||||
action=action)),
|
||||
status=202)
|
||||
|
||||
def _check_udp_listener_status(self, listener_id):
|
||||
if os.path.exists(util.keepalived_lvs_pids_path(listener_id)[0]):
|
||||
if os.path.exists(os.path.join(
|
||||
'/proc', util.get_keepalivedlvs_pid(listener_id))):
|
||||
# Check if the listener is disabled
|
||||
with open(util.keepalived_lvs_cfg_path(listener_id),
|
||||
'r') as file:
|
||||
cfg = file.read()
|
||||
m = re.search('virtual_server', cfg)
|
||||
if m:
|
||||
return consts.ACTIVE
|
||||
return consts.OFFLINE
|
||||
return consts.ERROR
|
||||
return consts.OFFLINE
|
||||
|
||||
def get_all_udp_listeners_status(self):
|
||||
"""Gets the status of all UDP listeners
|
||||
|
||||
This method will not consult the stats socket
|
||||
so a listener might show as ACTIVE but still be
|
||||
in ERROR
|
||||
"""
|
||||
listeners = list()
|
||||
|
||||
for udp_listener in util.get_udp_listeners():
|
||||
status = self._check_udp_listener_status(udp_listener)
|
||||
listeners.append({
|
||||
'status': status,
|
||||
'uuid': udp_listener,
|
||||
'type': 'lvs',
|
||||
})
|
||||
return listeners
|
||||
|
||||
def get_udp_listener_status(self, listener_id):
|
||||
"""Gets the status of a UDP listener
|
||||
|
||||
This method will consult the stats socket
|
||||
so calling this method will interfere with
|
||||
the health daemon with the risk of the amphora
|
||||
shut down
|
||||
|
||||
:param listener_id: The id of the listener
|
||||
"""
|
||||
self._check_udp_listener_exists(listener_id)
|
||||
|
||||
status = self._check_udp_listener_status(listener_id)
|
||||
|
||||
if status != consts.ACTIVE:
|
||||
stats = dict(
|
||||
status=status,
|
||||
uuid=listener_id,
|
||||
type=''
|
||||
)
|
||||
return webob.Response(json=stats)
|
||||
|
||||
stats = dict(
|
||||
status=status,
|
||||
uuid=listener_id,
|
||||
type='lvs'
|
||||
)
|
||||
|
||||
try:
|
||||
pool = keepalivedlvs_query.get_udp_listener_pool_status(
|
||||
listener_id)
|
||||
except subprocess.CalledProcessError as e:
|
||||
return webob.Response(json=dict(
|
||||
message="Error get kernel lvs status for udp listener",
|
||||
details=e.output), status=500)
|
||||
stats['pools'] = [pool]
|
||||
return webob.Response(json=stats)
|
||||
|
||||
def delete_udp_listener(self, listener_id):
|
||||
try:
|
||||
self._check_udp_listener_exists(listener_id)
|
||||
except exceptions.HTTPException:
|
||||
return webob.Response(json={'message': 'OK'})
|
||||
|
||||
# check if that keepalived is still running and if stop it
|
||||
keepalived_pid, vrrp_pid, check_pid = util.keepalived_lvs_pids_path(
|
||||
listener_id)
|
||||
if os.path.exists(keepalived_pid) and os.path.exists(
|
||||
os.path.join('/proc',
|
||||
util.get_keepalivedlvs_pid(listener_id))):
|
||||
cmd = ("/usr/sbin/service "
|
||||
"octavia-keepalivedlvs-{0} stop".format(listener_id))
|
||||
try:
|
||||
subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
|
||||
except subprocess.CalledProcessError as e:
|
||||
LOG.error("Failed to stop keepalivedlvs service: %s", e)
|
||||
return webob.Response(json=dict(
|
||||
message="Error stopping keepalivedlvs",
|
||||
details=e.output), status=500)
|
||||
|
||||
# Since the lvs check script based on the keepalived pid file for
|
||||
# checking whether it is alived. So here, we had stop the keepalived
|
||||
# process by the previous step, must make sure the pid files are not
|
||||
# exist.
|
||||
if (os.path.exists(keepalived_pid) or
|
||||
os.path.exists(vrrp_pid) or os.path.exists(check_pid)):
|
||||
for pid in [keepalived_pid, vrrp_pid, check_pid]:
|
||||
os.remove(pid)
|
||||
|
||||
# disable the service
|
||||
init_system = util.get_os_init_system()
|
||||
init_path = util.keepalived_lvs_init_path(init_system, listener_id)
|
||||
|
||||
if init_system == consts.INIT_SYSTEMD:
|
||||
init_disable_cmd = (
|
||||
"systemctl disable octavia-keepalivedlvs-"
|
||||
"{list}".format(list=listener_id))
|
||||
elif init_system == consts.INIT_SYSVINIT:
|
||||
init_disable_cmd = "insserv -r {file}".format(file=init_path)
|
||||
elif init_system != consts.INIT_UPSTART:
|
||||
raise util.UnknownInitError()
|
||||
|
||||
if init_system != consts.INIT_UPSTART:
|
||||
try:
|
||||
subprocess.check_output(init_disable_cmd.split(),
|
||||
stderr=subprocess.STDOUT)
|
||||
except subprocess.CalledProcessError as e:
|
||||
LOG.error("Failed to disable "
|
||||
"octavia-keepalivedlvs-%(list)s service: "
|
||||
"%(err)s", {'list': listener_id, 'err': e})
|
||||
return webob.Response(json=dict(
|
||||
message=(
|
||||
"Error disabling octavia-keepalivedlvs-"
|
||||
"{0} service".format(listener_id)),
|
||||
details=e.output), status=500)
|
||||
|
||||
# delete init script ,config file and log file for that listener
|
||||
if os.path.exists(init_path):
|
||||
os.remove(init_path)
|
||||
if os.path.exists(util.keepalived_lvs_cfg_path(listener_id)):
|
||||
os.remove(util.keepalived_lvs_cfg_path(listener_id))
|
||||
|
||||
return webob.Response(json={'message': 'OK'})
|
@ -362,7 +362,7 @@ class Listener(object):
|
||||
|
||||
return webob.Response(json={'message': 'OK'})
|
||||
|
||||
def get_all_listeners_status(self):
|
||||
def get_all_listeners_status(self, other_listeners=None):
|
||||
"""Gets the status of all listeners
|
||||
|
||||
This method will not consult the stats socket
|
||||
@ -386,6 +386,8 @@ class Listener(object):
|
||||
'type': listener_type,
|
||||
})
|
||||
|
||||
if other_listeners:
|
||||
listeners = listeners + other_listeners
|
||||
return webob.Response(json=listeners, content_type='application/json')
|
||||
|
||||
def get_listener_status(self, listener_id):
|
||||
|
@ -315,6 +315,10 @@ class RH(BaseOS):
|
||||
ETH_X_ALIAS_VIP_CONF = 'rh_plug_vip_ethX_alias.conf.j2'
|
||||
ROUTE_ETH_X_CONF = 'rh_route_ethX.conf.j2'
|
||||
RULE_ETH_X_CONF = 'rh_rule_ethX.conf.j2'
|
||||
# The reason of make them as jinja templates is the current scripts force
|
||||
# to add the iptables, so leave it now for future extending if possible.
|
||||
ETH_IFUP_LOCAL_SCRIPT = 'rh_plug_port_eth_ifup_local.conf.j2'
|
||||
ETH_IFDOWN_LOCAL_SCRIPT = 'rh_plug_port_eth_ifdown_local.conf.j2'
|
||||
|
||||
@classmethod
|
||||
def is_os_name(cls, os_name):
|
||||
@ -411,6 +415,8 @@ class RH(BaseOS):
|
||||
route_rules_interface_file_path, primary_interface,
|
||||
render_host_routes, template_rules, gateway, vip, netmask)
|
||||
|
||||
self._write_ifup_ifdown_local_scripts_if_possible()
|
||||
|
||||
def write_static_routes_interface_file(self, interface_file_path,
|
||||
interface, host_routes,
|
||||
template_routes, gateway,
|
||||
@ -460,6 +466,7 @@ class RH(BaseOS):
|
||||
self.write_static_routes_interface_file(
|
||||
routes_interface_file_path, netns_interface,
|
||||
host_routes, template_routes, None, None, None)
|
||||
self._write_ifup_ifdown_local_scripts_if_possible()
|
||||
|
||||
def bring_interfaces_up(self, ip, primary_interface, secondary_interface):
|
||||
if ip.version == 4:
|
||||
@ -473,6 +480,43 @@ class RH(BaseOS):
|
||||
def has_ifup_all(self):
|
||||
return False
|
||||
|
||||
def _write_ifup_ifdown_local_scripts_if_possible(self):
|
||||
if self._check_ifup_ifdown_local_scripts_exists():
|
||||
template_ifup_local = j2_env.get_template(
|
||||
self.ETH_IFUP_LOCAL_SCRIPT)
|
||||
self.write_port_interface_if_local_scripts(template_ifup_local)
|
||||
template_ifdown_local = j2_env.get_template(
|
||||
self.ETH_IFDOWN_LOCAL_SCRIPT)
|
||||
self.write_port_interface_if_local_scripts(template_ifdown_local,
|
||||
ifup=False)
|
||||
|
||||
def _check_ifup_ifdown_local_scripts_exists(self):
|
||||
file_names = ['ifup-local', 'ifdown-local']
|
||||
target_dir = '/sbin/'
|
||||
res = []
|
||||
for file_name in file_names:
|
||||
if os.path.exists(os.path.join(target_dir, file_name)):
|
||||
res.append(True)
|
||||
else:
|
||||
res.append(False)
|
||||
|
||||
# This means we only add the scripts when both of them are non-exists
|
||||
return not any(res)
|
||||
|
||||
def write_port_interface_if_local_scripts(
|
||||
self, template_script, ifup=True):
|
||||
file_name = 'ifup' + '-local'
|
||||
mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
|
||||
flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
|
||||
if not ifup:
|
||||
file_name = 'ifdown' + '-local'
|
||||
with os.fdopen(
|
||||
os.open(os.path.join(
|
||||
'/sbin/', file_name), flags, mode), 'w') as text_file:
|
||||
text = template_script.render()
|
||||
text_file.write(text)
|
||||
os.chmod(os.path.join('/sbin/', file_name), stat.S_IEXEC)
|
||||
|
||||
|
||||
class CentOS(RH):
|
||||
|
||||
|
@ -127,10 +127,26 @@ class Plug(object):
|
||||
sysctl = pyroute2.NSPopen(consts.AMPHORA_NAMESPACE,
|
||||
[consts.SYSCTL_CMD, '--system'],
|
||||
stdout=subprocess.PIPE)
|
||||
|
||||
sysctl.communicate()
|
||||
sysctl.wait()
|
||||
sysctl.release()
|
||||
|
||||
cmd_list = [['modprobe', 'ip_vs'],
|
||||
[consts.SYSCTL_CMD, '-w', 'net.ipv4.vs.conntrack=1']]
|
||||
if ip.version == 4:
|
||||
# For lvs function, enable ip_vs kernel module, enable ip_forward
|
||||
# conntrack in amphora network namespace.
|
||||
cmd_list.append([consts.SYSCTL_CMD, '-w', 'net.ipv4.ip_forward=1'])
|
||||
elif ip.version == 6:
|
||||
cmd_list.append([consts.SYSCTL_CMD, '-w',
|
||||
'net.ipv6.conf.all.forwarding=1'])
|
||||
for cmd in cmd_list:
|
||||
ns_exec = pyroute2.NSPopen(consts.AMPHORA_NAMESPACE, cmd,
|
||||
stdout=subprocess.PIPE)
|
||||
ns_exec.wait()
|
||||
ns_exec.release()
|
||||
|
||||
with pyroute2.IPRoute() as ipr:
|
||||
# Move the interfaces into the namespace
|
||||
idx = ipr.link_lookup(ifname=default_netns_interface)[0]
|
||||
|
@ -25,6 +25,7 @@ from octavia.amphorae.backends.agent.api_server import keepalived
|
||||
from octavia.amphorae.backends.agent.api_server import listener
|
||||
from octavia.amphorae.backends.agent.api_server import osutils
|
||||
from octavia.amphorae.backends.agent.api_server import plug
|
||||
from octavia.amphorae.backends.agent.api_server import udp_listener_base
|
||||
|
||||
PATH_PREFIX = '/' + api_server.VERSION
|
||||
|
||||
@ -42,12 +43,25 @@ def register_app_error_handler(app):
|
||||
app.register_error_handler(code, make_json_error)
|
||||
|
||||
|
||||
def check_and_return_request_listener_protocol(request):
|
||||
try:
|
||||
protocol_dict = request.get_json()
|
||||
assert type(protocol_dict) is dict
|
||||
assert 'protocol' in protocol_dict
|
||||
except Exception:
|
||||
raise exceptions.BadRequest(
|
||||
description='Invalid protocol information for Listener')
|
||||
return protocol_dict['protocol']
|
||||
|
||||
|
||||
class Server(object):
|
||||
def __init__(self):
|
||||
self.app = flask.Flask(__name__)
|
||||
self._osutils = osutils.BaseOS.get_os_util()
|
||||
self._keepalived = keepalived.Keepalived()
|
||||
self._listener = listener.Listener()
|
||||
self._udp_listener = (udp_listener_base.UdpListenerApiServerBase.
|
||||
get_server_driver())
|
||||
self._plug = plug.Plug(self._osutils)
|
||||
self._amphora_info = amphora_info.AmphoraInfo(self._osutils)
|
||||
|
||||
@ -57,10 +71,19 @@ class Server(object):
|
||||
'/listeners/<amphora_id>/<listener_id>/haproxy',
|
||||
view_func=self.upload_haproxy_config,
|
||||
methods=['PUT'])
|
||||
self.app.add_url_rule(rule=PATH_PREFIX +
|
||||
'/listeners/<amphora_id>/<listener_id>'
|
||||
'/udp_listener',
|
||||
view_func=self.upload_udp_listener_config,
|
||||
methods=['PUT'])
|
||||
self.app.add_url_rule(rule=PATH_PREFIX +
|
||||
'/listeners/<listener_id>/haproxy',
|
||||
view_func=self.get_haproxy_config,
|
||||
methods=['GET'])
|
||||
self.app.add_url_rule(rule=PATH_PREFIX +
|
||||
'/listeners/<listener_id>/udp_listener',
|
||||
view_func=self.get_udp_listener_config,
|
||||
methods=['GET'])
|
||||
self.app.add_url_rule(rule=PATH_PREFIX +
|
||||
'/listeners/<listener_id>/<action>',
|
||||
view_func=self.start_stop_listener,
|
||||
@ -113,25 +136,48 @@ class Server(object):
|
||||
def upload_haproxy_config(self, amphora_id, listener_id):
|
||||
return self._listener.upload_haproxy_config(amphora_id, listener_id)
|
||||
|
||||
def upload_udp_listener_config(self, amphora_id, listener_id):
|
||||
return self._udp_listener.upload_udp_listener_config(listener_id)
|
||||
|
||||
def get_haproxy_config(self, listener_id):
|
||||
return self._listener.get_haproxy_config(listener_id)
|
||||
|
||||
def get_udp_listener_config(self, listener_id):
|
||||
return self._udp_listener.get_udp_listener_config(listener_id)
|
||||
|
||||
def start_stop_listener(self, listener_id, action):
|
||||
protocol = check_and_return_request_listener_protocol(
|
||||
flask.request)
|
||||
if protocol == 'UDP':
|
||||
return self._udp_listener.manage_udp_listener(
|
||||
listener_id, action)
|
||||
return self._listener.start_stop_listener(listener_id, action)
|
||||
|
||||
def delete_listener(self, listener_id):
|
||||
protocol = check_and_return_request_listener_protocol(
|
||||
flask.request)
|
||||
if protocol == 'UDP':
|
||||
return self._udp_listener.delete_udp_listener(listener_id)
|
||||
return self._listener.delete_listener(listener_id)
|
||||
|
||||
def get_details(self):
|
||||
return self._amphora_info.compile_amphora_details()
|
||||
return self._amphora_info.compile_amphora_details(
|
||||
extend_udp_driver=self._udp_listener)
|
||||
|
||||
def get_info(self):
|
||||
return self._amphora_info.compile_amphora_info()
|
||||
return self._amphora_info.compile_amphora_info(
|
||||
extend_udp_driver=self._udp_listener)
|
||||
|
||||
def get_all_listeners_status(self):
|
||||
return self._listener.get_all_listeners_status()
|
||||
udp_listeners = self._udp_listener.get_all_udp_listeners_status()
|
||||
return self._listener.get_all_listeners_status(
|
||||
other_listeners=udp_listeners)
|
||||
|
||||
def get_listener_status(self, listener_id):
|
||||
protocol = check_and_return_request_listener_protocol(
|
||||
flask.request)
|
||||
if protocol == 'UDP':
|
||||
return self._udp_listener.get_udp_listener_status(listener_id)
|
||||
return self._listener.get_listener_status(listener_id)
|
||||
|
||||
def upload_certificate(self, listener_id, filename):
|
||||
|
@ -10,6 +10,12 @@ RemainAfterExit=yes
|
||||
ExecStart=-/sbin/ip netns add {{ amphora_nsname }}
|
||||
# Load the system sysctl into the new namespace
|
||||
ExecStart=-/sbin/ip netns exec {{ amphora_nsname }} sysctl --system
|
||||
# Enable kernel module ip_vs for lvs function in amphora network namespace
|
||||
ExecStart=-/sbin/ip netns exec {{ amphora_nsname }} modprobe ip_vs
|
||||
# Enable ip_forward and conntrack kernel configuration
|
||||
ExecStart=-/sbin/ip netns exec {{ amphora_nsname }} sysctl -w net.ipv4.ip_forward=1
|
||||
ExecStart=-/sbin/ip netns exec {{ amphora_nsname }} sysctl -w net.ipv4.vs.conntrack=1
|
||||
ExecStart=-/sbin/ip netns exec {{ amphora_nsname }} sysctl -w net.ipv6.conf.all.forwarding=1
|
||||
# We need the plugged_interfaces file sorted to join the host interfaces
|
||||
ExecStart=-/bin/sh -c '/usr/bin/sort -k 1 /var/lib/octavia/plugged_interfaces > /var/lib/octavia/plugged_interfaces.sorted'
|
||||
# Assign the interfaces into the namespace with the appropriate name
|
||||
|
@ -8,7 +8,11 @@ Wants=network-online.target
|
||||
SELinuxContext=system_u:system_r:keepalived_t:s0
|
||||
Type=forking
|
||||
KillMode=process
|
||||
{% if vrrp_pid and check_pid %}
|
||||
ExecStart=/sbin/ip netns exec {{ amphora_nsname }} {{ keepalived_cmd }} -D -d -f {{ keepalived_cfg }} -p {{ keepalived_pid }} -r {{ vrrp_pid }} -c {{ check_pid }}
|
||||
{% else %}
|
||||
ExecStart=/sbin/ip netns exec {{ amphora_nsname }} {{ keepalived_cmd }} -D -d -f {{ keepalived_cfg }} -p {{ keepalived_pid }}
|
||||
{% endif %}
|
||||
ExecReload=/bin/kill -HUP $MAINPID
|
||||
PIDFile={{ keepalived_pid }}
|
||||
|
||||
|
@ -18,7 +18,11 @@ DAEMON="ip netns exec {{ amphora_nsname }} {{ keepalived_cmd }}"
|
||||
NAME=octavia-keepalived
|
||||
DESC=octavia-keepalived
|
||||
TMPFILES="/tmp/.vrrp /tmp/.healthcheckers"
|
||||
{% if vrrp_pid and check_pid %}
|
||||
DAEMON_ARGS="-D -d -f {{ keepalived_cfg }} -p {{ keepalived_pid }} -r {{ vrrp_pid }} -c {{ check_pid }}"
|
||||
{% else %}
|
||||
DAEMON_ARGS="-D -d -f {{ keepalived_cfg }} -p {{ keepalived_pid }}"
|
||||
{% endif %}
|
||||
|
||||
#includes lsb functions
|
||||
. /lib/lsb/init-functions
|
||||
|
@ -22,4 +22,8 @@ stop on runlevel [!2345]
|
||||
|
||||
respawn
|
||||
|
||||
{% if vrrp_pid and check_pid %}
|
||||
exec /sbin/ip netns exec {{ amphora_nsname }} {{ keepalived_cmd }} -n -D -d -f {{ keepalived_cfg }} -p {{ keepalived_pid }} -r {{ vrrp_pid }} -c {{ check_pid }}
|
||||
{% else %}
|
||||
exec /sbin/ip netns exec {{ amphora_nsname }} {{ keepalived_cmd }} -n -D -d -f {{ keepalived_cfg }} -p {{ keepalived_pid }}
|
||||
{% endif %}
|
||||
|
@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Don't try to run the directory when it is empty
|
||||
shopt -s nullglob
|
||||
|
||||
status=0
|
||||
for file in {{ keepalived_lvs_pid_dir }}/*
|
||||
do
|
||||
file_ext=${file#*.}
|
||||
case $file_ext in
|
||||
pid) echo "Check keepalived pid file: " $file;;
|
||||
*) continue;;
|
||||
esac
|
||||
{% if init_system == consts.INIT_SYSTEMD %}
|
||||
systemctl status $(basename $file .pid) > /dev/null
|
||||
{% elif init_system in (consts.INIT_UPSTART, consts.INIT_SYSVINIT) %}
|
||||
kill -0 `cat $file`
|
||||
{% endif %}
|
||||
status=$(( $status + $? ))
|
||||
done
|
||||
exit $status
|
@ -27,6 +27,8 @@ mtu {{ mtu }}
|
||||
up route add -net {{ hr.network }} gw {{ hr.gw }} dev {{ interface }}
|
||||
down route del -net {{ hr.network }} gw {{ hr.gw }} dev {{ interface }}
|
||||
{%- endfor %}
|
||||
post-up /sbin/ip{{ '6' if ipv6 }}tables -t nat -A POSTROUTING -p udp -o {{ interface }} -j MASQUERADE
|
||||
post-down /sbin/ip{{ '6' if ipv6 }}tables -t nat -D POSTROUTING -p udp -o {{ interface }} -j MASQUERADE
|
||||
{%- else %}
|
||||
iface {{ interface }} inet dhcp
|
||||
auto {{ interface }}:0
|
||||
|
@ -53,3 +53,5 @@ post-down /sbin/ip {{ '-6 ' if vip_ipv6 }}route del {{ hr.network }} via {{ hr.g
|
||||
{%- endfor %}
|
||||
post-up /sbin/ip {{ '-6 ' if vip_ipv6 }}rule add from {{ vip }}/32 table 1 priority 100
|
||||
post-down /sbin/ip {{ '-6 ' if vip_ipv6 }}rule del from {{ vip }}/32 table 1 priority 100
|
||||
post-up /sbin/ip{{ '6' if vip_ipv6 }}tables -t nat -A POSTROUTING -p udp -o {{ interface }} -j MASQUERADE
|
||||
post-down /sbin/ip{{ '6' if vip_ipv6 }}tables -t nat -D POSTROUTING -p udp -o {{ interface }} -j MASQUERADE
|
||||
|
@ -0,0 +1,19 @@
|
||||
{# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#}
|
||||
# Generated by Octavia agent
|
||||
#!/bin/bash
|
||||
if [[ "$1" != "lo" ]]
|
||||
then
|
||||
/sbin/iptables -t nat -D POSTROUTING -o $1 -p udp -j MASQUERADE
|
||||
/sbin/ip6tables -t nat -D POSTROUTING -o $1 -p udp -j MASQUERADE
|
||||
fi
|
@ -0,0 +1,19 @@
|
||||
{# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#}
|
||||
# Generated by Octavia agent
|
||||
#!/bin/bash
|
||||
if [[ "$1" != "lo" ]]
|
||||
then
|
||||
/sbin/iptables -t nat -A POSTROUTING -o $1 -p udp -j MASQUERADE
|
||||
/sbin/ip6tables -t nat -A POSTROUTING -o $1 -p udp -j MASQUERADE
|
||||
fi
|
123
octavia/amphorae/backends/agent/api_server/udp_listener_base.py
Normal file
123
octavia/amphorae/backends/agent/api_server/udp_listener_base.py
Normal file
@ -0,0 +1,123 @@
|
||||
# Copyright 2018 OpenStack Foundation
|
||||
#
|
||||
# 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 abc
|
||||
|
||||
import six
|
||||
|
||||
from oslo_config import cfg
|
||||
from stevedore import driver as stevedore_driver
|
||||
|
||||
CONF = cfg.CONF
|
||||
UDP_SERVER_NAMESPACE = 'octavia.amphora.udp_api_server'
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class UdpListenerApiServerBase(object):
|
||||
"""Base UDP Listener Server API
|
||||
|
||||
"""
|
||||
|
||||
_SUBSCRIBED_AMP_COMPILE = []
|
||||
SERVER_INSTANCE = None
|
||||
|
||||
@classmethod
|
||||
def get_server_driver(cls):
|
||||
if not cls.SERVER_INSTANCE:
|
||||
cls.SERVER_INSTANCE = stevedore_driver.DriverManager(
|
||||
namespace=UDP_SERVER_NAMESPACE,
|
||||
name=CONF.amphora_agent.amphora_udp_driver,
|
||||
invoke_on_load=True,
|
||||
).driver
|
||||
return cls.SERVER_INSTANCE
|
||||
|
||||
def get_subscribed_amp_compile_info(self):
|
||||
return self._SUBSCRIBED_AMP_COMPILE
|
||||
|
||||
@abc.abstractmethod
|
||||
def upload_udp_listener_config(self, listener_id):
|
||||
"""Upload the configuration for UDP.
|
||||
|
||||
:param listener_id: The id of a UDP Listener
|
||||
|
||||
:returns: HTTP response with status code.
|
||||
:raises Exception: If any file / directory is not found or
|
||||
fail to create.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_udp_listener_config(self, listener_id):
|
||||
"""Gets the UDP Listener configuration details
|
||||
|
||||
:param listener_id: the id of the UDP Listener
|
||||
|
||||
:returns: HTTP response with status code.
|
||||
:raises Exception: If the listener is failed to find.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def manage_udp_listener(self, listener_id, action):
|
||||
"""Gets the UDP Listener configuration details
|
||||
|
||||
:param listener_id: the id of the UDP Listener
|
||||
:param action: the operation type.
|
||||
|
||||
:returns: HTTP response with status code.
|
||||
:raises Exception: If the listener is failed to find.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_all_udp_listeners_status(self):
|
||||
"""Gets the status of all UDP Listeners
|
||||
|
||||
This method will not consult the stats socket
|
||||
so a listener might show as ACTIVE but still be
|
||||
in ERROR
|
||||
|
||||
:returns: a list of UDP Listener status
|
||||
:raises Exception: If the listener pid located directory is not exist
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_udp_listener_status(self, listener_id):
|
||||
"""Gets the status of a UDP listener
|
||||
|
||||
:param listener_id: The id of the listener
|
||||
|
||||
:returns: HTTP response with status code.
|
||||
:raises Exception: If the listener is failed to find.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def delete_udp_listener(self, listener_id):
|
||||
"""Delete a UDP Listener from a amphora
|
||||
|
||||
:param listener_id: The id of the listener
|
||||
|
||||
:returns: HTTP response with status code.
|
||||
:raises Exception: If unsupport initial system of amphora.
|
||||
|
||||
"""
|
||||
pass
|
@ -14,6 +14,7 @@
|
||||
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
from oslo_config import cfg
|
||||
@ -41,6 +42,56 @@ def init_path(listener_id, init_system):
|
||||
raise UnknownInitError()
|
||||
|
||||
|
||||
def keepalived_lvs_dir():
|
||||
return os.path.join(CONF.haproxy_amphora.base_path, 'lvs')
|
||||
|
||||
|
||||
def keepalived_lvs_init_path(init_system, listener_id):
|
||||
if init_system == consts.INIT_SYSTEMD:
|
||||
return os.path.join(consts.SYSTEMD_DIR,
|
||||
consts.KEEPALIVED_SYSTEMD_PREFIX %
|
||||
str(listener_id))
|
||||
elif init_system == consts.INIT_UPSTART:
|
||||
return os.path.join(consts.UPSTART_DIR,
|
||||
consts.KEEPALIVED_UPSTART_PREFIX %
|
||||
str(listener_id))
|
||||
elif init_system == consts.INIT_SYSVINIT:
|
||||
return os.path.join(consts.SYSVINIT_DIR,
|
||||
consts.KEEPALIVED_SYSVINIT_PREFIX %
|
||||
str(listener_id))
|
||||
else:
|
||||
raise UnknownInitError()
|
||||
|
||||
|
||||
def keepalived_backend_check_script_dir():
|
||||
return os.path.join(CONF.haproxy_amphora.base_path, 'lvs/check/')
|
||||
|
||||
|
||||
def keepalived_backend_check_script_path():
|
||||
return os.path.join(keepalived_backend_check_script_dir(),
|
||||
'udp_check.sh')
|
||||
|
||||
|
||||
def keepalived_lvs_pids_path(listener_id):
|
||||
pids_path = {}
|
||||
for file_ext in ['pid', 'vrrp.pid', 'check.pid']:
|
||||
pids_path[file_ext] = (
|
||||
os.path.join(CONF.haproxy_amphora.base_path,
|
||||
('lvs/octavia-keepalivedlvs-%s.%s') %
|
||||
(str(listener_id), file_ext)))
|
||||
return pids_path['pid'], pids_path['vrrp.pid'], pids_path['check.pid']
|
||||
|
||||
|
||||
def keepalived_lvs_cfg_path(listener_id):
|
||||
return os.path.join(CONF.haproxy_amphora.base_path,
|
||||
('lvs/octavia-keepalivedlvs-%s.conf') %
|
||||
str(listener_id))
|
||||
|
||||
|
||||
def keepalived_lvs_iptables_dir():
|
||||
return os.path.join(CONF.haproxy_amphora.base_path, 'lvs/iptables/')
|
||||
|
||||
|
||||
def haproxy_dir(listener_id):
|
||||
return os.path.join(CONF.haproxy_amphora.base_path, listener_id)
|
||||
|
||||
@ -58,6 +109,12 @@ def get_haproxy_pid(listener_id):
|
||||
return f.readline().rstrip()
|
||||
|
||||
|
||||
def get_keepalivedlvs_pid(listener_id):
|
||||
pid_file, _, _ = keepalived_lvs_pids_path(listener_id)
|
||||
with open(pid_file, 'r') as f:
|
||||
return f.readline().rstrip()
|
||||
|
||||
|
||||
def haproxy_sock_path(listener_id):
|
||||
return os.path.join(CONF.haproxy_amphora.base_path, listener_id + '.sock')
|
||||
|
||||
@ -125,6 +182,24 @@ def is_listener_running(listener_id):
|
||||
os.path.join('/proc', get_haproxy_pid(listener_id)))
|
||||
|
||||
|
||||
def get_udp_listeners():
|
||||
result = []
|
||||
if os.path.exists(keepalived_lvs_dir()):
|
||||
for f in os.listdir(keepalived_lvs_dir()):
|
||||
if f.endswith('.conf'):
|
||||
prefix = f.split('.')[0]
|
||||
if re.search("octavia-keepalivedlvs-", prefix):
|
||||
result.append(f.split(
|
||||
'octavia-keepalivedlvs-')[1].split('.')[0])
|
||||
return result
|
||||
|
||||
|
||||
def is_udp_listener_running(listener_id):
|
||||
pid_file, _, _ = keepalived_lvs_pids_path(listener_id)
|
||||
return os.path.exists(pid_file) and os.path.exists(
|
||||
os.path.join('/proc', get_keepalivedlvs_pid(listener_id)))
|
||||
|
||||
|
||||
def get_os_init_system():
|
||||
if os.path.exists(consts.INIT_PROC_COMM_PATH):
|
||||
with open(consts.INIT_PROC_COMM_PATH, 'r') as init_comm:
|
||||
|
@ -42,3 +42,4 @@ agent_server_network_file = {{ agent_server_network_file }}
|
||||
{% endif -%}
|
||||
agent_request_read_timeout = {{ agent_request_read_timeout }}
|
||||
amphora_id = {{ amphora_id }}
|
||||
amphora_udp_driver = {{ amphora_udp_driver }}
|
||||
|
@ -25,6 +25,8 @@ import six
|
||||
from octavia.amphorae.backends.agent.api_server import util
|
||||
from octavia.amphorae.backends.health_daemon import health_sender
|
||||
from octavia.amphorae.backends.utils import haproxy_query
|
||||
from octavia.amphorae.backends.utils import keepalivedlvs_query
|
||||
|
||||
|
||||
if six.PY2:
|
||||
import Queue as queue # pylint: disable=wrong-import-order
|
||||
@ -143,4 +145,28 @@ def build_stats_message():
|
||||
pools = listener_dict['pools']
|
||||
pools[pool_id] = {"status": pool['status'],
|
||||
"members": pool['members']}
|
||||
|
||||
# UDP listener part
|
||||
udp_listener_ids = util.get_udp_listeners()
|
||||
if udp_listener_ids:
|
||||
listeners_stats = keepalivedlvs_query.get_udp_listeners_stats()
|
||||
if listeners_stats:
|
||||
for listener_id, listener_stats in listeners_stats.items():
|
||||
pool_status = keepalivedlvs_query.get_udp_listener_pool_status(
|
||||
listener_id)
|
||||
udp_listener_dict = dict()
|
||||
udp_listener_dict['status'] = listener_stats['status']
|
||||
udp_listener_dict['stats'] = {
|
||||
'tx': listener_stats['stats']['bout'],
|
||||
'rx': listener_stats['stats']['bin'],
|
||||
'conns': listener_stats['stats']['scur'],
|
||||
'totconns': listener_stats['stats']['stot'],
|
||||
'ereq': listener_stats['stats']['ereq']
|
||||
}
|
||||
if pool_status:
|
||||
udp_listener_dict['pools'] = {
|
||||
pool_status['lvs']['uuid']: {
|
||||
"status": pool_status['lvs']['status'],
|
||||
"members": pool_status['lvs']['members']}}
|
||||
msg['listeners'][listener_id] = udp_listener_dict
|
||||
return msg
|
||||
|
421
octavia/amphorae/backends/utils/keepalivedlvs_query.py
Normal file
421
octavia/amphorae/backends/utils/keepalivedlvs_query.py
Normal file
@ -0,0 +1,421 @@
|
||||
# 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 re
|
||||
import subprocess
|
||||
|
||||
import netaddr
|
||||
from oslo_log import log as logging
|
||||
|
||||
from octavia.amphorae.backends.agent.api_server import util
|
||||
from octavia.common import constants
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
KERNEL_LVS_PATH = '/proc/net/ip_vs'
|
||||
KERNEL_LVS_STATS_PATH = '/proc/net/ip_vs_stats'
|
||||
LVS_KEY_REGEX = re.compile(r"RemoteAddress:Port\s+(.*$)")
|
||||
V4_RS_VALUE_REGEX = re.compile(r"(\w{8}:\w{4})\s+(.*$)")
|
||||
V4_HEX_IP_REGEX = re.compile(r"(\w{2})(\w{2})(\w{2})(\w{2})")
|
||||
V6_RS_VALUE_REGEX = re.compile(r"(\[[[\w{4}:]+\b\]:\w{4})\s+(.*$)")
|
||||
|
||||
NS_REGEX = re.compile(r"net_namespace\s(\w+-\w+)")
|
||||
V4_VS_REGEX = re.compile(r"virtual_server\s([\d+\.]+\b)\s(\d{1,5})")
|
||||
V4_RS_REGEX = re.compile(r"real_server\s([\d+\.]+\b)\s(\d{1,5})")
|
||||
V6_VS_REGEX = re.compile(r"virtual_server\s([\w*:]+\b)\s(\d{1,5})")
|
||||
V6_RS_REGEX = re.compile(r"real_server\s([\w*:]+\b)\s(\d{1,5})")
|
||||
CONFIG_COMMENT_REGEX = re.compile(
|
||||
r"#\sConfiguration\sfor\s(\w+)\s(\w{8}-\w{4}-\w{4}-\w{4}-\w{12})")
|
||||
|
||||
|
||||
def read_kernel_file(ns_name, file_path):
|
||||
cmd = ("ip netns exec {ns} cat {lvs_stat_path}".format(
|
||||
ns=ns_name, lvs_stat_path=file_path))
|
||||
try:
|
||||
output = subprocess.check_output(cmd.split(),
|
||||
stderr=subprocess.STDOUT)
|
||||
except subprocess.CalledProcessError as e:
|
||||
LOG.error("Failed to get kernel lvs status in ns %(ns_name)s "
|
||||
"%(kernel_lvs_path)s: %(err)s %(out)s",
|
||||
{'ns_name': ns_name, 'kernel_lvs_path': file_path,
|
||||
'err': e, 'out': e.output})
|
||||
raise e
|
||||
# py3 treat the output as bytes type.
|
||||
if isinstance(output, bytes):
|
||||
output = output.decode('utf-8')
|
||||
return output
|
||||
|
||||
|
||||
def get_listener_realserver_mapping(ns_name, listener_ip_port):
|
||||
# returned result:
|
||||
# actual_member_result = {'rs_ip:listened_port': {
|
||||
# 'status': 'UP',
|
||||
# 'Forward': forward_type,
|
||||
# 'Weight': 5,
|
||||
# 'ActiveConn': 0,
|
||||
# 'InActConn': 0
|
||||
# }}
|
||||
try:
|
||||
listener_ip, listener_port = listener_ip_port.split(':')
|
||||
except ValueError:
|
||||
start = listener_ip_port.index('[') + 1
|
||||
end = listener_ip_port.index(']')
|
||||
listener_ip = listener_ip_port[start:end]
|
||||
listener_port = listener_ip_port[end + 2:]
|
||||
ip_obj = netaddr.IPAddress(listener_ip)
|
||||
output = read_kernel_file(ns_name, KERNEL_LVS_PATH).split('\n')
|
||||
ip_to_hex_format = ''
|
||||
if ip_obj.version == 4:
|
||||
for int_str in listener_ip.split('.'):
|
||||
if int(int_str) <= 15:
|
||||
str_piece = '0' + hex(int(int_str))[2:].upper()
|
||||
else:
|
||||
str_piece = hex(int(int_str))[2:].upper()
|
||||
ip_to_hex_format += str_piece
|
||||
elif ip_obj.version == 6:
|
||||
piece_list = []
|
||||
for word in ip_obj.words:
|
||||
str_len = len(hex(word)[2:])
|
||||
if str_len < 4:
|
||||
str_piece = '0' * (4 - str_len) + hex(word)[2:].lower()
|
||||
else:
|
||||
str_piece = hex(word)[2:].lower()
|
||||
piece_list.append(str_piece)
|
||||
ip_to_hex_format = ":".join(piece_list)
|
||||
ip_to_hex_format = '\[' + ip_to_hex_format + '\]'
|
||||
port_hex_format = hex(int(listener_port))[2:].upper()
|
||||
if len(port_hex_format) < 4:
|
||||
port_hex_format = ('0' * (4 - len(port_hex_format)) +
|
||||
port_hex_format)
|
||||
idex = ip_to_hex_format + ':' + port_hex_format
|
||||
|
||||
def _hit_identify(line):
|
||||
m = re.match(r'^UDP\s+%s\s+\w+' % idex, line)
|
||||
if m:
|
||||
return True
|
||||
return False
|
||||
actual_member_result = {}
|
||||
find_target_block = False
|
||||
result_keys = []
|
||||
for line in output:
|
||||
if 'RemoteAddress:Port' in line:
|
||||
result_keys = re.split(r'\s+',
|
||||
LVS_KEY_REGEX.findall(line)[0].strip())
|
||||
elif line.startswith('UDP') and find_target_block:
|
||||
break
|
||||
elif line.startswith('UDP') and _hit_identify(line):
|
||||
find_target_block = True
|
||||
elif find_target_block and line:
|
||||
rs_is_ipv4 = True
|
||||
all_values = V4_RS_VALUE_REGEX.findall(line)
|
||||
# If can not get all_values with ipv4 regex, then this line must be
|
||||
# a ipv6 real server record.
|
||||
if not all_values:
|
||||
all_values = V6_RS_VALUE_REGEX.findall(line)
|
||||
rs_is_ipv4 = False
|
||||
|
||||
all_values = all_values[0]
|
||||
ip_port = all_values[0]
|
||||
result_values = re.split(r"\s+", all_values[1].strip())
|
||||
if rs_is_ipv4:
|
||||
actual_member_ip_port = ip_port.split(':')
|
||||
hex_ip_list = V4_HEX_IP_REGEX.findall(
|
||||
actual_member_ip_port[0])[0]
|
||||
ip_string = ''
|
||||
for hex_ip in hex_ip_list:
|
||||
ip_string = ip_string + str(int(hex_ip, 16)) + '.'
|
||||
ip_string = ip_string[:-1]
|
||||
port_string = str(int(actual_member_ip_port[1], 16))
|
||||
member_ip_port_string = ip_string + ':' + port_string
|
||||
else:
|
||||
start = ip_port.index('[') + 1
|
||||
end = ip_port.index(']')
|
||||
ip_string = ip_port[start:end]
|
||||
port_string = ip_port[end + 2:]
|
||||
member_ip_port_string = '[' + str(
|
||||
netaddr.IPAddress(ip_string)) + ']:' + str(
|
||||
int(port_string, 16))
|
||||
result_key_count = len(result_keys)
|
||||
for index in range(result_key_count):
|
||||
if member_ip_port_string not in actual_member_result:
|
||||
actual_member_result[
|
||||
member_ip_port_string] = {'status': constants.UP,
|
||||
result_keys[index]:
|
||||
result_values[index]}
|
||||
else:
|
||||
# The other values include the weight
|
||||
actual_member_result[
|
||||
member_ip_port_string][
|
||||
result_keys[index]] = result_values[index]
|
||||
continue
|
||||
|
||||
return find_target_block, actual_member_result
|
||||
|
||||
|
||||
def get_udp_listener_resource_ipports_nsname(listener_id):
|
||||
# resource_ipport_mapping = {'Listener': {'id': listener-id,
|
||||
# 'ipport': ipport},
|
||||
# 'Pool': {'id': pool-id},
|
||||
# 'Members': [{'id': member-id-1,
|
||||
# 'ipport': ipport},
|
||||
# {'id': member-id-2,
|
||||
# 'ipport': ipport}],
|
||||
# 'HealthMonitor': {'id': healthmonitor-id}}
|
||||
resource_ipport_mapping = {}
|
||||
with open(util.keepalived_lvs_cfg_path(listener_id), 'r') as f:
|
||||
cfg = f.read()
|
||||
ns_name = NS_REGEX.findall(cfg)[0]
|
||||
listener_ip_port = V4_VS_REGEX.findall(cfg)
|
||||
if not listener_ip_port:
|
||||
listener_ip_port = V6_VS_REGEX.findall(cfg)
|
||||
listener_ip_port = listener_ip_port[0] if listener_ip_port else []
|
||||
|
||||
if not listener_ip_port:
|
||||
# If not get listener_ip_port from the lvs config file,
|
||||
# that means the udp listener's default pool have no enabled member
|
||||
# yet. But at this moment, we can get listener_id and ns_name, so
|
||||
# for this function, we will just return ns_name
|
||||
return resource_ipport_mapping, ns_name
|
||||
|
||||
cfg_line = cfg.split('\n')
|
||||
rs_ip_port_list = []
|
||||
for line in cfg_line:
|
||||
if 'real_server' in line:
|
||||
res = V4_RS_REGEX.findall(line)
|
||||
if not res:
|
||||
res = V6_RS_REGEX.findall(line)
|
||||
rs_ip_port_list.append(res[0])
|
||||
|
||||
resource_type_ids = CONFIG_COMMENT_REGEX.findall(cfg)
|
||||
|
||||
for resource_type, resource_id in resource_type_ids:
|
||||
value = {'id': resource_id}
|
||||
if resource_type == 'Member':
|
||||
resource_type = '%ss' % resource_type
|
||||
if resource_type not in resource_ipport_mapping:
|
||||
value = [value]
|
||||
if resource_type not in resource_ipport_mapping:
|
||||
resource_ipport_mapping[resource_type] = value
|
||||
elif resource_type == 'Members':
|
||||
resource_ipport_mapping[resource_type].append(value)
|
||||
|
||||
if rs_ip_port_list:
|
||||
rs_ip_port_count = len(rs_ip_port_list)
|
||||
for index in range(rs_ip_port_count):
|
||||
if netaddr.IPAddress(
|
||||
rs_ip_port_list[index][0]).version == 6:
|
||||
rs_ip_port_list[index] = (
|
||||
'[' + rs_ip_port_list[index][0] + ']',
|
||||
rs_ip_port_list[index][1])
|
||||
resource_ipport_mapping['Members'][index]['ipport'] = (
|
||||
rs_ip_port_list[index][0] + ':' +
|
||||
rs_ip_port_list[index][1])
|
||||
|
||||
if netaddr.IPAddress(listener_ip_port[0]).version == 6:
|
||||
listener_ip_port = (
|
||||
'[' + listener_ip_port[0] + ']', listener_ip_port[1])
|
||||
resource_ipport_mapping['Listener']['ipport'] = (
|
||||
listener_ip_port[0] + ':' + listener_ip_port[1])
|
||||
|
||||
return resource_ipport_mapping, ns_name
|
||||
|
||||
|
||||
def get_udp_listener_pool_status(listener_id):
|
||||
(resource_ipport_mapping,
|
||||
ns_name) = get_udp_listener_resource_ipports_nsname(listener_id)
|
||||
if 'Pool' not in resource_ipport_mapping:
|
||||
return {}
|
||||
elif 'Members' not in resource_ipport_mapping:
|
||||
return {'lvs': {
|
||||
'uuid': resource_ipport_mapping['Pool']['id'],
|
||||
'status': constants.DOWN,
|
||||
'members': {}
|
||||
}}
|
||||
|
||||
_, realserver_result = get_listener_realserver_mapping(
|
||||
ns_name, resource_ipport_mapping['Listener']['ipport'])
|
||||
pool_status = constants.UP
|
||||
member_results = {}
|
||||
if realserver_result:
|
||||
member_ip_port_list = [
|
||||
member['ipport'] for member in resource_ipport_mapping['Members']]
|
||||
down_member_ip_port_set = set(
|
||||
member_ip_port_list) - set(list(realserver_result.keys()))
|
||||
|
||||
for member_ip_port in member_ip_port_list:
|
||||
member_id = None
|
||||
for member in resource_ipport_mapping['Members']:
|
||||
if member['ipport'] == member_ip_port:
|
||||
member_id = member['id']
|
||||
if member_ip_port in down_member_ip_port_set:
|
||||
status = constants.DOWN
|
||||
elif int(realserver_result[member_ip_port]['Weight']) == 0:
|
||||
status = constants.DRAIN
|
||||
else:
|
||||
status = realserver_result[member_ip_port]['status']
|
||||
|
||||
if member_id:
|
||||
member_results[member_id] = status
|
||||
else:
|
||||
pool_status = constants.DOWN
|
||||
for member in resource_ipport_mapping['Members']:
|
||||
member_results[member['id']] = constants.DOWN
|
||||
|
||||
return {
|
||||
'lvs':
|
||||
{
|
||||
'uuid': resource_ipport_mapping['Pool']['id'],
|
||||
'status': pool_status,
|
||||
'members': member_results
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def get_ipvsadm_info(ns_name, is_stats_cmd=False):
|
||||
cmd_list = ['ip', 'netns', 'exec', ns_name, 'ipvsadm', '-Ln']
|
||||
if is_stats_cmd:
|
||||
cmd_list.append('--stats')
|
||||
output = subprocess.check_output(cmd_list, stderr=subprocess.STDOUT)
|
||||
if isinstance(output, bytes):
|
||||
output = output.decode('utf-8')
|
||||
output = output.split('\n')
|
||||
fields = []
|
||||
# mapping = {'listeneripport': {'Linstener': vs_values,
|
||||
# 'members': [rs_values1, rs_values2]}}
|
||||
last_key = None
|
||||
value_mapping = dict()
|
||||
output_line_num = len(output)
|
||||
|
||||
def split_line(line):
|
||||
return re.sub(r'\s+', ' ', line.strip()).split(' ')
|
||||
for line_num in range(output_line_num):
|
||||
# ipvsadm -Ln
|
||||
if 'Flags' in output[line_num]:
|
||||
fields = split_line(output[line_num])
|
||||
elif fields and 'Flags' in fields and fields.index('Flags') == len(
|
||||
fields) - 1:
|
||||
fields.extend(split_line(output[line_num]))
|
||||
# ipvsadm -Ln --stats
|
||||
elif 'Prot' in output[line_num]:
|
||||
fields = split_line(output[line_num])
|
||||
elif 'RemoteAddress' in output[line_num]:
|
||||
start = fields.index('LocalAddress:Port') + 1
|
||||
temp_fields = fields[start:]
|
||||
fields.extend(split_line(output[line_num]))
|
||||
fields.extend(temp_fields)
|
||||
# here we get the all fields
|
||||
elif constants.PROTOCOL_UDP in output[line_num]:
|
||||
# if UDP/TCP in this line, we can know this line is
|
||||
# VS configuration.
|
||||
vs_values = split_line(output[line_num])
|
||||
for value in vs_values:
|
||||
if ':' in value:
|
||||
value_mapping[value] = {'Listener': vs_values,
|
||||
'Members': []}
|
||||
last_key = value
|
||||
break
|
||||
# here the line must be a RS which belongs to a VS
|
||||
elif '->' in output[line_num] and last_key:
|
||||
rs_values = split_line(output[line_num])
|
||||
rs_values.remove('->')
|
||||
value_mapping[last_key]['Members'].append(rs_values)
|
||||
|
||||
index = fields.index('->')
|
||||
vs_fields = fields[:index]
|
||||
if 'Flags' in vs_fields:
|
||||
vs_fields.remove('Flags')
|
||||
rs_fields = fields[index + 1:]
|
||||
for key in list(value_mapping.keys()):
|
||||
value_mapping[key]['Listener'] = [
|
||||
i for i in zip(vs_fields, value_mapping[key]['Listener'])]
|
||||
member_res = []
|
||||
for member_value in value_mapping[key]['Members']:
|
||||
member_res.append([i for i in zip(rs_fields, member_value)])
|
||||
value_mapping[key]['Members'] = member_res
|
||||
|
||||
return value_mapping
|
||||
|
||||
|
||||
def get_udp_listeners_stats():
|
||||
udp_listener_ids = util.get_udp_listeners()
|
||||
need_check_listener_ids = [
|
||||
listener_id for listener_id in udp_listener_ids
|
||||
if util.is_udp_listener_running(listener_id)]
|
||||
ipport_mapping = dict()
|
||||
for check_listener_id in need_check_listener_ids:
|
||||
# resource_ipport_mapping = {'Listener': {'id': listener-id,
|
||||
# 'ipport': ipport},
|
||||
# 'Pool': {'id': pool-id},
|
||||
# 'Members': [{'id': member-id-1,
|
||||
# 'ipport': ipport},
|
||||
# {'id': member-id-2,
|
||||
# 'ipport': ipport}],
|
||||
# 'HealthMonitor': {'id': healthmonitor-id}}
|
||||
(resource_ipport_mapping,
|
||||
ns_name) = get_udp_listener_resource_ipports_nsname(check_listener_id)
|
||||
# If we can not read the lvs configuration from file, that means
|
||||
# the pool of this listener may own zero enabled member, but the
|
||||
# keepalived process is running. So we need to skip it.
|
||||
if not resource_ipport_mapping:
|
||||
continue
|
||||
ipport_mapping.update({check_listener_id: resource_ipport_mapping})
|
||||
|
||||
# So here, if we can not get any ipport_mapping,
|
||||
# we do nothing, just return
|
||||
if not ipport_mapping:
|
||||
return None
|
||||
|
||||
# contains bout, bin, scur, stot, ereq, status
|
||||
# bout(OutBytes), bin(InBytes), stot(Conns) from cmd ipvsadm -Ln --stats
|
||||
# scur(ActiveConn) from cmd ipvsadm -Ln
|
||||
# status, can see configuration in any cmd, treat it as OPEN
|
||||
# ereq is still 0, as UDP case does not support it.
|
||||
scur_res = get_ipvsadm_info(constants.AMPHORA_NAMESPACE)
|
||||
stats_res = get_ipvsadm_info(constants.AMPHORA_NAMESPACE,
|
||||
is_stats_cmd=True)
|
||||
listener_stats_res = dict()
|
||||
for listener_id, ipport in ipport_mapping.items():
|
||||
listener_ipport = ipport['Listener']['ipport']
|
||||
# This would be in Error, wait for the next loop to sync for the
|
||||
# listener at this moment. Also this is for skip the case no enabled
|
||||
# member in UDP listener, so we don't check it for failover.
|
||||
if listener_ipport not in scur_res or listener_ipport not in stats_res:
|
||||
continue
|
||||
|
||||
scur, bout, bin, stot, ereq = 0, 0, 0, 0, 0
|
||||
# As all results contain this listener, so its status should be OPEN
|
||||
status = constants.OPEN
|
||||
# Get scur
|
||||
for m in scur_res[listener_ipport]['Members']:
|
||||
for item in m:
|
||||
if item[0] == 'ActiveConn':
|
||||
scur += int(item[1])
|
||||
|
||||
# Get bout, bin, stot
|
||||
for item in stats_res[listener_ipport]['Listener']:
|
||||
if item[0] == 'Conns':
|
||||
stot = int(item[1])
|
||||
elif item[0] == 'OutBytes':
|
||||
bout = int(item[1])
|
||||
elif item[0] == 'InBytes':
|
||||
bin = int(item[1])
|
||||
|
||||
listener_stats_res.update({
|
||||
listener_id: {
|
||||
'stats': {
|
||||
'bout': bout,
|
||||
'bin': bin,
|
||||
'scur': scur,
|
||||
'stot': stot,
|
||||
'ereq': ereq},
|
||||
'status': status}})
|
||||
|
||||
return listener_stats_res
|
@ -30,6 +30,7 @@ from octavia.amphorae.drivers.keepalived import vrrp_rest_driver
|
||||
from octavia.common.config import cfg
|
||||
from octavia.common import constants as consts
|
||||
from octavia.common.jinja.haproxy import jinja_cfg
|
||||
from octavia.common.jinja.lvs import jinja_cfg as jinja_udp_cfg
|
||||
from octavia.common.tls_utils import cert_parser
|
||||
from octavia.common import utils
|
||||
|
||||
@ -59,6 +60,7 @@ class HaproxyAmphoraLoadBalancerDriver(
|
||||
base_crt_dir=CONF.haproxy_amphora.base_cert_dir,
|
||||
haproxy_template=CONF.haproxy_amphora.haproxy_template,
|
||||
connection_logging=CONF.haproxy_amphora.connection_logging)
|
||||
self.udp_jinja = jinja_udp_cfg.LvsJinjaTemplater()
|
||||
|
||||
def update_amphora_listeners(self, listeners, amphora_index,
|
||||
amphorae, timeout_dict=None):
|
||||
@ -87,36 +89,64 @@ class HaproxyAmphoraLoadBalancerDriver(
|
||||
for listener in listeners:
|
||||
LOG.debug("%s updating listener %s on amphora %s",
|
||||
self.__class__.__name__, listener.id, amp.id)
|
||||
certs = self._process_tls_certificates(listener)
|
||||
# Generate HaProxy configuration from listener object
|
||||
config = self.jinja.build_config(
|
||||
host_amphora=amp,
|
||||
listener=listener,
|
||||
tls_cert=certs['tls_cert'],
|
||||
user_group=CONF.haproxy_amphora.user_group)
|
||||
self.client.upload_config(amp, listener.id, config,
|
||||
timeout_dict=timeout_dict)
|
||||
self.client.reload_listener(amp, listener.id,
|
||||
timeout_dict=timeout_dict)
|
||||
|
||||
def update(self, listener, vip):
|
||||
LOG.debug("Amphora %s haproxy, updating listener %s, vip %s",
|
||||
self.__class__.__name__, listener.protocol_port,
|
||||
vip.ip_address)
|
||||
|
||||
# Process listener certificate info
|
||||
certs = self._process_tls_certificates(listener)
|
||||
|
||||
for amp in listener.load_balancer.amphorae:
|
||||
if amp.status != consts.DELETED:
|
||||
if listener.protocol == 'UDP':
|
||||
# Generate Keepalived LVS configuration from listener object
|
||||
config = self.udp_jinja.build_config(listener=listener)
|
||||
self.client.upload_udp_config(amp, listener.id, config,
|
||||
timeout_dict=timeout_dict)
|
||||
self.client.reload_listener(amp, listener.id,
|
||||
listener.protocol,
|
||||
timeout_dict=timeout_dict)
|
||||
else:
|
||||
certs = self._process_tls_certificates(listener)
|
||||
# Generate HaProxy configuration from listener object
|
||||
config = self.jinja.build_config(
|
||||
host_amphora=amp,
|
||||
listener=listener,
|
||||
tls_cert=certs['tls_cert'],
|
||||
user_group=CONF.haproxy_amphora.user_group)
|
||||
self.client.upload_config(amp, listener.id, config)
|
||||
self.client.reload_listener(amp, listener.id)
|
||||
self.client.upload_config(amp, listener.id, config,
|
||||
timeout_dict=timeout_dict)
|
||||
self.client.reload_listener(amp, listener.id,
|
||||
timeout_dict=timeout_dict)
|
||||
|
||||
def _udp_update(self, listener, vip):
|
||||
LOG.debug("Amphora %s keepalivedlvs, updating "
|
||||
"listener %s, vip %s",
|
||||
self.__class__.__name__, listener.protocol_port,
|
||||
vip.ip_address)
|
||||
|
||||
for amp in listener.load_balancer.amphorae:
|
||||
if amp.status != consts.DELETED:
|
||||
# Generate Keepalived LVS configuration from listener object
|
||||
config = self.udp_jinja.build_config(listener=listener)
|
||||
self.client.upload_udp_config(amp, listener.id, config)
|
||||
self.client.reload_listener(amp, listener.id,
|
||||
listener.protocol)
|
||||
|
||||
def update(self, listener, vip):
|
||||
if listener.protocol == 'UDP':
|
||||
self._udp_update(listener, vip)
|
||||
else:
|
||||
LOG.debug("Amphora %s haproxy, updating listener %s, "
|
||||
"vip %s", self.__class__.__name__,
|
||||
listener.protocol_port,
|
||||
vip.ip_address)
|
||||
|
||||
# Process listener certificate info
|
||||
certs = self._process_tls_certificates(listener)
|
||||
|
||||
for amp in listener.load_balancer.amphorae:
|
||||
if amp.status != consts.DELETED:
|
||||
# Generate HaProxy configuration from listener object
|
||||
config = self.jinja.build_config(
|
||||
host_amphora=amp,
|
||||
listener=listener,
|
||||
tls_cert=certs['tls_cert'],
|
||||
user_group=CONF.haproxy_amphora.user_group)
|
||||
self.client.upload_config(amp, listener.id, config)
|
||||
self.client.reload_listener(amp, listener.id,
|
||||
listener.protocol)
|
||||
|
||||
def upload_cert_amp(self, amp, pem):
|
||||
LOG.debug("Amphora %s updating cert in REST driver "
|
||||
@ -124,13 +154,37 @@ class HaproxyAmphoraLoadBalancerDriver(
|
||||
self.__class__.__name__, amp.id)
|
||||
self.client.update_cert_for_rotation(amp, pem)
|
||||
|
||||
def _check_if_need_add_listener_protocol(self, func):
|
||||
# as _apply func will be called by create/update/delete and some cert
|
||||
# related function. But there is only a port of them can accept
|
||||
# listener protocol parameter, including:
|
||||
# start/stop functions call _apply by functools.
|
||||
# delete function call _apply directly.
|
||||
# escape cert operation based on function name.
|
||||
# So add this check for verify if the target function need a protocol
|
||||
# parameter.
|
||||
called_by_functools = (not hasattr(func, '__name__') and
|
||||
hasattr(func, 'func') and
|
||||
func.func.__name__.find('cert') < 0)
|
||||
called_directly = (hasattr(func, '__name__') and
|
||||
func.__name__.find('cert') < 0)
|
||||
return called_directly or called_by_functools
|
||||
|
||||
def _apply(self, func, listener=None, amphora=None, *args):
|
||||
if amphora is None:
|
||||
for amp in listener.load_balancer.amphorae:
|
||||
if amp.status != consts.DELETED:
|
||||
if self._check_if_need_add_listener_protocol(func):
|
||||
_list = list(args)
|
||||
_list.append(listener.protocol)
|
||||
args = _list
|
||||
func(amp, listener.id, *args)
|
||||
else:
|
||||
if amphora.status != consts.DELETED:
|
||||
if self._check_if_need_add_listener_protocol(func):
|
||||
_list = list(args)
|
||||
_list.append(listener.protocol)
|
||||
args = _list
|
||||
func(amphora, listener.id, *args)
|
||||
|
||||
def stop(self, listener, vip):
|
||||
@ -374,17 +428,21 @@ class AmphoraAPIClient(object):
|
||||
data=config)
|
||||
return exc.check_exception(r)
|
||||
|
||||
def get_listener_status(self, amp, listener_id):
|
||||
def get_listener_status(self, amp, listener_id, protocol=None):
|
||||
protocol_dict = {'protocol': protocol}
|
||||
r = self.get(
|
||||
amp,
|
||||
'listeners/{listener_id}'.format(listener_id=listener_id))
|
||||
'listeners/{listener_id}'.format(listener_id=listener_id),
|
||||
json=protocol_dict)
|
||||
if exc.check_exception(r):
|
||||
return r.json()
|
||||
return None
|
||||
|
||||
def _action(self, action, amp, listener_id, timeout_dict=None):
|
||||
def _action(self, action, amp, listener_id, protocol, timeout_dict=None):
|
||||
protocol_dict = {'protocol': protocol}
|
||||
r = self.put(amp, 'listeners/{listener_id}/{action}'.format(
|
||||
listener_id=listener_id, action=action), timeout_dict=timeout_dict)
|
||||
listener_id=listener_id, action=action), timeout_dict=timeout_dict,
|
||||
json=protocol_dict)
|
||||
return exc.check_exception(r)
|
||||
|
||||
def upload_cert_pem(self, amp, listener_id, pem_filename, pem_file):
|
||||
@ -407,9 +465,11 @@ class AmphoraAPIClient(object):
|
||||
return r.json().get("md5sum")
|
||||
return None
|
||||
|
||||
def delete_listener(self, amp, listener_id):
|
||||
def delete_listener(self, amp, listener_id, protocol):
|
||||
protocol_dict = {'protocol': protocol}
|
||||
r = self.delete(
|
||||
amp, 'listeners/{listener_id}'.format(listener_id=listener_id))
|
||||
amp, 'listeners/{listener_id}'.format(listener_id=listener_id),
|
||||
json=protocol_dict)
|
||||
|
||||
return exc.check_exception(r, (404,))
|
||||
|
||||
@ -463,3 +523,11 @@ class AmphoraAPIClient(object):
|
||||
if exc.check_exception(r):
|
||||
return r.json()
|
||||
return None
|
||||
|
||||
def upload_udp_config(self, amp, listener_id, config, timeout_dict=None):
|
||||
r = self.put(
|
||||
amp,
|
||||
'listeners/{amphora_id}/{listener_id}/udp_listener'.format(
|
||||
amphora_id=amp.id, listener_id=listener_id), timeout_dict,
|
||||
data=config)
|
||||
return exc.check_exception(r)
|
||||
|
@ -135,6 +135,9 @@ amphora_agent_opts = [
|
||||
"controller to run before terminating the socket.")),
|
||||
# Do not specify in octavia.conf, loaded at runtime
|
||||
cfg.StrOpt('amphora_id', help=_("The amphora ID.")),
|
||||
cfg.StrOpt('amphora_udp_driver',
|
||||
default='keepalived_lvs',
|
||||
help='The UDP API backend for amphora agent.'),
|
||||
]
|
||||
|
||||
networking_opts = [
|
||||
|
@ -438,6 +438,7 @@ KEEPALIVED_JINJA2_UPSTART = 'keepalived.upstart.j2'
|
||||
KEEPALIVED_JINJA2_SYSTEMD = 'keepalived.systemd.j2'
|
||||
KEEPALIVED_JINJA2_SYSVINIT = 'keepalived.sysvinit.j2'
|
||||
CHECK_SCRIPT_CONF = 'keepalived_check_script.conf.j2'
|
||||
KEEPALIVED_CHECK_SCRIPT = 'keepalived_lvs_check_script.sh.j2'
|
||||
|
||||
PLUGGED_INTERFACES = '/var/lib/octavia/plugged_interfaces'
|
||||
HAPROXY_USER_GROUP_CFG = '/var/lib/octavia/haproxy-default-user-group.conf'
|
||||
@ -482,6 +483,10 @@ KEEPALIVED_SYSTEMD = 'octavia-keepalived.service'
|
||||
KEEPALIVED_SYSVINIT = 'octavia-keepalived'
|
||||
KEEPALIVED_UPSTART = 'octavia-keepalived.conf'
|
||||
|
||||
KEEPALIVED_SYSTEMD_PREFIX = 'octavia-keepalivedlvs-%s.service'
|
||||
KEEPALIVED_SYSVINIT_PREFIX = 'octavia-keepalivedlvs-%s'
|
||||
KEEPALIVED_UPSTART_PREFIX = 'octavia-keepalivedlvs-%s.conf'
|
||||
|
||||
# Authentication
|
||||
KEYSTONE = 'keystone'
|
||||
NOAUTH = 'noauth'
|
||||
|
@ -118,6 +118,31 @@ class UpdateHealthDb(update_base.HealthUpdateBase):
|
||||
|
||||
if db_lb:
|
||||
expected_listener_count = len(db_lb.listeners)
|
||||
|
||||
# For udp listener, the udp health won't send out by amp agent.
|
||||
# Once the default_pool of udp listener have the first enabled
|
||||
# member, then the health will be sent out. So during this period,
|
||||
# need to figure out the udp listener and ignore them by changing
|
||||
# expected_listener_count.
|
||||
for listener in db_lb.listeners:
|
||||
need_remove = False
|
||||
if listener.protocol == constants.PROTOCOL_UDP:
|
||||
enabled_members = ([member
|
||||
for member in
|
||||
listener.default_pool.members
|
||||
if member.enabled]
|
||||
if listener.default_pool else [])
|
||||
if listener.default_pool:
|
||||
if not listener.default_pool.members:
|
||||
need_remove = True
|
||||
elif not enabled_members:
|
||||
need_remove = True
|
||||
else:
|
||||
need_remove = True
|
||||
|
||||
if need_remove:
|
||||
expected_listener_count = expected_listener_count - 1
|
||||
|
||||
if 'PENDING' in db_lb.provisioning_status:
|
||||
ignore_listener_count = True
|
||||
else:
|
||||
|
@ -384,6 +384,16 @@ class AmphoraFlows(object):
|
||||
failover_amphora_flow.add(database_tasks.GetAmphoraeFromLoadbalancer(
|
||||
requires=constants.LOADBALANCER, provides=constants.AMPHORAE))
|
||||
|
||||
# Plug the VIP ports into the new amphora
|
||||
# The reason for moving these steps here is the udp listeners want to
|
||||
# do some kernel configuration before Listener update for forbidding
|
||||
# failure during rebuild amphora.
|
||||
failover_amphora_flow.add(network_tasks.PlugVIPPort(
|
||||
requires=(constants.AMPHORA, constants.AMPHORAE_NETWORK_CONFIG)))
|
||||
failover_amphora_flow.add(amphora_driver_tasks.AmphoraPostVIPPlug(
|
||||
requires=(constants.AMPHORA, constants.LOADBALANCER,
|
||||
constants.AMPHORAE_NETWORK_CONFIG)))
|
||||
|
||||
# Listeners update needs to be run on all amphora to update
|
||||
# their peer configurations. So parallelize this with an
|
||||
# unordered subflow.
|
||||
@ -414,13 +424,6 @@ class AmphoraFlows(object):
|
||||
|
||||
failover_amphora_flow.add(update_amps_subflow)
|
||||
|
||||
# Plug the VIP ports into the new amphora
|
||||
failover_amphora_flow.add(network_tasks.PlugVIPPort(
|
||||
requires=(constants.AMPHORA, constants.AMPHORAE_NETWORK_CONFIG)))
|
||||
failover_amphora_flow.add(amphora_driver_tasks.AmphoraPostVIPPlug(
|
||||
requires=(constants.AMPHORA, constants.LOADBALANCER,
|
||||
constants.AMPHORAE_NETWORK_CONFIG)))
|
||||
|
||||
# Plug the member networks into the new amphora
|
||||
failover_amphora_flow.add(network_tasks.CalculateAmphoraDelta(
|
||||
requires=(constants.LOADBALANCER, constants.AMPHORA),
|
||||
|
@ -136,29 +136,41 @@ class AllowedAddressPairsDriver(neutron_base.BaseNeutronDriver):
|
||||
rules = self.neutron_client.list_security_group_rules(
|
||||
security_group_id=sec_grp_id)
|
||||
updated_ports = [
|
||||
listener.protocol_port for listener in load_balancer.listeners
|
||||
(listener.protocol_port,
|
||||
constants.PROTOCOL_TCP.lower()
|
||||
if listener.protocol != constants.PROTOCOL_UDP else
|
||||
constants.PROTOCOL_UDP.lower())
|
||||
for listener in load_balancer.listeners
|
||||
if listener.provisioning_status != constants.PENDING_DELETE and
|
||||
listener.provisioning_status != constants.DELETED]
|
||||
# As the peer port will hold the tcp connection for keepalived and
|
||||
# haproxy session synchronization, so here the security group rule
|
||||
# should be just related with tcp protocol only.
|
||||
peer_ports = [
|
||||
listener.peer_port for listener in load_balancer.listeners
|
||||
(listener.peer_port,
|
||||
constants.PROTOCOL_TCP.lower())
|
||||
for listener in load_balancer.listeners
|
||||
if listener.provisioning_status != constants.PENDING_DELETE and
|
||||
listener.provisioning_status != constants.DELETED]
|
||||
updated_ports.extend(peer_ports)
|
||||
# Just going to use port_range_max for now because we can assume that
|
||||
# port_range_max and min will be the same since this driver is
|
||||
# responsible for creating these rules
|
||||
old_ports = [rule.get('port_range_max')
|
||||
old_ports = [(rule.get('port_range_max'), rule.get('protocol'))
|
||||
for rule in rules.get('security_group_rules', [])
|
||||
# Don't remove egress rules and don't
|
||||
# confuse other protocols with None ports
|
||||
# with the egress rules. VRRP uses protocol
|
||||
# 51 and 112
|
||||
if rule.get('direction') != 'egress' and
|
||||
rule.get('protocol', '').lower() == 'tcp']
|
||||
rule.get('protocol', '').lower() in ['tcp', 'udp']]
|
||||
add_ports = set(updated_ports) - set(old_ports)
|
||||
del_ports = set(old_ports) - set(updated_ports)
|
||||
for rule in rules.get('security_group_rules', []):
|
||||
if rule.get('port_range_max') in del_ports:
|
||||
if (rule.get('protocol', '') and
|
||||
rule.get('protocol', '').lower() in ['tcp', 'udp'] and
|
||||
(rule.get('port_range_max'),
|
||||
rule.get('protocol')) in del_ports):
|
||||
rule_id = rule.get('id')
|
||||
try:
|
||||
self.neutron_client.delete_security_group_rule(rule_id)
|
||||
@ -167,9 +179,10 @@ class AllowedAddressPairsDriver(neutron_base.BaseNeutronDriver):
|
||||
"it is already deleted.", rule_id)
|
||||
|
||||
ethertype = self._get_ethertype_for_ip(load_balancer.vip.ip_address)
|
||||
for port in add_ports:
|
||||
self._create_security_group_rule(sec_grp_id, 'TCP', port_min=port,
|
||||
port_max=port,
|
||||
for port_protocol in add_ports:
|
||||
self._create_security_group_rule(sec_grp_id, port_protocol[1],
|
||||
port_min=port_protocol[0],
|
||||
port_max=port_protocol[0],
|
||||
ethertype=ethertype)
|
||||
|
||||
# Currently we are using the VIP network for VRRP
|
||||
|
@ -57,6 +57,9 @@ class TestServerTestCase(base.TestCase):
|
||||
|
||||
self.conf = self.useFixture(oslo_fixture.Config(config.cfg.CONF))
|
||||
self.conf.config(group="haproxy_amphora", base_path='/var/lib/octavia')
|
||||
mock.patch('octavia.amphorae.backends.agent.api_server.server.'
|
||||
'check_and_return_request_listener_protocol',
|
||||
return_value='TCP').start()
|
||||
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
|
||||
'get_os_init_system', return_value=consts.INIT_SYSTEMD)
|
||||
@ -374,9 +377,13 @@ class TestServerTestCase(base.TestCase):
|
||||
def test_centos_info(self):
|
||||
self._test_info(consts.CENTOS)
|
||||
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.amphora_info.'
|
||||
'AmphoraInfo._get_extend_body_from_udp_driver',
|
||||
return_value={})
|
||||
@mock.patch('socket.gethostname')
|
||||
@mock.patch('subprocess.check_output')
|
||||
def _test_info(self, distro, mock_subbprocess, mock_hostname):
|
||||
def _test_info(self, distro, mock_subbprocess, mock_hostname,
|
||||
mock_get_extend_body):
|
||||
self.assertIn(distro, [consts.UBUNTU, consts.CENTOS])
|
||||
mock_hostname.side_effect = ['test-host']
|
||||
mock_subbprocess.side_effect = ['9.9.99-9']
|
||||
@ -990,6 +997,7 @@ class TestServerTestCase(base.TestCase):
|
||||
agent_server_network_file="/path/to/interfaces_file")
|
||||
self._test_plug_network(consts.CENTOS)
|
||||
|
||||
@mock.patch('os.chmod')
|
||||
@mock.patch('netifaces.interfaces')
|
||||
@mock.patch('netifaces.ifaddresses')
|
||||
@mock.patch('pyroute2.IPRoute')
|
||||
@ -1000,7 +1008,7 @@ class TestServerTestCase(base.TestCase):
|
||||
@mock.patch('os.path.isfile')
|
||||
def _test_plug_network(self, distro, mock_isfile, mock_int_exists,
|
||||
mock_check_output, mock_netns, mock_pyroute2,
|
||||
mock_ifaddress, mock_interfaces):
|
||||
mock_ifaddress, mock_interfaces, mock_os_chmod):
|
||||
self.assertIn(distro, [consts.UBUNTU, consts.CENTOS])
|
||||
port_info = {'mac_address': '123'}
|
||||
test_int_num = random.randint(0, 9999)
|
||||
@ -1205,7 +1213,11 @@ class TestServerTestCase(base.TestCase):
|
||||
'iface eth{int} inet static\n'
|
||||
'address 10.0.0.5\nbroadcast 10.0.0.255\n'
|
||||
'netmask 255.255.255.0\n'
|
||||
'mtu 1450\n'.format(int=test_int_num))
|
||||
'mtu 1450\n'
|
||||
'post-up /sbin/iptables -t nat -A POSTROUTING -p udp '
|
||||
'-o eth{int} -j MASQUERADE\n'
|
||||
'post-down /sbin/iptables -t nat -D POSTROUTING -p udp '
|
||||
'-o eth{int} -j MASQUERADE\n'.format(int=test_int_num))
|
||||
elif distro == consts.CENTOS:
|
||||
handle.write.assert_any_call(
|
||||
'\n\n# Generated by Octavia agent\n'
|
||||
@ -1279,7 +1291,11 @@ class TestServerTestCase(base.TestCase):
|
||||
'iface eth{int} inet6 static\n'
|
||||
'address 2001:0db8:0000:0000:0000:0000:0000:0002\n'
|
||||
'broadcast 2001:0db8:ffff:ffff:ffff:ffff:ffff:ffff\n'
|
||||
'netmask 32\nmtu 1450\n'.format(int=test_int_num))
|
||||
'netmask 32\nmtu 1450\n'
|
||||
'post-up /sbin/ip6tables -t nat -A POSTROUTING -p udp '
|
||||
'-o eth{int} -j MASQUERADE\n'
|
||||
'post-down /sbin/ip6tables -t nat -D POSTROUTING -p udp '
|
||||
'-o eth{int} -j MASQUERADE\n'.format(int=test_int_num))
|
||||
elif distro == consts.CENTOS:
|
||||
handle.write.assert_any_call(
|
||||
'\n\n# Generated by Octavia agent\n'
|
||||
@ -1384,6 +1400,7 @@ class TestServerTestCase(base.TestCase):
|
||||
def test_centos_plug_network_host_routes(self):
|
||||
self._test_plug_network_host_routes(consts.CENTOS)
|
||||
|
||||
@mock.patch('os.chmod')
|
||||
@mock.patch('netifaces.interfaces')
|
||||
@mock.patch('netifaces.ifaddresses')
|
||||
@mock.patch('pyroute2.IPRoute')
|
||||
@ -1391,7 +1408,8 @@ class TestServerTestCase(base.TestCase):
|
||||
@mock.patch('subprocess.check_output')
|
||||
def _test_plug_network_host_routes(self, distro, mock_check_output,
|
||||
mock_netns, mock_pyroute2,
|
||||
mock_ifaddress, mock_interfaces):
|
||||
mock_ifaddress, mock_interfaces,
|
||||
mock_os_chmod):
|
||||
|
||||
self.assertIn(distro, [consts.UBUNTU, consts.CENTOS])
|
||||
|
||||
@ -1466,8 +1484,12 @@ class TestServerTestCase(base.TestCase):
|
||||
'up route add -net ' + DEST2 + ' gw ' + NEXTHOP +
|
||||
' dev ' + consts.NETNS_PRIMARY_INTERFACE + '\n'
|
||||
'down route del -net ' + DEST2 + ' gw ' + NEXTHOP +
|
||||
' dev ' + consts.NETNS_PRIMARY_INTERFACE + '\n'
|
||||
)
|
||||
' dev ' + consts.NETNS_PRIMARY_INTERFACE + '\n' +
|
||||
'post-up /sbin/iptables -t nat -A POSTROUTING -p udp -o ' +
|
||||
consts.NETNS_PRIMARY_INTERFACE + ' -j MASQUERADE' + '\n' +
|
||||
'post-down /sbin/iptables -t nat -D POSTROUTING -p udp '
|
||||
'-o ' + consts.NETNS_PRIMARY_INTERFACE +
|
||||
' -j MASQUERADE' + '\n')
|
||||
elif distro == consts.CENTOS:
|
||||
handle.write.assert_any_call(
|
||||
'\n\n# Generated by Octavia agent\n'
|
||||
@ -1479,6 +1501,12 @@ class TestServerTestCase(base.TestCase):
|
||||
int=consts.NETNS_PRIMARY_INTERFACE,
|
||||
ip=IP,
|
||||
mask=NETMASK))
|
||||
flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
|
||||
mock_open.assert_any_call('/sbin/ifup-local', flags, mode)
|
||||
mock_open.assert_any_call('/sbin/ifdown-local', flags, mode)
|
||||
calls = [mock.call('/sbin/ifup-local', stat.S_IEXEC),
|
||||
mock.call('/sbin/ifdown-local', stat.S_IEXEC)]
|
||||
mock_os_chmod.assert_has_calls(calls)
|
||||
mock_check_output.assert_called_with(
|
||||
['ip', 'netns', 'exec', consts.AMPHORA_NAMESPACE,
|
||||
'ifup', consts.NETNS_PRIMARY_INTERFACE], stderr=-2)
|
||||
@ -1494,6 +1522,7 @@ class TestServerTestCase(base.TestCase):
|
||||
agent_server_network_file="/path/to/interfaces_file")
|
||||
self._test_plug_VIP4(consts.CENTOS)
|
||||
|
||||
@mock.patch('os.chmod')
|
||||
@mock.patch('shutil.copy2')
|
||||
@mock.patch('pyroute2.NSPopen')
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.'
|
||||
@ -1511,7 +1540,7 @@ class TestServerTestCase(base.TestCase):
|
||||
mock_copytree, mock_check_output, mock_netns,
|
||||
mock_netns_create, mock_pyroute2, mock_ifaddress,
|
||||
mock_interfaces, mock_int_exists, mock_nspopen,
|
||||
mock_copy2):
|
||||
mock_copy2, mock_os_chmod):
|
||||
|
||||
mock_isfile.return_value = True
|
||||
|
||||
@ -1717,7 +1746,11 @@ class TestServerTestCase(base.TestCase):
|
||||
'post-up /sbin/ip rule add from 203.0.113.2/32 table 1 '
|
||||
'priority 100\n'
|
||||
'post-down /sbin/ip rule del from 203.0.113.2/32 table 1 '
|
||||
'priority 100'.format(
|
||||
'priority 100\n'
|
||||
'post-up /sbin/iptables -t nat -A POSTROUTING -p udp '
|
||||
'-o eth1 -j MASQUERADE\n'
|
||||
'post-down /sbin/iptables -t nat -D POSTROUTING -p udp '
|
||||
'-o eth1 -j MASQUERADE'.format(
|
||||
netns_int=consts.NETNS_PRIMARY_INTERFACE))
|
||||
elif distro == consts.CENTOS:
|
||||
handle.write.assert_any_call(
|
||||
@ -1728,15 +1761,29 @@ class TestServerTestCase(base.TestCase):
|
||||
'NETMASK="255.255.255.0"\nGATEWAY="203.0.113.1"\n'
|
||||
'MTU="1450" \n'.format(
|
||||
netns_int=consts.NETNS_PRIMARY_INTERFACE))
|
||||
flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
|
||||
mock_open.assert_any_call('/sbin/ifup-local', flags, mode)
|
||||
mock_open.assert_any_call('/sbin/ifdown-local', flags, mode)
|
||||
calls = [mock.call('/sbin/ifup-local', stat.S_IEXEC),
|
||||
mock.call('/sbin/ifdown-local', stat.S_IEXEC)]
|
||||
mock_os_chmod.assert_has_calls(calls)
|
||||
mock_check_output.assert_called_with(
|
||||
['ip', 'netns', 'exec', consts.AMPHORA_NAMESPACE,
|
||||
'ifup', '{netns_int}:0'.format(
|
||||
netns_int=consts.NETNS_PRIMARY_INTERFACE)], stderr=-2)
|
||||
|
||||
# Verify sysctl was loaded
|
||||
mock_nspopen.assert_called_once_with(
|
||||
'amphora-haproxy', ['/sbin/sysctl', '--system'],
|
||||
stdout=subprocess.PIPE)
|
||||
calls = [mock.call('amphora-haproxy', ['/sbin/sysctl', '--system'],
|
||||
stdout=subprocess.PIPE),
|
||||
mock.call('amphora-haproxy', ['modprobe', 'ip_vs'],
|
||||
stdout=subprocess.PIPE),
|
||||
mock.call('amphora-haproxy',
|
||||
['/sbin/sysctl', '-w', 'net.ipv4.ip_forward=1'],
|
||||
stdout=subprocess.PIPE),
|
||||
mock.call('amphora-haproxy',
|
||||
['/sbin/sysctl', '-w', 'net.ipv4.vs.conntrack=1'],
|
||||
stdout=subprocess.PIPE)]
|
||||
mock_nspopen.assert_has_calls(calls, any_order=True)
|
||||
|
||||
# One Interface down, Happy Path IPv4
|
||||
mock_interfaces.side_effect = [['blah']]
|
||||
@ -1810,7 +1857,11 @@ class TestServerTestCase(base.TestCase):
|
||||
'post-up /sbin/ip rule add from 203.0.113.2/32 table 1 '
|
||||
'priority 100\n'
|
||||
'post-down /sbin/ip rule del from 203.0.113.2/32 table 1 '
|
||||
'priority 100'.format(
|
||||
'priority 100\n'
|
||||
'post-up /sbin/iptables -t nat -A POSTROUTING -p udp '
|
||||
'-o eth1 -j MASQUERADE\n'
|
||||
'post-down /sbin/iptables -t nat -D POSTROUTING -p udp '
|
||||
'-o eth1 -j MASQUERADE'.format(
|
||||
netns_int=consts.NETNS_PRIMARY_INTERFACE))
|
||||
elif distro == consts.CENTOS:
|
||||
handle.write.assert_any_call(
|
||||
@ -1857,6 +1908,7 @@ class TestServerTestCase(base.TestCase):
|
||||
def test_centos_plug_VIP6(self):
|
||||
self._test_plug_vip6(consts.CENTOS)
|
||||
|
||||
@mock.patch('os.chmod')
|
||||
@mock.patch('shutil.copy2')
|
||||
@mock.patch('pyroute2.NSPopen')
|
||||
@mock.patch('netifaces.interfaces')
|
||||
@ -1871,7 +1923,8 @@ class TestServerTestCase(base.TestCase):
|
||||
def _test_plug_vip6(self, distro, mock_isfile, mock_makedirs,
|
||||
mock_copytree, mock_check_output, mock_netns,
|
||||
mock_netns_create, mock_pyroute2, mock_ifaddress,
|
||||
mock_interfaces, mock_nspopen, mock_copy2):
|
||||
mock_interfaces, mock_nspopen, mock_copy2,
|
||||
mock_os_chmod):
|
||||
|
||||
mock_isfile.return_value = True
|
||||
|
||||
@ -2059,7 +2112,11 @@ class TestServerTestCase(base.TestCase):
|
||||
'priority 100\n'
|
||||
'post-down /sbin/ip -6 rule del from '
|
||||
'2001:0db8:0000:0000:0000:0000:0000:0002/32 table 1 '
|
||||
'priority 100'.format(
|
||||
'priority 100\n'
|
||||
'post-up /sbin/ip6tables -t nat -A POSTROUTING -p udp '
|
||||
'-o eth1 -j MASQUERADE\n'
|
||||
'post-down /sbin/ip6tables -t nat -D POSTROUTING -p udp '
|
||||
'-o eth1 -j MASQUERADE'.format(
|
||||
netns_int=consts.NETNS_PRIMARY_INTERFACE))
|
||||
elif distro == consts.CENTOS:
|
||||
handle.write.assert_any_call(
|
||||
@ -2085,9 +2142,18 @@ class TestServerTestCase(base.TestCase):
|
||||
netns_int=consts.NETNS_PRIMARY_INTERFACE)], stderr=-2)
|
||||
|
||||
# Verify sysctl was loaded
|
||||
mock_nspopen.assert_called_once_with(
|
||||
'amphora-haproxy', ['/sbin/sysctl', '--system'],
|
||||
stdout=subprocess.PIPE)
|
||||
calls = [mock.call('amphora-haproxy', ['/sbin/sysctl', '--system'],
|
||||
stdout=subprocess.PIPE),
|
||||
mock.call('amphora-haproxy', ['modprobe', 'ip_vs'],
|
||||
stdout=subprocess.PIPE),
|
||||
mock.call('amphora-haproxy',
|
||||
['/sbin/sysctl', '-w',
|
||||
'net.ipv6.conf.all.forwarding=1'],
|
||||
stdout=subprocess.PIPE),
|
||||
mock.call('amphora-haproxy',
|
||||
['/sbin/sysctl', '-w', 'net.ipv4.vs.conntrack=1'],
|
||||
stdout=subprocess.PIPE)]
|
||||
mock_nspopen.assert_has_calls(calls, any_order=True)
|
||||
|
||||
# One Interface down, Happy Path IPv6
|
||||
mock_interfaces.side_effect = [['blah']]
|
||||
@ -2157,7 +2223,11 @@ class TestServerTestCase(base.TestCase):
|
||||
'priority 100\n'
|
||||
'post-down /sbin/ip -6 rule del from '
|
||||
'2001:0db8:0000:0000:0000:0000:0000:0002/32 table 1 '
|
||||
'priority 100'.format(
|
||||
'priority 100\n'
|
||||
'post-up /sbin/ip6tables -t nat -A POSTROUTING -p udp '
|
||||
'-o eth1 -j MASQUERADE\n'
|
||||
'post-down /sbin/ip6tables -t nat -D POSTROUTING -p udp '
|
||||
'-o eth1 -j MASQUERADE'.format(
|
||||
netns_int=consts.NETNS_PRIMARY_INTERFACE))
|
||||
elif distro == consts.CENTOS:
|
||||
handle.write.assert_any_call(
|
||||
@ -2401,6 +2471,19 @@ class TestServerTestCase(base.TestCase):
|
||||
def test_centos_details(self):
|
||||
self._test_details(consts.CENTOS)
|
||||
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
|
||||
'get_udp_listeners',
|
||||
return_value=[])
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.'
|
||||
'amphora_info.AmphoraInfo.'
|
||||
'_get_extend_body_from_udp_driver',
|
||||
return_value={
|
||||
"keepalived_version": '1.1.11-1',
|
||||
"ipvsadm_version": '2.2.22-2'
|
||||
})
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.'
|
||||
'amphora_info.AmphoraInfo.'
|
||||
'_count_udp_listener_processes', return_value=0)
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.amphora_info.'
|
||||
'AmphoraInfo._count_haproxy_processes')
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.amphora_info.'
|
||||
@ -2419,7 +2502,8 @@ class TestServerTestCase(base.TestCase):
|
||||
def _test_details(self, distro, mock_subbprocess, mock_hostname,
|
||||
mock_get_listeners, mock_get_mem, mock_cpu,
|
||||
mock_statvfs, mock_load, mock_get_nets,
|
||||
mock_count_haproxy):
|
||||
mock_count_haproxy, mock_count_udp_listeners,
|
||||
mock_get_ext_from_udp_driver, mock_get_udp_listeners):
|
||||
|
||||
self.assertIn(distro, [consts.UBUNTU, consts.CENTOS])
|
||||
|
||||
@ -2514,6 +2598,8 @@ class TestServerTestCase(base.TestCase):
|
||||
'haproxy_count': haproxy_count,
|
||||
'haproxy_version': '9.9.99-9',
|
||||
'hostname': 'test-host',
|
||||
'ipvsadm_version': u'2.2.22-2',
|
||||
'keepalived_version': u'1.1.11-1',
|
||||
'listeners': [listener_id],
|
||||
'load': [load_1min, load_5min, load_15min],
|
||||
'memory': {'buffers': Buffers,
|
||||
@ -2531,7 +2617,8 @@ class TestServerTestCase(base.TestCase):
|
||||
'network_tx': eth3_tx}},
|
||||
'packages': {},
|
||||
'topology': consts.TOPOLOGY_SINGLE,
|
||||
'topology_status': consts.TOPOLOGY_STATUS_OK}
|
||||
'topology_status': consts.TOPOLOGY_STATUS_OK,
|
||||
'udp_listener_process_count': 0}
|
||||
|
||||
if distro == consts.UBUNTU:
|
||||
rv = self.ubuntu_app.get('/' + api_server.VERSION + '/details')
|
||||
|
@ -27,11 +27,26 @@ class TestAmphoraInfo(base.TestCase):
|
||||
|
||||
API_VERSION = random.randrange(0, 10000)
|
||||
HAPROXY_VERSION = random.randrange(0, 10000)
|
||||
KEEPALIVED_VERSION = random.randrange(0, 10000)
|
||||
IPVSADM_VERSION = random.randrange(0, 10000)
|
||||
FAKE_LISTENER_ID_1 = uuidutils.generate_uuid()
|
||||
FAKE_LISTENER_ID_2 = uuidutils.generate_uuid()
|
||||
FAKE_LISTENER_ID_3 = uuidutils.generate_uuid()
|
||||
FAKE_LISTENER_ID_4 = uuidutils.generate_uuid()
|
||||
|
||||
def setUp(self):
|
||||
super(TestAmphoraInfo, self).setUp()
|
||||
self.osutils_mock = mock.MagicMock()
|
||||
self.amp_info = amphora_info.AmphoraInfo(self.osutils_mock)
|
||||
self.udp_driver = mock.MagicMock()
|
||||
|
||||
def _return_version(self, package_name):
|
||||
if package_name == 'ipvsadm':
|
||||
return self.IPVSADM_VERSION
|
||||
elif package_name == 'keepalived':
|
||||
return self.KEEPALIVED_VERSION
|
||||
else:
|
||||
return self.HAPROXY_VERSION
|
||||
|
||||
@mock.patch.object(amphora_info, "webob")
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.'
|
||||
@ -49,6 +64,186 @@ class TestAmphoraInfo(base.TestCase):
|
||||
mock_webob.Response.assert_called_once_with(json=expected_dict)
|
||||
api_server.VERSION = original_version
|
||||
|
||||
@mock.patch.object(amphora_info, "webob")
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.'
|
||||
'amphora_info.AmphoraInfo._get_version_of_installed_package')
|
||||
@mock.patch('socket.gethostname', return_value='FAKE_HOST')
|
||||
def test_compile_amphora_info_for_udp(self, mock_gethostname,
|
||||
mock_pkg_version, mock_webob):
|
||||
|
||||
mock_pkg_version.side_effect = self._return_version
|
||||
self.udp_driver.get_subscribed_amp_compile_info.side_effect = [
|
||||
['keepalived', 'ipvsadm']]
|
||||
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,
|
||||
'keepalived_version': self.KEEPALIVED_VERSION,
|
||||
'ipvsadm_version': self.IPVSADM_VERSION
|
||||
}
|
||||
self.amp_info.compile_amphora_info(extend_udp_driver=self.udp_driver)
|
||||
mock_webob.Response.assert_called_once_with(json=expected_dict)
|
||||
api_server.VERSION = original_version
|
||||
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
|
||||
'get_listeners', return_value=[FAKE_LISTENER_ID_1,
|
||||
FAKE_LISTENER_ID_2])
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.'
|
||||
'amphora_info.AmphoraInfo._get_meminfo')
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.'
|
||||
'amphora_info.AmphoraInfo._cpu')
|
||||
@mock.patch('os.statvfs')
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.'
|
||||
'amphora_info.AmphoraInfo._get_networks')
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.'
|
||||
'amphora_info.AmphoraInfo._load')
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.'
|
||||
'amphora_info.AmphoraInfo._get_version_of_installed_package')
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.'
|
||||
'amphora_info.AmphoraInfo._count_haproxy_processes')
|
||||
@mock.patch('socket.gethostname', return_value='FAKE_HOST')
|
||||
def test_compile_amphora_details(self, mhostname, m_count, m_pkg_version,
|
||||
m_load, m_get_nets, m_os, m_cpu,
|
||||
mget_mem, mget_listener):
|
||||
mget_mem.return_value = {'SwapCached': 0, 'Buffers': 344792,
|
||||
'MemTotal': 21692784, 'Cached': 4271856,
|
||||
'Slab': 534384, 'MemFree': 12685624,
|
||||
'Shmem': 9520}
|
||||
m_cpu.return_value = {'user': '252551', 'softirq': '8336',
|
||||
'system': '52554', 'total': 7503411}
|
||||
m_pkg_version.side_effect = self._return_version
|
||||
mdisk_info = mock.MagicMock()
|
||||
m_os.return_value = mdisk_info
|
||||
mdisk_info.f_blocks = 34676992
|
||||
mdisk_info.f_bfree = 28398016
|
||||
mdisk_info.f_frsize = 4096
|
||||
mdisk_info.f_bavail = 26630646
|
||||
m_get_nets.return_value = {'eth1': {'network_rx': 996,
|
||||
'network_tx': 418},
|
||||
'eth2': {'network_rx': 848,
|
||||
'network_tx': 578}}
|
||||
m_load.return_value = ['0.09', '0.11', '0.10']
|
||||
m_count.return_value = 5
|
||||
original_version = api_server.VERSION
|
||||
api_server.VERSION = self.API_VERSION
|
||||
expected_dict = {u'active': True,
|
||||
u'api_version': self.API_VERSION,
|
||||
u'cpu': {u'soft_irq': u'8336',
|
||||
u'system': u'52554',
|
||||
u'total': 7503411,
|
||||
u'user': u'252551'},
|
||||
u'disk': {u'available': 109079126016,
|
||||
u'used': 25718685696},
|
||||
u'haproxy_count': 5,
|
||||
u'haproxy_version': self.HAPROXY_VERSION,
|
||||
u'hostname': u'FAKE_HOST',
|
||||
u'listeners': [self.FAKE_LISTENER_ID_1,
|
||||
self.FAKE_LISTENER_ID_2],
|
||||
u'load': [u'0.09', u'0.11', u'0.10'],
|
||||
u'memory': {u'buffers': 344792,
|
||||
u'cached': 4271856,
|
||||
u'free': 12685624,
|
||||
u'shared': 9520,
|
||||
u'slab': 534384,
|
||||
u'swap_used': 0,
|
||||
u'total': 21692784},
|
||||
u'networks': {u'eth1': {u'network_rx': 996,
|
||||
u'network_tx': 418},
|
||||
u'eth2': {u'network_rx': 848,
|
||||
u'network_tx': 578}},
|
||||
u'packages': {},
|
||||
u'topology': u'SINGLE',
|
||||
u'topology_status': u'OK'}
|
||||
actual = self.amp_info.compile_amphora_details()
|
||||
self.assertEqual(expected_dict, actual.json)
|
||||
api_server.VERSION = original_version
|
||||
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
|
||||
'get_udp_listeners',
|
||||
return_value=[FAKE_LISTENER_ID_3, FAKE_LISTENER_ID_4])
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
|
||||
'get_listeners', return_value=[FAKE_LISTENER_ID_1,
|
||||
FAKE_LISTENER_ID_2])
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.'
|
||||
'amphora_info.AmphoraInfo._get_meminfo')
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.'
|
||||
'amphora_info.AmphoraInfo._cpu')
|
||||
@mock.patch('os.statvfs')
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.'
|
||||
'amphora_info.AmphoraInfo._get_networks')
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.'
|
||||
'amphora_info.AmphoraInfo._load')
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.'
|
||||
'amphora_info.AmphoraInfo._get_version_of_installed_package')
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.'
|
||||
'amphora_info.AmphoraInfo._count_haproxy_processes')
|
||||
@mock.patch('socket.gethostname', return_value='FAKE_HOST')
|
||||
def test_compile_amphora_details_for_udp(self, mhostname, m_count,
|
||||
m_pkg_version, m_load, m_get_nets,
|
||||
m_os, m_cpu, mget_mem,
|
||||
mget_listener, mget_udp_listener):
|
||||
mget_mem.return_value = {'SwapCached': 0, 'Buffers': 344792,
|
||||
'MemTotal': 21692784, 'Cached': 4271856,
|
||||
'Slab': 534384, 'MemFree': 12685624,
|
||||
'Shmem': 9520}
|
||||
m_cpu.return_value = {'user': '252551', 'softirq': '8336',
|
||||
'system': '52554', 'total': 7503411}
|
||||
m_pkg_version.side_effect = self._return_version
|
||||
mdisk_info = mock.MagicMock()
|
||||
m_os.return_value = mdisk_info
|
||||
mdisk_info.f_blocks = 34676992
|
||||
mdisk_info.f_bfree = 28398016
|
||||
mdisk_info.f_frsize = 4096
|
||||
mdisk_info.f_bavail = 26630646
|
||||
m_get_nets.return_value = {'eth1': {'network_rx': 996,
|
||||
'network_tx': 418},
|
||||
'eth2': {'network_rx': 848,
|
||||
'network_tx': 578}}
|
||||
m_load.return_value = ['0.09', '0.11', '0.10']
|
||||
m_count.return_value = 5
|
||||
self.udp_driver.get_subscribed_amp_compile_info.return_value = [
|
||||
'keepalived', 'ipvsadm']
|
||||
self.udp_driver.is_listener_running.side_effect = [True, False]
|
||||
original_version = api_server.VERSION
|
||||
api_server.VERSION = self.API_VERSION
|
||||
expected_dict = {u'active': True,
|
||||
u'api_version': self.API_VERSION,
|
||||
u'cpu': {u'soft_irq': u'8336',
|
||||
u'system': u'52554',
|
||||
u'total': 7503411,
|
||||
u'user': u'252551'},
|
||||
u'disk': {u'available': 109079126016,
|
||||
u'used': 25718685696},
|
||||
u'haproxy_count': 5,
|
||||
u'haproxy_version': self.HAPROXY_VERSION,
|
||||
u'keepalived_version': self.KEEPALIVED_VERSION,
|
||||
u'ipvsadm_version': self.IPVSADM_VERSION,
|
||||
u'udp_listener_process_count': 1,
|
||||
u'hostname': u'FAKE_HOST',
|
||||
u'listeners': list(set([self.FAKE_LISTENER_ID_1,
|
||||
self.FAKE_LISTENER_ID_2,
|
||||
self.FAKE_LISTENER_ID_3,
|
||||
self.FAKE_LISTENER_ID_4])),
|
||||
u'load': [u'0.09', u'0.11', u'0.10'],
|
||||
u'memory': {u'buffers': 344792,
|
||||
u'cached': 4271856,
|
||||
u'free': 12685624,
|
||||
u'shared': 9520,
|
||||
u'slab': 534384,
|
||||
u'swap_used': 0,
|
||||
u'total': 21692784},
|
||||
u'networks': {u'eth1': {u'network_rx': 996,
|
||||
u'network_tx': 418},
|
||||
u'eth2': {u'network_rx': 848,
|
||||
u'network_tx': 578}},
|
||||
u'packages': {},
|
||||
u'topology': u'SINGLE',
|
||||
u'topology_status': u'OK'}
|
||||
actual = self.amp_info.compile_amphora_details(self.udp_driver)
|
||||
self.assertEqual(expected_dict, actual.json)
|
||||
api_server.VERSION = original_version
|
||||
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
|
||||
'is_listener_running')
|
||||
def test__count_haproxy_process(self, mock_is_running):
|
||||
@ -63,6 +258,29 @@ class TestAmphoraInfo(base.TestCase):
|
||||
[uuidutils.generate_uuid(), uuidutils.generate_uuid()])
|
||||
self.assertEqual(1, result)
|
||||
|
||||
def test__count_udp_listener_processes(self):
|
||||
self.udp_driver.is_listener_running.side_effect = [True, False, True]
|
||||
expected = 2
|
||||
actual = self.amp_info._count_udp_listener_processes(
|
||||
self.udp_driver, [self.FAKE_LISTENER_ID_1,
|
||||
self.FAKE_LISTENER_ID_2,
|
||||
self.FAKE_LISTENER_ID_3])
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.'
|
||||
'amphora_info.AmphoraInfo._get_version_of_installed_package')
|
||||
def test__get_extend_body_from_udp_driver(self, m_get_version):
|
||||
self.udp_driver.get_subscribed_amp_compile_info.return_value = [
|
||||
'keepalived', 'ipvsadm']
|
||||
m_get_version.side_effect = self._return_version
|
||||
expected = {
|
||||
"keepalived_version": self.KEEPALIVED_VERSION,
|
||||
"ipvsadm_version": self.IPVSADM_VERSION
|
||||
}
|
||||
actual = self.amp_info._get_extend_body_from_udp_driver(
|
||||
self.udp_driver)
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test__get_meminfo(self):
|
||||
# Known data test
|
||||
meminfo = ('MemTotal: 21692784 kB\n'
|
||||
|
@ -0,0 +1,422 @@
|
||||
# Copyright 2015 Hewlett Packard Enterprise Development Company LP
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import os
|
||||
import stat
|
||||
import subprocess
|
||||
|
||||
import flask
|
||||
import mock
|
||||
from werkzeug import exceptions
|
||||
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from octavia.amphorae.backends.agent.api_server import keepalivedlvs
|
||||
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.tests.common import utils as test_utils
|
||||
from octavia.tests.unit import base
|
||||
|
||||
|
||||
class KeepalivedLvsTestCase(base.TestCase):
|
||||
FAKE_ID = uuidutils.generate_uuid()
|
||||
LISTENER_ID = 'listener-1111-1111-1111-listenerid00'
|
||||
POOL_ID = 'poolpool-1111-1111-1111-poolid000000'
|
||||
MEMBER_ID1 = 'memberid-1111-1111-1111-memberid1111'
|
||||
MEMBER_ID2 = 'memberid-2222-2222-2222-memberid2222'
|
||||
HEALTHMONITOR_ID = 'hmidhmid-1111-1111-1111-healthmonito'
|
||||
NORMAL_CFG_CONTENT = (
|
||||
"# Configuration for Listener %(listener_id)s\n\n"
|
||||
"net_namespace haproxy-amphora\n\n"
|
||||
"virtual_server 10.0.0.2 80 {\n"
|
||||
" lb_algo rr\n"
|
||||
" lb_kind NAT\n"
|
||||
" protocol udp\n"
|
||||
" delay_loop 30\n"
|
||||
" delay_before_retry 31\n"
|
||||
" retry 3\n\n\n"
|
||||
" # Configuration for Pool %(pool_id)s\n"
|
||||
" # Configuration for HealthMonitor %(hm_id)s\n"
|
||||
" # Configuration for Member %(member1_id)s\n"
|
||||
" real_server 10.0.0.99 82 {\n"
|
||||
" weight 13\n"
|
||||
" inhibit_on_failure\n"
|
||||
" uthreshold 98\n"
|
||||
" persistence_timeout 33\n"
|
||||
" persistence_granularity 255.255.0.0\n"
|
||||
" delay_before_retry 31\n"
|
||||
" retry 3\n"
|
||||
" MISC_CHECK {\n"
|
||||
" misc_path \"/var/lib/octavia/lvs/check/"
|
||||
"udp_check.sh 10.0.0.99 82\"\n"
|
||||
" misc_timeout 30\n"
|
||||
" misc_dynamic\n"
|
||||
" }\n"
|
||||
" }\n\n"
|
||||
" # Configuration for Member %(member2_id)s\n"
|
||||
" real_server 10.0.0.98 82 {\n"
|
||||
" weight 13\n"
|
||||
" inhibit_on_failure\n"
|
||||
" uthreshold 98\n"
|
||||
" persistence_timeout 33\n"
|
||||
" persistence_granularity 255.255.0.0\n"
|
||||
" delay_before_retry 31\n"
|
||||
" retry 3\n"
|
||||
" MISC_CHECK {\n"
|
||||
" misc_path \"/var/lib/octavia/lvs/check/"
|
||||
"udp_check.sh 10.0.0.98 82\"\n"
|
||||
" misc_timeout 30\n"
|
||||
" misc_dynamic\n"
|
||||
" }\n"
|
||||
" }\n\n"
|
||||
"}\n\n") % {'listener_id': LISTENER_ID, 'pool_id': POOL_ID,
|
||||
'hm_id': HEALTHMONITOR_ID, 'member1_id': MEMBER_ID1,
|
||||
'member2_id': MEMBER_ID2}
|
||||
PROC_CONTENT = (
|
||||
"IP Virtual Server version 1.2.1 (size=4096)\n"
|
||||
"Prot LocalAddress:Port Scheduler Flags\n"
|
||||
" -> RemoteAddress:Port Forward Weight ActiveConn InActConn\n"
|
||||
"UDP 0A000002:0050 sh\n"
|
||||
" -> 0A000063:0052 Masq 13 1 0\n"
|
||||
" -> 0A000062:0052 Masq 13 1 0\n"
|
||||
)
|
||||
NORMAL_PID_CONTENT = "1988"
|
||||
TEST_URL = server.PATH_PREFIX + '/listeners/%s/%s/udp_listener'
|
||||
|
||||
def setUp(self):
|
||||
super(KeepalivedLvsTestCase, self).setUp()
|
||||
self.app = flask.Flask(__name__)
|
||||
self.client = self.app.test_client()
|
||||
self._ctx = self.app.test_request_context()
|
||||
self._ctx.push()
|
||||
self.test_keepalivedlvs = keepalivedlvs.KeepalivedLvs()
|
||||
self.app.add_url_rule(
|
||||
rule=self.TEST_URL % ('<amphora_id>', '<listener_id>'),
|
||||
view_func=(lambda amphora_id, listener_id:
|
||||
self.test_keepalivedlvs.upload_udp_listener_config(
|
||||
listener_id)),
|
||||
methods=['PUT'])
|
||||
|
||||
@mock.patch('pyroute2.NetNS')
|
||||
@mock.patch('shutil.copy2')
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
|
||||
'get_os_init_system', return_value=consts.INIT_SYSTEMD)
|
||||
@mock.patch('os.chmod')
|
||||
@mock.patch('os.path.exists')
|
||||
@mock.patch('os.makedirs')
|
||||
@mock.patch('os.remove')
|
||||
@mock.patch('subprocess.check_output')
|
||||
def test_upload_udp_listener_config_no_vrrp_check_dir(
|
||||
self, m_check_output, m_os_rm, m_os_mkdir, m_exists, m_os_chmod,
|
||||
m_os_sysinit, m_copy2, mock_netns):
|
||||
m_exists.side_effect = [False, False, True, True, False, False]
|
||||
cfg_path = util.keepalived_lvs_cfg_path(self.FAKE_ID)
|
||||
m = self.useFixture(test_utils.OpenFixture(cfg_path)).mock_open
|
||||
|
||||
with mock.patch('os.open') as m_open, mock.patch.object(os,
|
||||
'fdopen',
|
||||
m) as m_fdopen:
|
||||
m_open.side_effect = ['TEST-WRITE-CFG',
|
||||
'TEST-WRITE-SYSINIT']
|
||||
res = self.client.put(self.TEST_URL % ('123', self.FAKE_ID),
|
||||
data=self.NORMAL_CFG_CONTENT)
|
||||
os_mkdir_calls = [
|
||||
mock.call(util.keepalived_lvs_dir()),
|
||||
mock.call(util.keepalived_backend_check_script_dir())
|
||||
]
|
||||
m_os_mkdir.assert_has_calls(os_mkdir_calls)
|
||||
|
||||
m_os_chmod.assert_called_with(
|
||||
util.keepalived_backend_check_script_path(), stat.S_IEXEC)
|
||||
flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
|
||||
mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
|
||||
systemd_cfg_path = util.keepalived_lvs_init_path(
|
||||
consts.INIT_SYSTEMD, self.FAKE_ID)
|
||||
m_open_calls = [
|
||||
mock.call(cfg_path, flags, mode),
|
||||
mock.call(systemd_cfg_path, flags, mode)
|
||||
]
|
||||
m_open.assert_has_calls(m_open_calls)
|
||||
m_fdopen.assert_any_call('TEST-WRITE-CFG', 'wb')
|
||||
m_fdopen.assert_any_call('TEST-WRITE-SYSINIT', 'w')
|
||||
self.assertEqual(200, res.status_code)
|
||||
|
||||
@mock.patch('pyroute2.NetNS')
|
||||
@mock.patch('shutil.copy2')
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
|
||||
'get_os_init_system', return_value=consts.INIT_SYSTEMD)
|
||||
@mock.patch('os.chmod')
|
||||
@mock.patch('os.path.exists')
|
||||
@mock.patch('os.makedirs')
|
||||
@mock.patch('os.remove')
|
||||
@mock.patch('subprocess.check_output')
|
||||
def test_upload_udp_listener_config_with_vrrp_check_dir(
|
||||
self, m_check_output, m_os_rm, m_os_mkdir, m_exists, m_os_chmod,
|
||||
m_os_sysinit, m_copy2, mock_netns):
|
||||
m_exists.side_effect = [False, False, True, True, True, False, False]
|
||||
cfg_path = util.keepalived_lvs_cfg_path(self.FAKE_ID)
|
||||
m = self.useFixture(test_utils.OpenFixture(cfg_path)).mock_open
|
||||
|
||||
with mock.patch('os.open') as m_open, mock.patch.object(os,
|
||||
'fdopen',
|
||||
m) as m_fdopen:
|
||||
m_open.side_effect = ['TEST-WRITE-CFG',
|
||||
'TEST-WRITE-SYSINIT',
|
||||
'TEST-WRITE-UDP-VRRP-CHECK']
|
||||
res = self.client.put(self.TEST_URL % ('123', self.FAKE_ID),
|
||||
data=self.NORMAL_CFG_CONTENT)
|
||||
os_mkdir_calls = [
|
||||
mock.call(util.keepalived_lvs_dir()),
|
||||
mock.call(util.keepalived_backend_check_script_dir())
|
||||
]
|
||||
m_os_mkdir.assert_has_calls(os_mkdir_calls)
|
||||
|
||||
m_os_chmod.assert_called_with(
|
||||
util.keepalived_backend_check_script_path(), stat.S_IEXEC)
|
||||
flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
|
||||
mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
|
||||
systemd_cfg_path = util.keepalived_lvs_init_path(
|
||||
consts.INIT_SYSTEMD, self.FAKE_ID)
|
||||
script_path = os.path.join(
|
||||
util.keepalived_check_scripts_dir(),
|
||||
keepalivedlvs.KEEPALIVED_CHECK_SCRIPT_NAME)
|
||||
m_open_calls = [
|
||||
mock.call(cfg_path, flags, mode),
|
||||
mock.call(systemd_cfg_path, flags, mode),
|
||||
mock.call(script_path, flags, stat.S_IEXEC)
|
||||
]
|
||||
m_open.assert_has_calls(m_open_calls)
|
||||
m_fdopen.assert_any_call('TEST-WRITE-CFG', 'wb')
|
||||
m_fdopen.assert_any_call('TEST-WRITE-SYSINIT', 'w')
|
||||
m_fdopen.assert_any_call('TEST-WRITE-UDP-VRRP-CHECK', 'w')
|
||||
self.assertEqual(200, res.status_code)
|
||||
|
||||
@mock.patch('shutil.copy2')
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
|
||||
'get_os_init_system', return_value=consts.INIT_SYSTEMD)
|
||||
@mock.patch('os.chmod')
|
||||
@mock.patch('os.path.exists')
|
||||
@mock.patch('os.makedirs')
|
||||
@mock.patch('os.remove')
|
||||
@mock.patch('subprocess.check_output')
|
||||
def test_upload_udp_listener_config_start_service_failure(
|
||||
self, m_check_output, m_os_rm, m_os_mkdir, m_exists, m_os_chmod,
|
||||
m_os_sysinit, m_copy2):
|
||||
m_exists.side_effect = [False, False, True, True, True, False]
|
||||
m_check_output.side_effect = subprocess.CalledProcessError(1, 'blah!')
|
||||
cfg_path = util.keepalived_lvs_cfg_path(self.FAKE_ID)
|
||||
m = self.useFixture(test_utils.OpenFixture(cfg_path)).mock_open
|
||||
|
||||
with mock.patch('os.open') as m_open, mock.patch.object(os,
|
||||
'fdopen',
|
||||
m) as m_fdopen:
|
||||
m_open.side_effect = ['TEST-WRITE-CFG',
|
||||
'TEST-WRITE-SYSINIT']
|
||||
res = self.client.put(self.TEST_URL % ('123', self.FAKE_ID),
|
||||
data=self.NORMAL_CFG_CONTENT)
|
||||
os_mkdir_calls = [
|
||||
mock.call(util.keepalived_lvs_dir()),
|
||||
mock.call(util.keepalived_backend_check_script_dir())
|
||||
]
|
||||
m_os_mkdir.assert_has_calls(os_mkdir_calls)
|
||||
|
||||
m_os_chmod.assert_called_with(
|
||||
util.keepalived_backend_check_script_path(), stat.S_IEXEC)
|
||||
flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
|
||||
mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
|
||||
systemd_cfg_path = util.keepalived_lvs_init_path(
|
||||
consts.INIT_SYSTEMD, self.FAKE_ID)
|
||||
m_open_calls = [
|
||||
mock.call(cfg_path, flags, mode),
|
||||
mock.call(systemd_cfg_path, flags, mode)
|
||||
]
|
||||
m_open.assert_has_calls(m_open_calls)
|
||||
m_fdopen.assert_any_call('TEST-WRITE-CFG', 'wb')
|
||||
m_fdopen.assert_any_call('TEST-WRITE-SYSINIT', 'w')
|
||||
self.assertEqual(500, res.status_code)
|
||||
|
||||
@mock.patch('subprocess.check_output')
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.'
|
||||
'keepalivedlvs.KeepalivedLvs.'
|
||||
'_check_udp_listener_exists')
|
||||
def test_manage_udp_listener(self, mock_udp_exist, mock_check_output):
|
||||
res = self.test_keepalivedlvs.manage_udp_listener(self.FAKE_ID,
|
||||
'start')
|
||||
cmd = ("/usr/sbin/service octavia-keepalivedlvs-{listener_id}"
|
||||
" {action}".format(listener_id=self.FAKE_ID, action='start'))
|
||||
mock_check_output.assert_called_once_with(cmd.split(),
|
||||
stderr=subprocess.STDOUT)
|
||||
self.assertEqual(202, res.status_code)
|
||||
|
||||
res = self.test_keepalivedlvs.manage_udp_listener(self.FAKE_ID,
|
||||
'restart')
|
||||
self.assertEqual(400, res.status_code)
|
||||
|
||||
mock_check_output.side_effect = subprocess.CalledProcessError(1,
|
||||
'blah!')
|
||||
|
||||
res = self.test_keepalivedlvs.manage_udp_listener(self.FAKE_ID,
|
||||
'start')
|
||||
self.assertEqual(500, res.status_code)
|
||||
|
||||
@mock.patch('octavia.amphorae.backends.utils.keepalivedlvs_query.'
|
||||
'get_listener_realserver_mapping')
|
||||
@mock.patch('subprocess.check_output', return_value=PROC_CONTENT)
|
||||
@mock.patch('os.path.exists')
|
||||
def test_get_udp_listener_status(self, m_exist, m_check_output,
|
||||
mget_mapping):
|
||||
mget_mapping.return_value = (
|
||||
True, {'10.0.0.99:82': {'status': 'UP',
|
||||
'Weight': '13',
|
||||
'InActConn': '0',
|
||||
'ActiveConn': '0'},
|
||||
'10.0.0.98:82': {'status': 'UP',
|
||||
'Weight': '13',
|
||||
'InActConn': '0',
|
||||
'ActiveConn': '0'}})
|
||||
pid_path = ('/var/lib/octavia/lvs/octavia-'
|
||||
'keepalivedlvs-%s.pid' % self.FAKE_ID)
|
||||
self.useFixture(test_utils.OpenFixture(pid_path,
|
||||
self.NORMAL_PID_CONTENT))
|
||||
|
||||
cfg_path = ('/var/lib/octavia/lvs/octavia-'
|
||||
'keepalivedlvs-%s.conf' % self.FAKE_ID)
|
||||
self.useFixture(test_utils.OpenFixture(cfg_path,
|
||||
self.NORMAL_CFG_CONTENT))
|
||||
|
||||
m_exist.return_value = True
|
||||
expected = {'status': 'ACTIVE',
|
||||
'pools': [{'lvs': {
|
||||
'members': {self.MEMBER_ID1: 'UP',
|
||||
self.MEMBER_ID2: 'UP'},
|
||||
'status': 'UP',
|
||||
'uuid': self.POOL_ID}}],
|
||||
'type': 'lvs', 'uuid': self.FAKE_ID}
|
||||
res = self.test_keepalivedlvs.get_udp_listener_status(self.FAKE_ID)
|
||||
self.assertEqual(200, res.status_code)
|
||||
self.assertEqual(expected, res.json)
|
||||
|
||||
@mock.patch('os.path.exists')
|
||||
def test_get_udp_listener_status_no_exists(self, m_exist):
|
||||
m_exist.return_value = False
|
||||
self.assertRaises(exceptions.HTTPException,
|
||||
self.test_keepalivedlvs.get_udp_listener_status,
|
||||
self.FAKE_ID)
|
||||
|
||||
@mock.patch('os.path.exists')
|
||||
def test_get_udp_listener_status_offline_status(self, m_exist):
|
||||
m_exist.return_value = True
|
||||
pid_path = ('/var/lib/octavia/lvs/octavia-'
|
||||
'keepalivedlvs-%s.pid' % self.FAKE_ID)
|
||||
self.useFixture(test_utils.OpenFixture(pid_path,
|
||||
self.NORMAL_PID_CONTENT))
|
||||
cfg_path = ('/var/lib/octavia/lvs/octavia-'
|
||||
'keepalivedlvs-%s.conf' % self.FAKE_ID)
|
||||
self.useFixture(test_utils.OpenFixture(cfg_path, 'NO VS CONFIG'))
|
||||
expected = {'status': 'OFFLINE',
|
||||
'type': '',
|
||||
'uuid': self.FAKE_ID}
|
||||
res = self.test_keepalivedlvs.get_udp_listener_status(self.FAKE_ID)
|
||||
self.assertEqual(200, res.status_code)
|
||||
self.assertEqual(expected, res.json)
|
||||
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
|
||||
'get_udp_listeners', return_value=[LISTENER_ID])
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
|
||||
'get_os_init_system', return_value=consts.INIT_SYSTEMD)
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
|
||||
'get_keepalivedlvs_pid')
|
||||
@mock.patch('subprocess.check_output')
|
||||
@mock.patch('os.remove')
|
||||
@mock.patch('os.path.exists')
|
||||
def test_delete_udp_listener(self, m_exist, m_remove, m_check_output,
|
||||
mget_pid, m_init_sys, mget_udp_listeners):
|
||||
m_exist.return_value = True
|
||||
res = self.test_keepalivedlvs.delete_udp_listener(self.FAKE_ID)
|
||||
|
||||
cmd1 = ("/usr/sbin/service "
|
||||
"octavia-keepalivedlvs-{0} stop".format(self.FAKE_ID))
|
||||
cmd2 = ("systemctl disable "
|
||||
"octavia-keepalivedlvs-{list}".format(list=self.FAKE_ID))
|
||||
calls = [
|
||||
mock.call(cmd1.split(), stderr=subprocess.STDOUT),
|
||||
mock.call(cmd2.split(), stderr=subprocess.STDOUT)
|
||||
]
|
||||
m_check_output.assert_has_calls(calls)
|
||||
self.assertEqual(200, res.status_code)
|
||||
|
||||
@mock.patch.object(keepalivedlvs, "webob")
|
||||
@mock.patch('os.path.exists')
|
||||
def test_delete_udp_listener_not_exist(self, m_exist, m_webob):
|
||||
m_exist.return_value = False
|
||||
self.test_keepalivedlvs.delete_udp_listener(self.FAKE_ID)
|
||||
calls = [
|
||||
mock.call(
|
||||
json=dict(message='UDP Listener Not Found',
|
||||
details="No UDP listener with UUID: "
|
||||
"{0}".format(self.FAKE_ID)), status=404),
|
||||
mock.call(json={'message': 'OK'})
|
||||
]
|
||||
m_webob.Response.assert_has_calls(calls)
|
||||
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
|
||||
'get_keepalivedlvs_pid')
|
||||
@mock.patch('subprocess.check_output')
|
||||
@mock.patch('os.path.exists')
|
||||
def test_delete_udp_listener_stop_service_fail(self, m_exist,
|
||||
m_check_output, mget_pid):
|
||||
m_exist.return_value = True
|
||||
m_check_output.side_effect = subprocess.CalledProcessError(1,
|
||||
'Woops!')
|
||||
res = self.test_keepalivedlvs.delete_udp_listener(self.FAKE_ID)
|
||||
self.assertEqual(500, res.status_code)
|
||||
self.assertEqual({'message': 'Error stopping keepalivedlvs',
|
||||
'details': None}, res.json)
|
||||
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
|
||||
'get_os_init_system', return_value=consts.INIT_SYSVINIT)
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
|
||||
'get_keepalivedlvs_pid')
|
||||
@mock.patch('subprocess.check_output')
|
||||
@mock.patch('os.remove')
|
||||
@mock.patch('os.path.exists')
|
||||
def test_delete_udp_listener_disable_service_fail(self, m_exist, m_remove,
|
||||
m_check_output, mget_pid,
|
||||
m_init_sys):
|
||||
m_exist.return_value = True
|
||||
m_check_output.side_effect = [True,
|
||||
subprocess.CalledProcessError(
|
||||
1, 'Woops!')]
|
||||
res = self.test_keepalivedlvs.delete_udp_listener(self.FAKE_ID)
|
||||
self.assertEqual(500, res.status_code)
|
||||
self.assertEqual({
|
||||
'message': 'Error disabling '
|
||||
'octavia-keepalivedlvs-%s service' % self.FAKE_ID,
|
||||
'details': None}, res.json)
|
||||
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
|
||||
'get_os_init_system')
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
|
||||
'get_keepalivedlvs_pid')
|
||||
@mock.patch('subprocess.check_output')
|
||||
@mock.patch('os.remove')
|
||||
@mock.patch('os.path.exists')
|
||||
def test_delete_udp_listener_unsupported_sysinit(self, m_exist, m_remove,
|
||||
m_check_output, mget_pid,
|
||||
m_init_sys):
|
||||
m_exist.return_value = True
|
||||
self.assertRaises(
|
||||
util.UnknownInitError, self.test_keepalivedlvs.delete_udp_listener,
|
||||
self.FAKE_ID)
|
@ -88,9 +88,17 @@ class TestPlug(base.TestCase):
|
||||
'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)
|
||||
calls = [mock.call('amphora-haproxy', ['/sbin/sysctl', '--system'],
|
||||
stdout=subprocess.PIPE),
|
||||
mock.call('amphora-haproxy', ['modprobe', 'ip_vs'],
|
||||
stdout=subprocess.PIPE),
|
||||
mock.call('amphora-haproxy',
|
||||
['/sbin/sysctl', '-w', 'net.ipv4.ip_forward=1'],
|
||||
stdout=subprocess.PIPE),
|
||||
mock.call('amphora-haproxy',
|
||||
['/sbin/sysctl', '-w', 'net.ipv4.vs.conntrack=1'],
|
||||
stdout=subprocess.PIPE)]
|
||||
mock_nspopen.assert_has_calls(calls, any_order=True)
|
||||
|
||||
@mock.patch('pyroute2.NSPopen')
|
||||
@mock.patch.object(plug, "webob")
|
||||
@ -116,9 +124,18 @@ class TestPlug(base.TestCase):
|
||||
'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)
|
||||
calls = [mock.call('amphora-haproxy', ['/sbin/sysctl', '--system'],
|
||||
stdout=subprocess.PIPE),
|
||||
mock.call('amphora-haproxy', ['modprobe', 'ip_vs'],
|
||||
stdout=subprocess.PIPE),
|
||||
mock.call('amphora-haproxy',
|
||||
['/sbin/sysctl', '-w',
|
||||
'net.ipv6.conf.all.forwarding=1'],
|
||||
stdout=subprocess.PIPE),
|
||||
mock.call('amphora-haproxy',
|
||||
['/sbin/sysctl', '-w', 'net.ipv4.vs.conntrack=1'],
|
||||
stdout=subprocess.PIPE)]
|
||||
mock_nspopen.assert_has_calls(calls, any_order=True)
|
||||
|
||||
@mock.patch.object(plug, "webob")
|
||||
@mock.patch('pyroute2.IPRoute')
|
||||
@ -191,7 +208,11 @@ class TestPlugNetwork(base.TestCase):
|
||||
'up route add -net {dest1} gw {nexthop} dev {netns_interface}\n'
|
||||
'down route del -net {dest1} gw {nexthop} dev {netns_interface}\n'
|
||||
'up route add -net {dest2} gw {nexthop} dev {netns_interface}\n'
|
||||
'down route del -net {dest2} gw {nexthop} dev {netns_interface}\n')
|
||||
'down route del -net {dest2} gw {nexthop} dev {netns_interface}\n'
|
||||
'post-up /sbin/iptables -t nat -A POSTROUTING -p udp -o '
|
||||
'eth1234 -j MASQUERADE\n'
|
||||
'post-down /sbin/iptables -t nat -D POSTROUTING -p udp -o eth1234 '
|
||||
'-j MASQUERADE\n')
|
||||
|
||||
template_port = osutils.j2_env.get_template('plug_port_ethX.conf.j2')
|
||||
text = self.test_plug._osutils._generate_network_file_text(
|
||||
|
@ -34,6 +34,8 @@ class AgentJinjaTestCase(base.TestCase):
|
||||
agent_server_cert='/etc/octavia/certs/server.pem')
|
||||
self.conf.config(group="amphora_agent",
|
||||
agent_server_network_dir='/etc/network/interfaces.d/')
|
||||
self.conf.config(group='amphora_agent',
|
||||
amphora_udp_driver='keepalived_lvs'),
|
||||
self.conf.config(group="haproxy_amphora",
|
||||
base_cert_dir='/var/lib/octavia/certs')
|
||||
self.conf.config(group="haproxy_amphora", use_upstart='True')
|
||||
@ -79,7 +81,8 @@ class AgentJinjaTestCase(base.TestCase):
|
||||
'agent_server_network_dir = '
|
||||
'/etc/network/interfaces.d/\n'
|
||||
'agent_request_read_timeout = 120\n'
|
||||
'amphora_id = ' + AMP_ID)
|
||||
'amphora_id = ' + AMP_ID + '\n'
|
||||
'amphora_udp_driver = keepalived_lvs')
|
||||
agent_cfg = ajc.build_agent_config(AMP_ID)
|
||||
self.assertEqual(expected_config, agent_cfg)
|
||||
|
||||
@ -114,6 +117,42 @@ class AgentJinjaTestCase(base.TestCase):
|
||||
'agent_server_network_file = '
|
||||
'/etc/network/interfaces\n'
|
||||
'agent_request_read_timeout = 120\n'
|
||||
'amphora_id = ' + AMP_ID)
|
||||
'amphora_id = ' + AMP_ID + '\n'
|
||||
'amphora_udp_driver = keepalived_lvs')
|
||||
agent_cfg = ajc.build_agent_config(AMP_ID)
|
||||
self.assertEqual(expected_config, agent_cfg)
|
||||
|
||||
def test_build_agent_config_with_new_udp_driver(self):
|
||||
ajc = agent_jinja_cfg.AgentJinjaTemplater()
|
||||
self.conf.config(group='amphora_agent',
|
||||
agent_server_network_file=None)
|
||||
self.conf.config(group="amphora_agent",
|
||||
amphora_udp_driver='new_udp_driver')
|
||||
expected_config = ('\n[DEFAULT]\n'
|
||||
'debug = False\n\n'
|
||||
'[haproxy_amphora]\n'
|
||||
'base_cert_dir = /var/lib/octavia/certs\n'
|
||||
'base_path = /var/lib/octavia\n'
|
||||
'bind_host = 0.0.0.0\n'
|
||||
'bind_port = 9443\n'
|
||||
'haproxy_cmd = /usr/sbin/haproxy\n'
|
||||
'respawn_count = 2\n'
|
||||
'respawn_interval = 2\n'
|
||||
'use_upstart = True\n'
|
||||
'user_group = nogroup\n\n'
|
||||
'[health_manager]\n'
|
||||
'controller_ip_port_list = 192.0.2.10:5555\n'
|
||||
'heartbeat_interval = 10\n'
|
||||
'heartbeat_key = TEST\n\n'
|
||||
'[amphora_agent]\n'
|
||||
'agent_server_ca = '
|
||||
'/etc/octavia/certs/client_ca.pem\n'
|
||||
'agent_server_cert = '
|
||||
'/etc/octavia/certs/server.pem\n'
|
||||
'agent_server_network_dir = '
|
||||
'/etc/network/interfaces.d/\n'
|
||||
'agent_request_read_timeout = 120\n'
|
||||
'amphora_id = ' + AMP_ID + '\n'
|
||||
'amphora_udp_driver = new_udp_driver')
|
||||
agent_cfg = ajc.build_agent_config(AMP_ID)
|
||||
self.assertEqual(expected_config, agent_cfg)
|
||||
|
@ -20,6 +20,7 @@ from oslo_utils import uuidutils
|
||||
import six
|
||||
|
||||
from octavia.amphorae.backends.health_daemon import health_daemon
|
||||
from octavia.common import constants
|
||||
import octavia.tests.unit.base as base
|
||||
|
||||
if six.PY2:
|
||||
@ -353,6 +354,65 @@ class TestHealthDaemon(base.TestCase):
|
||||
|
||||
self.assertEqual(msg['listeners'][LISTENER_ID1]['pools'], {})
|
||||
|
||||
@mock.patch("octavia.amphorae.backends.utils.keepalivedlvs_query."
|
||||
"get_udp_listener_pool_status")
|
||||
@mock.patch("octavia.amphorae.backends.utils.keepalivedlvs_query."
|
||||
"get_udp_listeners_stats")
|
||||
@mock.patch("octavia.amphorae.backends.agent.api_server.util."
|
||||
"get_udp_listeners")
|
||||
def test_bulid_stats_message_with_udp_listener(
|
||||
self, mock_get_udp_listeners, mock_get_listener_stats,
|
||||
mock_get_pool_status):
|
||||
udp_listener_id1 = uuidutils.generate_uuid()
|
||||
udp_listener_id2 = uuidutils.generate_uuid()
|
||||
udp_listener_id3 = uuidutils.generate_uuid()
|
||||
pool_id = uuidutils.generate_uuid()
|
||||
member_id1 = uuidutils.generate_uuid()
|
||||
member_id2 = uuidutils.generate_uuid()
|
||||
mock_get_udp_listeners.return_value = [udp_listener_id1,
|
||||
udp_listener_id2,
|
||||
udp_listener_id3]
|
||||
mock_get_listener_stats.return_value = {
|
||||
udp_listener_id1: {
|
||||
'status': constants.OPEN,
|
||||
'stats': {'bin': 6387472, 'stot': 5, 'bout': 7490,
|
||||
'ereq': 0, 'scur': 0}},
|
||||
udp_listener_id3: {
|
||||
'status': constants.DOWN,
|
||||
'stats': {'bin': 0, 'stot': 0, 'bout': 0,
|
||||
'ereq': 0, 'scur': 0}}
|
||||
}
|
||||
udp_pool_status = {
|
||||
'lvs': {
|
||||
'uuid': pool_id,
|
||||
'status': constants.UP,
|
||||
'members': {member_id1: constants.UP,
|
||||
member_id2: constants.UP}}}
|
||||
mock_get_pool_status.side_effect = (
|
||||
lambda x: udp_pool_status if x == udp_listener_id1 else {})
|
||||
# the first listener can get all necessary info.
|
||||
# the second listener can not get listener stats, so we won't report it
|
||||
# the third listener can get listener stats, but can not get pool
|
||||
# status, so the result will just contain the listener status for it.
|
||||
expected = {
|
||||
'listeners': {
|
||||
udp_listener_id1: {
|
||||
'status': constants.OPEN,
|
||||
'pools': {
|
||||
pool_id: {
|
||||
'status': constants.UP,
|
||||
'members': {
|
||||
member_id1: constants.UP,
|
||||
member_id2: constants.UP}}},
|
||||
'stats': {'conns': 0, 'totconns': 5, 'ereq': 0,
|
||||
'rx': 6387472, 'tx': 7490}},
|
||||
udp_listener_id3: {
|
||||
'status': constants.DOWN,
|
||||
'stats': {'conns': 0, 'totconns': 0, 'ereq': 0,
|
||||
'rx': 0, 'tx': 0}}}, 'id': None, 'seq': mock.ANY}
|
||||
msg = health_daemon.build_stats_message()
|
||||
self.assertEqual(expected, msg)
|
||||
|
||||
|
||||
class FileNotFoundError(IOError):
|
||||
errno = 2
|
||||
|
@ -0,0 +1,397 @@
|
||||
# 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 mock
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from octavia.amphorae.backends.agent.api_server import util
|
||||
from octavia.amphorae.backends.utils import keepalivedlvs_query as lvs_query
|
||||
from octavia.common import constants
|
||||
from octavia.tests.common import utils as test_utils
|
||||
from octavia.tests.unit import base
|
||||
|
||||
# Kernal_file_sample which is in /proc/net/ip_vs
|
||||
# The realservers and the listened ports are
|
||||
# 10.0.0.25:2222, 10.0.0.35:3333.
|
||||
# The virtual server and the listened port is
|
||||
# 10.0.0.37:7777.
|
||||
KERNAL_FILE_SAMPLE_V4 = (
|
||||
"IP Virtual Server version 1.2.1 (size=4096)\n"
|
||||
"Prot LocalAddress:Port Scheduler Flags\n"
|
||||
" -> RemoteAddress:Port Forward Weight ActiveConn InActConn\n"
|
||||
"UDP 0A000025:1E61 rr\n"
|
||||
" -> 0A000023:0D05 Masq 2 0 0\n"
|
||||
" -> 0A000019:08AE Masq 3 0 0")
|
||||
# Kernal_file_sample which is in /proc/net/ip_vs
|
||||
# The realservers and the listened ports are
|
||||
# [fd79:35e2:9963:0:f816:3eff:feca:b7bf]:2222,
|
||||
# [fd79:35e2:9963:0:f816:3eff:fe9d:94df]:3333.
|
||||
# The virtual server and the listened port is
|
||||
# [fd79:35e2:9963:0:f816:3eff:fe6d:7a2a]:7777.
|
||||
KERNAL_FILE_SAMPLE_V6 = (
|
||||
"IP Virtual Server version 1.2.1 (size=4096)\n"
|
||||
"Prot LocalAddress:Port Scheduler Flags\n"
|
||||
" -> RemoteAddress:Port Forward Weight ActiveConn InActConn\n"
|
||||
"UDP [fd79:35e2:9963:0000:f816:3eff:fe6d:7a2a]:1E61 rr\n"
|
||||
" -> [fd79:35e2:9963:0000:f816:3eff:feca:b7bf]:08AE "
|
||||
"Masq 3 0 0\n"
|
||||
" -> [fd79:35e2:9963:0000:f816:3eff:fe9d:94df]:0D05 "
|
||||
"Masq 2 0 0")
|
||||
|
||||
CFG_FILE_TEMPLATE_v4 = (
|
||||
"# Configuration for Listener %(listener_id)s\n\n"
|
||||
"net_namespace %(ns_name)s\n\n"
|
||||
"virtual_server 10.0.0.37 7777 {\n"
|
||||
" lb_algo rr\n"
|
||||
" lb_kind NAT\n"
|
||||
" protocol udp\n\n\n"
|
||||
" # Configuration for Pool %(pool_id)s\n"
|
||||
" # Configuration for Member %(member_id1)s\n"
|
||||
" real_server 10.0.0.25 2222 {\n"
|
||||
" weight 3\n"
|
||||
" inhibit_on_failure\n"
|
||||
" persistence_timeout 5\n"
|
||||
" persistence_granularity 255.0.0.0\n\n"
|
||||
" }\n\n"
|
||||
" # Configuration for Member %(member_id2)s\n"
|
||||
" real_server 10.0.0.35 3333 {\n"
|
||||
" weight 2\n"
|
||||
" inhibit_on_failure\n"
|
||||
" persistence_timeout 5\n"
|
||||
" persistence_granularity 255.0.0.0\n\n"
|
||||
" }\n\n"
|
||||
"}")
|
||||
|
||||
CFG_FILE_TEMPLATE_v6 = (
|
||||
"# Configuration for Listener %(listener_id)s\n\n"
|
||||
"net_namespace %(ns_name)s\n\n"
|
||||
"virtual_server fd79:35e2:9963:0:f816:3eff:fe6d:7a2a 7777 {\n"
|
||||
" lb_algo rr\n"
|
||||
" lb_kind NAT\n"
|
||||
" protocol udp\n\n\n"
|
||||
" # Configuration for Pool %(pool_id)s\n"
|
||||
" # Configuration for Member %(member_id1)s\n"
|
||||
" real_server fd79:35e2:9963:0:f816:3eff:feca:b7bf 2222 {\n"
|
||||
" weight 3\n"
|
||||
" inhibit_on_failure\n"
|
||||
" }\n\n"
|
||||
" # Configuration for Member %(member_id2)s\n"
|
||||
" real_server fd79:35e2:9963:0:f816:3eff:fe9d:94df 3333 {\n"
|
||||
" weight 2\n"
|
||||
" inhibit_on_failure\n"
|
||||
" }\n\n"
|
||||
"}")
|
||||
|
||||
IPVSADM_OUTPUT_TEMPLATE = (
|
||||
"IP Virtual Server version 1.2.1 (size=4096)\n"
|
||||
"Prot LocalAddress:Port Scheduler Flags\n"
|
||||
" -> RemoteAddress:Port Forward Weight ActiveConn InActConn\n"
|
||||
"UDP %(listener_ipport)s rr\n"
|
||||
" -> %(member1_ipport)s Masq 3 0 0\n"
|
||||
" -> %(member2_ipport)s Masq 2 0 0")
|
||||
|
||||
IPVSADM_STATS_OUTPUT_TEMPLATE = (
|
||||
"IP Virtual Server version 1.2.1 (size=4096)\n"
|
||||
"Prot LocalAddress:Port Conns InPkts OutPkts "
|
||||
"InBytes OutBytes\n"
|
||||
" -> RemoteAddress:Port\n"
|
||||
"UDP %(listener_ipport)s 5 4264 5"
|
||||
" 6387472 7490\n"
|
||||
" -> %(member1_ipport)s 2 1706 2"
|
||||
" 2555588 2996\n"
|
||||
" -> %(member2_ipport)s 3 2558 3"
|
||||
" 3831884 4494")
|
||||
|
||||
|
||||
class LvsQueryTestCase(base.TestCase):
|
||||
def setUp(self):
|
||||
super(LvsQueryTestCase, self).setUp()
|
||||
self.listener_id_v4 = uuidutils.generate_uuid()
|
||||
self.pool_id_v4 = uuidutils.generate_uuid()
|
||||
self.member_id1_v4 = uuidutils.generate_uuid()
|
||||
self.member_id2_v4 = uuidutils.generate_uuid()
|
||||
self.listener_id_v6 = uuidutils.generate_uuid()
|
||||
self.pool_id_v6 = uuidutils.generate_uuid()
|
||||
self.member_id1_v6 = uuidutils.generate_uuid()
|
||||
self.member_id2_v6 = uuidutils.generate_uuid()
|
||||
cfg_content_v4 = CFG_FILE_TEMPLATE_v4 % {
|
||||
'listener_id': self.listener_id_v4,
|
||||
'ns_name': constants.AMPHORA_NAMESPACE,
|
||||
'pool_id': self.pool_id_v4,
|
||||
'member_id1': self.member_id1_v4,
|
||||
'member_id2': self.member_id2_v4
|
||||
}
|
||||
cfg_content_v6 = CFG_FILE_TEMPLATE_v6 % {
|
||||
'listener_id': self.listener_id_v6,
|
||||
'ns_name': constants.AMPHORA_NAMESPACE,
|
||||
'pool_id': self.pool_id_v6,
|
||||
'member_id1': self.member_id1_v6,
|
||||
'member_id2': self.member_id2_v6
|
||||
}
|
||||
self.useFixture(test_utils.OpenFixture(
|
||||
util.keepalived_lvs_cfg_path(self.listener_id_v4), cfg_content_v4))
|
||||
self.useFixture(test_utils.OpenFixture(
|
||||
util.keepalived_lvs_cfg_path(self.listener_id_v6), cfg_content_v6))
|
||||
|
||||
@mock.patch('subprocess.check_output')
|
||||
def test_get_listener_realserver_mapping(self, mock_check_output):
|
||||
# Ipv4 resolver
|
||||
input_listener_ip_port = '10.0.0.37:7777'
|
||||
target_ns = constants.AMPHORA_NAMESPACE
|
||||
mock_check_output.return_value = KERNAL_FILE_SAMPLE_V4
|
||||
result = lvs_query.get_listener_realserver_mapping(
|
||||
target_ns, input_listener_ip_port)
|
||||
expected = {'10.0.0.25:2222': {'status': 'UP',
|
||||
'Forward': 'Masq',
|
||||
'Weight': '3',
|
||||
'ActiveConn': '0',
|
||||
'InActConn': '0'},
|
||||
'10.0.0.35:3333': {'status': 'UP',
|
||||
'Forward': 'Masq',
|
||||
'Weight': '2',
|
||||
'ActiveConn': '0',
|
||||
'InActConn': '0'}}
|
||||
self.assertEqual((True, expected), result)
|
||||
|
||||
# Ipv6 resolver
|
||||
input_listener_ip_port = '[fd79:35e2:9963:0:f816:3eff:fe6d:7a2a]:7777'
|
||||
mock_check_output.return_value = KERNAL_FILE_SAMPLE_V6
|
||||
result = lvs_query.get_listener_realserver_mapping(
|
||||
target_ns, input_listener_ip_port)
|
||||
expected = {'[fd79:35e2:9963:0:f816:3eff:feca:b7bf]:2222':
|
||||
{'status': constants.UP,
|
||||
'Forward': 'Masq',
|
||||
'Weight': '3',
|
||||
'ActiveConn': '0',
|
||||
'InActConn': '0'},
|
||||
'[fd79:35e2:9963:0:f816:3eff:fe9d:94df]:3333':
|
||||
{'status': constants.UP,
|
||||
'Forward': 'Masq',
|
||||
'Weight': '2',
|
||||
'ActiveConn': '0',
|
||||
'InActConn': '0'}}
|
||||
self.assertEqual((True, expected), result)
|
||||
|
||||
# negetive cases
|
||||
mock_check_output.return_value = KERNAL_FILE_SAMPLE_V4
|
||||
for listener_ip_port in ['10.0.0.37:7776', '10.0.0.31:7777']:
|
||||
result = lvs_query.get_listener_realserver_mapping(
|
||||
target_ns, listener_ip_port)
|
||||
self.assertEqual((False, {}), result)
|
||||
|
||||
mock_check_output.return_value = KERNAL_FILE_SAMPLE_V6
|
||||
for listener_ip_port in [
|
||||
'[fd79:35e2:9963:0:f816:3eff:fe6d:7a2a]:7776',
|
||||
'[fd79:35e2:9973:0:f816:3eff:fe6d:7a2a]:7777']:
|
||||
result = lvs_query.get_listener_realserver_mapping(
|
||||
target_ns, listener_ip_port)
|
||||
self.assertEqual((False, {}), result)
|
||||
|
||||
def test_get_udp_listener_resource_ipports_nsname(self):
|
||||
# ipv4
|
||||
res = lvs_query.get_udp_listener_resource_ipports_nsname(
|
||||
self.listener_id_v4)
|
||||
expected = {'Listener': {'id': self.listener_id_v4,
|
||||
'ipport': '10.0.0.37:7777'},
|
||||
'Pool': {'id': self.pool_id_v4},
|
||||
'Members': [{'id': self.member_id1_v4,
|
||||
'ipport': '10.0.0.25:2222'},
|
||||
{'id': self.member_id2_v4,
|
||||
'ipport': '10.0.0.35:3333'}]}
|
||||
self.assertEqual((expected, constants.AMPHORA_NAMESPACE), res)
|
||||
|
||||
# ipv6
|
||||
res = lvs_query.get_udp_listener_resource_ipports_nsname(
|
||||
self.listener_id_v6)
|
||||
expected = {'Listener': {
|
||||
'id': self.listener_id_v6,
|
||||
'ipport': '[fd79:35e2:9963:0:f816:3eff:fe6d:7a2a]:7777'},
|
||||
'Pool': {'id': self.pool_id_v6},
|
||||
'Members': [
|
||||
{'id': self.member_id1_v6,
|
||||
'ipport': '[fd79:35e2:9963:0:f816:3eff:feca:b7bf]:2222'},
|
||||
{'id': self.member_id2_v6,
|
||||
'ipport': '[fd79:35e2:9963:0:f816:3eff:fe9d:94df]:3333'}]}
|
||||
self.assertEqual((expected, constants.AMPHORA_NAMESPACE), res)
|
||||
|
||||
@mock.patch('subprocess.check_output')
|
||||
def test_get_udp_listener_pool_status(self, mock_check_output):
|
||||
# test with ipv4 and ipv6
|
||||
mock_check_output.return_value = KERNAL_FILE_SAMPLE_V4
|
||||
res = lvs_query.get_udp_listener_pool_status(self.listener_id_v4)
|
||||
expected = {
|
||||
'lvs':
|
||||
{'uuid': self.pool_id_v4,
|
||||
'status': constants.UP,
|
||||
'members': {self.member_id1_v4: constants.UP,
|
||||
self.member_id2_v4: constants.UP}}}
|
||||
self.assertEqual(expected, res)
|
||||
|
||||
mock_check_output.return_value = KERNAL_FILE_SAMPLE_V6
|
||||
res = lvs_query.get_udp_listener_pool_status(self.listener_id_v6)
|
||||
expected = {
|
||||
'lvs':
|
||||
{'uuid': self.pool_id_v6,
|
||||
'status': constants.UP,
|
||||
'members': {self.member_id1_v6: constants.UP,
|
||||
self.member_id2_v6: constants.UP}}}
|
||||
self.assertEqual(expected, res)
|
||||
|
||||
@mock.patch('octavia.amphorae.backends.utils.keepalivedlvs_query.'
|
||||
'get_udp_listener_resource_ipports_nsname')
|
||||
def test_get_udp_listener_pool_status_when_no_pool(
|
||||
self, mock_get_resource_ipports):
|
||||
# Just test with ipv4, ipv6 tests is same.
|
||||
# the returned resource_ipport_mapping doesn't contains the 'Pool'
|
||||
# resource, that means the listener doesn't have a pool resource, it
|
||||
# isn't usable at this moment, then the pool status will
|
||||
# return nothing.
|
||||
mock_get_resource_ipports.return_value = (
|
||||
{
|
||||
'Listener': {
|
||||
'id': self.listener_id_v4,
|
||||
'ipport': '10.0.0.37:7777'}},
|
||||
constants.AMPHORA_NAMESPACE)
|
||||
res = lvs_query.get_udp_listener_pool_status(self.listener_id_v4)
|
||||
self.assertEqual({}, res)
|
||||
|
||||
@mock.patch('octavia.amphorae.backends.utils.keepalivedlvs_query.'
|
||||
'get_udp_listener_resource_ipports_nsname')
|
||||
def test_get_udp_listener_pool_status_when_no_members(
|
||||
self, mock_get_resource_ipports):
|
||||
# Just test with ipv4, ipv6 tests is same.
|
||||
# the returned resource_ipport_mapping doesn't contains the 'Members'
|
||||
# resources, that means the pool of listener doesn't have a enabled
|
||||
# pool resource, so the pool is not usable, then the pool status will
|
||||
# return DOWN.
|
||||
mock_get_resource_ipports.return_value = (
|
||||
{
|
||||
'Listener': {'id': self.listener_id_v4,
|
||||
'ipport': '10.0.0.37:7777'},
|
||||
'Pool': {'id': self.pool_id_v4}},
|
||||
constants.AMPHORA_NAMESPACE)
|
||||
res = lvs_query.get_udp_listener_pool_status(self.listener_id_v4)
|
||||
expected = {'lvs': {
|
||||
'uuid': self.pool_id_v4,
|
||||
'status': constants.DOWN,
|
||||
'members': {}
|
||||
}}
|
||||
self.assertEqual(expected, res)
|
||||
|
||||
@mock.patch('octavia.amphorae.backends.utils.keepalivedlvs_query.'
|
||||
'get_listener_realserver_mapping')
|
||||
def test_get_udp_listener_pool_status_when_not_get_realserver_result(
|
||||
self, mock_get_mapping):
|
||||
# This will hit if the kernel lvs file (/proc/net/ip_vs)
|
||||
# lose its content. So at this moment, eventhough we configure the
|
||||
# pool and member into udp keepalived config file, we have to set
|
||||
# ths status of pool and its members to DOWN.
|
||||
mock_get_mapping.return_value = (False, {})
|
||||
res = lvs_query.get_udp_listener_pool_status(self.listener_id_v4)
|
||||
expected = {
|
||||
'lvs':
|
||||
{'uuid': self.pool_id_v4,
|
||||
'status': constants.DOWN,
|
||||
'members': {self.member_id1_v4: constants.DOWN,
|
||||
self.member_id2_v4: constants.DOWN}}}
|
||||
self.assertEqual(expected, res)
|
||||
|
||||
@mock.patch('subprocess.check_output')
|
||||
def test_get_ipvsadm_info(self, mock_check_output):
|
||||
for ip_list in [["10.0.0.37:7777", "10.0.0.25:2222", "10.0.0.35:3333"],
|
||||
["[fd79:35e2:9963:0:f816:3eff:fe6d:7a2a]:7777",
|
||||
"[fd79:35e2:9963:0:f816:3eff:feca:b7bf]:2222",
|
||||
"[fd79:35e2:9963:0:f816:3eff:fe9d:94df]:3333"]]:
|
||||
mock_check_output.return_value = IPVSADM_OUTPUT_TEMPLATE % {
|
||||
"listener_ipport": ip_list[0],
|
||||
"member1_ipport": ip_list[1],
|
||||
"member2_ipport": ip_list[2]}
|
||||
res = lvs_query.get_ipvsadm_info(constants.AMPHORA_NAMESPACE)
|
||||
# This expected result can referece on IPVSADM_OUTPUT_TEMPLATE,
|
||||
# that means the function can get every element of the virtual
|
||||
# server and the real servers.
|
||||
expected = {
|
||||
ip_list[0]: {
|
||||
'Listener': [('Prot', 'UDP'),
|
||||
('LocalAddress:Port', ip_list[0]),
|
||||
('Scheduler', 'rr')],
|
||||
'Members': [[('RemoteAddress:Port', ip_list[1]),
|
||||
('Forward', 'Masq'), ('Weight', '3'),
|
||||
('ActiveConn', '0'), ('InActConn', '0')],
|
||||
[('RemoteAddress:Port', ip_list[2]),
|
||||
('Forward', 'Masq'), ('Weight', '2'),
|
||||
('ActiveConn', '0'), ('InActConn', '0')]]}}
|
||||
self.assertEqual(expected, res)
|
||||
|
||||
# ipvsadm stats
|
||||
mock_check_output.return_value = IPVSADM_STATS_OUTPUT_TEMPLATE % {
|
||||
"listener_ipport": ip_list[0],
|
||||
"member1_ipport": ip_list[1],
|
||||
"member2_ipport": ip_list[2]}
|
||||
res = lvs_query.get_ipvsadm_info(constants.AMPHORA_NAMESPACE,
|
||||
is_stats_cmd=True)
|
||||
expected = {
|
||||
ip_list[0]:
|
||||
{'Listener': [('Prot', 'UDP'),
|
||||
('LocalAddress:Port', ip_list[0]),
|
||||
('Conns', '5'),
|
||||
('InPkts', '4264'),
|
||||
('OutPkts', '5'),
|
||||
('InBytes', '6387472'),
|
||||
('OutBytes', '7490')],
|
||||
'Members': [[('RemoteAddress:Port', ip_list[1]),
|
||||
('Conns', '2'),
|
||||
('InPkts', '1706'),
|
||||
('OutPkts', '2'),
|
||||
('InBytes', '2555588'),
|
||||
('OutBytes', '2996')],
|
||||
[('RemoteAddress:Port', ip_list[2]),
|
||||
('Conns', '3'),
|
||||
('InPkts', '2558'),
|
||||
('OutPkts', '3'),
|
||||
('InBytes', '3831884'),
|
||||
('OutBytes', '4494')]]}}
|
||||
self.assertEqual(expected, res)
|
||||
|
||||
@mock.patch('subprocess.check_output')
|
||||
@mock.patch("octavia.amphorae.backends.agent.api_server.util."
|
||||
"is_udp_listener_running", return_value=True)
|
||||
@mock.patch("octavia.amphorae.backends.agent.api_server.util."
|
||||
"get_udp_listeners")
|
||||
def test_get_udp_listeners_stats(
|
||||
self, mock_get_listener, mock_is_running, mock_check_output):
|
||||
# The ipv6 test is same with ipv4, so just test ipv4 here
|
||||
mock_get_listener.return_value = [self.listener_id_v4]
|
||||
output_list = list()
|
||||
output_list.append(IPVSADM_OUTPUT_TEMPLATE % {
|
||||
"listener_ipport": "10.0.0.37:7777",
|
||||
"member1_ipport": "10.0.0.25:2222",
|
||||
"member2_ipport": "10.0.0.35:3333"})
|
||||
output_list.append(IPVSADM_STATS_OUTPUT_TEMPLATE % {
|
||||
"listener_ipport": "10.0.0.37:7777",
|
||||
"member1_ipport": "10.0.0.25:2222",
|
||||
"member2_ipport": "10.0.0.35:3333"})
|
||||
mock_check_output.side_effect = output_list
|
||||
res = lvs_query.get_udp_listeners_stats()
|
||||
# We can check the expected result referece the stats sample,
|
||||
# that means this func can compute the stats info of single listener.
|
||||
expected = {self.listener_id_v4: {
|
||||
'status': constants.OPEN,
|
||||
'stats': {'bin': 6387472, 'stot': 5, 'bout': 7490,
|
||||
'ereq': 0, 'scur': 0}}}
|
||||
self.assertEqual(expected, res)
|
||||
|
||||
# if no udp listener need to be collected.
|
||||
# Then this function will return nothing.
|
||||
mock_is_running.return_value = False
|
||||
res = lvs_query.get_udp_listeners_stats()
|
||||
self.assertIsNone(res)
|
@ -41,6 +41,9 @@ FAKE_UUID_1 = uuidutils.generate_uuid()
|
||||
FAKE_VRRP_IP = '10.1.0.1'
|
||||
FAKE_MAC_ADDRESS = '123'
|
||||
FAKE_MTU = 1450
|
||||
FAKE_MEMBER_IP_PORT_NAME_1 = "10.0.0.10:1003"
|
||||
FAKE_MEMBER_IP_PORT_NAME_2 = "10.0.0.11:1004"
|
||||
FAKE_PROTOCOL = 'test-protocol'
|
||||
|
||||
|
||||
class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
|
||||
@ -61,9 +64,16 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
|
||||
self.driver.cert_parser = mock.MagicMock()
|
||||
self.driver.client = mock.MagicMock()
|
||||
self.driver.jinja = mock.MagicMock()
|
||||
self.driver.udp_jinja = mock.MagicMock()
|
||||
|
||||
# Build sample Listener and VIP configs
|
||||
self.sl = sample_configs.sample_listener_tuple(tls=True, sni=True)
|
||||
self.sl_udp = sample_configs.sample_listener_tuple(
|
||||
proto=constants.PROTOCOL_UDP,
|
||||
persistence_type=constants.SESSION_PERSISTENCE_SOURCE_IP,
|
||||
persistence_timeout=33,
|
||||
persistence_granularity='255.255.0.0',
|
||||
monitor_proto=constants.HEALTH_MONITOR_UDP_CONNECT)
|
||||
self.amp = self.sl.load_balancer.amphorae[0]
|
||||
self.sv = sample_configs.sample_vip_tuple()
|
||||
self.lb = self.sl.load_balancer
|
||||
@ -192,7 +202,21 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
|
||||
self.amp, self.sl.id, 'fake_config')
|
||||
# start should be called once
|
||||
self.driver.client.reload_listener.assert_called_once_with(
|
||||
self.amp, self.sl.id)
|
||||
self.amp, self.sl.id, self.sl.protocol)
|
||||
|
||||
def test_udp_update(self):
|
||||
self.driver.udp_jinja.build_config.side_effect = ['fake_udp_config']
|
||||
|
||||
# Execute driver method
|
||||
self.driver.update(self.sl_udp, self.sv)
|
||||
|
||||
# upload only one config file
|
||||
self.driver.client.upload_udp_config.assert_called_once_with(
|
||||
self.amp, self.sl_udp.id, 'fake_udp_config')
|
||||
|
||||
# start should be called once
|
||||
self.driver.client.reload_listener.assert_called_once_with(
|
||||
self.amp, self.sl_udp.id, self.sl_udp.protocol)
|
||||
|
||||
def test_upload_cert_amp(self):
|
||||
self.driver.upload_cert_amp(self.amp, six.b('test'))
|
||||
@ -200,10 +224,18 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
|
||||
self.amp, six.b('test'))
|
||||
|
||||
def test_stop(self):
|
||||
self.driver.client.stop_listener.__name__ = 'stop_listener'
|
||||
# Execute driver method
|
||||
self.driver.stop(self.sl, self.sv)
|
||||
self.driver.client.stop_listener.assert_called_once_with(
|
||||
self.amp, self.sl.id)
|
||||
self.amp, self.sl.id, self.sl.protocol)
|
||||
|
||||
def test_udp_stop(self):
|
||||
self.driver.client.stop_listener.__name__ = 'stop_listener'
|
||||
# Execute driver method - UDP case
|
||||
self.driver.stop(self.sl_udp, self.sv)
|
||||
self.driver.client.stop_listener.assert_called_once_with(
|
||||
self.amp, self.sl_udp.id, self.sl_udp.protocol)
|
||||
|
||||
def test_start(self):
|
||||
amp1 = mock.MagicMock()
|
||||
@ -212,28 +244,46 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
|
||||
listener = mock.MagicMock()
|
||||
listener.id = uuidutils.generate_uuid()
|
||||
listener.load_balancer.amphorae = [amp1, amp2]
|
||||
listener.protocol = 'listener_protocol'
|
||||
self.driver.client.start_listener.__name__ = 'start_listener'
|
||||
# Execute driver method
|
||||
self.driver.start(listener, self.sv)
|
||||
self.driver.client.start_listener.assert_called_once_with(
|
||||
amp1, listener.id)
|
||||
amp1, listener.id, 'listener_protocol')
|
||||
|
||||
def test_start_with_amphora(self):
|
||||
# Execute driver method
|
||||
amp = mock.MagicMock()
|
||||
self.driver.client.start_listener.__name__ = 'start_listener'
|
||||
self.driver.start(self.sl, self.sv, self.amp)
|
||||
self.driver.client.start_listener.assert_called_once_with(
|
||||
self.amp, self.sl.id)
|
||||
self.amp, self.sl.id, self.sl.protocol)
|
||||
|
||||
self.driver.client.start_listener.reset_mock()
|
||||
amp.status = constants.DELETED
|
||||
self.driver.start(self.sl, self.sv, amp)
|
||||
self.driver.client.start_listener.assert_not_called()
|
||||
|
||||
def test_udp_start(self):
|
||||
self.driver.client.start_listener.__name__ = 'start_listener'
|
||||
# Execute driver method
|
||||
self.driver.start(self.sl_udp, self.sv)
|
||||
self.driver.client.start_listener.assert_called_once_with(
|
||||
self.amp, self.sl_udp.id, self.sl_udp.protocol)
|
||||
|
||||
def test_delete(self):
|
||||
self.driver.client.delete_listener.__name__ = 'delete_listener'
|
||||
# Execute driver method
|
||||
self.driver.delete(self.sl, self.sv)
|
||||
self.driver.client.delete_listener.assert_called_once_with(
|
||||
self.amp, self.sl.id)
|
||||
self.amp, self.sl.id, self.sl.protocol)
|
||||
|
||||
def test_udp_delete(self):
|
||||
self.driver.client.delete_listener.__name__ = 'delete_listener'
|
||||
# Execute driver method
|
||||
self.driver.delete(self.sl_udp, self.sv)
|
||||
self.driver.client.delete_listener.assert_called_once_with(
|
||||
self.amp, self.sl_udp.id, self.sl_udp.protocol)
|
||||
|
||||
def test_get_info(self):
|
||||
self.driver.client.get_info.return_value = 'FAKE_INFO'
|
||||
@ -482,9 +532,31 @@ class TestAmphoraAPIClientTest(base.TestCase):
|
||||
m.get("{base}/listeners/{listener_id}".format(
|
||||
base=self.base_url, listener_id=FAKE_UUID_1),
|
||||
json=listener)
|
||||
status = self.driver.get_listener_status(self.amp, FAKE_UUID_1)
|
||||
status = self.driver.get_listener_status(self.amp, FAKE_UUID_1,
|
||||
protocol='TCP')
|
||||
self.assertEqual(listener, status)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_get_udp_listener_status(self, m):
|
||||
udp_listener = {"status": "ACTIVE", "type": "lvs",
|
||||
"uuid": FAKE_UUID_1,
|
||||
"pools": [{
|
||||
"UDP-Listener-%s-pool" % FAKE_UUID_1:
|
||||
{
|
||||
"status": "UP",
|
||||
"members": [
|
||||
{FAKE_MEMBER_IP_PORT_NAME_1: "DOWN"},
|
||||
{FAKE_MEMBER_IP_PORT_NAME_2: "ACTIVE"},
|
||||
]
|
||||
}
|
||||
}]}
|
||||
m.get("{base}/listeners/{listener_id}".format(
|
||||
base=self.base_url, listener_id=FAKE_UUID_1),
|
||||
json=udp_listener)
|
||||
status = self.driver.get_listener_status(self.amp, FAKE_UUID_1,
|
||||
protocol='UDP')
|
||||
self.assertEqual(udp_listener, status)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_get_listener_status_unauthorized(self, m):
|
||||
m.get("{base}/listeners/{listener_id}".format(
|
||||
@ -526,7 +598,7 @@ class TestAmphoraAPIClientTest(base.TestCase):
|
||||
def test_start_listener(self, m):
|
||||
m.put("{base}/listeners/{listener_id}/start".format(
|
||||
base=self.base_url, listener_id=FAKE_UUID_1))
|
||||
self.driver.start_listener(self.amp, FAKE_UUID_1)
|
||||
self.driver.start_listener(self.amp, FAKE_UUID_1, FAKE_PROTOCOL)
|
||||
self.assertTrue(m.called)
|
||||
|
||||
@requests_mock.mock()
|
||||
@ -536,7 +608,7 @@ class TestAmphoraAPIClientTest(base.TestCase):
|
||||
status_code=404,
|
||||
headers={'content-type': 'application/json'})
|
||||
self.assertRaises(exc.NotFound, self.driver.start_listener,
|
||||
self.amp, FAKE_UUID_1)
|
||||
self.amp, FAKE_UUID_1, FAKE_PROTOCOL)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_start_listener_unauthorized(self, m):
|
||||
@ -544,7 +616,7 @@ class TestAmphoraAPIClientTest(base.TestCase):
|
||||
base=self.base_url, listener_id=FAKE_UUID_1),
|
||||
status_code=401)
|
||||
self.assertRaises(exc.Unauthorized, self.driver.start_listener,
|
||||
self.amp, FAKE_UUID_1)
|
||||
self.amp, FAKE_UUID_1, FAKE_PROTOCOL)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_start_listener_server_error(self, m):
|
||||
@ -552,7 +624,7 @@ class TestAmphoraAPIClientTest(base.TestCase):
|
||||
base=self.base_url, listener_id=FAKE_UUID_1),
|
||||
status_code=500)
|
||||
self.assertRaises(exc.InternalServerError, self.driver.start_listener,
|
||||
self.amp, FAKE_UUID_1)
|
||||
self.amp, FAKE_UUID_1, FAKE_PROTOCOL)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_start_listener_service_unavailable(self, m):
|
||||
@ -560,13 +632,13 @@ class TestAmphoraAPIClientTest(base.TestCase):
|
||||
base=self.base_url, listener_id=FAKE_UUID_1),
|
||||
status_code=503)
|
||||
self.assertRaises(exc.ServiceUnavailable, self.driver.start_listener,
|
||||
self.amp, FAKE_UUID_1)
|
||||
self.amp, FAKE_UUID_1, FAKE_PROTOCOL)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_stop_listener(self, m):
|
||||
m.put("{base}/listeners/{listener_id}/stop".format(
|
||||
base=self.base_url, listener_id=FAKE_UUID_1))
|
||||
self.driver.stop_listener(self.amp, FAKE_UUID_1)
|
||||
self.driver.stop_listener(self.amp, FAKE_UUID_1, FAKE_PROTOCOL)
|
||||
self.assertTrue(m.called)
|
||||
|
||||
@requests_mock.mock()
|
||||
@ -576,7 +648,7 @@ class TestAmphoraAPIClientTest(base.TestCase):
|
||||
status_code=404,
|
||||
headers={'content-type': 'application/json'})
|
||||
self.assertRaises(exc.NotFound, self.driver.stop_listener,
|
||||
self.amp, FAKE_UUID_1)
|
||||
self.amp, FAKE_UUID_1, FAKE_PROTOCOL)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_stop_listener_unauthorized(self, m):
|
||||
@ -584,7 +656,7 @@ class TestAmphoraAPIClientTest(base.TestCase):
|
||||
base=self.base_url, listener_id=FAKE_UUID_1),
|
||||
status_code=401)
|
||||
self.assertRaises(exc.Unauthorized, self.driver.stop_listener,
|
||||
self.amp, FAKE_UUID_1)
|
||||
self.amp, FAKE_UUID_1, FAKE_PROTOCOL)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_stop_listener_server_error(self, m):
|
||||
@ -592,7 +664,7 @@ class TestAmphoraAPIClientTest(base.TestCase):
|
||||
base=self.base_url, listener_id=FAKE_UUID_1),
|
||||
status_code=500)
|
||||
self.assertRaises(exc.InternalServerError, self.driver.stop_listener,
|
||||
self.amp, FAKE_UUID_1)
|
||||
self.amp, FAKE_UUID_1, FAKE_PROTOCOL)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_stop_listener_service_unavailable(self, m):
|
||||
@ -600,13 +672,13 @@ class TestAmphoraAPIClientTest(base.TestCase):
|
||||
base=self.base_url, listener_id=FAKE_UUID_1),
|
||||
status_code=503)
|
||||
self.assertRaises(exc.ServiceUnavailable, self.driver.stop_listener,
|
||||
self.amp, FAKE_UUID_1)
|
||||
self.amp, FAKE_UUID_1, FAKE_PROTOCOL)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_delete_listener(self, m):
|
||||
m.delete("{base}/listeners/{listener_id}".format(
|
||||
base=self.base_url, listener_id=FAKE_UUID_1), json={})
|
||||
self.driver.delete_listener(self.amp, FAKE_UUID_1)
|
||||
self.driver.delete_listener(self.amp, FAKE_UUID_1, FAKE_PROTOCOL)
|
||||
self.assertTrue(m.called)
|
||||
|
||||
@requests_mock.mock()
|
||||
@ -615,7 +687,7 @@ class TestAmphoraAPIClientTest(base.TestCase):
|
||||
base=self.base_url, listener_id=FAKE_UUID_1),
|
||||
status_code=404,
|
||||
headers={'content-type': 'application/json'})
|
||||
self.driver.delete_listener(self.amp, FAKE_UUID_1)
|
||||
self.driver.delete_listener(self.amp, FAKE_UUID_1, FAKE_PROTOCOL)
|
||||
self.assertTrue(m.called)
|
||||
|
||||
@requests_mock.mock()
|
||||
@ -624,7 +696,7 @@ class TestAmphoraAPIClientTest(base.TestCase):
|
||||
base=self.base_url, listener_id=FAKE_UUID_1),
|
||||
status_code=401)
|
||||
self.assertRaises(exc.Unauthorized, self.driver.delete_listener,
|
||||
self.amp, FAKE_UUID_1)
|
||||
self.amp, FAKE_UUID_1, FAKE_PROTOCOL)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_delete_listener_server_error(self, m):
|
||||
@ -632,7 +704,7 @@ class TestAmphoraAPIClientTest(base.TestCase):
|
||||
base=self.base_url, listener_id=FAKE_UUID_1),
|
||||
status_code=500)
|
||||
self.assertRaises(exc.InternalServerError, self.driver.delete_listener,
|
||||
self.amp, FAKE_UUID_1)
|
||||
self.amp, FAKE_UUID_1, FAKE_PROTOCOL)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_delete_listener_service_unavailable(self, m):
|
||||
@ -640,7 +712,7 @@ class TestAmphoraAPIClientTest(base.TestCase):
|
||||
base=self.base_url, listener_id=FAKE_UUID_1),
|
||||
status_code=503)
|
||||
self.assertRaises(exc.ServiceUnavailable, self.driver.delete_listener,
|
||||
self.amp, FAKE_UUID_1)
|
||||
self.amp, FAKE_UUID_1, FAKE_PROTOCOL)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_upload_cert_pem(self, m):
|
||||
@ -875,6 +947,68 @@ class TestAmphoraAPIClientTest(base.TestCase):
|
||||
self.assertRaises(exc.ServiceUnavailable, self.driver.upload_config,
|
||||
self.amp, FAKE_UUID_1, config)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_upload_udp_config(self, m):
|
||||
config = {"name": "fake_config"}
|
||||
m.put(
|
||||
"{base}/listeners/"
|
||||
"{amphora_id}/{listener_id}/udp_listener".format(
|
||||
amphora_id=self.amp.id, base=self.base_url,
|
||||
listener_id=FAKE_UUID_1),
|
||||
json=config)
|
||||
self.driver.upload_udp_config(self.amp, FAKE_UUID_1, config)
|
||||
self.assertTrue(m.called)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_upload_udp_invalid_config(self, m):
|
||||
config = '{"name": "bad_config"}'
|
||||
m.put(
|
||||
"{base}/listeners/"
|
||||
"{amphora_id}/{listener_id}/udp_listener".format(
|
||||
amphora_id=self.amp.id, base=self.base_url,
|
||||
listener_id=FAKE_UUID_1),
|
||||
status_code=400)
|
||||
self.assertRaises(exc.InvalidRequest, self.driver.upload_udp_config,
|
||||
self.amp, FAKE_UUID_1, config)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_upload_udp_config_unauthorized(self, m):
|
||||
config = '{"name": "bad_config"}'
|
||||
m.put(
|
||||
"{base}/listeners/"
|
||||
"{amphora_id}/{listener_id}/udp_listener".format(
|
||||
amphora_id=self.amp.id, base=self.base_url,
|
||||
listener_id=FAKE_UUID_1),
|
||||
status_code=401)
|
||||
self.assertRaises(exc.Unauthorized, self.driver.upload_udp_config,
|
||||
self.amp, FAKE_UUID_1, config)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_upload_udp_config_server_error(self, m):
|
||||
config = '{"name": "bad_config"}'
|
||||
m.put(
|
||||
"{base}/listeners/"
|
||||
"{amphora_id}/{listener_id}/udp_listener".format(
|
||||
amphora_id=self.amp.id, base=self.base_url,
|
||||
listener_id=FAKE_UUID_1),
|
||||
status_code=500)
|
||||
self.assertRaises(exc.InternalServerError,
|
||||
self.driver.upload_udp_config,
|
||||
self.amp, FAKE_UUID_1, config)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_upload_udp_config_service_unavailable(self, m):
|
||||
config = '{"name": "bad_config"}'
|
||||
m.put(
|
||||
"{base}/listeners/"
|
||||
"{amphora_id}/{listener_id}/udp_listener".format(
|
||||
amphora_id=self.amp.id, base=self.base_url,
|
||||
listener_id=FAKE_UUID_1),
|
||||
status_code=503)
|
||||
self.assertRaises(exc.ServiceUnavailable,
|
||||
self.driver.upload_udp_config,
|
||||
self.amp, FAKE_UUID_1, config)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_plug_vip(self, m):
|
||||
m.post("{base}/plug/vip/{vip}".format(
|
||||
|
@ -1079,6 +1079,57 @@ class TestUpdateHealthDb(base.TestCase):
|
||||
self.mock_session(), mock_lb.id,
|
||||
operating_status='ONLINE')
|
||||
|
||||
def test_update_health_forbid_to_stale_udp_listener_amphora(self):
|
||||
health = {
|
||||
"id": self.FAKE_UUID_1,
|
||||
"listeners": {},
|
||||
"recv_time": time.time()
|
||||
}
|
||||
|
||||
mock_lb = mock.Mock()
|
||||
mock_lb.id = self.FAKE_UUID_1
|
||||
mock_lb.pools = []
|
||||
mock_lb.listeners = []
|
||||
mock_lb.provisioning_status = constants.ACTIVE
|
||||
mock_lb.operating_status = 'blah'
|
||||
|
||||
# The default pool of udp listener1 has no enabled member
|
||||
mock_member1 = mock.Mock()
|
||||
mock_member1.id = 'member-id-1'
|
||||
mock_member1.enabled = False
|
||||
mock_pool1 = mock.Mock()
|
||||
mock_pool1.id = "pool-id-1"
|
||||
mock_pool1.members = [mock_member1]
|
||||
mock_listener1 = mock.Mock()
|
||||
mock_listener1.id = 'listener-id-1'
|
||||
mock_listener1.default_pool = mock_pool1
|
||||
mock_listener1.protocol = constants.PROTOCOL_UDP
|
||||
|
||||
# The default pool of udp listener2 has no member
|
||||
mock_pool2 = mock.Mock()
|
||||
mock_pool2.id = "pool-id-2"
|
||||
mock_pool2.members = []
|
||||
mock_listener2 = mock.Mock()
|
||||
mock_listener2.id = 'listener-id-2'
|
||||
mock_listener2.default_pool = mock_pool2
|
||||
mock_listener2.protocol = constants.PROTOCOL_UDP
|
||||
|
||||
# The udp listener3 has no default_pool
|
||||
mock_listener3 = mock.Mock()
|
||||
mock_listener3.id = 'listener-id-3'
|
||||
mock_listener3.default_pool = None
|
||||
mock_listener3.protocol = constants.PROTOCOL_UDP
|
||||
|
||||
mock_lb.listeners.extend([mock_listener1, mock_listener2,
|
||||
mock_listener3])
|
||||
mock_lb.pools.extend([mock_pool1, mock_pool2])
|
||||
|
||||
self.amphora_repo.get_lb_for_amphora.return_value = mock_lb
|
||||
self.hm.update_health(health, '192.0.2.1')
|
||||
self.assertTrue(self.amphora_repo.get_lb_for_amphora.called)
|
||||
self.assertTrue(self.loadbalancer_repo.update.called)
|
||||
self.assertTrue(self.amphora_health_repo.replace.called)
|
||||
|
||||
|
||||
class TestUpdateStatsDb(base.TestCase):
|
||||
|
||||
|
@ -639,8 +639,12 @@ class TestAllowedAddressPairsDriver(base.TestCase):
|
||||
server=t_constants.MOCK_COMPUTE_ID, port_id=port2.get('id'))
|
||||
|
||||
def test_update_vip(self):
|
||||
listeners = [data_models.Listener(protocol_port=80, peer_port=1024),
|
||||
data_models.Listener(protocol_port=443, peer_port=1025)]
|
||||
listeners = [data_models.Listener(protocol_port=80, peer_port=1024,
|
||||
protocol=constants.PROTOCOL_TCP),
|
||||
data_models.Listener(protocol_port=443, peer_port=1025,
|
||||
protocol=constants.PROTOCOL_TCP),
|
||||
data_models.Listener(protocol_port=50, peer_port=1026,
|
||||
protocol=constants.PROTOCOL_UDP)]
|
||||
vip = data_models.Vip(ip_address='10.0.0.2')
|
||||
lb = data_models.LoadBalancer(id='1', listeners=listeners, vip=vip)
|
||||
list_sec_grps = self.driver.neutron_client.list_security_groups
|
||||
@ -661,17 +665,27 @@ class TestAllowedAddressPairsDriver(base.TestCase):
|
||||
'security_group_rule': {
|
||||
'security_group_id': 'secgrp-1',
|
||||
'direction': 'ingress',
|
||||
'protocol': 'TCP',
|
||||
'protocol': 'tcp',
|
||||
'port_range_min': 1024,
|
||||
'port_range_max': 1024,
|
||||
'ethertype': 'IPv4'
|
||||
}
|
||||
}
|
||||
expected_create_rule_udp_peer = {
|
||||
'security_group_rule': {
|
||||
'security_group_id': 'secgrp-1',
|
||||
'direction': 'ingress',
|
||||
'protocol': 'tcp',
|
||||
'port_range_min': 1026,
|
||||
'port_range_max': 1026,
|
||||
'ethertype': 'IPv4'
|
||||
}
|
||||
}
|
||||
expected_create_rule_2 = {
|
||||
'security_group_rule': {
|
||||
'security_group_id': 'secgrp-1',
|
||||
'direction': 'ingress',
|
||||
'protocol': 'TCP',
|
||||
'protocol': 'tcp',
|
||||
'port_range_min': 1025,
|
||||
'port_range_max': 1025,
|
||||
'ethertype': 'IPv4'
|
||||
@ -681,20 +695,39 @@ class TestAllowedAddressPairsDriver(base.TestCase):
|
||||
'security_group_rule': {
|
||||
'security_group_id': 'secgrp-1',
|
||||
'direction': 'ingress',
|
||||
'protocol': 'TCP',
|
||||
'protocol': 'tcp',
|
||||
'port_range_min': 443,
|
||||
'port_range_max': 443,
|
||||
'ethertype': 'IPv4'
|
||||
}
|
||||
}
|
||||
expected_create_rule_udp = {
|
||||
'security_group_rule': {
|
||||
'security_group_id': 'secgrp-1',
|
||||
'direction': 'ingress',
|
||||
'protocol': 'udp',
|
||||
'port_range_min': 50,
|
||||
'port_range_max': 50,
|
||||
'ethertype': 'IPv4'
|
||||
}
|
||||
}
|
||||
|
||||
create_rule.assert_has_calls([mock.call(expected_create_rule_1),
|
||||
mock.call(expected_create_rule_udp_peer),
|
||||
mock.call(expected_create_rule_2),
|
||||
mock.call(expected_create_rule_3)])
|
||||
mock.call(expected_create_rule_3),
|
||||
mock.call(expected_create_rule_udp)],
|
||||
any_order=True)
|
||||
|
||||
def test_update_vip_when_listener_deleted(self):
|
||||
listeners = [data_models.Listener(protocol_port=80),
|
||||
listeners = [data_models.Listener(protocol_port=80,
|
||||
protocol=constants.PROTOCOL_TCP),
|
||||
data_models.Listener(
|
||||
protocol_port=443,
|
||||
protocol=constants.PROTOCOL_TCP,
|
||||
provisioning_status=constants.PENDING_DELETE),
|
||||
data_models.Listener(
|
||||
protocol_port=50, protocol=constants.PROTOCOL_UDP,
|
||||
provisioning_status=constants.PENDING_DELETE)]
|
||||
vip = data_models.Vip(ip_address='10.0.0.2')
|
||||
lb = data_models.LoadBalancer(id='1', listeners=listeners, vip=vip)
|
||||
@ -703,7 +736,8 @@ class TestAllowedAddressPairsDriver(base.TestCase):
|
||||
fake_rules = {
|
||||
'security_group_rules': [
|
||||
{'id': 'rule-80', 'port_range_max': 80, 'protocol': 'tcp'},
|
||||
{'id': 'rule-22', 'port_range_max': 443, 'protocol': 'tcp'}
|
||||
{'id': 'rule-22', 'port_range_max': 443, 'protocol': 'tcp'},
|
||||
{'id': 'rule-udp-50', 'port_range_max': 50, 'protocol': 'tcp'}
|
||||
]
|
||||
}
|
||||
list_rules = self.driver.neutron_client.list_security_group_rules
|
||||
@ -711,7 +745,8 @@ class TestAllowedAddressPairsDriver(base.TestCase):
|
||||
delete_rule = self.driver.neutron_client.delete_security_group_rule
|
||||
create_rule = self.driver.neutron_client.create_security_group_rule
|
||||
self.driver.update_vip(lb)
|
||||
delete_rule.assert_called_once_with('rule-22')
|
||||
delete_rule.assert_has_calls(
|
||||
[mock.call('rule-22'), mock.call('rule-udp-50')])
|
||||
self.assertTrue(create_rule.called)
|
||||
|
||||
def test_update_vip_when_no_listeners(self):
|
||||
|
@ -68,6 +68,8 @@ octavia.amphora.health_update_drivers =
|
||||
octavia.amphora.stats_update_drivers =
|
||||
stats_logger = octavia.controller.healthmanager.health_drivers.update_logging:StatsUpdateLogger
|
||||
stats_db = octavia.controller.healthmanager.health_drivers.update_db:UpdateStatsDb
|
||||
octavia.amphora.udp_api_server =
|
||||
keepalived_lvs = octavia.amphorae.backends.agent.api_server.keepalivedlvs:KeepalivedLvs
|
||||
octavia.controller.queues =
|
||||
noop_event_streamer = octavia.controller.queue.event_queue:EventStreamerNoop
|
||||
queue_event_streamer = octavia.controller.queue.event_queue:EventStreamerNeutron
|
||||
|
Loading…
Reference in New Issue
Block a user