Delete SSH amphora driver
The old SSH amphora driver is not being used by anyone anymore, nor is it being maintained. This patch removes it from the Octavia code tree. Closes-Bug: 1534218 Change-Id: I006f1c794e1ab0483886d06495ca6649f0afe479
This commit is contained in:
parent
27ca78a5de
commit
2a0a0944bf
@ -140,8 +140,7 @@
|
||||
# client_ca = /etc/octavia/certs/ca_01.pem
|
||||
|
||||
# Amphora driver options are amphora_noop_driver,
|
||||
# amphora_haproxy_rest_driver,
|
||||
# amphora_haproxy_ssh_driver
|
||||
# amphora_haproxy_rest_driver
|
||||
#
|
||||
# amphora_driver = amphora_noop_driver
|
||||
#
|
||||
|
@ -1,310 +0,0 @@
|
||||
# Copyright (c) 2015 Rackspace
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
import os
|
||||
import socket
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
from oslo_log import log as logging
|
||||
import paramiko
|
||||
import six
|
||||
from stevedore import driver as stevedore_driver
|
||||
|
||||
from octavia.amphorae.driver_exceptions import exceptions as exc
|
||||
from octavia.amphorae.drivers import driver_base as driver_base
|
||||
from octavia.amphorae.drivers.haproxy.jinja import jinja_cfg
|
||||
from octavia.common.config import cfg
|
||||
from octavia.common import constants
|
||||
from octavia.common.tls_utils import cert_parser
|
||||
from octavia.i18n import _LW
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
NEUTRON_VERSION = '2.0'
|
||||
VIP_ROUTE_TABLE = 'vip'
|
||||
|
||||
# ip and route commands
|
||||
CMD_DHCLIENT = "dhclient {0}"
|
||||
CMD_ADD_IP_ADDR = "ip addr add {0}/24 dev {1}"
|
||||
CMD_SHOW_IP_ADDR = "ip addr show {0}"
|
||||
CMD_GREP_LINK_BY_MAC = ("ip link | grep {mac_address} -m 1 -B 1 "
|
||||
"| awk 'NR==1{{print $2}}'")
|
||||
CMD_CREATE_VIP_ROUTE_TABLE = (
|
||||
"su -c 'echo \"1 {0}\" >> /etc/iproute2/rt_tables'"
|
||||
)
|
||||
CMD_ADD_ROUTE_TO_TABLE = "ip route add {0} dev {1} table {2}"
|
||||
CMD_ADD_DEFAULT_ROUTE_TO_TABLE = ("ip route add default via {0} "
|
||||
"dev {1} table {2}")
|
||||
CMD_ADD_RULE_FROM_NET_TO_TABLE = "ip rule add from {0} table {1}"
|
||||
CMD_ADD_RULE_TO_NET_TO_TABLE = "ip rule add to {0} table {1}"
|
||||
|
||||
|
||||
class HaproxyManager(driver_base.AmphoraLoadBalancerDriver):
|
||||
|
||||
amp_config = cfg.CONF.haproxy_amphora
|
||||
|
||||
def __init__(self):
|
||||
super(HaproxyManager, self).__init__()
|
||||
self.amphoraconfig = {}
|
||||
self.client = paramiko.SSHClient()
|
||||
self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
self.cert_manager = stevedore_driver.DriverManager(
|
||||
namespace='octavia.cert_manager',
|
||||
name=cfg.CONF.certificates.cert_manager,
|
||||
invoke_on_load=True,
|
||||
).driver
|
||||
self.jinja = jinja_cfg.JinjaTemplater(
|
||||
base_amp_path=self.amp_config.base_path,
|
||||
base_crt_dir=self.amp_config.base_cert_dir,
|
||||
haproxy_template=self.amp_config.haproxy_template)
|
||||
|
||||
def get_logger(self):
|
||||
return LOG
|
||||
|
||||
def update(self, listener, vip):
|
||||
LOG.debug("Amphora %s haproxy, updating listener %s, vip %s",
|
||||
self.__class__.__name__, listener.protocol_port,
|
||||
vip.ip_address)
|
||||
|
||||
# Set a path variable to hold where configurations will live
|
||||
conf_path = '{0}/{1}'.format(self.amp_config.base_path, listener.id)
|
||||
|
||||
# Process listener certificate info
|
||||
certs = self._process_tls_certificates(listener)
|
||||
|
||||
# Generate HaProxy configuration from listener object
|
||||
config = self.jinja.build_config(listener, certs['tls_cert'])
|
||||
|
||||
# Build a list of commands to send to the exec method
|
||||
commands = ['chmod 600 {0}/haproxy.cfg'.format(conf_path),
|
||||
'haproxy -f {0}/haproxy.cfg -p {0}/{1}.pid -sf '
|
||||
'$(cat {0}/{1}.pid)'.format(conf_path, listener.id)]
|
||||
|
||||
# Exec appropriate commands on all amphorae
|
||||
self._exec_on_amphorae(
|
||||
listener.load_balancer.amphorae, commands,
|
||||
make_dir=conf_path, data=[config],
|
||||
upload_dir='{0}/haproxy.cfg'.format(conf_path))
|
||||
|
||||
def stop(self, listener, vip):
|
||||
LOG.debug("Amphora %s haproxy, disabling listener %s, vip %s",
|
||||
self.__class__.__name__,
|
||||
listener.protocol_port, vip.ip_address)
|
||||
|
||||
# Exec appropriate commands on all amphorae
|
||||
self._exec_on_amphorae(listener.load_balancer.amphorae,
|
||||
['kill -9 $(cat {0}/{1}/{1}.pid)'.format(
|
||||
self.amp_config.base_path, listener.id)])
|
||||
|
||||
def delete(self, listener, vip):
|
||||
LOG.debug("Amphora %s haproxy, deleting listener %s, vip %s",
|
||||
self.__class__.__name__,
|
||||
listener.protocol_port, vip.ip_address)
|
||||
|
||||
# Define the two operations that need to happen per amphora
|
||||
stop = 'kill -9 $(cat {0}/{1}/{1}.pid)'.format(
|
||||
self.amp_config.base_path, listener.id)
|
||||
delete = 'rm -rf {0}/{1}'.format(self.amp_config.base_path,
|
||||
listener.id)
|
||||
|
||||
# Exec appropriate commands on all amphorae
|
||||
self._exec_on_amphorae(listener.load_balancer.amphorae, [stop, delete])
|
||||
|
||||
def start(self, listener, vip):
|
||||
LOG.debug("Amphora %s haproxy, enabling listener %s, vip %s",
|
||||
self.__class__.__name__,
|
||||
listener.protocol_port, vip.ip_address)
|
||||
|
||||
# Define commands to execute on the amphorae
|
||||
commands = [
|
||||
'haproxy -f {0}/{1}/haproxy.cfg -p {0}/{1}/{1}.pid'.format(
|
||||
self.amp_config.base_path, listener.id)]
|
||||
|
||||
# Exec appropriate commands on all amphorae
|
||||
self._exec_on_amphorae(listener.load_balancer.amphorae, commands)
|
||||
|
||||
def get_info(self, amphora):
|
||||
LOG.debug("Amphora %s haproxy, info amphora %s",
|
||||
self.__class__.__name__, amphora.id)
|
||||
# info = self.amphora_client.get_info()
|
||||
# self.amphoraconfig[amphora.id] = (amphora.id, info)
|
||||
|
||||
def get_diagnostics(self, amphora):
|
||||
LOG.debug("Amphora %s haproxy, get diagnostics amphora %s",
|
||||
self.__class__.__name__, amphora.id)
|
||||
self.amphoraconfig[amphora.id] = (amphora.id, 'get_diagnostics')
|
||||
|
||||
def finalize_amphora(self, amphora):
|
||||
LOG.debug("Amphora %s no-op, finalize amphora %s",
|
||||
self.__class__.__name__, amphora.id)
|
||||
self.amphoraconfig[amphora.id] = (amphora.id, 'finalize amphora')
|
||||
|
||||
def _configure_amp_routes(self, vip_iface, amp_net_config):
|
||||
subnet = amp_net_config.vip_subnet
|
||||
command = CMD_CREATE_VIP_ROUTE_TABLE.format(VIP_ROUTE_TABLE)
|
||||
self._execute_command(command, run_as_root=True)
|
||||
command = CMD_ADD_ROUTE_TO_TABLE.format(
|
||||
subnet.cidr, vip_iface, VIP_ROUTE_TABLE)
|
||||
self._execute_command(command, run_as_root=True)
|
||||
command = CMD_ADD_DEFAULT_ROUTE_TO_TABLE.format(
|
||||
subnet.gateway_ip, vip_iface, VIP_ROUTE_TABLE)
|
||||
self._execute_command(command, run_as_root=True)
|
||||
command = CMD_ADD_RULE_FROM_NET_TO_TABLE.format(
|
||||
subnet.cidr, VIP_ROUTE_TABLE)
|
||||
self._execute_command(command, run_as_root=True)
|
||||
command = CMD_ADD_RULE_TO_NET_TO_TABLE.format(
|
||||
subnet.cidr, VIP_ROUTE_TABLE)
|
||||
self._execute_command(command, run_as_root=True)
|
||||
|
||||
def _configure_amp_interface(self, iface, secondary_ip=None):
|
||||
# just grab the ip from dhcp
|
||||
command = CMD_DHCLIENT.format(iface)
|
||||
self._execute_command(command, run_as_root=True)
|
||||
if secondary_ip:
|
||||
# add secondary_ip
|
||||
command = CMD_ADD_IP_ADDR.format(secondary_ip, iface)
|
||||
self._execute_command(command, run_as_root=True)
|
||||
# log interface details
|
||||
command = CMD_SHOW_IP_ADDR.format(iface)
|
||||
self._execute_command(command)
|
||||
|
||||
def post_vip_plug(self, load_balancer, amphorae_network_config):
|
||||
LOG.debug("Add vip to interface for all amphora on %s",
|
||||
load_balancer.id)
|
||||
|
||||
for amp in load_balancer.amphorae:
|
||||
if amp.status != constants.DELETED:
|
||||
# Connect to amphora
|
||||
self._connect(hostname=amp.lb_network_ip)
|
||||
|
||||
mac = amphorae_network_config.get(amp.id).vrrp_port.mac_address
|
||||
stdout, _ = self._execute_command(
|
||||
CMD_GREP_LINK_BY_MAC.format(mac_address=mac))
|
||||
iface = stdout[:-2]
|
||||
if not iface:
|
||||
self.client.close()
|
||||
continue
|
||||
self._configure_amp_interface(
|
||||
iface, secondary_ip=load_balancer.vip.ip_address)
|
||||
self._configure_amp_routes(
|
||||
iface, amphorae_network_config.get(amp.id))
|
||||
|
||||
def post_network_plug(self, amphora, port):
|
||||
self._connect(hostname=amphora.lb_network_ip)
|
||||
stdout, _ = self._execute_command(
|
||||
CMD_GREP_LINK_BY_MAC.format(mac_address=port.mac_address))
|
||||
iface = stdout[:-2]
|
||||
if not iface:
|
||||
self.client.close()
|
||||
return
|
||||
self._configure_amp_interface(iface)
|
||||
self.client.close()
|
||||
|
||||
def _execute_command(self, command, run_as_root=False):
|
||||
if run_as_root and not self._is_root():
|
||||
command = "sudo {0}".format(command)
|
||||
_, stdout, stderr = self.client.exec_command(command)
|
||||
stdout = stdout.read()
|
||||
stderr = stderr.read()
|
||||
LOG.debug('Sent command %s', command)
|
||||
LOG.debug('Returned stdout: %s', stdout)
|
||||
LOG.debug('Returned stderr: %s', stderr)
|
||||
return stdout, stderr
|
||||
|
||||
def _connect(self, hostname):
|
||||
for attempts in six.moves.xrange(
|
||||
self.amp_config.connection_max_retries):
|
||||
try:
|
||||
self.client.connect(hostname=hostname,
|
||||
username=self.amp_config.username,
|
||||
key_filename=self.amp_config.key_path)
|
||||
except socket.error:
|
||||
LOG.warn(_LW("Could not ssh to instance"))
|
||||
time.sleep(self.amp_config.connection_retry_interval)
|
||||
if attempts >= self.amp_config.connection_max_retries:
|
||||
raise exc.TimeOutException()
|
||||
else:
|
||||
return
|
||||
raise exc.UnavailableException()
|
||||
|
||||
def _process_tls_certificates(self, listener):
|
||||
"""Processes TLS data from the listener.
|
||||
|
||||
Converts and uploads PEM data to the Amphora API
|
||||
|
||||
return TLS_CERT and SNI_CERTS
|
||||
"""
|
||||
data = []
|
||||
|
||||
certs = cert_parser.load_certificates_data(
|
||||
self.cert_manager, listener)
|
||||
sni_containers = certs['sni_certs']
|
||||
tls_cert = certs['tls_cert']
|
||||
if certs['tls_cert'] is not None:
|
||||
data.append(cert_parser.build_pem(tls_cert))
|
||||
if sni_containers:
|
||||
for sni_cont in sni_containers:
|
||||
data.append(cert_parser.build_pem(sni_cont))
|
||||
|
||||
if data:
|
||||
cert_dir = os.path.join(self.amp_config.base_cert_dir, listener.id)
|
||||
listener_cert = '{0}/{1}.pem'.format(cert_dir, tls_cert.primary_cn)
|
||||
self._exec_on_amphorae(
|
||||
listener.load_balancer.amphorae, [
|
||||
'chmod 600 {0}/*.pem'.format(cert_dir)],
|
||||
make_dir=cert_dir,
|
||||
data=data, upload_dir=listener_cert)
|
||||
|
||||
return certs
|
||||
|
||||
def _exec_on_amphorae(self, amphorae, commands, make_dir=None, data=None,
|
||||
upload_dir=None):
|
||||
data = data or []
|
||||
temps = []
|
||||
# Write data to temp file to prepare for upload
|
||||
for datum in data:
|
||||
temp = tempfile.NamedTemporaryFile(delete=True)
|
||||
temp.write(datum.encode('ascii'))
|
||||
temp.flush()
|
||||
temps.append(temp)
|
||||
|
||||
for amp in amphorae:
|
||||
if amp.status != constants.DELETED:
|
||||
# Connect to amphora
|
||||
self._connect(hostname=amp.lb_network_ip)
|
||||
|
||||
# Setup for file upload
|
||||
if make_dir:
|
||||
mkdir_cmd = 'mkdir -p {0}'.format(make_dir)
|
||||
self._execute_command(mkdir_cmd, run_as_root=True)
|
||||
chown_cmd = 'chown -R {0} {1}'.format(
|
||||
self.amp_config.username, make_dir)
|
||||
self._execute_command(chown_cmd, run_as_root=True)
|
||||
|
||||
# Upload files to location
|
||||
if temps:
|
||||
sftp = self.client.open_sftp()
|
||||
for temp in temps:
|
||||
sftp.put(temp.name, upload_dir)
|
||||
|
||||
# Execute remaining commands
|
||||
for command in commands:
|
||||
self._execute_command(command, run_as_root=True)
|
||||
self.client.close()
|
||||
|
||||
# Close the temp file
|
||||
for temp in temps:
|
||||
temp.close()
|
||||
|
||||
def _is_root(self):
|
||||
return cfg.CONF.haproxy_amphora.username == 'root'
|
@ -1,325 +0,0 @@
|
||||
# Copyright (c) 2015 Rackspace
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import os
|
||||
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
from oslo_utils import uuidutils
|
||||
import paramiko
|
||||
|
||||
from octavia.amphorae.drivers.haproxy.jinja import jinja_cfg
|
||||
from octavia.amphorae.drivers.haproxy import ssh_driver
|
||||
from octavia.certificates.manager import cert_mgr
|
||||
from octavia.common import data_models
|
||||
from octavia.common import keystone
|
||||
from octavia.common.tls_utils import cert_parser
|
||||
from octavia.db import models as models
|
||||
from octavia.network import data_models as network_models
|
||||
from octavia.tests.unit import base
|
||||
from octavia.tests.unit.common.sample_configs import sample_configs
|
||||
|
||||
|
||||
MOCK_NETWORK_ID = '1'
|
||||
MOCK_SUBNET_ID = '2'
|
||||
MOCK_PORT_ID = '3'
|
||||
MOCK_COMPUTE_ID = '4'
|
||||
MOCK_AMP_ID = '5'
|
||||
MOCK_IP_ADDRESS = '10.0.0.1'
|
||||
MOCK_CIDR = '10.0.0.0/24'
|
||||
|
||||
|
||||
class TestSshDriver(base.TestCase):
|
||||
FAKE_UUID_1 = uuidutils.generate_uuid()
|
||||
|
||||
@mock.patch('octavia.common.keystone.get_session',
|
||||
return_value=mock.MagicMock)
|
||||
def setUp(self, mock_session):
|
||||
super(TestSshDriver, self).setUp()
|
||||
mock.MagicMock(keystone.get_session())
|
||||
self.driver = ssh_driver.HaproxyManager()
|
||||
self.listener = sample_configs.sample_listener_tuple()
|
||||
self.vip = sample_configs.sample_vip_tuple()
|
||||
self.amphora = models.Amphora()
|
||||
self.amphora.id = self.FAKE_UUID_1
|
||||
self.driver.cert_manager = mock.Mock(
|
||||
spec=cert_mgr.CertManager)
|
||||
self.driver.client = mock.Mock(spec=paramiko.SSHClient)
|
||||
self.driver.client.exec_command.return_value = (
|
||||
mock.Mock(), mock.Mock(), mock.Mock())
|
||||
self.driver.amp_config = mock.MagicMock()
|
||||
self.port = network_models.Port(mac_address='123')
|
||||
|
||||
def test_update(self):
|
||||
with mock.patch.object(
|
||||
self.driver, '_process_tls_certificates') as process_tls_patch:
|
||||
with mock.patch.object(jinja_cfg.JinjaTemplater,
|
||||
'build_config') as build_conf:
|
||||
# Build sample Listener and VIP configs
|
||||
listener = sample_configs.sample_listener_tuple(tls=True,
|
||||
sni=True)
|
||||
vip = sample_configs.sample_vip_tuple()
|
||||
|
||||
process_tls_patch.return_value = {
|
||||
'tls_cert': listener.default_tls_container,
|
||||
'sni_certs': listener.sni_containers
|
||||
}
|
||||
build_conf.return_value = 'sampleConfig'
|
||||
|
||||
# Execute driver method
|
||||
self.driver.update(listener, vip)
|
||||
|
||||
# Verify calls
|
||||
process_tls_patch.assert_called_once_with(listener)
|
||||
build_conf.assert_called_once_with(
|
||||
listener, listener.default_tls_container)
|
||||
self.driver.client.connect.assert_called_once_with(
|
||||
hostname=listener.load_balancer.amphorae[0].lb_network_ip,
|
||||
key_filename=self.driver.amp_config.key_path,
|
||||
username=self.driver.amp_config.username)
|
||||
self.driver.client.open_sftp.assert_called_once_with()
|
||||
self.driver.client.open_sftp().put.assert_called_once_with(
|
||||
mock.ANY, mock.ANY
|
||||
)
|
||||
self.driver.client.exec_command.assert_has_calls([
|
||||
mock.call(mock.ANY),
|
||||
mock.call(mock.ANY),
|
||||
mock.call(mock.ANY),
|
||||
mock.call(mock.ANY)
|
||||
])
|
||||
self.driver.client.close.assert_called_once_with()
|
||||
|
||||
def test_stop(self):
|
||||
# Build sample Listener and VIP configs
|
||||
listener = sample_configs.sample_listener_tuple(
|
||||
tls=True, sni=True)
|
||||
vip = sample_configs.sample_vip_tuple()
|
||||
|
||||
# Execute driver method
|
||||
self.driver.start(listener, vip)
|
||||
self.driver.client.connect.assert_called_once_with(
|
||||
hostname=listener.load_balancer.amphorae[0].lb_network_ip,
|
||||
key_filename=self.driver.amp_config.key_path,
|
||||
username=self.driver.amp_config.username)
|
||||
self.driver.client.exec_command.assert_called_once_with(
|
||||
'sudo haproxy -f {0}/{1}/haproxy.cfg -p {0}/{1}/{1}.pid'.format(
|
||||
self.driver.amp_config.base_path, listener.id))
|
||||
self.driver.client.close.assert_called_once_with()
|
||||
|
||||
def test_start(self):
|
||||
# Build sample Listener and VIP configs
|
||||
listener = sample_configs.sample_listener_tuple(
|
||||
tls=True, sni=True)
|
||||
vip = sample_configs.sample_vip_tuple()
|
||||
|
||||
# Execute driver method
|
||||
self.driver.start(listener, vip)
|
||||
self.driver.client.connect.assert_called_once_with(
|
||||
hostname=listener.load_balancer.amphorae[0].lb_network_ip,
|
||||
key_filename=self.driver.amp_config.key_path,
|
||||
username=self.driver.amp_config.username)
|
||||
self.driver.client.exec_command.assert_called_once_with(
|
||||
'sudo haproxy -f {0}/{1}/haproxy.cfg -p {0}/{1}/{1}.pid'.format(
|
||||
self.driver.amp_config.base_path, listener.id))
|
||||
self.driver.client.close.assert_called_once_with()
|
||||
|
||||
def test_delete(self):
|
||||
|
||||
# Build sample Listener and VIP configs
|
||||
listener = sample_configs.sample_listener_tuple(
|
||||
tls=True, sni=True)
|
||||
vip = sample_configs.sample_vip_tuple()
|
||||
|
||||
# Execute driver method
|
||||
self.driver.delete(listener, vip)
|
||||
|
||||
# Verify call
|
||||
self.driver.client.connect.assert_called_once_with(
|
||||
hostname=listener.load_balancer.amphorae[0].lb_network_ip,
|
||||
key_filename=self.driver.amp_config.key_path,
|
||||
username=self.driver.amp_config.username)
|
||||
exec_command_calls = [
|
||||
mock.call('sudo kill -9 $(cat {0}/sample_listener_id_1'
|
||||
'/sample_listener_id_1.pid)'
|
||||
.format(self.driver.amp_config.base_path)),
|
||||
mock.call('sudo rm -rf {0}/sample_listener_id_1'.format(
|
||||
self.driver.amp_config.base_path))]
|
||||
self.driver.client.exec_command.assert_has_calls(exec_command_calls)
|
||||
self.driver.client.close.assert_called_once_with()
|
||||
|
||||
def test_get_info(self):
|
||||
pass
|
||||
|
||||
def test_get_diagnostics(self):
|
||||
pass
|
||||
|
||||
def test_finalize_amphora(self):
|
||||
pass
|
||||
|
||||
def test_process_tls_certificates(self):
|
||||
listener = sample_configs.sample_listener_tuple(tls=True, sni=True)
|
||||
|
||||
with mock.patch.object(cert_parser, 'build_pem') as bp:
|
||||
with mock.patch.object(cert_parser,
|
||||
'load_certificates_data') as cd:
|
||||
with mock.patch.object(cert_parser,
|
||||
'get_host_names') as cp:
|
||||
with mock.patch.object(self.driver,
|
||||
'_exec_on_amphorae') as ea:
|
||||
self.driver.barbican_client = mock.MagicMock()
|
||||
cp.return_value = {'cn': 'fakeCN'}
|
||||
pem = 'imapem'
|
||||
bp.return_value = pem
|
||||
tls_cont = data_models.TLSContainer(
|
||||
primary_cn='fakecn',
|
||||
certificate='fakecert',
|
||||
private_key='fakepk')
|
||||
sni_cont1 = data_models.TLSContainer(
|
||||
primary_cn='fakecn1',
|
||||
certificate='fakecert',
|
||||
private_key='fakepk')
|
||||
sni_cont2 = data_models.TLSContainer(
|
||||
primary_cn='fakecn2',
|
||||
certificate='fakecert',
|
||||
private_key='fakepk')
|
||||
cd.return_value = {'tls_cert': tls_cont,
|
||||
'sni_certs': [sni_cont1, sni_cont2]}
|
||||
|
||||
self.driver._process_tls_certificates(listener)
|
||||
|
||||
# Ensure upload_cert is called three times
|
||||
calls_bbq = [mock.call(self.driver.cert_manager,
|
||||
listener)]
|
||||
cd.assert_has_calls(calls_bbq)
|
||||
|
||||
calls_bp = [
|
||||
mock.call(tls_cont),
|
||||
mock.call(sni_cont1),
|
||||
mock.call(sni_cont2)]
|
||||
bp.assert_has_calls(calls_bp)
|
||||
|
||||
cert_dir = os.path.join(
|
||||
self.driver.amp_config.base_cert_dir, listener.id)
|
||||
cmd = 'chmod 600 {base_path}/*.pem'.format(
|
||||
base_path=cert_dir)
|
||||
listener_cert = '{0}/fakecn.pem'.format(cert_dir)
|
||||
|
||||
ea.assert_has_calls([
|
||||
mock.call(listener.load_balancer.amphorae,
|
||||
[cmd], make_dir=cert_dir,
|
||||
data=[pem, pem, pem],
|
||||
upload_dir=listener_cert)])
|
||||
|
||||
@mock.patch.object(ssh_driver.HaproxyManager, '_execute_command')
|
||||
def test_post_vip_plug_no_down_links(self, exec_command):
|
||||
amps = [data_models.Amphora(id=MOCK_AMP_ID, compute_id=MOCK_COMPUTE_ID,
|
||||
lb_network_ip=MOCK_IP_ADDRESS)]
|
||||
vip = data_models.Vip(ip_address=MOCK_IP_ADDRESS)
|
||||
lb = data_models.LoadBalancer(amphorae=amps, vip=vip)
|
||||
amphorae_net_config = {amps[0].id: network_models.AmphoraNetworkConfig(
|
||||
amphora=amps[0],
|
||||
vrrp_port=self.port
|
||||
)}
|
||||
exec_command.return_value = ('', '')
|
||||
self.driver.post_vip_plug(lb, amphorae_net_config)
|
||||
exec_command.assert_called_once_with(
|
||||
ssh_driver.CMD_GREP_LINK_BY_MAC.format(mac_address='123'))
|
||||
|
||||
@mock.patch.object(ssh_driver.HaproxyManager, '_execute_command')
|
||||
def test_post_vip_plug(self, exec_command):
|
||||
amps = [data_models.Amphora(id=MOCK_AMP_ID, compute_id=MOCK_COMPUTE_ID,
|
||||
lb_network_ip=MOCK_IP_ADDRESS)]
|
||||
vip = data_models.Vip(ip_address=MOCK_IP_ADDRESS)
|
||||
lb = data_models.LoadBalancer(amphorae=amps, vip=vip)
|
||||
vip_subnet = network_models.Subnet(id=MOCK_SUBNET_ID,
|
||||
gateway_ip=MOCK_IP_ADDRESS,
|
||||
cidr=MOCK_CIDR)
|
||||
vip_port = network_models.Port(id=MOCK_PORT_ID,
|
||||
device_id=MOCK_COMPUTE_ID)
|
||||
amphorae_net_config = {amps[0].id: network_models.AmphoraNetworkConfig(
|
||||
amphora=amps[0],
|
||||
vip_subnet=vip_subnet,
|
||||
vip_port=vip_port,
|
||||
vrrp_port=self.port
|
||||
)}
|
||||
iface = 'eth1'
|
||||
exec_command.return_value = ('{0}: '.format(iface), '')
|
||||
self.driver.post_vip_plug(lb, amphorae_net_config)
|
||||
grep_call = mock.call(
|
||||
ssh_driver.CMD_GREP_LINK_BY_MAC.format(mac_address='123'))
|
||||
dhclient_call = mock.call(ssh_driver.CMD_DHCLIENT.format(iface),
|
||||
run_as_root=True)
|
||||
add_ip_call = mock.call(ssh_driver.CMD_ADD_IP_ADDR.format(
|
||||
MOCK_IP_ADDRESS, iface), run_as_root=True)
|
||||
show_ip_call = mock.call(ssh_driver.CMD_SHOW_IP_ADDR.format(iface))
|
||||
create_vip_table_call = mock.call(
|
||||
ssh_driver.CMD_CREATE_VIP_ROUTE_TABLE.format(
|
||||
ssh_driver.VIP_ROUTE_TABLE),
|
||||
run_as_root=True
|
||||
)
|
||||
add_route_call = mock.call(
|
||||
ssh_driver.CMD_ADD_ROUTE_TO_TABLE.format(
|
||||
MOCK_CIDR, iface, ssh_driver.VIP_ROUTE_TABLE),
|
||||
run_as_root=True
|
||||
)
|
||||
add_default_route_call = mock.call(
|
||||
ssh_driver.CMD_ADD_DEFAULT_ROUTE_TO_TABLE.format(
|
||||
MOCK_IP_ADDRESS, iface, ssh_driver.VIP_ROUTE_TABLE),
|
||||
run_as_root=True
|
||||
)
|
||||
add_rule_from_call = mock.call(
|
||||
ssh_driver.CMD_ADD_RULE_FROM_NET_TO_TABLE.format(
|
||||
MOCK_CIDR, ssh_driver.VIP_ROUTE_TABLE),
|
||||
run_as_root=True
|
||||
)
|
||||
add_rule_to_call = mock.call(
|
||||
ssh_driver.CMD_ADD_RULE_TO_NET_TO_TABLE.format(
|
||||
MOCK_CIDR, ssh_driver.VIP_ROUTE_TABLE),
|
||||
run_as_root=True
|
||||
)
|
||||
exec_command.assert_has_calls([grep_call, dhclient_call, add_ip_call,
|
||||
show_ip_call, create_vip_table_call,
|
||||
add_route_call, add_default_route_call,
|
||||
add_rule_from_call, add_rule_to_call])
|
||||
self.assertEqual(9, exec_command.call_count)
|
||||
|
||||
@mock.patch.object(ssh_driver.HaproxyManager, '_execute_command')
|
||||
def test_post_network_plug_no_down_links(self, exec_command):
|
||||
amp = data_models.Amphora(id=MOCK_AMP_ID, compute_id=MOCK_COMPUTE_ID,
|
||||
lb_network_ip=MOCK_IP_ADDRESS)
|
||||
exec_command.return_value = ('', '')
|
||||
self.driver.post_network_plug(amp, self.port)
|
||||
exec_command.assert_called_once_with(
|
||||
ssh_driver.CMD_GREP_LINK_BY_MAC.format(mac_address='123'))
|
||||
|
||||
@mock.patch.object(ssh_driver.HaproxyManager, '_execute_command')
|
||||
def test_post_network_plug(self, exec_command):
|
||||
amp = data_models.Amphora(id=MOCK_AMP_ID, compute_id=MOCK_COMPUTE_ID,
|
||||
lb_network_ip=MOCK_IP_ADDRESS)
|
||||
iface = 'eth1'
|
||||
exec_command.return_value = ('{0}: '.format(iface), '')
|
||||
self.driver.post_network_plug(amp, self.port)
|
||||
grep_call = mock.call(
|
||||
ssh_driver.CMD_GREP_LINK_BY_MAC.format(mac_address='123'))
|
||||
dhclient_call = mock.call(ssh_driver.CMD_DHCLIENT.format(iface),
|
||||
run_as_root=True)
|
||||
show_ip_call = mock.call(ssh_driver.CMD_SHOW_IP_ADDR.format(iface))
|
||||
exec_command.assert_has_calls([grep_call, dhclient_call, show_ip_call])
|
||||
self.assertEqual(3, exec_command.call_count)
|
||||
|
||||
def test_is_root(self):
|
||||
cfg.CONF.set_override('username', 'root', group='haproxy_amphora')
|
||||
self.assertTrue(self.driver._is_root())
|
||||
cfg.CONF.set_override('username', 'blah', group='haproxy_amphora')
|
||||
self.assertFalse(self.driver._is_root())
|
@ -28,7 +28,7 @@ AUTH_VERSION = '2'
|
||||
class TestAmphoraFlows(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
cfg.CONF.set_override('amphora_driver', 'amphora_haproxy_ssh_driver',
|
||||
cfg.CONF.set_override('amphora_driver', 'amphora_haproxy_rest_driver',
|
||||
group='controller_worker')
|
||||
self.AmpFlow = amphora_flows.AmphoraFlows()
|
||||
conf = oslo_fixture.Config(cfg.CONF)
|
||||
@ -45,8 +45,9 @@ class TestAmphoraFlows(base.TestCase):
|
||||
self.assertIn(constants.AMPHORA, amp_flow.provides)
|
||||
self.assertIn(constants.AMPHORA_ID, amp_flow.provides)
|
||||
self.assertIn(constants.COMPUTE_ID, amp_flow.provides)
|
||||
self.assertIn(constants.SERVER_PEM, amp_flow.provides)
|
||||
|
||||
self.assertEqual(4, len(amp_flow.provides))
|
||||
self.assertEqual(5, len(amp_flow.provides))
|
||||
self.assertEqual(0, len(amp_flow.requires))
|
||||
|
||||
def test_get_create_amphora_flow_cert(self):
|
||||
@ -66,7 +67,7 @@ class TestAmphoraFlows(base.TestCase):
|
||||
self.assertEqual(0, len(amp_flow.requires))
|
||||
|
||||
def test_get_create_amphora_for_lb_flow(self):
|
||||
cfg.CONF.set_override('amphora_driver', 'amphora_haproxy_ssh_driver',
|
||||
cfg.CONF.set_override('amphora_driver', 'amphora_haproxy_rest_driver',
|
||||
group='controller_worker')
|
||||
|
||||
amp_flow = self.AmpFlow._get_create_amp_for_lb_subflow(
|
||||
@ -80,8 +81,9 @@ class TestAmphoraFlows(base.TestCase):
|
||||
self.assertIn(constants.AMPHORA_ID, amp_flow.provides)
|
||||
self.assertIn(constants.COMPUTE_ID, amp_flow.provides)
|
||||
self.assertIn(constants.COMPUTE_OBJ, amp_flow.provides)
|
||||
self.assertIn(constants.SERVER_PEM, amp_flow.provides)
|
||||
|
||||
self.assertEqual(4, len(amp_flow.provides))
|
||||
self.assertEqual(5, len(amp_flow.provides))
|
||||
self.assertEqual(1, len(amp_flow.requires))
|
||||
|
||||
def test_get_cert_create_amphora_for_lb_flow(self):
|
||||
@ -205,16 +207,21 @@ class TestAmphoraFlows(base.TestCase):
|
||||
|
||||
self.assertIn(constants.FAILED_AMPHORA, amp_flow.requires)
|
||||
self.assertIn(constants.LOADBALANCER_ID, amp_flow.requires)
|
||||
|
||||
self.assertIn(constants.AMP_DATA, amp_flow.provides)
|
||||
self.assertIn(constants.AMPHORA, amp_flow.provides)
|
||||
self.assertIn(constants.AMPHORA_ID, amp_flow.provides)
|
||||
self.assertIn(constants.AMPHORAE_NETWORK_CONFIG, amp_flow.provides)
|
||||
self.assertIn(constants.COMPUTE_ID, amp_flow.provides)
|
||||
self.assertIn(constants.COMPUTE_OBJ, amp_flow.provides)
|
||||
self.assertIn(constants.PORTS, amp_flow.provides)
|
||||
self.assertIn(constants.LISTENERS, amp_flow.provides)
|
||||
self.assertIn(constants.LOADBALANCER, amp_flow.provides)
|
||||
self.assertIn(constants.MEMBER_PORTS, amp_flow.provides)
|
||||
self.assertIn(constants.PORTS, amp_flow.provides)
|
||||
self.assertIn(constants.VIP, amp_flow.provides)
|
||||
|
||||
self.assertEqual(2, len(amp_flow.requires))
|
||||
self.assertEqual(11, len(amp_flow.provides))
|
||||
self.assertEqual(12, len(amp_flow.provides))
|
||||
|
||||
amp_flow = self.AmpFlow.get_failover_flow(role=constants.ROLE_MASTER)
|
||||
|
||||
@ -222,16 +229,21 @@ class TestAmphoraFlows(base.TestCase):
|
||||
|
||||
self.assertIn(constants.FAILED_AMPHORA, amp_flow.requires)
|
||||
self.assertIn(constants.LOADBALANCER_ID, amp_flow.requires)
|
||||
|
||||
self.assertIn(constants.AMP_DATA, amp_flow.provides)
|
||||
self.assertIn(constants.AMPHORA, amp_flow.provides)
|
||||
self.assertIn(constants.AMPHORA_ID, amp_flow.provides)
|
||||
self.assertIn(constants.AMPHORAE_NETWORK_CONFIG, amp_flow.provides)
|
||||
self.assertIn(constants.COMPUTE_ID, amp_flow.provides)
|
||||
self.assertIn(constants.COMPUTE_OBJ, amp_flow.provides)
|
||||
self.assertIn(constants.PORTS, amp_flow.provides)
|
||||
self.assertIn(constants.LISTENERS, amp_flow.provides)
|
||||
self.assertIn(constants.LOADBALANCER, amp_flow.provides)
|
||||
self.assertIn(constants.MEMBER_PORTS, amp_flow.provides)
|
||||
self.assertIn(constants.PORTS, amp_flow.provides)
|
||||
self.assertIn(constants.VIP, amp_flow.provides)
|
||||
|
||||
self.assertEqual(2, len(amp_flow.requires))
|
||||
self.assertEqual(11, len(amp_flow.provides))
|
||||
self.assertEqual(12, len(amp_flow.provides))
|
||||
|
||||
amp_flow = self.AmpFlow.get_failover_flow(role=constants.ROLE_BACKUP)
|
||||
|
||||
@ -239,16 +251,21 @@ class TestAmphoraFlows(base.TestCase):
|
||||
|
||||
self.assertIn(constants.FAILED_AMPHORA, amp_flow.requires)
|
||||
self.assertIn(constants.LOADBALANCER_ID, amp_flow.requires)
|
||||
|
||||
self.assertIn(constants.AMP_DATA, amp_flow.provides)
|
||||
self.assertIn(constants.AMPHORA, amp_flow.provides)
|
||||
self.assertIn(constants.AMPHORA_ID, amp_flow.provides)
|
||||
self.assertIn(constants.AMPHORAE_NETWORK_CONFIG, amp_flow.provides)
|
||||
self.assertIn(constants.COMPUTE_ID, amp_flow.provides)
|
||||
self.assertIn(constants.COMPUTE_OBJ, amp_flow.provides)
|
||||
self.assertIn(constants.PORTS, amp_flow.provides)
|
||||
self.assertIn(constants.LISTENERS, amp_flow.provides)
|
||||
self.assertIn(constants.LOADBALANCER, amp_flow.provides)
|
||||
self.assertIn(constants.MEMBER_PORTS, amp_flow.provides)
|
||||
self.assertIn(constants.PORTS, amp_flow.provides)
|
||||
self.assertIn(constants.VIP, amp_flow.provides)
|
||||
|
||||
self.assertEqual(2, len(amp_flow.requires))
|
||||
self.assertEqual(11, len(amp_flow.provides))
|
||||
self.assertEqual(12, len(amp_flow.provides))
|
||||
|
||||
amp_flow = self.AmpFlow.get_failover_flow(role='BOGUSROLE')
|
||||
|
||||
@ -256,16 +273,21 @@ class TestAmphoraFlows(base.TestCase):
|
||||
|
||||
self.assertIn(constants.FAILED_AMPHORA, amp_flow.requires)
|
||||
self.assertIn(constants.LOADBALANCER_ID, amp_flow.requires)
|
||||
|
||||
self.assertIn(constants.AMP_DATA, amp_flow.provides)
|
||||
self.assertIn(constants.AMPHORA, amp_flow.provides)
|
||||
self.assertIn(constants.AMPHORA_ID, amp_flow.provides)
|
||||
self.assertIn(constants.AMPHORAE_NETWORK_CONFIG, amp_flow.provides)
|
||||
self.assertIn(constants.COMPUTE_ID, amp_flow.provides)
|
||||
self.assertIn(constants.COMPUTE_OBJ, amp_flow.provides)
|
||||
self.assertIn(constants.PORTS, amp_flow.provides)
|
||||
self.assertIn(constants.LISTENERS, amp_flow.provides)
|
||||
self.assertIn(constants.LOADBALANCER, amp_flow.provides)
|
||||
self.assertIn(constants.MEMBER_PORTS, amp_flow.provides)
|
||||
self.assertIn(constants.PORTS, amp_flow.provides)
|
||||
self.assertIn(constants.VIP, amp_flow.provides)
|
||||
|
||||
self.assertEqual(2, len(amp_flow.requires))
|
||||
self.assertEqual(11, len(amp_flow.provides))
|
||||
self.assertEqual(12, len(amp_flow.provides))
|
||||
|
||||
def test_cert_rotate_amphora_flow(self):
|
||||
cfg.CONF.set_override('amphora_driver', 'amphora_haproxy_rest_driver',
|
||||
|
@ -49,7 +49,6 @@ octavia.api.handlers =
|
||||
octavia.amphora.drivers =
|
||||
amphora_noop_driver = octavia.amphorae.drivers.noop_driver.driver:NoopAmphoraLoadBalancerDriver
|
||||
amphora_haproxy_rest_driver = octavia.amphorae.drivers.haproxy.rest_api_driver:HaproxyAmphoraLoadBalancerDriver
|
||||
amphora_haproxy_ssh_driver = octavia.amphorae.drivers.haproxy.ssh_driver:HaproxyManager
|
||||
octavia.controller.queues =
|
||||
noop_event_streamer = octavia.controller.queue.event_queue:EventStreamerNoop
|
||||
queue_event_streamer = octavia.controller.queue.event_queue:EventStreamerNeutron
|
||||
|
@ -89,7 +89,7 @@ The Active/Standby loadbalancers require the following high level changes:
|
||||
the same listeners, and pools configuration. Note: topology is a property
|
||||
of a load balancer and not of one of its amphorae.
|
||||
|
||||
* Extend the amphora driver interface, the amphora REST/SSH drivers, and Jinja
|
||||
* Extend the amphora driver interface, the amphora REST driver, and Jinja
|
||||
configuration templates for the newly introduced VRRP service [4].
|
||||
|
||||
* Develop a Keepalived driver.
|
||||
|
Loading…
Reference in New Issue
Block a user