haproxy reference amphora REST API client
Adds rest driver methods Adds rest driver tests Add cert task for generating server certs Modified compute task/flow Fixed local certificate stuff Refactored to use requests-mock inetad of responses Added a "conditiobal flow" for REST Cleaned up and changed the code to work with https://review.openstack.org/#/c/160034/ Replaces: https://review.openstack.org/#/c/144348/ https://review.openstack.org/#/c/145637/14 Change-Id: Ibcbf0717b785aab4c604deef1061e8b2fa41006c Co-Authored-By: Phillip Toohill <phillip.toohill@rackspace.com> Co-Authored-By: German Eichberger <german.eichberger@hp.com> Co-Authored-By: Stephen Balukoff <sbalukoff@bluebox.net> Implements: bp/haproxy-amphora-driver
This commit is contained in:
parent
792a116523
commit
0abcbc4f7d
|
@ -61,6 +61,7 @@ function octavia_configure {
|
||||||
|
|
||||||
# Setting other required default options
|
# Setting other required default options
|
||||||
iniset $OCTAVIA_CONF controller_worker amphora_driver amphora_haproxy_ssh_driver
|
iniset $OCTAVIA_CONF controller_worker amphora_driver amphora_haproxy_ssh_driver
|
||||||
|
#iniset $OCTAVIA_CONF controller_worker amphora_driver amphora_haproxy_rest_driver
|
||||||
iniset $OCTAVIA_CONF controller_worker compute_driver compute_nova_driver
|
iniset $OCTAVIA_CONF controller_worker compute_driver compute_nova_driver
|
||||||
iniset $OCTAVIA_CONF controller_worker network_driver allowed_address_pairs_driver
|
iniset $OCTAVIA_CONF controller_worker network_driver allowed_address_pairs_driver
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ set -eux
|
||||||
|
|
||||||
install-packages libffi-dev libssl-dev
|
install-packages libffi-dev libssl-dev
|
||||||
cd /opt/amphora-agent/
|
cd /opt/amphora-agent/
|
||||||
|
pip install -r requirements.txt
|
||||||
python setup.py install
|
python setup.py install
|
||||||
cp etc/init/octavia-agent.conf /etc/init/
|
cp etc/init/octavia-agent.conf /etc/init/
|
||||||
mkdir /etc/octavia
|
mkdir /etc/octavia
|
||||||
|
|
|
@ -53,7 +53,6 @@
|
||||||
# Network to communicate with amphora
|
# Network to communicate with amphora
|
||||||
# lb_network_name =
|
# lb_network_name =
|
||||||
|
|
||||||
|
|
||||||
[haproxy_amphora]
|
[haproxy_amphora]
|
||||||
# base_path = /var/lib/octavia
|
# base_path = /var/lib/octavia
|
||||||
# base_cert_dir = /var/lib/octavia/certs
|
# base_cert_dir = /var/lib/octavia/certs
|
||||||
|
@ -62,8 +61,12 @@
|
||||||
# base_log_dir = /logs
|
# base_log_dir = /logs
|
||||||
# connection_max_retries = 10
|
# connection_max_retries = 10
|
||||||
# connection_retry_interval = 5
|
# connection_retry_interval = 5
|
||||||
|
# Cert manager options are local_cert_manager,
|
||||||
|
# barbican_cert_manager,
|
||||||
|
#
|
||||||
|
# cert_manager = barbican_cert_manager
|
||||||
|
|
||||||
#SSH Driver specific
|
# SSH Driver specific
|
||||||
# username = ubuntu
|
# username = ubuntu
|
||||||
# key_path = /opt/stack/.ssh/id_rsa
|
# key_path = /opt/stack/.ssh/id_rsa
|
||||||
|
|
||||||
|
@ -86,6 +89,7 @@
|
||||||
# amp_ssh_key_name =
|
# amp_ssh_key_name =
|
||||||
# amp_network =
|
# amp_network =
|
||||||
# amp_secgroup_list =
|
# amp_secgroup_list =
|
||||||
|
# client_ca = /etc/octavia/certs/ca_01.pem
|
||||||
|
|
||||||
# Amphora driver options are amphora_noop_driver,
|
# Amphora driver options are amphora_noop_driver,
|
||||||
# amphora_haproxy_rest_driver,
|
# amphora_haproxy_rest_driver,
|
||||||
|
@ -102,6 +106,10 @@
|
||||||
# allowed_address_pairs_driver
|
# allowed_address_pairs_driver
|
||||||
#
|
#
|
||||||
# network_driver = network_noop_driver
|
# network_driver = network_noop_driver
|
||||||
|
#
|
||||||
|
# Certificate Generator options are local_cert_generator
|
||||||
|
# barbican_cert_generator
|
||||||
|
# cert_generator = local_cert_generator
|
||||||
|
|
||||||
[task_flow]
|
[task_flow]
|
||||||
# engine = serial
|
# engine = serial
|
||||||
|
@ -121,4 +129,4 @@
|
||||||
# rpc_thread_pool_size = 2
|
# rpc_thread_pool_size = 2
|
||||||
|
|
||||||
# Topic (i.e. Queue) Name
|
# Topic (i.e. Queue) Name
|
||||||
# topic = octavia_prov
|
# topic = octavia_prov
|
||||||
|
|
|
@ -361,7 +361,7 @@ def _check_ssl_filename_format(filename):
|
||||||
|
|
||||||
|
|
||||||
def _cert_dir(listener_id):
|
def _cert_dir(listener_id):
|
||||||
return os.path.join(util.CONF.haproxy_amphora.haproxy_cert_dir,
|
return os.path.join(util.CONF.haproxy_amphora.base_cert_dir,
|
||||||
listener_id)
|
listener_id)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,110 @@
|
||||||
|
# Copyright 2014 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 octavia.common.data_models as models
|
||||||
|
|
||||||
|
|
||||||
|
class Topology(models.BaseDataModel):
|
||||||
|
|
||||||
|
def __init__(self, hostname=None, uuid=None, topology=None, role=None,
|
||||||
|
ip=None, ha_ip=None):
|
||||||
|
self.hostname = hostname
|
||||||
|
self.uuid = uuid
|
||||||
|
self.topology = topology
|
||||||
|
self.role = role
|
||||||
|
self.ip = ip
|
||||||
|
self.ha_ip = ha_ip
|
||||||
|
|
||||||
|
|
||||||
|
class Info(models.BaseDataModel):
|
||||||
|
|
||||||
|
def __init__(self, hostname=None, uuid=None, version=None,
|
||||||
|
api_version=None):
|
||||||
|
self.hostname = hostname
|
||||||
|
self.uuid = uuid
|
||||||
|
self.version = version
|
||||||
|
self.api_version = api_version
|
||||||
|
|
||||||
|
|
||||||
|
class Details(models.BaseDataModel):
|
||||||
|
|
||||||
|
def __init__(self, hostname=None, uuid=None, version=None,
|
||||||
|
api_version=None, network_tx=None, network_rx=None,
|
||||||
|
active=None, haproxy_count=None, cpu=None, memory=None,
|
||||||
|
disk=None, load=None, listeners=None, packages=None):
|
||||||
|
self.hostname = hostname
|
||||||
|
self.uuid = uuid,
|
||||||
|
self.version = version
|
||||||
|
self.api_version = api_version
|
||||||
|
self.network_tx = network_tx
|
||||||
|
self.network_rx = network_rx
|
||||||
|
self.active = active
|
||||||
|
self.haproxy_count = haproxy_count
|
||||||
|
self.cpu = cpu
|
||||||
|
self.memory = memory
|
||||||
|
self.disk = disk
|
||||||
|
self.load = load or []
|
||||||
|
self.listeners = listeners or []
|
||||||
|
self.packages = packages or []
|
||||||
|
|
||||||
|
|
||||||
|
class CPU(models.BaseDataModel):
|
||||||
|
|
||||||
|
def __init__(self, total=None, user=None, system=None, soft_irq=None):
|
||||||
|
self.total = total
|
||||||
|
self.user = user
|
||||||
|
self.system = system
|
||||||
|
self.soft_irq = soft_irq
|
||||||
|
|
||||||
|
|
||||||
|
class Memory(models.BaseDataModel):
|
||||||
|
|
||||||
|
def __init__(self, total=None, free=None, available=None, buffers=None,
|
||||||
|
cached=None, swap_used=None, shared=None, slab=None,
|
||||||
|
committed_as=None):
|
||||||
|
self.total = total
|
||||||
|
self.free = free
|
||||||
|
self.available = available
|
||||||
|
self.buffers = buffers
|
||||||
|
self.cached = cached
|
||||||
|
self.swap_used = swap_used
|
||||||
|
self.shared = shared
|
||||||
|
self.slab = slab
|
||||||
|
self.committed_as = committed_as
|
||||||
|
|
||||||
|
|
||||||
|
class Disk(models.BaseDataModel):
|
||||||
|
|
||||||
|
def __init__(self, used=None, available=None):
|
||||||
|
self.used = used
|
||||||
|
self.available = available
|
||||||
|
|
||||||
|
|
||||||
|
class ListenerStatus(models.BaseDataModel):
|
||||||
|
|
||||||
|
def __init__(self, status=None, uuid=None, provisioning_status=None,
|
||||||
|
type=None, pools=None):
|
||||||
|
self.status = status
|
||||||
|
self.uuid = uuid
|
||||||
|
self.provisioning_status = provisioning_status
|
||||||
|
self.type = type
|
||||||
|
self.pools = pools or []
|
||||||
|
|
||||||
|
|
||||||
|
class Pool(models.BaseDataModel):
|
||||||
|
|
||||||
|
def __init__(self, uuid=None, status=None, members=None):
|
||||||
|
self.uuid = uuid
|
||||||
|
self.status = status
|
||||||
|
self.members = members or []
|
|
@ -0,0 +1,72 @@
|
||||||
|
# Copyright 2014 Rackspace
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from webob import exc
|
||||||
|
|
||||||
|
|
||||||
|
def check_exception(response):
|
||||||
|
status_code = response.status_code
|
||||||
|
responses = {
|
||||||
|
401: Unauthorized,
|
||||||
|
403: InvalidRequest,
|
||||||
|
404: NotFound,
|
||||||
|
405: InvalidRequest,
|
||||||
|
409: Conflict,
|
||||||
|
500: InternalServerError,
|
||||||
|
503: ServiceUnavailable
|
||||||
|
}
|
||||||
|
if status_code in responses:
|
||||||
|
raise responses[status_code]()
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
class APIException(exc.HTTPClientError):
|
||||||
|
msg = "Something unknown went wrong"
|
||||||
|
code = 500
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
self.msg = self.msg % kwargs
|
||||||
|
super(APIException, self).__init__(detail=self.msg)
|
||||||
|
|
||||||
|
|
||||||
|
class Unauthorized(APIException):
|
||||||
|
msg = "Unauthorized"
|
||||||
|
code = 401
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidRequest(APIException):
|
||||||
|
msg = "Invalid request"
|
||||||
|
code = 403
|
||||||
|
|
||||||
|
|
||||||
|
class NotFound(APIException):
|
||||||
|
msg = "Not Found"
|
||||||
|
code = 404
|
||||||
|
|
||||||
|
|
||||||
|
class Conflict(APIException):
|
||||||
|
msg = "Conflict"
|
||||||
|
code = 409
|
||||||
|
|
||||||
|
|
||||||
|
class InternalServerError(APIException):
|
||||||
|
msg = "Internal Server Error"
|
||||||
|
code = 500
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceUnavailable(APIException):
|
||||||
|
msg = "Service Unavailable"
|
||||||
|
code = 503
|
|
@ -0,0 +1,299 @@
|
||||||
|
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||||
|
# 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 functools
|
||||||
|
import hashlib
|
||||||
|
import time
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
import requests
|
||||||
|
from stevedore import driver as stevedore_driver
|
||||||
|
|
||||||
|
from octavia.amphorae.drivers import driver_base as driver_base
|
||||||
|
from octavia.amphorae.drivers.haproxy import exceptions as exc
|
||||||
|
from octavia.amphorae.drivers.haproxy.jinja import jinja_cfg
|
||||||
|
from octavia.common.config import cfg
|
||||||
|
from octavia.common import data_models as data_models
|
||||||
|
from octavia.common.tls_utils import cert_parser
|
||||||
|
from octavia.i18n import _LW
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
API_VERSION = '0.5'
|
||||||
|
OCTAVIA_API_CLIENT = (
|
||||||
|
"Octavia HaProxy Rest Client/{version} "
|
||||||
|
"(https://wiki.openstack.org/wiki/Octavia)").format(version=API_VERSION)
|
||||||
|
CONF = cfg.CONF
|
||||||
|
CONF.import_group('haproxy_amphora', 'octavia.common.config')
|
||||||
|
|
||||||
|
|
||||||
|
class HaproxyAmphoraLoadBalancerDriver(driver_base.AmphoraLoadBalancerDriver):
|
||||||
|
def __init__(self):
|
||||||
|
super(HaproxyAmphoraLoadBalancerDriver, self).__init__()
|
||||||
|
self.client = AmphoraAPIClient()
|
||||||
|
self.cert_manager = stevedore_driver.DriverManager(
|
||||||
|
namespace='octavia.cert_manager',
|
||||||
|
name=CONF.haproxy_amphora.cert_manager,
|
||||||
|
invoke_on_load=True,
|
||||||
|
).driver
|
||||||
|
self.jinja = jinja_cfg.JinjaTemplater(
|
||||||
|
base_amp_path=CONF.haproxy_amphora.base_path,
|
||||||
|
base_crt_dir=CONF.haproxy_amphora.base_cert_dir,
|
||||||
|
haproxy_template=CONF.haproxy_amphora.haproxy_template)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
# Generate HaProxy configuration from listener object
|
||||||
|
config = self.jinja.build_config(listener, certs['tls_cert'],
|
||||||
|
certs['sni_certs'])
|
||||||
|
|
||||||
|
for amp in listener.load_balancer.amphorae:
|
||||||
|
self.client.upload_config(amp, listener.id, config)
|
||||||
|
# todo (german): add a method to REST interface to reload or start
|
||||||
|
# without having to check
|
||||||
|
# Is that listener running?
|
||||||
|
r = self.client.get_listener_status(amp,
|
||||||
|
listener.id)
|
||||||
|
if r['status'] == 'ACTIVE':
|
||||||
|
self.client.reload_listener(amp, listener.id)
|
||||||
|
else:
|
||||||
|
self.client.start_listener(amp, listener.id)
|
||||||
|
|
||||||
|
def _apply(self, func, listener=None, *args):
|
||||||
|
for amp in listener.load_balancer.amphorae:
|
||||||
|
func(amp, listener.id, *args)
|
||||||
|
|
||||||
|
def stop(self, listener, vip):
|
||||||
|
self._apply(self.client.stop_listener, listener)
|
||||||
|
|
||||||
|
def start(self, listener, vip):
|
||||||
|
self._apply(self.client.start_listener, listener)
|
||||||
|
|
||||||
|
def delete(self, listener, vip):
|
||||||
|
self._apply(self.client.delete_listener, listener)
|
||||||
|
|
||||||
|
def get_info(self, amphora):
|
||||||
|
self.driver.get_info(amphora.lb_network_ip)
|
||||||
|
|
||||||
|
def get_diagnostics(self, amphora):
|
||||||
|
self.driver.get_diagnostics(amphora.lb_network_ip)
|
||||||
|
|
||||||
|
def finalize_amphora(self, amphora):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def post_vip_plug(self, load_balancer):
|
||||||
|
for amp in load_balancer.amphorae:
|
||||||
|
self.client.plug_vip(amp, load_balancer.vip.ip_address)
|
||||||
|
|
||||||
|
def post_network_plug(self, amphora):
|
||||||
|
self.client.plug_network(amphora)
|
||||||
|
|
||||||
|
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
|
||||||
|
"""
|
||||||
|
tls_cert = None
|
||||||
|
sni_certs = []
|
||||||
|
|
||||||
|
certs = []
|
||||||
|
|
||||||
|
if listener.tls_certificate_id:
|
||||||
|
tls_cert = self._map_cert_tls_container(
|
||||||
|
self.cert_manager.get_cert(listener.tls_certificate_id))
|
||||||
|
certs.append(tls_cert)
|
||||||
|
if listener.sni_containers:
|
||||||
|
for sni_cont in listener.sni_containers:
|
||||||
|
bbq_container = self._map_cert_tls_container(
|
||||||
|
self.cert_manager.get_cert(sni_cont.tls_container.id))
|
||||||
|
sni_certs.append(bbq_container)
|
||||||
|
certs.append(bbq_container)
|
||||||
|
|
||||||
|
for cert in certs:
|
||||||
|
pem = self._build_pem(cert)
|
||||||
|
md5 = hashlib.md5(pem).hexdigest()
|
||||||
|
name = '{cn}.pem'.format(cn=cert.primary_cn)
|
||||||
|
self._apply(self._upload_cert, listener, pem, md5, name)
|
||||||
|
|
||||||
|
return {'tls_cert': tls_cert, 'sni_certs': sni_certs}
|
||||||
|
|
||||||
|
def _upload_cert(self, amp, listener_id, pem, md5, name):
|
||||||
|
try:
|
||||||
|
if self.client.get_cert_md5sum(amp, listener_id, name) == md5:
|
||||||
|
return
|
||||||
|
except exc.NotFound:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.client.upload_cert_pem(
|
||||||
|
amp, listener_id, name, pem)
|
||||||
|
|
||||||
|
def _get_primary_cn(self, tls_cert):
|
||||||
|
"""Returns primary CN for Certificate."""
|
||||||
|
return cert_parser.get_host_names(tls_cert.get_certificate())['cn']
|
||||||
|
|
||||||
|
def _map_cert_tls_container(self, cert):
|
||||||
|
return data_models.TLSContainer(
|
||||||
|
primary_cn=self._get_primary_cn(cert),
|
||||||
|
private_key=cert.get_private_key(),
|
||||||
|
certificate=cert.get_certificate(),
|
||||||
|
intermediates=cert.get_intermediates())
|
||||||
|
|
||||||
|
def _build_pem(self, tls_cert):
|
||||||
|
"""Concatenate TLS Certificate fields to create a PEM
|
||||||
|
|
||||||
|
encoded certificate file
|
||||||
|
"""
|
||||||
|
# TODO(ptoohill): Maybe this should be part of utils or manager?
|
||||||
|
pem = tls_cert.intermediates[:]
|
||||||
|
pem.extend([tls_cert.certificate, tls_cert.private_key])
|
||||||
|
|
||||||
|
return "\n".join(pem)
|
||||||
|
|
||||||
|
|
||||||
|
# Check a custom hostname
|
||||||
|
class CustomHostNameCheckingAdapter(requests.adapters.HTTPAdapter):
|
||||||
|
def cert_verify(self, conn, url, verify, cert):
|
||||||
|
conn.assert_hostname = self.uuid
|
||||||
|
return super(CustomHostNameCheckingAdapter,
|
||||||
|
self).cert_verify(conn, url, verify, cert)
|
||||||
|
|
||||||
|
|
||||||
|
class AmphoraAPIClient(object):
|
||||||
|
def __init__(self):
|
||||||
|
super(AmphoraAPIClient, self).__init__()
|
||||||
|
self.secure = False
|
||||||
|
|
||||||
|
self.get = functools.partial(self.request, 'get')
|
||||||
|
self.post = functools.partial(self.request, 'post')
|
||||||
|
self.put = functools.partial(self.request, 'put')
|
||||||
|
self.delete = functools.partial(self.request, 'delete')
|
||||||
|
self.head = functools.partial(self.request, 'head')
|
||||||
|
|
||||||
|
self.start_listener = functools.partial(self._action, 'start')
|
||||||
|
self.stop_listener = functools.partial(self._action, 'stop')
|
||||||
|
self.reload_listener = functools.partial(self._action, 'reload')
|
||||||
|
|
||||||
|
self.session = requests.Session()
|
||||||
|
self.session.cert = CONF.haproxy_amphora.client_cert
|
||||||
|
self.ssl_adapter = CustomHostNameCheckingAdapter()
|
||||||
|
self.session.mount('https://', self.ssl_adapter)
|
||||||
|
|
||||||
|
def _base_url(self, ip):
|
||||||
|
return "https://{ip}:{port}/{version}/".format(
|
||||||
|
ip=ip,
|
||||||
|
port=CONF.haproxy_amphora.bind_port,
|
||||||
|
version=API_VERSION)
|
||||||
|
|
||||||
|
def request(self, method, amp, path='/', **kwargs):
|
||||||
|
LOG.debug("request url " + path)
|
||||||
|
_request = getattr(self.session, method.lower())
|
||||||
|
_url = self._base_url(amp.lb_network_ip) + path
|
||||||
|
|
||||||
|
reqargs = {
|
||||||
|
'verify': CONF.haproxy_amphora.server_ca,
|
||||||
|
'url': _url, }
|
||||||
|
reqargs.update(kwargs)
|
||||||
|
headers = reqargs.setdefault('headers', {})
|
||||||
|
|
||||||
|
headers['User-Agent'] = OCTAVIA_API_CLIENT
|
||||||
|
self.ssl_adapter.uuid = amp.id
|
||||||
|
# Keep retrying
|
||||||
|
for attempts in xrange(CONF.haproxy_amphora.connection_max_retries):
|
||||||
|
try:
|
||||||
|
r = _request(**reqargs)
|
||||||
|
except requests.ConnectionError:
|
||||||
|
LOG.warn(_LW("Could not talk to instance"))
|
||||||
|
time.sleep(CONF.haproxy_amphora.connection_retry_interval)
|
||||||
|
if attempts >= CONF.haproxy_amphora.connection_max_retries:
|
||||||
|
raise exc.TimeOutException()
|
||||||
|
else:
|
||||||
|
return r
|
||||||
|
raise exc.UnavailableException()
|
||||||
|
|
||||||
|
def upload_config(self, amp, listener_id, config):
|
||||||
|
r = self.put(
|
||||||
|
amp,
|
||||||
|
'listeners/{listener_id}/haproxy'.format(listener_id=listener_id),
|
||||||
|
data=config)
|
||||||
|
return exc.check_exception(r)
|
||||||
|
|
||||||
|
def get_listener_status(self, amp, listener_id):
|
||||||
|
r = self.get(
|
||||||
|
amp,
|
||||||
|
'listeners/{listener_id}'.format(listener_id=listener_id))
|
||||||
|
if exc.check_exception(r):
|
||||||
|
return r.json()
|
||||||
|
|
||||||
|
def _action(self, action, amp, listener_id):
|
||||||
|
r = self.put(amp, 'listeners/{listener_id}/{action}'.format(
|
||||||
|
listener_id=listener_id, action=action))
|
||||||
|
return exc.check_exception(r)
|
||||||
|
|
||||||
|
def upload_cert_pem(self, amp, listener_id, pem_filename, pem_file):
|
||||||
|
r = self.put(
|
||||||
|
amp,
|
||||||
|
'listeners/{listener_id}/certificates/{filename}'.format(
|
||||||
|
listener_id=listener_id, filename=pem_filename),
|
||||||
|
data=pem_file)
|
||||||
|
return exc.check_exception(r)
|
||||||
|
|
||||||
|
def get_cert_md5sum(self, amp, listener_id, pem_filename):
|
||||||
|
r = self.get(amp,
|
||||||
|
'listeners/{listener_id}/certificates/{filename}'.format(
|
||||||
|
listener_id=listener_id, filename=pem_filename))
|
||||||
|
if exc.check_exception(r):
|
||||||
|
return r.json().get("md5sum")
|
||||||
|
|
||||||
|
def delete_listener(self, amp, listener_id):
|
||||||
|
r = self.delete(
|
||||||
|
amp, 'listeners/{listener_id}'.format(listener_id=listener_id))
|
||||||
|
return exc.check_exception(r)
|
||||||
|
|
||||||
|
def get_info(self, amp):
|
||||||
|
r = self.get(amp, "info")
|
||||||
|
if exc.check_exception(r):
|
||||||
|
return r.json()
|
||||||
|
|
||||||
|
def get_details(self, amp):
|
||||||
|
r = self.get(amp, "details")
|
||||||
|
if exc.check_exception(r):
|
||||||
|
return r.json()
|
||||||
|
|
||||||
|
def get_all_listeners(self, amp):
|
||||||
|
r = self.get(amp, "listeners")
|
||||||
|
if exc.check_exception(r):
|
||||||
|
return r.json()
|
||||||
|
|
||||||
|
def delete_cert_pem(self, amp, listener_id, pem_filename):
|
||||||
|
r = self.delete(
|
||||||
|
amp,
|
||||||
|
'listeners/{listener_id}/certificates/{filename}'.format(
|
||||||
|
listener_id=listener_id, filename=pem_filename))
|
||||||
|
return exc.check_exception(r)
|
||||||
|
|
||||||
|
def plug_network(self, amp):
|
||||||
|
r = self.post(amp, 'plug/network')
|
||||||
|
return exc.check_exception(r)
|
||||||
|
|
||||||
|
def plug_vip(self, amp, vip):
|
||||||
|
r = self.post(amp, 'plug/vip/{vip}'.format(vip=vip))
|
||||||
|
return exc.check_exception(r)
|
|
@ -93,10 +93,19 @@ class LocalCertGenerator(cert_gen.CertGenerator):
|
||||||
cls._validate_cert(ca_cert, ca_key, ca_key_pass)
|
cls._validate_cert(ca_cert, ca_key, ca_key_pass)
|
||||||
if not ca_digest:
|
if not ca_digest:
|
||||||
ca_digest = CONF.certificates.signing_digest
|
ca_digest = CONF.certificates.signing_digest
|
||||||
|
if not ca_cert:
|
||||||
|
with open(CONF.certificates.ca_certificate, 'r') as f:
|
||||||
|
ca_cert = f.read()
|
||||||
|
if not ca_key:
|
||||||
|
with open(CONF.certificates.ca_private_key, 'r') as f:
|
||||||
|
ca_key = f.read()
|
||||||
|
if not ca_key_pass:
|
||||||
|
ca_key_pass = CONF.certificates.ca_private_key_passphrase
|
||||||
|
|
||||||
try:
|
try:
|
||||||
lo_cert = crypto.load_certificate(crypto.FILETYPE_PEM, ca_cert)
|
lo_cert = crypto.load_certificate(crypto.FILETYPE_PEM, ca_cert)
|
||||||
lo_key = crypto.load_privatekey(crypto.FILETYPE_PEM, ca_key,
|
lo_key = crypto.load_privatekey(crypto.FILETYPE_PEM, ca_key,
|
||||||
passphrase=ca_key_pass)
|
ca_key_pass)
|
||||||
lo_req = crypto.load_certificate_request(crypto.FILETYPE_PEM, csr)
|
lo_req = crypto.load_certificate_request(crypto.FILETYPE_PEM, csr)
|
||||||
|
|
||||||
new_cert = crypto.X509()
|
new_cert = crypto.X509()
|
||||||
|
@ -147,11 +156,17 @@ class LocalCertGenerator(cert_gen.CertGenerator):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _generate_csr(cls, cn, private_key, passphrase=None):
|
def _generate_csr(cls, cn, private_key, passphrase=None):
|
||||||
pk = crypto.load_privatekey(
|
if passphrase:
|
||||||
crypto.FILETYPE_PEM,
|
pk = crypto.load_privatekey(
|
||||||
private_key,
|
crypto.FILETYPE_PEM,
|
||||||
passphrase
|
private_key,
|
||||||
)
|
passphrase
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
pk = crypto.load_privatekey(
|
||||||
|
crypto.FILETYPE_PEM,
|
||||||
|
private_key
|
||||||
|
)
|
||||||
csr = crypto.X509Req()
|
csr = crypto.X509Req()
|
||||||
csr.set_pubkey(pk)
|
csr.set_pubkey(pk)
|
||||||
subject = csr.get_subject()
|
subject = csr.get_subject()
|
||||||
|
|
|
@ -113,6 +113,9 @@ haproxy_amphora_opts = [
|
||||||
cfg.IntOpt('connection_retry_interval',
|
cfg.IntOpt('connection_retry_interval',
|
||||||
default=5,
|
default=5,
|
||||||
help=_('Retry timeout between attempts in seconds.')),
|
help=_('Retry timeout between attempts in seconds.')),
|
||||||
|
cfg.StrOpt('cert_manager',
|
||||||
|
default='barbican_cert_manager',
|
||||||
|
help=_('Name of the cert manager to use')),
|
||||||
|
|
||||||
# REST server
|
# REST server
|
||||||
cfg.StrOpt('bind_host', default='0.0.0.0',
|
cfg.StrOpt('bind_host', default='0.0.0.0',
|
||||||
|
@ -135,7 +138,12 @@ haproxy_amphora_opts = [
|
||||||
cfg.StrOpt('agent_server_network_dir',
|
cfg.StrOpt('agent_server_network_dir',
|
||||||
default='/etc/network/interfaces.d/',
|
default='/etc/network/interfaces.d/',
|
||||||
help=_("The directory where new network interfaces "
|
help=_("The directory where new network interfaces "
|
||||||
"are located"))
|
"are located")),
|
||||||
|
# REST client
|
||||||
|
cfg.StrOpt('client_cert', default='/etc/octavia/certs/client.pem',
|
||||||
|
help=_("The client certificate to talk to the agent")),
|
||||||
|
cfg.StrOpt('server_ca', default='/etc/octavia/certs/server_ca.pem',
|
||||||
|
help=_("The ca which signed the server certificates")),
|
||||||
]
|
]
|
||||||
|
|
||||||
controller_worker_opts = [
|
controller_worker_opts = [
|
||||||
|
@ -160,6 +168,9 @@ controller_worker_opts = [
|
||||||
cfg.ListOpt('amp_secgroup_list',
|
cfg.ListOpt('amp_secgroup_list',
|
||||||
default='',
|
default='',
|
||||||
help=_('List of security groups to attach to the Amphora')),
|
help=_('List of security groups to attach to the Amphora')),
|
||||||
|
cfg.StrOpt('client_ca',
|
||||||
|
default='/etc/octavia/certs/ca_01.pem',
|
||||||
|
help=_('Client CA for the amphora agent to use')),
|
||||||
cfg.StrOpt('amphora_driver',
|
cfg.StrOpt('amphora_driver',
|
||||||
default='amphora_noop_driver',
|
default='amphora_noop_driver',
|
||||||
help=_('Name of the amphora driver to use')),
|
help=_('Name of the amphora driver to use')),
|
||||||
|
@ -168,7 +179,11 @@ controller_worker_opts = [
|
||||||
help=_('Name of the compute driver to use')),
|
help=_('Name of the compute driver to use')),
|
||||||
cfg.StrOpt('network_driver',
|
cfg.StrOpt('network_driver',
|
||||||
default='network_noop_driver',
|
default='network_noop_driver',
|
||||||
help=_('Name of the network driver to use'))
|
help=_('Name of the network driver to use')),
|
||||||
|
cfg.StrOpt('cert_generator',
|
||||||
|
default='local_cert_generator',
|
||||||
|
help=_('Name of the cert generator to use'))
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
task_flow_opts = [
|
task_flow_opts = [
|
||||||
|
@ -194,6 +209,7 @@ cfg.CONF.register_cli_opts(core_cli_opts)
|
||||||
cfg.CONF.import_group('keystone_authtoken', 'keystonemiddleware.auth_token')
|
cfg.CONF.import_group('keystone_authtoken', 'keystonemiddleware.auth_token')
|
||||||
cfg.CONF.register_opts(keystone_authtoken_v3_opts,
|
cfg.CONF.register_opts(keystone_authtoken_v3_opts,
|
||||||
group='keystone_authtoken_v3')
|
group='keystone_authtoken_v3')
|
||||||
|
|
||||||
# Ensure that the control exchange is set correctly
|
# Ensure that the control exchange is set correctly
|
||||||
messaging.set_transport_defaults(control_exchange='octavia')
|
messaging.set_transport_defaults(control_exchange='octavia')
|
||||||
_SQL_CONNECTION_DEFAULT = 'sqlite://'
|
_SQL_CONNECTION_DEFAULT = 'sqlite://'
|
||||||
|
|
|
@ -94,6 +94,7 @@ VIP = 'vip'
|
||||||
POOL = 'pool'
|
POOL = 'pool'
|
||||||
POOL_ID = 'pool_id'
|
POOL_ID = 'pool_id'
|
||||||
OBJECT = 'object'
|
OBJECT = 'object'
|
||||||
|
SERVER_PEM = 'server_pem'
|
||||||
|
|
||||||
CREATE_AMPHORA_FLOW = 'octavia-create-amphora-flow'
|
CREATE_AMPHORA_FLOW = 'octavia-create-amphora-flow'
|
||||||
CREATE_AMPHORA_FOR_LB_FLOW = 'octavia-create-amp-for-lb-flow'
|
CREATE_AMPHORA_FOR_LB_FLOW = 'octavia-create-amp-for-lb-flow'
|
||||||
|
|
|
@ -37,6 +37,10 @@ class BaseDataModel(object):
|
||||||
return self.to_dict() == other.to_dict()
|
return self.to_dict() == other.to_dict()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, dict):
|
||||||
|
return cls(**dict)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _name(cls):
|
def _name(cls):
|
||||||
"""Returns class name in a more human readable form."""
|
"""Returns class name in a more human readable form."""
|
||||||
|
|
|
@ -20,17 +20,21 @@ from taskflow import retry
|
||||||
from octavia.common import constants
|
from octavia.common import constants
|
||||||
from octavia.controller.worker.flows import load_balancer_flows
|
from octavia.controller.worker.flows import load_balancer_flows
|
||||||
from octavia.controller.worker.tasks import amphora_driver_tasks
|
from octavia.controller.worker.tasks import amphora_driver_tasks
|
||||||
|
from octavia.controller.worker.tasks import cert_task
|
||||||
from octavia.controller.worker.tasks import compute_tasks
|
from octavia.controller.worker.tasks import compute_tasks
|
||||||
from octavia.controller.worker.tasks import controller_tasks
|
from octavia.controller.worker.tasks import controller_tasks
|
||||||
from octavia.controller.worker.tasks import database_tasks
|
from octavia.controller.worker.tasks import database_tasks
|
||||||
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
CONF.import_group('controller_worker', 'octavia.common.config')
|
CONF.import_group('controller_worker', 'octavia.common.config')
|
||||||
|
|
||||||
|
|
||||||
class AmphoraFlows(object):
|
class AmphoraFlows(object):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
# for some reason only this has the values from the config file
|
||||||
|
self.REST_AMPHORA_DRIVER = (CONF.controller_worker.amphora_driver ==
|
||||||
|
'amphora_haproxy_rest_driver')
|
||||||
self._lb_flows = load_balancer_flows.LoadBalancerFlows()
|
self._lb_flows = load_balancer_flows.LoadBalancerFlows()
|
||||||
|
|
||||||
def get_create_amphora_flow(self):
|
def get_create_amphora_flow(self):
|
||||||
|
@ -45,9 +49,16 @@ class AmphoraFlows(object):
|
||||||
create_amphora_flow = linear_flow.Flow(constants.CREATE_AMPHORA_FLOW)
|
create_amphora_flow = linear_flow.Flow(constants.CREATE_AMPHORA_FLOW)
|
||||||
create_amphora_flow.add(database_tasks.CreateAmphoraInDB(
|
create_amphora_flow.add(database_tasks.CreateAmphoraInDB(
|
||||||
provides=constants.AMPHORA_ID))
|
provides=constants.AMPHORA_ID))
|
||||||
create_amphora_flow.add(compute_tasks.ComputeCreate(
|
if self.REST_AMPHORA_DRIVER:
|
||||||
requires=constants.AMPHORA_ID,
|
create_amphora_flow.add(cert_task.GenerateServerPEMTask(
|
||||||
provides=constants.COMPUTE_ID))
|
provides=constants.SERVER_PEM))
|
||||||
|
create_amphora_flow.add(compute_tasks.CertComputeCreate(
|
||||||
|
requires=(constants.AMPHORA_ID, constants.SERVER_PEM),
|
||||||
|
provides=constants.COMPUTE_ID))
|
||||||
|
else:
|
||||||
|
create_amphora_flow.add(compute_tasks.ComputeCreate(
|
||||||
|
requires=constants.AMPHORA_ID,
|
||||||
|
provides=constants.COMPUTE_ID))
|
||||||
create_amphora_flow.add(database_tasks.MarkAmphoraBootingInDB(
|
create_amphora_flow.add(database_tasks.MarkAmphoraBootingInDB(
|
||||||
requires=(constants.AMPHORA_ID, constants.COMPUTE_ID)))
|
requires=(constants.AMPHORA_ID, constants.COMPUTE_ID)))
|
||||||
wait_flow = linear_flow.Flow('wait_for_amphora',
|
wait_flow = linear_flow.Flow('wait_for_amphora',
|
||||||
|
@ -80,9 +91,16 @@ class AmphoraFlows(object):
|
||||||
CREATE_AMPHORA_FOR_LB_FLOW)
|
CREATE_AMPHORA_FOR_LB_FLOW)
|
||||||
create_amp_for_lb_flow.add(database_tasks.CreateAmphoraInDB(
|
create_amp_for_lb_flow.add(database_tasks.CreateAmphoraInDB(
|
||||||
provides=constants.AMPHORA_ID))
|
provides=constants.AMPHORA_ID))
|
||||||
create_amp_for_lb_flow.add(compute_tasks.ComputeCreate(
|
if self.REST_AMPHORA_DRIVER:
|
||||||
requires=constants.AMPHORA_ID,
|
create_amp_for_lb_flow.add(cert_task.GenerateServerPEMTask(
|
||||||
provides=constants.COMPUTE_ID))
|
provides=constants.SERVER_PEM))
|
||||||
|
create_amp_for_lb_flow.add(compute_tasks.CertComputeCreate(
|
||||||
|
requires=(constants.AMPHORA_ID, constants.SERVER_PEM),
|
||||||
|
provides=constants.COMPUTE_ID))
|
||||||
|
else:
|
||||||
|
create_amp_for_lb_flow.add(compute_tasks.ComputeCreate(
|
||||||
|
requires=constants.AMPHORA_ID,
|
||||||
|
provides=constants.COMPUTE_ID))
|
||||||
create_amp_for_lb_flow.add(database_tasks.UpdateAmphoraComputeId(
|
create_amp_for_lb_flow.add(database_tasks.UpdateAmphoraComputeId(
|
||||||
requires=(constants.AMPHORA_ID, constants.COMPUTE_ID)))
|
requires=(constants.AMPHORA_ID, constants.COMPUTE_ID)))
|
||||||
create_amp_for_lb_flow.add(database_tasks.MarkAmphoraBootingInDB(
|
create_amp_for_lb_flow.add(database_tasks.MarkAmphoraBootingInDB(
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from oslo.config import cfg
|
||||||
|
from stevedore import driver as stevedore_driver
|
||||||
|
from taskflow import task
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
CONF.import_group('controller_worker', 'octavia.common.config')
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
CERT_VALIDITY = 365
|
||||||
|
|
||||||
|
|
||||||
|
class BaseCertTask(task.Task):
|
||||||
|
"""Base task to load drivers common to the tasks."""
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super(BaseCertTask, self).__init__(**kwargs)
|
||||||
|
self.cert_generator = stevedore_driver.DriverManager(
|
||||||
|
namespace='octavia.cert_generator',
|
||||||
|
name=CONF.controller_worker.cert_generator,
|
||||||
|
invoke_on_load=True,
|
||||||
|
).driver
|
||||||
|
|
||||||
|
|
||||||
|
class GenerateServerPEMTask(BaseCertTask):
|
||||||
|
"""Create the server certs for the agent comm
|
||||||
|
|
||||||
|
Use the amphora_id for the CN
|
||||||
|
"""
|
||||||
|
|
||||||
|
def execute(self, amphora_id):
|
||||||
|
cert = self.cert_generator.generate_cert_key_pair(
|
||||||
|
cn=amphora_id,
|
||||||
|
validity=CERT_VALIDITY)
|
||||||
|
|
||||||
|
return cert.certificate + cert.private_key
|
|
@ -46,12 +46,14 @@ class BaseComputeTask(task.Task):
|
||||||
class ComputeCreate(BaseComputeTask):
|
class ComputeCreate(BaseComputeTask):
|
||||||
"""Create the compute instance for a new amphora."""
|
"""Create the compute instance for a new amphora."""
|
||||||
|
|
||||||
def execute(self, amphora_id):
|
def execute(self, amphora_id, config_drive_files=None):
|
||||||
"""Create an amphora
|
"""Create an amphora
|
||||||
|
|
||||||
:returns: an amphora
|
:returns: an amphora
|
||||||
"""
|
"""
|
||||||
LOG.debug("Nova Create execute for amphora with id %s" % amphora_id)
|
|
||||||
|
LOG.debug("Nova Create execute for amphora with id %s"
|
||||||
|
% amphora_id)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
compute_id = self.compute.build(
|
compute_id = self.compute.build(
|
||||||
|
@ -60,7 +62,8 @@ class ComputeCreate(BaseComputeTask):
|
||||||
image_id=CONF.controller_worker.amp_image_id,
|
image_id=CONF.controller_worker.amp_image_id,
|
||||||
key_name=CONF.controller_worker.amp_ssh_key_name,
|
key_name=CONF.controller_worker.amp_ssh_key_name,
|
||||||
sec_groups=CONF.controller_worker.amp_secgroup_list,
|
sec_groups=CONF.controller_worker.amp_secgroup_list,
|
||||||
network_ids=[CONF.controller_worker.amp_network])
|
network_ids=[CONF.controller_worker.amp_network],
|
||||||
|
config_drive_files=config_drive_files)
|
||||||
|
|
||||||
LOG.debug("Server created with id: %s for amphora id: %s" %
|
LOG.debug("Server created with id: %s for amphora id: %s" %
|
||||||
(compute_id, amphora_id))
|
(compute_id, amphora_id))
|
||||||
|
@ -90,6 +93,23 @@ class ComputeCreate(BaseComputeTask):
|
||||||
" with exception %s"), e)
|
" with exception %s"), e)
|
||||||
|
|
||||||
|
|
||||||
|
class CertComputeCreate(ComputeCreate):
|
||||||
|
def execute(self, amphora_id, server_pem):
|
||||||
|
"""Create an amphora
|
||||||
|
|
||||||
|
:returns: an amphora
|
||||||
|
"""
|
||||||
|
|
||||||
|
# load client certificate
|
||||||
|
with open(CONF.controller_worker.client_ca, 'r') as client_ca:
|
||||||
|
config_drive_files = {
|
||||||
|
# '/etc/octavia/octavia.conf'
|
||||||
|
'/etc/octavia/certs/server.pem': server_pem,
|
||||||
|
'/etc/octavia/certs/client_ca.pem': client_ca}
|
||||||
|
return super(CertComputeCreate, self).execute(amphora_id,
|
||||||
|
config_drive_files)
|
||||||
|
|
||||||
|
|
||||||
class DeleteAmphoraeOnLoadBalancer(BaseComputeTask):
|
class DeleteAmphoraeOnLoadBalancer(BaseComputeTask):
|
||||||
"""Delete the amphorae on a load balancer.
|
"""Delete the amphorae on a load balancer.
|
||||||
|
|
||||||
|
|
|
@ -349,7 +349,8 @@ class ServerTestCase(base.TestCase):
|
||||||
details='No certificate with filename: test.pem',
|
details='No certificate with filename: test.pem',
|
||||||
message='Certificate Not Found'),
|
message='Certificate Not Found'),
|
||||||
json.loads(rv.data))
|
json.loads(rv.data))
|
||||||
mock_exists.assert_called_once_with('/tmp/123/test.pem')
|
mock_exists.assert_called_once_with(
|
||||||
|
'/var/lib/octavia/certs/123/test.pem')
|
||||||
|
|
||||||
# wrong file name
|
# wrong file name
|
||||||
mock_exists.side_effect = [True]
|
mock_exists.side_effect = [True]
|
||||||
|
@ -363,7 +364,8 @@ class ServerTestCase(base.TestCase):
|
||||||
'/listeners/123/certificates/test.pem')
|
'/listeners/123/certificates/test.pem')
|
||||||
self.assertEqual(200, rv.status_code)
|
self.assertEqual(200, rv.status_code)
|
||||||
self.assertEqual(OK, json.loads(rv.data))
|
self.assertEqual(OK, json.loads(rv.data))
|
||||||
mock_remove.assert_called_once_with('/tmp/123/test.pem')
|
mock_remove.assert_called_once_with(
|
||||||
|
'/var/lib/octavia/certs/123/test.pem')
|
||||||
|
|
||||||
@mock.patch('os.path.exists')
|
@mock.patch('os.path.exists')
|
||||||
def test_get_certificate_md5(self, mock_exists):
|
def test_get_certificate_md5(self, mock_exists):
|
||||||
|
@ -381,7 +383,7 @@ class ServerTestCase(base.TestCase):
|
||||||
details='No certificate with filename: test.pem',
|
details='No certificate with filename: test.pem',
|
||||||
message='Certificate Not Found'),
|
message='Certificate Not Found'),
|
||||||
json.loads(rv.data))
|
json.loads(rv.data))
|
||||||
mock_exists.assert_called_with('/tmp/123/test.pem')
|
mock_exists.assert_called_with('/var/lib/octavia/certs/123/test.pem')
|
||||||
|
|
||||||
# wrong file name
|
# wrong file name
|
||||||
mock_exists.side_effect = [True]
|
mock_exists.side_effect = [True]
|
||||||
|
|
|
@ -0,0 +1,601 @@
|
||||||
|
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||||
|
# 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 mock
|
||||||
|
from oslo_utils import uuidutils
|
||||||
|
import requests_mock
|
||||||
|
|
||||||
|
from octavia.amphorae.drivers.haproxy import exceptions as exc
|
||||||
|
from octavia.amphorae.drivers.haproxy import rest_api_driver as driver
|
||||||
|
from octavia.db import models
|
||||||
|
from octavia.tests.unit import base as base
|
||||||
|
from octavia.tests.unit.common.sample_configs import sample_configs
|
||||||
|
|
||||||
|
|
||||||
|
class HaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
|
||||||
|
FAKE_UUID_1 = uuidutils.generate_uuid()
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(HaproxyAmphoraLoadBalancerDriverTest, self).setUp()
|
||||||
|
self.driver = driver.HaproxyAmphoraLoadBalancerDriver()
|
||||||
|
|
||||||
|
self.driver.cert_manager = mock.MagicMock()
|
||||||
|
self.driver.client = mock.MagicMock()
|
||||||
|
self.driver.jinja = mock.MagicMock()
|
||||||
|
|
||||||
|
# Build sample Listener and VIP configs
|
||||||
|
self.sl = sample_configs.sample_listener_tuple(tls=True, sni=True)
|
||||||
|
self.amp = self.sl.load_balancer.amphorae[0]
|
||||||
|
self.sv = sample_configs.sample_vip_tuple()
|
||||||
|
self.lb = self.sl.load_balancer
|
||||||
|
|
||||||
|
@mock.patch('octavia.common.tls_utils.cert_parser.get_host_names')
|
||||||
|
def test_update(self, mock_cert):
|
||||||
|
mock_cert.return_value = {'cn': 'fakeCN'}
|
||||||
|
self.driver.client.get_cert_md5sum.side_effect = [
|
||||||
|
exc.NotFound, 'Fake_MD5', 'd41d8cd98f00b204e9800998ecf8427e']
|
||||||
|
self.driver.jinja.build_config.side_effect = ['fake_config']
|
||||||
|
self.driver.client.get_listener_status.side_effect = [
|
||||||
|
dict(status='ACTIVE')]
|
||||||
|
|
||||||
|
# Execute driver method
|
||||||
|
self.driver.update(self.sl, self.sv)
|
||||||
|
|
||||||
|
# verify result
|
||||||
|
# this is called 3 times
|
||||||
|
self.driver.client.get_cert_md5sum.assert_called_with(
|
||||||
|
self.amp, self.sl.id, 'fakeCN.pem')
|
||||||
|
# this is called twice (last MD5 matches)
|
||||||
|
self.driver.client.upload_cert_pem.assert_called_with(
|
||||||
|
self.amp, self.sl.id, 'fakeCN.pem', '')
|
||||||
|
self.assertEqual(2, self.driver.client.upload_cert_pem.call_count)
|
||||||
|
# upload only one config file
|
||||||
|
self.driver.client.upload_config.assert_called_once_with(
|
||||||
|
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)
|
||||||
|
|
||||||
|
# listener down
|
||||||
|
self.driver.client.get_cert_md5sum.side_effect = [
|
||||||
|
'd41d8cd98f00b204e9800998ecf8427e'] * 3
|
||||||
|
self.driver.jinja.build_config.side_effect = ['fake_config']
|
||||||
|
self.driver.client.get_listener_status.side_effect = [
|
||||||
|
dict(status='BLAH')]
|
||||||
|
|
||||||
|
self.driver.update(self.sl, self.sv)
|
||||||
|
|
||||||
|
self.driver.client.start_listener.assert_called_once_with(
|
||||||
|
self.amp, self.sl.id)
|
||||||
|
|
||||||
|
def test_stop(self):
|
||||||
|
# Execute driver method
|
||||||
|
self.driver.stop(self.sl, self.sv)
|
||||||
|
self.driver.client.stop_listener.assert_called_once_with(
|
||||||
|
self.amp, self.sl.id)
|
||||||
|
|
||||||
|
def test_start(self):
|
||||||
|
# Execute driver method
|
||||||
|
self.driver.start(self.sl, self.sv)
|
||||||
|
self.driver.client.start_listener.assert_called_once_with(
|
||||||
|
self.amp, self.sl.id)
|
||||||
|
|
||||||
|
def test_delete(self):
|
||||||
|
# Execute driver method
|
||||||
|
self.driver.delete(self.sl, self.sv)
|
||||||
|
self.driver.client.delete_listener.assert_called_once_with(
|
||||||
|
self.amp, self.sl.id)
|
||||||
|
|
||||||
|
def test_get_info(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_get_diagnostics(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_finalize_amphora(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_post_vip_plug(self):
|
||||||
|
self.driver.post_vip_plug(self.lb)
|
||||||
|
self.driver.client.plug_vip.assert_called_once_with(
|
||||||
|
self.amp, self.lb.vip.ip_address)
|
||||||
|
|
||||||
|
def test_post_network_plug(self):
|
||||||
|
self.driver.post_network_plug(self.amp)
|
||||||
|
self.driver.client.plug_network.assert_called_once_with(self.amp)
|
||||||
|
|
||||||
|
|
||||||
|
class AmphoraAPIClientTest(base.TestCase):
|
||||||
|
FAKE_UUID_1 = uuidutils.generate_uuid()
|
||||||
|
FAKE_PEM_FILENAME = "file_name"
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(AmphoraAPIClientTest, self).setUp()
|
||||||
|
self.driver = driver.AmphoraAPIClient()
|
||||||
|
self.base_url = "https://127.0.0.1:8443/0.5"
|
||||||
|
self.amp = models.Amphora(lb_network_ip='127.0.0.1', compute_id='123')
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_get_info(self, m):
|
||||||
|
info = {"hostname": "some_hostname", "version": "some_version",
|
||||||
|
"api_version": "0.5", "uuid": self.FAKE_UUID_1}
|
||||||
|
m.get("{base}/info".format(base=self.base_url),
|
||||||
|
json=info)
|
||||||
|
information = self.driver.get_info(self.amp)
|
||||||
|
self.assertEqual(info, information)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_get_info_unauthorized(self, m):
|
||||||
|
m.get("{base}/info".format(base=self.base_url),
|
||||||
|
status_code=401)
|
||||||
|
self.assertRaises(exc.Unauthorized, self.driver.get_info, self.amp)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_get_info_missing(self, m):
|
||||||
|
m.get("{base}/info".format(base=self.base_url),
|
||||||
|
status_code=404)
|
||||||
|
self.assertRaises(exc.NotFound, self.driver.get_info, self.amp)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_get_info_server_error(self, m):
|
||||||
|
m.get("{base}/info".format(base=self.base_url),
|
||||||
|
status_code=500)
|
||||||
|
self.assertRaises(exc.InternalServerError, self.driver.get_info,
|
||||||
|
self.amp)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_get_info_service_unavailable(self, m):
|
||||||
|
m.get("{base}/info".format(base=self.base_url),
|
||||||
|
status_code=503)
|
||||||
|
self.assertRaises(exc.ServiceUnavailable, self.driver.get_info,
|
||||||
|
self.amp)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_get_details(self, m):
|
||||||
|
details = {"hostname": "some_hostname", "version": "some_version",
|
||||||
|
"api_version": "0.5", "uuid": self.FAKE_UUID_1,
|
||||||
|
"network_tx": "some_tx", "network_rx": "some_rx",
|
||||||
|
"active": True, "haproxy_count": 10}
|
||||||
|
m.get("{base}/details".format(base=self.base_url),
|
||||||
|
json=details)
|
||||||
|
amp_details = self.driver.get_details(self.amp)
|
||||||
|
self.assertEqual(details, amp_details)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_get_details_unauthorized(self, m):
|
||||||
|
m.get("{base}/details".format(base=self.base_url),
|
||||||
|
status_code=401)
|
||||||
|
self.assertRaises(exc.Unauthorized, self.driver.get_details, self.amp)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_get_details_missing(self, m):
|
||||||
|
m.get("{base}/details".format(base=self.base_url),
|
||||||
|
status_code=404)
|
||||||
|
self.assertRaises(exc.NotFound, self.driver.get_details, self.amp)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_get_details_server_error(self, m):
|
||||||
|
m.get("{base}/details".format(base=self.base_url),
|
||||||
|
status_code=500)
|
||||||
|
self.assertRaises(exc.InternalServerError, self.driver.get_details,
|
||||||
|
self.amp)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_get_details_service_unavailable(self, m):
|
||||||
|
m.get("{base}/details".format(base=self.base_url),
|
||||||
|
status_code=503)
|
||||||
|
self.assertRaises(exc.ServiceUnavailable, self.driver.get_details,
|
||||||
|
self.amp)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_get_all_listeners(self, m):
|
||||||
|
listeners = [{"status": "ONLINE", "provisioning_status": "ACTIVE",
|
||||||
|
"type": "PASSIVE", "uuid": self.FAKE_UUID_1}]
|
||||||
|
m.get("{base}/listeners".format(base=self.base_url),
|
||||||
|
json=listeners)
|
||||||
|
all_listeners = self.driver.get_all_listeners(self.amp)
|
||||||
|
self.assertEqual(listeners, all_listeners)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_get_all_listeners_unauthorized(self, m):
|
||||||
|
m.get("{base}/listeners".format(base=self.base_url),
|
||||||
|
status_code=401)
|
||||||
|
self.assertRaises(exc.Unauthorized, self.driver.get_all_listeners,
|
||||||
|
self.amp)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_get_all_listeners_missing(self, m):
|
||||||
|
m.get("{base}/listeners".format(base=self.base_url),
|
||||||
|
status_code=404)
|
||||||
|
self.assertRaises(exc.NotFound, self.driver.get_all_listeners,
|
||||||
|
self.amp)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_get_all_listeners_server_error(self, m):
|
||||||
|
m.get("{base}/listeners".format(base=self.base_url),
|
||||||
|
status_code=500)
|
||||||
|
self.assertRaises(exc.InternalServerError,
|
||||||
|
self.driver.get_all_listeners, self.amp)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_get_all_listeners_service_unavailable(self, m):
|
||||||
|
m.get("{base}/listeners".format(base=self.base_url),
|
||||||
|
status_code=503)
|
||||||
|
self.assertRaises(exc.ServiceUnavailable,
|
||||||
|
self.driver.get_all_listeners, self.amp)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_get_listener_status(self, m):
|
||||||
|
listener = {"status": "ONLINE", "provisioning_status": "ACTIVE",
|
||||||
|
"type": "PASSIVE", "uuid": self.FAKE_UUID_1}
|
||||||
|
m.get("{base}/listeners/{listener_id}".format(
|
||||||
|
base=self.base_url, listener_id=self.FAKE_UUID_1),
|
||||||
|
json=listener)
|
||||||
|
status = self.driver.get_listener_status(self.amp, self.FAKE_UUID_1)
|
||||||
|
self.assertEqual(listener, status)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_get_listener_status_unauthorized(self, m):
|
||||||
|
m.get("{base}/listeners/{listener_id}".format(
|
||||||
|
base=self.base_url, listener_id=self.FAKE_UUID_1),
|
||||||
|
status_code=401)
|
||||||
|
self.assertRaises(exc.Unauthorized,
|
||||||
|
self.driver.get_listener_status, self.amp,
|
||||||
|
self.FAKE_UUID_1)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_get_listener_status_missing(self, m):
|
||||||
|
m.get("{base}/listeners/{listener_id}".format(
|
||||||
|
base=self.base_url, listener_id=self.FAKE_UUID_1),
|
||||||
|
status_code=404)
|
||||||
|
self.assertRaises(exc.NotFound,
|
||||||
|
self.driver.get_listener_status, self.amp,
|
||||||
|
self.FAKE_UUID_1)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_get_listener_status_server_error(self, m):
|
||||||
|
m.get("{base}/listeners/{listener_id}".format(
|
||||||
|
base=self.base_url, listener_id=self.FAKE_UUID_1),
|
||||||
|
status_code=500)
|
||||||
|
self.assertRaises(exc.InternalServerError,
|
||||||
|
self.driver.get_listener_status, self.amp,
|
||||||
|
self.FAKE_UUID_1)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_get_listener_status_service_unavailable(self, m):
|
||||||
|
m.get("{base}/listeners/{listener_id}".format(
|
||||||
|
base=self.base_url, listener_id=self.FAKE_UUID_1),
|
||||||
|
status_code=503)
|
||||||
|
self.assertRaises(exc.ServiceUnavailable,
|
||||||
|
self.driver.get_listener_status, self.amp,
|
||||||
|
self.FAKE_UUID_1)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_start_listener(self, m):
|
||||||
|
m.put("{base}/listeners/{listener_id}/start".format(
|
||||||
|
base=self.base_url, listener_id=self.FAKE_UUID_1))
|
||||||
|
self.driver.start_listener(self.amp, self.FAKE_UUID_1)
|
||||||
|
self.assertTrue(m.called)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_start_listener_missing(self, m):
|
||||||
|
m.put("{base}/listeners/{listener_id}/start".format(
|
||||||
|
base=self.base_url, listener_id=self.FAKE_UUID_1),
|
||||||
|
status_code=404)
|
||||||
|
self.assertRaises(exc.NotFound, self.driver.start_listener,
|
||||||
|
self.amp, self.FAKE_UUID_1)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_start_listener_unauthorized(self, m):
|
||||||
|
m.put("{base}/listeners/{listener_id}/start".format(
|
||||||
|
base=self.base_url, listener_id=self.FAKE_UUID_1),
|
||||||
|
status_code=401)
|
||||||
|
self.assertRaises(exc.Unauthorized, self.driver.start_listener,
|
||||||
|
self.amp, self.FAKE_UUID_1)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_start_listener_server_error(self, m):
|
||||||
|
m.put("{base}/listeners/{listener_id}/start".format(
|
||||||
|
base=self.base_url, listener_id=self.FAKE_UUID_1),
|
||||||
|
status_code=500)
|
||||||
|
self.assertRaises(exc.InternalServerError, self.driver.start_listener,
|
||||||
|
self.amp, self.FAKE_UUID_1)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_start_listener_service_unavailable(self, m):
|
||||||
|
m.put("{base}/listeners/{listener_id}/start".format(
|
||||||
|
base=self.base_url, listener_id=self.FAKE_UUID_1),
|
||||||
|
status_code=503)
|
||||||
|
self.assertRaises(exc.ServiceUnavailable, self.driver.start_listener,
|
||||||
|
self.amp, self.FAKE_UUID_1)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_stop_listener(self, m):
|
||||||
|
m.put("{base}/listeners/{listener_id}/stop".format(
|
||||||
|
base=self.base_url, listener_id=self.FAKE_UUID_1))
|
||||||
|
self.driver.stop_listener(self.amp, self.FAKE_UUID_1)
|
||||||
|
self.assertTrue(m.called)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_stop_listener_missing(self, m):
|
||||||
|
m.put("{base}/listeners/{listener_id}/stop".format(
|
||||||
|
base=self.base_url, listener_id=self.FAKE_UUID_1),
|
||||||
|
status_code=404)
|
||||||
|
self.assertRaises(exc.NotFound, self.driver.stop_listener,
|
||||||
|
self.amp, self.FAKE_UUID_1)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_stop_listener_unauthorized(self, m):
|
||||||
|
m.put("{base}/listeners/{listener_id}/stop".format(
|
||||||
|
base=self.base_url, listener_id=self.FAKE_UUID_1),
|
||||||
|
status_code=401)
|
||||||
|
self.assertRaises(exc.Unauthorized, self.driver.stop_listener,
|
||||||
|
self.amp, self.FAKE_UUID_1)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_stop_listener_server_error(self, m):
|
||||||
|
m.put("{base}/listeners/{listener_id}/stop".format(
|
||||||
|
base=self.base_url, listener_id=self.FAKE_UUID_1),
|
||||||
|
status_code=500)
|
||||||
|
self.assertRaises(exc.InternalServerError, self.driver.stop_listener,
|
||||||
|
self.amp, self.FAKE_UUID_1)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_stop_listener_service_unavailable(self, m):
|
||||||
|
m.put("{base}/listeners/{listener_id}/stop".format(
|
||||||
|
base=self.base_url, listener_id=self.FAKE_UUID_1),
|
||||||
|
status_code=503)
|
||||||
|
self.assertRaises(exc.ServiceUnavailable, self.driver.stop_listener,
|
||||||
|
self.amp, self.FAKE_UUID_1)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_delete_listener(self, m):
|
||||||
|
m.delete("{base}/listeners/{listener_id}".format(
|
||||||
|
base=self.base_url, listener_id=self.FAKE_UUID_1), json={})
|
||||||
|
self.driver.delete_listener(self.amp, self.FAKE_UUID_1)
|
||||||
|
self.assertTrue(m.called)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_delete_listener_missing(self, m):
|
||||||
|
m.delete("{base}/listeners/{listener_id}".format(
|
||||||
|
base=self.base_url, listener_id=self.FAKE_UUID_1),
|
||||||
|
status_code=404)
|
||||||
|
self.assertRaises(exc.NotFound, self.driver.delete_listener,
|
||||||
|
self.amp, self.FAKE_UUID_1)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_delete_listener_unauthorized(self, m):
|
||||||
|
m.delete("{base}/listeners/{listener_id}".format(
|
||||||
|
base=self.base_url, listener_id=self.FAKE_UUID_1),
|
||||||
|
status_code=401)
|
||||||
|
self.assertRaises(exc.Unauthorized, self.driver.delete_listener,
|
||||||
|
self.amp, self.FAKE_UUID_1)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_delete_listener_server_error(self, m):
|
||||||
|
m.delete("{base}/listeners/{listener_id}".format(
|
||||||
|
base=self.base_url, listener_id=self.FAKE_UUID_1),
|
||||||
|
status_code=500)
|
||||||
|
self.assertRaises(exc.InternalServerError, self.driver.delete_listener,
|
||||||
|
self.amp, self.FAKE_UUID_1)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_delete_listener_service_unavailable(self, m):
|
||||||
|
m.delete("{base}/listeners/{listener_id}".format(
|
||||||
|
base=self.base_url, listener_id=self.FAKE_UUID_1),
|
||||||
|
status_code=503)
|
||||||
|
self.assertRaises(exc.ServiceUnavailable, self.driver.delete_listener,
|
||||||
|
self.amp, self.FAKE_UUID_1)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_upload_cert_pem(self, m):
|
||||||
|
m.put("{base}/listeners/{listener_id}/certificates/{filename}".format(
|
||||||
|
base=self.base_url, listener_id=self.FAKE_UUID_1,
|
||||||
|
filename=self.FAKE_PEM_FILENAME))
|
||||||
|
self.driver.upload_cert_pem(self.amp, self.FAKE_UUID_1,
|
||||||
|
self.FAKE_PEM_FILENAME,
|
||||||
|
"some_file")
|
||||||
|
self.assertTrue(m.called)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_upload_invalid_cert_pem(self, m):
|
||||||
|
m.put("{base}/listeners/{listener_id}/certificates/{filename}".format(
|
||||||
|
base=self.base_url, listener_id=self.FAKE_UUID_1,
|
||||||
|
filename=self.FAKE_PEM_FILENAME), status_code=403)
|
||||||
|
self.assertRaises(exc.InvalidRequest, self.driver.upload_cert_pem,
|
||||||
|
self.amp, self.FAKE_UUID_1, self.FAKE_PEM_FILENAME,
|
||||||
|
"some_file")
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_upload_cert_pem_unauthorized(self, m):
|
||||||
|
m.put("{base}/listeners/{listener_id}/certificates/{filename}".format(
|
||||||
|
base=self.base_url, listener_id=self.FAKE_UUID_1,
|
||||||
|
filename=self.FAKE_PEM_FILENAME), status_code=401)
|
||||||
|
self.assertRaises(exc.Unauthorized, self.driver.upload_cert_pem,
|
||||||
|
self.amp, self.FAKE_UUID_1, self.FAKE_PEM_FILENAME,
|
||||||
|
"some_file")
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_upload_cert_pem_server_error(self, m):
|
||||||
|
m.put("{base}/listeners/{listener_id}/certificates/{filename}".format(
|
||||||
|
base=self.base_url, listener_id=self.FAKE_UUID_1,
|
||||||
|
filename=self.FAKE_PEM_FILENAME), status_code=500)
|
||||||
|
self.assertRaises(exc.InternalServerError, self.driver.upload_cert_pem,
|
||||||
|
self.amp, self.FAKE_UUID_1, self.FAKE_PEM_FILENAME,
|
||||||
|
"some_file")
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_upload_cert_pem_service_unavailable(self, m):
|
||||||
|
m.put("{base}/listeners/{listener_id}/certificates/{filename}".format(
|
||||||
|
base=self.base_url, listener_id=self.FAKE_UUID_1,
|
||||||
|
filename=self.FAKE_PEM_FILENAME), status_code=503)
|
||||||
|
self.assertRaises(exc.ServiceUnavailable, self.driver.upload_cert_pem,
|
||||||
|
self.amp, self.FAKE_UUID_1, self.FAKE_PEM_FILENAME,
|
||||||
|
"some_file")
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_get_cert_5sum(self, m):
|
||||||
|
md5sum = {"md5sum": "some_real_sum"}
|
||||||
|
m.get("{base}/listeners/{listener_id}/certificates/{filename}".format(
|
||||||
|
base=self.base_url, listener_id=self.FAKE_UUID_1,
|
||||||
|
filename=self.FAKE_PEM_FILENAME), json=md5sum)
|
||||||
|
sum_test = self.driver.get_cert_md5sum(self.amp, self.FAKE_UUID_1,
|
||||||
|
self.FAKE_PEM_FILENAME)
|
||||||
|
self.assertIsNotNone(sum_test)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_get_cert_5sum_missing(self, m):
|
||||||
|
m.get("{base}/listeners/{listener_id}/certificates/{filename}".format(
|
||||||
|
base=self.base_url, listener_id=self.FAKE_UUID_1,
|
||||||
|
filename=self.FAKE_PEM_FILENAME), status_code=404)
|
||||||
|
self.assertRaises(exc.NotFound, self.driver.get_cert_md5sum,
|
||||||
|
self.amp, self.FAKE_UUID_1, self.FAKE_PEM_FILENAME)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_get_cert_5sum_unauthorized(self, m):
|
||||||
|
m.get("{base}/listeners/{listener_id}/certificates/{filename}".format(
|
||||||
|
base=self.base_url, listener_id=self.FAKE_UUID_1,
|
||||||
|
filename=self.FAKE_PEM_FILENAME), status_code=401)
|
||||||
|
self.assertRaises(exc.Unauthorized, self.driver.get_cert_md5sum,
|
||||||
|
self.amp, self.FAKE_UUID_1, self.FAKE_PEM_FILENAME)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_get_cert_5sum_server_error(self, m):
|
||||||
|
m.get("{base}/listeners/{listener_id}/certificates/{filename}".format(
|
||||||
|
base=self.base_url, listener_id=self.FAKE_UUID_1,
|
||||||
|
filename=self.FAKE_PEM_FILENAME), status_code=500)
|
||||||
|
self.assertRaises(exc.InternalServerError, self.driver.get_cert_md5sum,
|
||||||
|
self.amp, self.FAKE_UUID_1, self.FAKE_PEM_FILENAME)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_get_cert_5sum_service_unavailable(self, m):
|
||||||
|
m.get("{base}/listeners/{listener_id}/certificates/{filename}".format(
|
||||||
|
base=self.base_url, listener_id=self.FAKE_UUID_1,
|
||||||
|
filename=self.FAKE_PEM_FILENAME), status_code=503)
|
||||||
|
self.assertRaises(exc.ServiceUnavailable, self.driver.get_cert_md5sum,
|
||||||
|
self.amp, self.FAKE_UUID_1, self.FAKE_PEM_FILENAME)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_delete_cert_pem(self, m):
|
||||||
|
m.delete(
|
||||||
|
"{base}/listeners/{listener_id}/certificates/{filename}".format(
|
||||||
|
base=self.base_url, listener_id=self.FAKE_UUID_1,
|
||||||
|
filename=self.FAKE_PEM_FILENAME))
|
||||||
|
self.driver.delete_cert_pem(self.amp, self.FAKE_UUID_1,
|
||||||
|
self.FAKE_PEM_FILENAME)
|
||||||
|
self.assertTrue(m.called)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_delete_cert_pem_missing(self, m):
|
||||||
|
m.delete(
|
||||||
|
"{base}/listeners/{listener_id}/certificates/{filename}".format(
|
||||||
|
base=self.base_url, listener_id=self.FAKE_UUID_1,
|
||||||
|
filename=self.FAKE_PEM_FILENAME), status_code=404)
|
||||||
|
self.assertRaises(exc.NotFound, self.driver.delete_cert_pem, self.amp,
|
||||||
|
self.FAKE_UUID_1, self.FAKE_PEM_FILENAME)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_delete_cert_pem_unauthorized(self, m):
|
||||||
|
m.delete(
|
||||||
|
"{base}/listeners/{listener_id}/certificates/{filename}".format(
|
||||||
|
base=self.base_url, listener_id=self.FAKE_UUID_1,
|
||||||
|
filename=self.FAKE_PEM_FILENAME), status_code=401)
|
||||||
|
self.assertRaises(exc.Unauthorized, self.driver.delete_cert_pem,
|
||||||
|
self.amp, self.FAKE_UUID_1, self.FAKE_PEM_FILENAME)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_delete_cert_pem_server_error(self, m):
|
||||||
|
m.delete(
|
||||||
|
"{base}/listeners/{listener_id}/certificates/{filename}".format(
|
||||||
|
base=self.base_url, listener_id=self.FAKE_UUID_1,
|
||||||
|
filename=self.FAKE_PEM_FILENAME), status_code=500)
|
||||||
|
self.assertRaises(exc.InternalServerError, self.driver.delete_cert_pem,
|
||||||
|
self.amp, self.FAKE_UUID_1, self.FAKE_PEM_FILENAME)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_delete_cert_pem_service_unavailable(self, m):
|
||||||
|
m.delete(
|
||||||
|
"{base}/listeners/{listener_id}/certificates/{filename}".format(
|
||||||
|
base=self.base_url, listener_id=self.FAKE_UUID_1,
|
||||||
|
filename=self.FAKE_PEM_FILENAME), status_code=503)
|
||||||
|
self.assertRaises(exc.ServiceUnavailable, self.driver.delete_cert_pem,
|
||||||
|
self.amp, self.FAKE_UUID_1, self.FAKE_PEM_FILENAME)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_upload_config(self, m):
|
||||||
|
config = {"name": "fake_config"}
|
||||||
|
m.put(
|
||||||
|
"{base}/listeners/{listener_id}/haproxy".format(
|
||||||
|
base=self.base_url, listener_id=self.FAKE_UUID_1),
|
||||||
|
json=config)
|
||||||
|
self.driver.upload_config(self.amp, self.FAKE_UUID_1, config)
|
||||||
|
self.assertTrue(m.called)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_upload_invalid_config(self, m):
|
||||||
|
config = '{"name": "bad_config"}'
|
||||||
|
m.put(
|
||||||
|
"{base}/listeners/{listener_id}/haproxy".format(
|
||||||
|
base=self.base_url, listener_id=self.FAKE_UUID_1),
|
||||||
|
status_code=403)
|
||||||
|
self.assertRaises(exc.InvalidRequest, self.driver.upload_config,
|
||||||
|
self.amp, self.FAKE_UUID_1, config)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_upload_config_unauthorized(self, m):
|
||||||
|
config = '{"name": "bad_config"}'
|
||||||
|
m.put(
|
||||||
|
"{base}/listeners/{listener_id}/haproxy".format(
|
||||||
|
base=self.base_url, listener_id=self.FAKE_UUID_1),
|
||||||
|
status_code=401)
|
||||||
|
self.assertRaises(exc.Unauthorized, self.driver.upload_config,
|
||||||
|
self.amp, self.FAKE_UUID_1, config)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_upload_config_server_error(self, m):
|
||||||
|
config = '{"name": "bad_config"}'
|
||||||
|
m.put(
|
||||||
|
"{base}/listeners/{listener_id}/haproxy".format(
|
||||||
|
base=self.base_url, listener_id=self.FAKE_UUID_1),
|
||||||
|
status_code=500)
|
||||||
|
self.assertRaises(exc.InternalServerError, self.driver.upload_config,
|
||||||
|
self.amp, self.FAKE_UUID_1, config)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_upload_config_service_unavailable(self, m):
|
||||||
|
config = '{"name": "bad_config"}'
|
||||||
|
m.put(
|
||||||
|
"{base}/listeners/{listener_id}/haproxy".format(
|
||||||
|
base=self.base_url, listener_id=self.FAKE_UUID_1),
|
||||||
|
status_code=503)
|
||||||
|
self.assertRaises(exc.ServiceUnavailable, self.driver.upload_config,
|
||||||
|
self.amp, self.FAKE_UUID_1, config)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_plug_vip(self, m):
|
||||||
|
FAKE_IP = 'fake'
|
||||||
|
m.post("{base}/plug/vip/{vip}".format(
|
||||||
|
base=self.base_url, vip=FAKE_IP)
|
||||||
|
)
|
||||||
|
self.driver.plug_vip(self.amp, FAKE_IP)
|
||||||
|
self.assertTrue(m.called)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_plug_network(self, m):
|
||||||
|
m.post("{base}/plug/network".format(
|
||||||
|
base=self.base_url)
|
||||||
|
)
|
||||||
|
self.driver.plug_network(self.amp)
|
||||||
|
self.assertTrue(m.called)
|
|
@ -27,6 +27,8 @@ AUTH_VERSION = '2'
|
||||||
class TestAmphoraFlows(base.TestCase):
|
class TestAmphoraFlows(base.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
cfg.CONF.set_override('amphora_driver', 'amphora_haproxy_ssh_driver',
|
||||||
|
group='controller_worker')
|
||||||
self.AmpFlow = amphora_flows.AmphoraFlows()
|
self.AmpFlow = amphora_flows.AmphoraFlows()
|
||||||
conf = oslo_fixture.Config(cfg.CONF)
|
conf = oslo_fixture.Config(cfg.CONF)
|
||||||
conf.config(group="keystone_authtoken", auth_version=AUTH_VERSION)
|
conf.config(group="keystone_authtoken", auth_version=AUTH_VERSION)
|
||||||
|
@ -46,6 +48,22 @@ class TestAmphoraFlows(base.TestCase):
|
||||||
self.assertEqual(len(amp_flow.provides), 3)
|
self.assertEqual(len(amp_flow.provides), 3)
|
||||||
self.assertEqual(len(amp_flow.requires), 0)
|
self.assertEqual(len(amp_flow.requires), 0)
|
||||||
|
|
||||||
|
def test_get_create_amphora_flow_cert(self):
|
||||||
|
cfg.CONF.set_override('amphora_driver', 'amphora_haproxy_rest_driver',
|
||||||
|
group='controller_worker')
|
||||||
|
self.AmpFlow = amphora_flows.AmphoraFlows()
|
||||||
|
|
||||||
|
amp_flow = self.AmpFlow.get_create_amphora_flow()
|
||||||
|
|
||||||
|
self.assertIsInstance(amp_flow, flow.Flow)
|
||||||
|
|
||||||
|
self.assertIn(constants.AMPHORA, amp_flow.provides)
|
||||||
|
self.assertIn(constants.AMPHORA_ID, amp_flow.provides)
|
||||||
|
self.assertIn(constants.COMPUTE_ID, amp_flow.provides)
|
||||||
|
|
||||||
|
self.assertEqual(len(amp_flow.provides), 4)
|
||||||
|
self.assertEqual(len(amp_flow.requires), 0)
|
||||||
|
|
||||||
def test_get_create_amphora_for_lb_flow(self):
|
def test_get_create_amphora_for_lb_flow(self):
|
||||||
|
|
||||||
amp_flow = self.AmpFlow.get_create_amphora_for_lb_flow()
|
amp_flow = self.AmpFlow.get_create_amphora_for_lb_flow()
|
||||||
|
@ -64,6 +82,26 @@ class TestAmphoraFlows(base.TestCase):
|
||||||
self.assertEqual(len(amp_flow.provides), 7)
|
self.assertEqual(len(amp_flow.provides), 7)
|
||||||
self.assertEqual(len(amp_flow.requires), 1)
|
self.assertEqual(len(amp_flow.requires), 1)
|
||||||
|
|
||||||
|
def test_get_cert_create_amphora_for_lb_flow(self):
|
||||||
|
cfg.CONF.set_override('amphora_driver', 'amphora_haproxy_rest_driver',
|
||||||
|
group='controller_worker')
|
||||||
|
self.AmpFlow = amphora_flows.AmphoraFlows()
|
||||||
|
amp_flow = self.AmpFlow.get_create_amphora_for_lb_flow()
|
||||||
|
|
||||||
|
self.assertIsInstance(amp_flow, flow.Flow)
|
||||||
|
|
||||||
|
self.assertIn(constants.LOADBALANCER_ID, amp_flow.requires)
|
||||||
|
self.assertIn(constants.AMPHORA, amp_flow.provides)
|
||||||
|
self.assertIn(constants.LOADBALANCER, amp_flow.provides)
|
||||||
|
self.assertIn(constants.VIP, amp_flow.provides)
|
||||||
|
self.assertIn(constants.AMPS_DATA, amp_flow.provides)
|
||||||
|
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.assertEqual(len(amp_flow.provides), 8)
|
||||||
|
self.assertEqual(len(amp_flow.requires), 1)
|
||||||
|
|
||||||
def test_get_delete_amphora_flow(self):
|
def test_get_delete_amphora_flow(self):
|
||||||
|
|
||||||
amp_flow = self.AmpFlow.get_delete_amphora_flow()
|
amp_flow = self.AmpFlow.get_delete_amphora_flow()
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
import mock
|
||||||
|
|
||||||
|
from octavia.certificates.common import local
|
||||||
|
from octavia.controller.worker.tasks import cert_task
|
||||||
|
import octavia.tests.unit.base as base
|
||||||
|
|
||||||
|
|
||||||
|
class TestCertTasks(base.TestCase):
|
||||||
|
@mock.patch('stevedore.driver.DriverManager.driver')
|
||||||
|
def test_execute(self, mock_driver):
|
||||||
|
dummy_cert = local.LocalCert('test_cert', 'test_key')
|
||||||
|
mock_driver.generate_cert_key_pair.side_effect = [dummy_cert]
|
||||||
|
c = cert_task.GenerateServerPEMTask()
|
||||||
|
pem = c.execute('123')
|
||||||
|
self.assertEqual(
|
||||||
|
pem, dummy_cert.get_certificate() + dummy_cert.get_private_key())
|
||||||
|
mock_driver.generate_cert_key_pair.assert_called_once_with(
|
||||||
|
cn='123', validity=365)
|
|
@ -73,10 +73,10 @@ class TestComputeTasks(base.TestCase):
|
||||||
@mock.patch('stevedore.driver.DriverManager.driver')
|
@mock.patch('stevedore.driver.DriverManager.driver')
|
||||||
def test_compute_create(self, mock_driver):
|
def test_compute_create(self, mock_driver):
|
||||||
|
|
||||||
mock_driver.build.side_effect = [COMPUTE_ID, TestException('test')]
|
|
||||||
|
|
||||||
# Test execute()
|
|
||||||
createcompute = compute_tasks.ComputeCreate()
|
createcompute = compute_tasks.ComputeCreate()
|
||||||
|
|
||||||
|
mock_driver.build.side_effect = [COMPUTE_ID, TestException('test')]
|
||||||
|
# Test execute()
|
||||||
compute_id = createcompute.execute(_amphora_mock.id)
|
compute_id = createcompute.execute(_amphora_mock.id)
|
||||||
|
|
||||||
# Validate that the build method was called properly
|
# Validate that the build method was called properly
|
||||||
|
@ -86,17 +86,65 @@ class TestComputeTasks(base.TestCase):
|
||||||
image_id=AMP_IMAGE_ID,
|
image_id=AMP_IMAGE_ID,
|
||||||
key_name=AMP_SSH_KEY_NAME,
|
key_name=AMP_SSH_KEY_NAME,
|
||||||
sec_groups=AMP_SEC_GROUPS,
|
sec_groups=AMP_SEC_GROUPS,
|
||||||
network_ids=[AMP_NET])
|
network_ids=[AMP_NET],
|
||||||
|
config_drive_files=None)
|
||||||
|
|
||||||
# Make sure it returns the expected compute_id
|
# Make sure it returns the expected compute_id
|
||||||
assert(compute_id == COMPUTE_ID)
|
assert(compute_id == COMPUTE_ID)
|
||||||
|
|
||||||
# Test that a build exception is raised
|
# Test that a build exception is raised
|
||||||
|
|
||||||
createcompute = compute_tasks.ComputeCreate()
|
createcompute = compute_tasks.ComputeCreate()
|
||||||
self.assertRaises(TestException,
|
self.assertRaises(TestException,
|
||||||
createcompute.execute,
|
createcompute.execute,
|
||||||
_amphora_mock)
|
_amphora_mock, 'test_cert')
|
||||||
|
|
||||||
|
# Test revert()
|
||||||
|
|
||||||
|
_amphora_mock.compute_id = COMPUTE_ID
|
||||||
|
|
||||||
|
createcompute = compute_tasks.ComputeCreate()
|
||||||
|
createcompute.revert(compute_id, _amphora_mock.id)
|
||||||
|
|
||||||
|
# Validate that the delete method was called properly
|
||||||
|
mock_driver.delete.assert_called_once_with(
|
||||||
|
COMPUTE_ID)
|
||||||
|
|
||||||
|
# Test that a delete exception is not raised
|
||||||
|
|
||||||
|
createcompute.revert(COMPUTE_ID, _amphora_mock.id)
|
||||||
|
|
||||||
|
@mock.patch('stevedore.driver.DriverManager.driver')
|
||||||
|
def test_compute_create_cert(self, mock_driver):
|
||||||
|
|
||||||
|
createcompute = compute_tasks.CertComputeCreate()
|
||||||
|
|
||||||
|
mock_driver.build.side_effect = [COMPUTE_ID, TestException('test')]
|
||||||
|
m = mock.mock_open(read_data='test')
|
||||||
|
with mock.patch('__builtin__.open', m, create=True):
|
||||||
|
# Test execute()
|
||||||
|
compute_id = createcompute.execute(_amphora_mock.id, 'test_cert')
|
||||||
|
|
||||||
|
# Validate that the build method was called properly
|
||||||
|
mock_driver.build.assert_called_once_with(
|
||||||
|
name="amphora-" + _amphora_mock.id,
|
||||||
|
amphora_flavor=AMP_FLAVOR_ID,
|
||||||
|
image_id=AMP_IMAGE_ID,
|
||||||
|
key_name=AMP_SSH_KEY_NAME,
|
||||||
|
sec_groups=AMP_SEC_GROUPS,
|
||||||
|
network_ids=[AMP_NET],
|
||||||
|
config_drive_files={
|
||||||
|
'/etc/octavia/certs/server.pem': 'test_cert',
|
||||||
|
'/etc/octavia/certs/client_ca.pem': m.return_value})
|
||||||
|
|
||||||
|
# Make sure it returns the expected compute_id
|
||||||
|
assert(compute_id == COMPUTE_ID)
|
||||||
|
|
||||||
|
# Test that a build exception is raised
|
||||||
|
with mock.patch('__builtin__.open', m, create=True):
|
||||||
|
createcompute = compute_tasks.ComputeCreate()
|
||||||
|
self.assertRaises(TestException,
|
||||||
|
createcompute.execute,
|
||||||
|
_amphora_mock, 'test_cert')
|
||||||
|
|
||||||
# Test revert()
|
# Test revert()
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ octavia.api.handlers =
|
||||||
queue_producer = octavia.api.v1.handlers.queue.producer:ProducerHandler
|
queue_producer = octavia.api.v1.handlers.queue.producer:ProducerHandler
|
||||||
octavia.amphora.drivers =
|
octavia.amphora.drivers =
|
||||||
amphora_noop_driver = octavia.amphorae.drivers.noop_driver.driver:NoopAmphoraLoadBalancerDriver
|
amphora_noop_driver = octavia.amphorae.drivers.noop_driver.driver:NoopAmphoraLoadBalancerDriver
|
||||||
amphora_haproxy_rest_driver = octavia.amphorae.drivers.haproxy.rest_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
|
amphora_haproxy_ssh_driver = octavia.amphorae.drivers.haproxy.ssh_driver:HaproxyManager
|
||||||
octavia.compute.drivers =
|
octavia.compute.drivers =
|
||||||
compute_noop_driver = octavia.compute.drivers.noop_driver.driver:NoopComputeDriver
|
compute_noop_driver = octavia.compute.drivers.noop_driver.driver:NoopComputeDriver
|
||||||
|
@ -52,3 +52,10 @@ octavia.compute.drivers =
|
||||||
octavia.network.drivers =
|
octavia.network.drivers =
|
||||||
network_noop_driver = octavia.network.drivers.noop_driver.driver:NoopNetworkDriver
|
network_noop_driver = octavia.network.drivers.noop_driver.driver:NoopNetworkDriver
|
||||||
allowed_address_pairs_driver = octavia.network.drivers.neutron.allowed_address_pairs:AllowedAddressPairsDriver
|
allowed_address_pairs_driver = octavia.network.drivers.neutron.allowed_address_pairs:AllowedAddressPairsDriver
|
||||||
|
octavia.cert_generator =
|
||||||
|
local_cert_generator = octavia.certificates.generator.local:LocalCertGenerator
|
||||||
|
barbican_cert_generator = octavia.certificates.generator.barbican:BarbicanCertGenerator
|
||||||
|
octavia.cert_manager =
|
||||||
|
local_cert_manager = octavia.certificates.manager.local:LocalCertManager
|
||||||
|
barbican_cert_manager = octavia.certificates.manager.barbican:BarbicanCertManager
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
# of appearance. Changing the order has an impact on the overall integration
|
# of appearance. Changing the order has an impact on the overall integration
|
||||||
# process, which may cause wedges in the gate later.
|
# process, which may cause wedges in the gate later.
|
||||||
hacking<0.10,>=0.9.1
|
hacking<0.10,>=0.9.1
|
||||||
|
requests-mock
|
||||||
cliff>=1.13.0 # Apache-2.0
|
cliff>=1.13.0 # Apache-2.0
|
||||||
coverage>=3.6
|
coverage>=3.6
|
||||||
discover
|
discover
|
||||||
|
@ -15,3 +15,4 @@ testrepository>=0.0.18
|
||||||
testtools>=1.4.0
|
testtools>=1.4.0
|
||||||
WebTest>=2.0
|
WebTest>=2.0
|
||||||
doc8 # Apache-2.0
|
doc8 # Apache-2.0
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue