Implement provider drivers - Load Balancer

This patch adds provider driver support to the Octavia v2 API, starting
with the load balancer API.

This patch also creates a provider driver for Octavia, initially fully
implementing the load balancer methods.

Follow on patches will implement the remain parts of the API.

Change-Id: Ia15280827799d1800c23ed76d2af0e3596b9d2f7
Story: 1655768
Task: 5165
This commit is contained in:
Michael Johnson 2018-04-23 17:36:05 -07:00
parent dfa7ef2ab3
commit 7b2621fe29
30 changed files with 1903 additions and 196 deletions

View File

@ -43,6 +43,12 @@
# Enable/disable ability for users to create PING type Health Monitors
# allow_ping_health_monitors = True
# List of enabled provider drivers
# enabled_provider_drivers = octavia, amphora
# Default provider driver
# default_provider_driver = amphora
[database]
# This line MUST be changed to actually run the plugin.
# Example:

View File

@ -28,6 +28,7 @@ app = {
# WSME Configurations
# See https://wsme.readthedocs.org/en/latest/integrate.html#configuration
wsme = {
# Keeping debug True for now so we can easily troubleshoot.
'debug': True
# Provider driver uses 501 if the driver is not installed.
# Don't dump a stack trace for 501s
'debug': False
}

View File

@ -0,0 +1,11 @@
# 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.

View File

@ -0,0 +1,163 @@
# Copyright 2018 Rackspace, US Inc.
#
# 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 oslo_config import cfg
from oslo_log import log as logging
import oslo_messaging as messaging
from octavia.api.drivers import exceptions
from octavia.api.drivers import provider_base as driver_base
from octavia.api.drivers import utils as driver_utils
from octavia.common import constants as consts
from octavia.common import data_models
from octavia.common import utils
from octavia.network import base as network_base
CONF = cfg.CONF
CONF.import_group('oslo_messaging', 'octavia.common.config')
LOG = logging.getLogger(__name__)
class AmphoraProviderDriver(driver_base.ProviderDriver):
def __init__(self):
super(AmphoraProviderDriver, self).__init__()
topic = cfg.CONF.oslo_messaging.topic
self.transport = messaging.get_rpc_transport(cfg.CONF)
self.target = messaging.Target(
namespace=consts.RPC_NAMESPACE_CONTROLLER_AGENT,
topic=topic, version="1.0", fanout=False)
self.client = messaging.RPCClient(self.transport, target=self.target)
# Load Balancer
def create_vip_port(self, loadbalancer_id, project_id, vip_dictionary):
vip_obj = driver_utils.provider_vip_dict_to_vip_obj(vip_dictionary)
lb_obj = data_models.LoadBalancer(id=loadbalancer_id,
project_id=project_id, vip=vip_obj)
network_driver = utils.get_network_driver()
try:
vip = network_driver.allocate_vip(lb_obj)
except network_base.AllocateVIPException as e:
raise exceptions.DriverError(user_fault_string=e.orig_msg,
operator_fault_string=e.orig_msg)
LOG.info('Amphora provider created VIP port %s for load balancer %s.',
vip.port_id, loadbalancer_id)
return driver_utils.vip_dict_to_provider_dict(vip.to_dict())
def loadbalancer_create(self, loadbalancer):
payload = {consts.LOAD_BALANCER_ID: loadbalancer.loadbalancer_id}
self.client.cast({}, 'create_load_balancer', **payload)
def loadbalancer_delete(self, loadbalancer_id, cascade=False):
payload = {consts.LOAD_BALANCER_ID: loadbalancer_id,
'cascade': cascade}
self.client.cast({}, 'delete_load_balancer', **payload)
def loadbalancer_failover(self, loadbalancer_id):
payload = {consts.LOAD_BALANCER_ID: loadbalancer_id}
self.client.cast({}, 'failover_load_balancer', **payload)
def loadbalancer_update(self, loadbalancer):
# Adapt the provider data model to the queue schema
lb_dict = loadbalancer.to_dict()
if 'admin_state_up' in lb_dict:
lb_dict['enabled'] = lb_dict.pop('admin_state_up')
lb_id = lb_dict.pop('loadbalancer_id')
payload = {consts.LOAD_BALANCER_ID: lb_id,
consts.LOAD_BALANCER_UPDATES: lb_dict}
self.client.cast({}, 'update_load_balancer', **payload)
# Listener
def listener_create(self, listener):
payload = {consts.LISTENER_ID: listener.listener_id}
self.client.cast({}, 'create_listener', **payload)
def listener_delete(self, listener_id):
payload = {consts.LISTENER_ID: listener_id}
self.client.cast({}, 'delete_listener', **payload)
def listener_update(self, listener):
pass
# Pool
def pool_create(self, pool):
payload = {consts.POOL_ID: pool.pool_id}
self.client.cast({}, 'create_pool', **payload)
def pool_delete(self, pool_id):
payload = {consts.POOL_ID: pool_id}
self.client.cast({}, 'delete_pool', **payload)
def pool_update(self, pool):
pass
# Member
def member_create(self, member):
payload = {consts.MEMBER_ID: member.member_id}
self.client.cast({}, 'create_member', **payload)
def member_delete(self, member_id):
payload = {consts.MEMBER_ID: member_id}
self.client.cast({}, 'delete_member', **payload)
def member_update(self, member):
pass
def member_batch_update(self, members):
pass
# Health Monitor
def health_monitor_create(self, healthmonitor):
payload = {consts.HEALTH_MONITOR_ID: healthmonitor.healthmonitor_id}
self.client.cast({}, 'create_health_monitor', **payload)
def health_monitor_delete(self, healthmonitor_id):
payload = {consts.HEALTH_MONITOR_ID: healthmonitor_id}
self.client.cast({}, 'delete_health_monitor', **payload)
def health_monitor_update(self, healthmonitor):
pass
# L7 Policy
def l7policy_create(self, l7policy):
payload = {consts.L7POLICY_ID: l7policy.l7policy_id}
self.client.cast({}, 'create_l7policy', **payload)
def l7policy_delete(self, l7policy_id):
payload = {consts.L7POLICY_ID: l7policy_id}
self.client.cast({}, 'delete_l7policy', **payload)
def l7policy_update(self, l7policy):
pass
# L7 Rule
def l7rule_create(self, l7rule):
payload = {consts.L7RULE_ID: l7rule.l7rule_id}
self.client.cast({}, 'create_l7rule', **payload)
def l7rule_delete(self, l7rule_id):
payload = {consts.L7RULE_ID: l7rule_id}
self.client.cast({}, 'delete_l7rule', **payload)
def l7rule_update(self, l7rule):
pass
# Flavor
def get_supported_flavor_metadata(self):
pass
def validate_flavor(self, flavor_metadata):
pass

View File

@ -17,9 +17,14 @@
import six
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
class BaseDataModel(object):
def to_dict(self, calling_classes=None, recurse=False, **kwargs):
def to_dict(self, calling_classes=None, recurse=False,
render_unsets=False, **kwargs):
"""Converts a data model to a dictionary."""
calling_classes = calling_classes or []
ret = {}
@ -36,24 +41,35 @@ class BaseDataModel(object):
if type(self) not in calling_classes:
ret[attr].append(
item.to_dict(calling_classes=(
calling_classes + [type(self)])))
calling_classes + [type(self)]),
render_unsets=render_unsets))
else:
ret[attr] = None
ret[attr].append(None)
else:
ret[attr] = item
ret[attr].append(item)
elif isinstance(getattr(self, attr), BaseDataModel):
if type(self) not in calling_classes:
ret[attr] = value.to_dict(
render_unsets=render_unsets,
calling_classes=calling_classes + [type(self)])
else:
ret[attr] = None
elif six.PY2 and isinstance(value, six.text_type):
ret[attr.encode('utf8')] = value.encode('utf8')
elif isinstance(value, UnsetType):
if render_unsets:
ret[attr] = None
else:
continue
else:
ret[attr] = value
else:
if isinstance(getattr(self, attr), (BaseDataModel, list)):
if (isinstance(getattr(self, attr), (BaseDataModel, list)) or
isinstance(value, UnsetType)):
if render_unsets:
ret[attr] = None
else:
continue
else:
ret[attr] = value
@ -72,32 +88,49 @@ class BaseDataModel(object):
return cls(**dict)
class UnsetType(object):
def __bool__(self):
return False
__nonzero__ = __bool__
def __repr__(self):
return 'Unset'
Unset = UnsetType()
class LoadBalancer(BaseDataModel):
def __init__(self, admin_state_up=None, description=None, flavor=None,
listeners=None, loadbalancer_id=None, name=None,
project_id=None, vip_address=None, vip_network_id=None,
vip_port_id=None, vip_subnet_id=None):
def __init__(self, admin_state_up=Unset, description=Unset, flavor=Unset,
listeners=Unset, loadbalancer_id=Unset, name=Unset,
pools=Unset, project_id=Unset, vip_address=Unset,
vip_network_id=Unset, vip_port_id=Unset, vip_subnet_id=Unset,
vip_qos_policy_id=Unset):
self.admin_state_up = admin_state_up
self.description = description
self.flavor = flavor or {}
self.listeners = listeners or []
self.flavor = flavor
self.listeners = listeners
self.loadbalancer_id = loadbalancer_id
self.name = name
self.pools = pools
self.project_id = project_id
self.vip_address = vip_address
self.vip_network_id = vip_network_id
self.vip_port_id = vip_port_id
self.vip_subnet_id = vip_subnet_id
self.vip_qos_policy_id = vip_qos_policy_id
class Listener(BaseDataModel):
def __init__(self, admin_state_up=None, connection_limit=None,
default_pool=None, default_pool_id=None,
default_tls_container=None, description=None,
insert_headers=None, l7policies=None, listener_id=None,
loadbalancer_id=None, name=None, protocol=None,
protocol_port=None, sni_containers=None):
def __init__(self, admin_state_up=Unset, connection_limit=Unset,
default_pool=Unset, default_pool_id=Unset,
default_tls_container=Unset, description=Unset,
insert_headers=Unset, l7policies=Unset, listener_id=Unset,
loadbalancer_id=Unset, name=Unset, protocol=Unset,
protocol_port=Unset, sni_containers=Unset,
timeout_client_data=Unset, timeout_member_connect=Unset,
timeout_member_data=Unset, timeout_tcp_inspect=Unset):
self.admin_state_up = admin_state_up
self.connection_limit = connection_limit
@ -105,40 +138,43 @@ class Listener(BaseDataModel):
self.default_pool_id = default_pool_id
self.default_tls_container = default_tls_container
self.description = description
self.insert_headers = insert_headers or {}
self.l7policies = l7policies or []
self.insert_headers = insert_headers
self.l7policies = l7policies
self.listener_id = listener_id
self.loadbalancer_id = loadbalancer_id
self.name = name
self.protocol = protocol
self.protocol_port = protocol_port
self.sni_containers = sni_containers
self.timeout_client_data = timeout_client_data
self.timeout_member_connect = timeout_member_connect
self.timeout_member_data = timeout_member_data
self.timeout_tcp_inspect = timeout_tcp_inspect
class Pool(BaseDataModel):
def __init__(self, admin_state_up=None, description=None,
healthmonitor=None, lb_algorithm=None, listener_id=None,
loadbalancer_id=None, members=None, name=None, pool_id=None,
protocol=None, session_persistence=None):
def __init__(self, admin_state_up=Unset, description=Unset,
healthmonitor=Unset, lb_algorithm=Unset,
loadbalancer_id=Unset, members=Unset, name=Unset,
pool_id=Unset, protocol=Unset, session_persistence=Unset):
self.admin_state_up = admin_state_up
self.description = description
self.healthmonitor = healthmonitor
self.lb_algorithm = lb_algorithm
self.listener_id = listener_id
self.loadbalancer_id = loadbalancer_id
self.members = members or []
self.members = members
self.name = name
self.pool_id = pool_id
self.protocol = protocol
self.session_persistence = session_persistence or {}
self.session_persistence = session_persistence
class Member(BaseDataModel):
def __init__(self, address=None, admin_state_up=None, member_id=None,
monitor_address=None, monitor_port=None, name=None,
pool_id=None, protocol_port=None, subnet_id=None,
weight=None):
def __init__(self, address=Unset, admin_state_up=Unset, member_id=Unset,
monitor_address=Unset, monitor_port=Unset, name=Unset,
pool_id=Unset, protocol_port=Unset, subnet_id=Unset,
weight=Unset, backup=Unset):
self.address = address
self.admin_state_up = admin_state_up
@ -150,13 +186,14 @@ class Member(BaseDataModel):
self.protocol_port = protocol_port
self.subnet_id = subnet_id
self.weight = weight
self.backup = backup
class HealthMonitor(BaseDataModel):
def __init__(self, admin_state_up=None, delay=None, expected_codes=None,
healthmonitor_id=None, http_method=None, max_retries=None,
max_retries_down=None, name=None, pool_id=None, timeout=None,
type=None, url_path=None):
def __init__(self, admin_state_up=Unset, delay=Unset, expected_codes=Unset,
healthmonitor_id=Unset, http_method=Unset, max_retries=Unset,
max_retries_down=Unset, name=Unset, pool_id=Unset,
timeout=Unset, type=Unset, url_path=Unset):
self.admin_state_up = admin_state_up
self.delay = delay
@ -173,9 +210,10 @@ class HealthMonitor(BaseDataModel):
class L7Policy(BaseDataModel):
def __init__(self, action=None, admin_state_up=None, description=None,
l7policy_id=None, listener_id=None, name=None, position=None,
redirect_pool_id=None, redirect_url=None, rules=None):
def __init__(self, action=Unset, admin_state_up=Unset, description=Unset,
l7policy_id=Unset, listener_id=Unset, name=Unset,
position=Unset, redirect_pool_id=Unset, redirect_url=Unset,
rules=Unset):
self.action = action
self.admin_state_up = admin_state_up
@ -186,13 +224,13 @@ class L7Policy(BaseDataModel):
self.position = position
self.redirect_pool_id = redirect_pool_id
self.redirect_url = redirect_url
self.rules = rules or []
self.rules = rules
class L7Rule(BaseDataModel):
def __init__(self, admin_state_up=None, compare_type=None, invert=None,
key=None, l7policy_id=None, l7rule_id=None, type=None,
value=None):
def __init__(self, admin_state_up=Unset, compare_type=Unset, invert=Unset,
key=Unset, l7policy_id=Unset, l7rule_id=Unset, type=Unset,
value=Unset):
self.admin_state_up = admin_state_up
self.compare_type = compare_type
@ -205,10 +243,12 @@ class L7Rule(BaseDataModel):
class VIP(BaseDataModel):
def __init__(self, vip_address=None, vip_network_id=None, vip_port_id=None,
vip_subnet_id=None):
def __init__(self, vip_address=Unset, vip_network_id=Unset,
vip_port_id=Unset, vip_subnet_id=Unset,
vip_qos_policy_id=Unset):
self.vip_address = vip_address
self.vip_network_id = vip_network_id
self.vip_port_id = vip_port_id
self.vip_subnet_id = vip_subnet_id
self.vip_qos_policy_id = vip_qos_policy_id

View File

@ -0,0 +1,50 @@
# Copyright 2018 Rackspace, US Inc.
#
# 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 oslo_config import cfg
from oslo_log import log as logging
from stevedore import driver as stevedore_driver
from wsme import types as wtypes
from octavia.common import exceptions
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
def get_driver(provider):
# If this came in None it must be a load balancer that existed before
# provider support was added. These must be of type 'amphora' and not
# whatever the current "default" is set to.
if isinstance(provider, wtypes.UnsetType):
provider = CONF.api_settings.default_provider_driver
elif not provider:
provider = 'amphora'
if provider not in CONF.api_settings.enabled_provider_drivers:
LOG.warning("Requested provider driver '%s' was not enabled in the "
"configuration file.", provider)
raise exceptions.ProviderNotEnabled(prov=provider)
try:
driver = stevedore_driver.DriverManager(
namespace='octavia.api.drivers',
name=provider,
invoke_on_load=True).driver
driver.name = provider
except Exception as e:
LOG.error('Unable to load provider driver %s due to: %s',
provider, e)
raise exceptions.ProviderNotFound(prov=provider)
return driver

View File

@ -27,14 +27,15 @@ class NoopManager(object):
self.driverconfig = {}
# Load Balancer
def create_vip_port(self, loadbalancer_id, vip_dictionary):
def create_vip_port(self, loadbalancer_id, project_id, vip_dictionary):
LOG.debug('Provider %s no-op, create_vip_port loadbalancer %s',
self.__class__.__name__, loadbalancer_id)
self.driverconfig[loadbalancer_id] = (loadbalancer_id, vip_dictionary,
self.driverconfig[loadbalancer_id] = (loadbalancer_id, project_id,
vip_dictionary,
'create_vip_port')
vip_address = vip_dictionary.get('vip_address', '192.0.2.5')
vip_address = vip_dictionary.get('vip_address', '198.0.2.5')
vip_network_id = vip_dictionary.get('vip_network_id',
uuidutils.generate_uuid())
vip_port_id = vip_dictionary.get('vip_port_id',
@ -222,8 +223,9 @@ class NoopProviderDriver(driver_base.ProviderDriver):
self.driver = NoopManager()
# Load Balancer
def create_vip_port(self, loadbalancer_id, vip_dictionary):
return self.driver.create_vip_port(loadbalancer_id, vip_dictionary)
def create_vip_port(self, loadbalancer_id, project_id, vip_dictionary):
return self.driver.create_vip_port(loadbalancer_id, project_id,
vip_dictionary)
def loadbalancer_create(self, loadbalancer):
self.driver.loadbalancer_create(loadbalancer)

View File

@ -19,8 +19,11 @@ from octavia.api.drivers import exceptions
class ProviderDriver(object):
# name is for internal Octavia use and should not be used by drivers
name = None
# Load Balancer
def create_vip_port(self, loadbalancer_id, vip_dictionary):
def create_vip_port(self, loadbalancer_id, project_id, vip_dictionary):
"""Creates a port for a load balancer VIP.
If the driver supports creating VIP ports, the driver will create a
@ -30,6 +33,8 @@ class ProviderDriver(object):
:param loadbalancer_id: ID of loadbalancer.
:type loadbalancer_id: string
:param project_id: The project ID to create the VIP under.
:type project_id: string
:param: vip_dictionary: The VIP dictionary.
:type vip_dictionary: dict
:returns: VIP dictionary with vip_port_id.
@ -37,7 +42,11 @@ class ProviderDriver(object):
:raises NotImplementedError: The driver does not support creating
VIP ports.
"""
raise exceptions.NotImplementedError()
raise exceptions.NotImplementedError(
user_fault_string='This provider does not support creating VIP '
'ports.',
operator_fault_string='This provider does not support creating '
'VIP ports. Octavia will create it.')
def loadbalancer_create(self, loadbalancer):
"""Creates a new load balancer.
@ -50,7 +59,11 @@ class ProviderDriver(object):
:raises UnsupportedOptionError: The driver does not
support one of the configuration options.
"""
raise exceptions.NotImplementedError()
raise exceptions.NotImplementedError(
user_fault_string='This provider does not support creating '
'load balancers.',
operator_fault_string='This provider does not support creating '
'load balancers. What?')
def loadbalancer_delete(self, loadbalancer_id, cascade=False):
"""Deletes a load balancer.
@ -64,7 +77,11 @@ class ProviderDriver(object):
:raises DriverError: An unexpected error occurred in the driver.
:raises NotImplementedError: if driver does not support request.
"""
raise exceptions.NotImplementedError()
raise exceptions.NotImplementedError(
user_fault_string='This provider does not support deleting '
'load balancers.',
operator_fault_string='This provider does not support deleting '
'load balancers.')
def loadbalancer_failover(self, loadbalancer_id):
"""Performs a fail over of a load balancer.
@ -75,7 +92,11 @@ class ProviderDriver(object):
:raises DriverError: An unexpected error occurred in the driver.
:raises: NotImplementedError if driver does not support request.
"""
raise exceptions.NotImplementedError()
raise exceptions.NotImplementedError(
user_fault_string='This provider does not support failing over '
'load balancers.',
operator_fault_string='This provider does not support failing '
'over load balancers.')
def loadbalancer_update(self, loadbalancer):
"""Updates a load balancer.
@ -88,7 +109,11 @@ class ProviderDriver(object):
:raises UnsupportedOptionError: The driver does not
support one of the configuration options.
"""
raise exceptions.NotImplementedError()
raise exceptions.NotImplementedError(
user_fault_string='This provider does not support updating '
'load balancers.',
operator_fault_string='This provider does not support updating '
'load balancers.')
# Listener
def listener_create(self, listener):
@ -102,7 +127,11 @@ class ProviderDriver(object):
:raises UnsupportedOptionError: if driver does not
support one of the configuration options.
"""
raise exceptions.NotImplementedError()
raise exceptions.NotImplementedError(
user_fault_string='This provider does not support creating '
'listeners.',
operator_fault_string='This provider does not support creating '
'listeners.')
def listener_delete(self, listener_id):
"""Deletes a listener.
@ -113,7 +142,11 @@ class ProviderDriver(object):
:raises DriverError: An unexpected error occurred in the driver.
:raises NotImplementedError: if driver does not support request.
"""
raise exceptions.NotImplementedError()
raise exceptions.NotImplementedError(
user_fault_string='This provider does not support deleting '
'listeners.',
operator_fault_string='This provider does not support deleting '
'listeners.')
def listener_update(self, listener):
"""Updates a listener.
@ -126,7 +159,11 @@ class ProviderDriver(object):
:raises UnsupportedOptionError: if driver does not
support one of the configuration options.
"""
raise exceptions.NotImplementedError()
raise exceptions.NotImplementedError(
user_fault_string='This provider does not support updating '
'listeners.',
operator_fault_string='This provider does not support updating '
'listeners.')
# Pool
def pool_create(self, pool):
@ -140,7 +177,11 @@ class ProviderDriver(object):
:raises UnsupportedOptionError: if driver does not
support one of the configuration options.
"""
raise exceptions.NotImplementedError()
raise exceptions.NotImplementedError(
user_fault_string='This provider does not support creating '
'pools.',
operator_fault_string='This provider does not support creating '
'pools.')
def pool_delete(self, pool_id):
"""Deletes a pool and its members.
@ -151,7 +192,11 @@ class ProviderDriver(object):
:raises DriverError: An unexpected error occurred in the driver.
:raises NotImplementedError: if driver does not support request.
"""
raise exceptions.NotImplementedError()
raise exceptions.NotImplementedError(
user_fault_string='This provider does not support deleting '
'pools.',
operator_fault_string='This provider does not support deleting '
'pools.')
def pool_update(self, pool):
"""Updates a pool.
@ -164,7 +209,11 @@ class ProviderDriver(object):
:raises UnsupportedOptionError: if driver does not
support one of the configuration options.
"""
raise exceptions.NotImplementedError()
raise exceptions.NotImplementedError(
user_fault_string='This provider does not support updating '
'pools.',
operator_fault_string='This provider does not support updating '
'pools.')
# Member
def member_create(self, member):
@ -178,7 +227,11 @@ class ProviderDriver(object):
:raises UnsupportedOptionError: if driver does not
support one of the configuration options.
"""
raise exceptions.NotImplementedError()
raise exceptions.NotImplementedError(
user_fault_string='This provider does not support creating '
'members.',
operator_fault_string='This provider does not support creating '
'members.')
def member_delete(self, member_id):
"""Deletes a pool member.
@ -189,7 +242,11 @@ class ProviderDriver(object):
:raises DriverError: An unexpected error occurred in the driver.
:raises NotImplementedError: if driver does not support request.
"""
raise exceptions.NotImplementedError()
raise exceptions.NotImplementedError(
user_fault_string='This provider does not support deleting '
'members.',
operator_fault_string='This provider does not support deleting '
'members.')
def member_update(self, member):
"""Updates a pool member.
@ -202,7 +259,11 @@ class ProviderDriver(object):
:raises UnsupportedOptionError: if driver does not
support one of the configuration options.
"""
raise exceptions.NotImplementedError()
raise exceptions.NotImplementedError(
user_fault_string='This provider does not support updating '
'members.',
operator_fault_string='This provider does not support updating '
'members.')
def member_batch_update(self, members):
"""Creates, updates, or deletes a set of pool members.
@ -215,7 +276,11 @@ class ProviderDriver(object):
:raises UnsupportedOptionError: if driver does not
support one of the configuration options.
"""
raise exceptions.NotImplementedError()
raise exceptions.NotImplementedError(
user_fault_string='This provider does not support batch '
'updating members.',
operator_fault_string='This provider does not support batch '
'updating members.')
# Health Monitor
def health_monitor_create(self, healthmonitor):
@ -229,7 +294,11 @@ class ProviderDriver(object):
:raises UnsupportedOptionError: if driver does not
support one of the configuration options.
"""
raise exceptions.NotImplementedError()
raise exceptions.NotImplementedError(
user_fault_string='This provider does not support creating '
'health monitors.',
operator_fault_string='This provider does not support creating '
'health monitors.')
def health_monitor_delete(self, healthmonitor_id):
"""Deletes a healthmonitor_id.
@ -240,7 +309,11 @@ class ProviderDriver(object):
:raises DriverError: An unexpected error occurred in the driver.
:raises NotImplementedError: if driver does not support request.
"""
raise exceptions.NotImplementedError()
raise exceptions.NotImplementedError(
user_fault_string='This provider does not support deleting '
'health monitors.',
operator_fault_string='This provider does not support deleting '
'health monitors.')
def health_monitor_update(self, healthmonitor):
"""Updates a health monitor.
@ -253,7 +326,11 @@ class ProviderDriver(object):
:raises UnsupportedOptionError: if driver does not
support one of the configuration options.
"""
raise exceptions.NotImplementedError()
raise exceptions.NotImplementedError(
user_fault_string='This provider does not support updating '
'health monitors.',
operator_fault_string='This provider does not support updating '
'health monitors.')
# L7 Policy
def l7policy_create(self, l7policy):
@ -267,7 +344,11 @@ class ProviderDriver(object):
:raises UnsupportedOptionError: if driver does not
support one of the configuration options.
"""
raise exceptions.NotImplementedError()
raise exceptions.NotImplementedError(
user_fault_string='This provider does not support creating '
'l7policies.',
operator_fault_string='This provider does not support creating '
'l7policies.')
def l7policy_delete(self, l7policy_id):
"""Deletes an L7 policy.
@ -278,7 +359,11 @@ class ProviderDriver(object):
:raises DriverError: An unexpected error occurred in the driver.
:raises NotImplementedError: if driver does not support request.
"""
raise exceptions.NotImplementedError()
raise exceptions.NotImplementedError(
user_fault_string='This provider does not support deleting '
'l7policies.',
operator_fault_string='This provider does not support deleting '
'l7policies.')
def l7policy_update(self, l7policy):
"""Updates an L7 policy.
@ -291,7 +376,11 @@ class ProviderDriver(object):
:raises UnsupportedOptionError: if driver does not
support one of the configuration options.
"""
raise exceptions.NotImplementedError()
raise exceptions.NotImplementedError(
user_fault_string='This provider does not support updating '
'l7policies.',
operator_fault_string='This provider does not support updating '
'l7policies.')
# L7 Rule
def l7rule_create(self, l7rule):
@ -305,7 +394,11 @@ class ProviderDriver(object):
:raises UnsupportedOptionError: if driver does not
support one of the configuration options.
"""
raise exceptions.NotImplementedError()
raise exceptions.NotImplementedError(
user_fault_string='This provider does not support creating '
'l7rules.',
operator_fault_string='This provider does not support creating '
'l7rules.')
def l7rule_delete(self, l7rule_id):
"""Deletes an L7 rule.
@ -316,7 +409,11 @@ class ProviderDriver(object):
:raises DriverError: An unexpected error occurred in the driver.
:raises NotImplementedError: if driver does not support request.
"""
raise exceptions.NotImplementedError()
raise exceptions.NotImplementedError(
user_fault_string='This provider does not support deleting '
'l7rules.',
operator_fault_string='This provider does not support deleting '
'l7rules.')
def l7rule_update(self, l7rule):
"""Updates an L7 rule.
@ -329,7 +426,11 @@ class ProviderDriver(object):
:raises UnsupportedOptionError: if driver does not
support one of the configuration options.
"""
raise exceptions.NotImplementedError()
raise exceptions.NotImplementedError(
user_fault_string='This provider does not support updating '
'l7rules.',
operator_fault_string='This provider does not support updating '
'l7rules.')
# Flavor
def get_supported_flavor_metadata(self):
@ -342,7 +443,11 @@ class ProviderDriver(object):
:raises DriverError: An unexpected error occurred in the driver.
:raises NotImplementedError: The driver does not support flavors.
"""
raise exceptions.NotImplementedError()
raise exceptions.NotImplementedError(
user_fault_string='This provider does not support getting the '
'supported flavor metadata.',
operator_fault_string='This provider does not support getting '
'the supported flavor metadata.')
def validate_flavor(self, flavor_metadata):
"""Validates if driver can support the flavor.
@ -355,4 +460,8 @@ class ProviderDriver(object):
:raises UnsupportedOptionError: if driver does not
support one of the configuration options.
"""
raise exceptions.NotImplementedError()
raise exceptions.NotImplementedError(
user_fault_string='This provider does not support validating '
'flavors.',
operator_fault_string='This provider does not support validating '
'the supported flavor metadata.')

View File

@ -0,0 +1,353 @@
# Copyright 2018 Rackspace, US Inc.
#
# 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 copy
from oslo_config import cfg
from oslo_log import log as logging
from stevedore import driver as stevedore_driver
from octavia.api.drivers import data_models as driver_dm
from octavia.api.drivers import exceptions as driver_exceptions
from octavia.common import data_models
from octavia.common import exceptions
from octavia.common.tls_utils import cert_parser
from octavia.i18n import _
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
def call_provider(provider, driver_method, *args, **kwargs):
"""Wrap calls to the provider driver to handle driver errors.
This allows Octavia to return user friendly errors when a provider driver
has an issue.
:param driver_method: Method in the driver to call.
:raises ProviderDriverError: Catch all driver error.
:raises ProviderNotImplementedError: The driver doesn't support this
action.
:raises ProviderUnsupportedOptionError: The driver doesn't support a
provided option.
"""
try:
return driver_method(*args, **kwargs)
except driver_exceptions.DriverError as e:
LOG.exception("Provider '%s' raised a driver error: %s",
provider, e.operator_fault_string)
raise exceptions.ProviderDriverError(prov=provider,
user_msg=e.user_fault_string)
except driver_exceptions.NotImplementedError as e:
LOG.info("Provider '%s' raised a not implemented error: %s",
provider, e.operator_fault_string)
raise exceptions.ProviderNotImplementedError(
prov=provider, user_msg=e.user_fault_string)
except driver_exceptions.UnsupportedOptionError as e:
LOG.info("Provider '%s' raised an unsupported option error: "
"%s", provider, e.operator_fault_string)
raise exceptions.ProviderUnsupportedOptionError(
prov=provider, user_msg=e.user_fault_string)
except Exception as e:
LOG.exception("Provider '%s' raised an unkown error: %s",
provider, e)
raise exceptions.ProviderDriverError(prov=provider, user_msg=e)
def _base_to_provider_dict(current_dict, include_project_id=False):
new_dict = copy.deepcopy(current_dict)
if 'provisioning_status' in new_dict:
del new_dict['provisioning_status']
if 'operating_status' in new_dict:
del new_dict['operating_status']
if 'provider' in new_dict:
del new_dict['provider']
if 'created_at' in new_dict:
del new_dict['created_at']
if 'updated_at' in new_dict:
del new_dict['updated_at']
if 'enabled' in new_dict:
new_dict['admin_state_up'] = new_dict.pop('enabled')
if 'project_id' in new_dict and not include_project_id:
del new_dict['project_id']
return new_dict
# Note: The provider dict returned from this method will have provider
# data model objects in it.
def lb_dict_to_provider_dict(lb_dict, vip=None,
db_pools=None, db_listeners=None):
new_lb_dict = _base_to_provider_dict(lb_dict, include_project_id=True)
new_lb_dict['loadbalancer_id'] = new_lb_dict.pop('id')
if vip:
new_lb_dict['vip_address'] = vip.ip_address
new_lb_dict['vip_network_id'] = vip.network_id
new_lb_dict['vip_port_id'] = vip.port_id
new_lb_dict['vip_subnet_id'] = vip.subnet_id
new_lb_dict['vip_qos_policy_id'] = vip.qos_policy_id
if db_pools:
new_lb_dict['pools'] = db_pools_to_provider_pools(db_pools)
if db_listeners:
new_lb_dict['listeners'] = db_listeners_to_provider_listeners(
db_listeners)
return new_lb_dict
def db_listeners_to_provider_listeners(db_listeners):
provider_listeners = []
for listener in db_listeners:
new_listener_dict = listener_dict_to_provider_dict(
listener.to_dict(recurse=True))
if ('default_pool' in new_listener_dict and
new_listener_dict['default_pool']):
provider_pool = db_pool_to_provider_pool(listener.default_pool)
new_listener_dict['default_pool_id'] = provider_pool.pool_id
new_listener_dict['default_pool'] = provider_pool
if 'l7policies' in new_listener_dict:
new_listener_dict['l7policies'] = (
db_l7policies_to_provider_l7policies(listener.l7policies))
provider_listeners.append(
driver_dm.Listener.from_dict(new_listener_dict))
return provider_listeners
def listener_dict_to_provider_dict(listener_dict):
new_listener_dict = _base_to_provider_dict(listener_dict)
new_listener_dict['listener_id'] = new_listener_dict.pop('id')
if 'load_balancer_id' in new_listener_dict:
new_listener_dict['loadbalancer_id'] = new_listener_dict.pop(
'load_balancer_id')
# Pull the certs out of the certificate manager to pass to the provider
if 'tls_certificate_id' in new_listener_dict:
del new_listener_dict['tls_certificate_id']
if 'sni_containers' in new_listener_dict:
del new_listener_dict['sni_containers']
listener_obj = data_models.Listener(**listener_dict)
if listener_obj.tls_certificate_id or listener_obj.sni_containers:
SNI_objs = []
for sni in listener_obj.sni_containers:
if isinstance(sni, data_models.SNI):
SNI_objs.append(sni)
elif isinstance(sni, dict):
sni_obj = data_models.SNI(**sni)
SNI_objs.append(sni_obj)
else:
raise Exception(_('Invalid SNI container on listener'))
listener_obj.sni_containers = SNI_objs
cert_manager = stevedore_driver.DriverManager(
namespace='octavia.cert_manager',
name=CONF.certificates.cert_manager,
invoke_on_load=True,
).driver
cert_dict = cert_parser.load_certificates_data(cert_manager,
listener_obj)
new_listener_dict['default_tls_container'] = cert_dict['tls_cert']
new_listener_dict['sni_containers'] = cert_dict['sni_certs']
# Remove the DB back references
if 'load_balancer' in new_listener_dict:
del new_listener_dict['load_balancer']
if 'peer_port' in new_listener_dict:
del new_listener_dict['peer_port']
if 'pools' in new_listener_dict:
del new_listener_dict['pools']
if 'stats' in new_listener_dict:
del new_listener_dict['stats']
if ('default_pool' in new_listener_dict and
new_listener_dict['default_pool']):
pool = new_listener_dict.pop('default_pool')
new_listener_dict['default_pool'] = pool_dict_to_provider_dict(pool)
provider_l7policies = []
l7policies = new_listener_dict.pop('l7policies')
for l7policy in l7policies:
provider_l7policy = l7policy_dict_to_provider_dict(l7policy)
provider_l7policies.append(provider_l7policy)
new_listener_dict['l7policies'] = provider_l7policies
return new_listener_dict
def db_pools_to_provider_pools(db_pools):
provider_pools = []
for pool in db_pools:
provider_pools.append(db_pool_to_provider_pool(pool))
return provider_pools
def db_pool_to_provider_pool(db_pool):
new_pool_dict = pool_dict_to_provider_dict(db_pool.to_dict(recurse=True))
# Replace the sub-dicts with objects
if 'health_monitor' in new_pool_dict:
del new_pool_dict['health_monitor']
if db_pool.health_monitor:
provider_healthmonitor = db_HM_to_provider_HM(db_pool.health_monitor)
new_pool_dict['healthmonitor'] = provider_healthmonitor
# Don't leave a 'members' None here, we want it to pass through to Unset
if 'members' in new_pool_dict:
del new_pool_dict['members']
if db_pool.members:
provider_members = db_members_to_provider_members(db_pool.members)
new_pool_dict['members'] = provider_members
return driver_dm.Pool.from_dict(new_pool_dict)
def pool_dict_to_provider_dict(pool_dict):
new_pool_dict = _base_to_provider_dict(pool_dict)
new_pool_dict['pool_id'] = new_pool_dict.pop('id')
# Remove the DB back references
if ('session_persistence' in new_pool_dict and
new_pool_dict['session_persistence']):
if 'pool_id' in new_pool_dict['session_persistence']:
del new_pool_dict['session_persistence']['pool_id']
if 'pool' in new_pool_dict['session_persistence']:
del new_pool_dict['session_persistence']['pool']
if 'l7policies' in new_pool_dict:
del new_pool_dict['l7policies']
if 'listeners' in new_pool_dict:
del new_pool_dict['listeners']
if 'load_balancer' in new_pool_dict:
del new_pool_dict['load_balancer']
if 'load_balancer_id' in new_pool_dict:
new_pool_dict['loadbalancer_id'] = new_pool_dict.pop(
'load_balancer_id')
if 'health_monitor' in new_pool_dict and new_pool_dict['health_monitor']:
hm = new_pool_dict.pop('health_monitor')
new_pool_dict['healthmonitor'] = hm_dict_to_provider_dict(hm)
if 'members' in new_pool_dict and new_pool_dict['members']:
members = new_pool_dict.pop('members')
provider_members = []
for member in members:
provider_member = member_dict_to_provider_dict(member)
provider_members.append(provider_member)
new_pool_dict['members'] = provider_members
return new_pool_dict
def db_members_to_provider_members(db_members):
provider_members = []
for member in db_members:
new_member_dict = member_dict_to_provider_dict(member.to_dict())
provider_members.append(driver_dm.Member.from_dict(new_member_dict))
return provider_members
def member_dict_to_provider_dict(member_dict):
new_member_dict = _base_to_provider_dict(member_dict)
new_member_dict['member_id'] = new_member_dict.pop('id')
if 'ip_address' in new_member_dict:
new_member_dict['address'] = new_member_dict.pop('ip_address')
# Remove the DB back references
if 'pool' in new_member_dict:
del new_member_dict['pool']
return new_member_dict
def db_HM_to_provider_HM(db_hm):
new_HM_dict = hm_dict_to_provider_dict(db_hm.to_dict())
return driver_dm.HealthMonitor.from_dict(new_HM_dict)
def hm_dict_to_provider_dict(hm_dict):
new_hm_dict = _base_to_provider_dict(hm_dict)
new_hm_dict['healthmonitor_id'] = new_hm_dict.pop('id')
if 'fall_threshold' in new_hm_dict:
new_hm_dict['max_retries_down'] = new_hm_dict.pop('fall_threshold')
if 'rise_threshold' in new_hm_dict:
new_hm_dict['max_retries'] = new_hm_dict.pop('rise_threshold')
# Remove the DB back references
if 'pool' in new_hm_dict:
del new_hm_dict['pool']
return new_hm_dict
def db_l7policies_to_provider_l7policies(db_l7policies):
provider_l7policies = []
for l7policy in db_l7policies:
new_l7policy_dict = l7policy_dict_to_provider_dict(
l7policy.to_dict(recurse=True))
if 'l7rules' in new_l7policy_dict:
del new_l7policy_dict['l7rules']
new_l7rules = db_l7rules_to_provider_l7rules(l7policy.l7rules)
new_l7policy_dict['rules'] = new_l7rules
provider_l7policies.append(
driver_dm.L7Policy.from_dict(new_l7policy_dict))
return provider_l7policies
def l7policy_dict_to_provider_dict(l7policy_dict):
new_l7policy_dict = _base_to_provider_dict(l7policy_dict)
new_l7policy_dict['l7policy_id'] = new_l7policy_dict.pop('id')
# Remove the DB back references
if 'listener' in new_l7policy_dict:
del new_l7policy_dict['listener']
if 'redirect_pool' in new_l7policy_dict:
del new_l7policy_dict['redirect_pool']
if 'l7rules' in new_l7policy_dict and new_l7policy_dict['l7rules']:
rules = new_l7policy_dict.pop('l7rules')
provider_rules = []
for rule in rules:
provider_rule = l7rule_dict_to_provider_dict(rule)
provider_rules.append(provider_rule)
new_l7policy_dict['rules'] = provider_rules
return new_l7policy_dict
def db_l7rules_to_provider_l7rules(db_l7rules):
provider_l7rules = []
for l7rule in db_l7rules:
new_l7rule_dict = l7rule_dict_to_provider_dict(l7rule.to_dict())
provider_l7rules.append(driver_dm.L7Rule.from_dict(new_l7rule_dict))
return provider_l7rules
def l7rule_dict_to_provider_dict(l7rule_dict):
new_l7rule_dict = _base_to_provider_dict(l7rule_dict)
new_l7rule_dict['l7rule_id'] = new_l7rule_dict.pop('id')
# Remove the DB back references
if 'l7policy' in new_l7rule_dict:
del new_l7rule_dict['l7policy']
return new_l7rule_dict
def vip_dict_to_provider_dict(vip_dict):
new_vip_dict = {}
if 'ip_address' in vip_dict:
new_vip_dict['vip_address'] = vip_dict['ip_address']
if 'network_id' in vip_dict:
new_vip_dict['vip_network_id'] = vip_dict['network_id']
if 'port_id' in vip_dict:
new_vip_dict['vip_port_id'] = vip_dict['port_id']
if 'subnet_id' in vip_dict:
new_vip_dict['vip_subnet_id'] = vip_dict['subnet_id']
if 'qos_policy_id' in vip_dict:
new_vip_dict['vip_qos_policy_id'] = vip_dict['qos_policy_id']
return new_vip_dict
def provider_vip_dict_to_vip_obj(vip_dictionary):
vip_obj = data_models.Vip()
if 'vip_address' in vip_dictionary:
vip_obj.ip_address = vip_dictionary['vip_address']
if 'vip_network_id' in vip_dictionary:
vip_obj.network_id = vip_dictionary['vip_network_id']
if 'vip_port_id' in vip_dictionary:
vip_obj.port_id = vip_dictionary['vip_port_id']
if 'vip_subnet_id' in vip_dictionary:
vip_obj.subnet_id = vip_dictionary['vip_subnet_id']
if 'vip_qos_policy_id' in vip_dictionary:
vip_obj.qos_policy_id = vip_dictionary['vip_qos_policy_id']
return vip_obj

View File

@ -197,6 +197,7 @@ class L7PolicyController(base.BaseController):
l7rule.L7RuleController(db_policy.id)._graph_create(
lock_session, r))
db_policy.l7rules = new_rules
return db_policy
@wsme_pecan.wsexpose(l7policy_types.L7PolicyRootResponse,

View File

@ -272,7 +272,8 @@ class ListenersController(base.BaseController):
l7p['redirect_pool_id'] = pool_id
new_l7ps.append(l7policy.L7PolicyController()._graph_create(
lock_session, l7p))
return db_listener, new_l7ps
db_listener.l7policies = new_l7ps
return db_listener
@wsme_pecan.wsexpose(listener_types.ListenerRootResponse, wtypes.text,
body=listener_types.ListenerRootPUT, status_code=200)

View File

@ -22,6 +22,9 @@ import pecan
from wsme import types as wtypes
from wsmeext import pecan as wsme_pecan
from octavia.api.drivers import data_models as driver_dm
from octavia.api.drivers import driver_factory
from octavia.api.drivers import utils as driver_utils
from octavia.api.v2.controllers import base
from octavia.api.v2.controllers import listener
from octavia.api.v2.controllers import pool
@ -249,6 +252,9 @@ class LoadBalancersController(base.BaseController):
self._validate_vip_request_object(load_balancer)
# Load the driver early as it also provides validation
driver = driver_factory.get_driver(load_balancer.provider)
lock_session = db_api.get_session(autocommit=False)
try:
if self.repositories.check_quota_met(
@ -265,35 +271,58 @@ class LoadBalancersController(base.BaseController):
))
vip_dict = lb_dict.pop('vip', {})
# Make sure we store the right provider in the DB
lb_dict['provider'] = driver.name
# NoneType can be weird here, have to force type a second time
listeners = lb_dict.pop('listeners', []) or []
pools = lb_dict.pop('pools', []) or []
# TODO(johnsom) Remove provider and flavor from the lb_dict
# as they have not been implemented beyond the API yet.
# Remove these lines as they are implemented.
if 'provider' in lb_dict:
del lb_dict['provider']
if 'flavor_id' in lb_dict:
del lb_dict['flavor_id']
# TODO(johnsom) Remove flavor from the lb_dict
# as it has not been implemented beyond the API yet.
# Remove this line when it is implemented.
lb_dict.pop('flavor', None)
db_lb = self.repositories.create_load_balancer_and_vip(
lock_session, lb_dict, vip_dict)
# create vip port if not exist
# See if the provider driver wants to create the VIP port
try:
provider_vip_dict = driver_utils.vip_dict_to_provider_dict(
vip_dict)
vip_dict = driver_utils.call_provider(
driver.name, driver.create_vip_port, db_lb.id,
db_lb.project_id, provider_vip_dict)
vip = driver_utils.provider_vip_dict_to_vip_obj(vip_dict)
except exceptions.ProviderNotImplementedError:
# create vip port if not exist, driver didn't want to create
# the VIP port
vip = self._create_vip_port_if_not_exist(db_lb)
LOG.info('Created VIP port %s for provider %s.',
vip.port_id, driver.name)
self.repositories.vip.update(
lock_session, db_lb.id,
ip_address=vip.ip_address,
port_id=vip.port_id,
network_id=vip.network_id,
subnet_id=vip.subnet_id
)
subnet_id=vip.subnet_id)
if listeners or pools:
db_pools, db_lists = self._graph_create(
context.session, lock_session, db_lb, listeners, pools)
# Prepare the data for the driver data model
driver_lb_dict = driver_utils.lb_dict_to_provider_dict(
lb_dict, vip, db_pools, db_lists)
# Dispatch to the driver
LOG.info("Sending create Load Balancer %s to provider %s",
db_lb.id, driver.name)
driver_utils.call_provider(
driver.name, driver.loadbalancer_create,
driver_dm.LoadBalancer.from_dict(driver_lb_dict))
lock_session.commit()
except odb_exceptions.DBDuplicateEntry:
lock_session.rollback()
@ -302,17 +331,6 @@ class LoadBalancersController(base.BaseController):
with excutils.save_and_reraise_exception():
lock_session.rollback()
# Handler will be responsible for sending to controller
try:
LOG.info("Sending created Load Balancer %s to the handler",
db_lb.id)
self.handler.create(db_lb)
except Exception:
with excutils.save_and_reraise_exception(reraise=False):
self.repositories.load_balancer.update(
context.session, db_lb.id,
provisioning_status=constants.ERROR)
db_lb = self._get_db_lb(context.session, db_lb.id)
result = self._convert_db_to_type(
@ -394,8 +412,7 @@ class LoadBalancersController(base.BaseController):
attr=attr))
p['load_balancer_id'] = db_lb.id
p['project_id'] = db_lb.project_id
new_pool, new_hm, new_members = (
pool.PoolsController()._graph_create(
new_pool = (pool.PoolsController()._graph_create(
session, lock_session, p))
new_pools.append(new_pool)
pool_name_ids[new_pool.name] = new_pool.id
@ -442,14 +459,39 @@ class LoadBalancersController(base.BaseController):
wtypes.UnsetType) and
db_lb.vip.qos_policy_id != load_balancer.vip_qos_policy_id):
validate.qos_policy_exists(load_balancer.vip_qos_policy_id)
self._test_lb_status(context.session, id)
try:
LOG.info("Sending updated Load Balancer %s to the handler", id)
self.handler.update(db_lb, load_balancer)
except Exception:
with excutils.save_and_reraise_exception(reraise=False):
self.repositories.load_balancer.update(
context.session, id, provisioning_status=constants.ERROR)
# Load the driver early as it also provides validation
driver = driver_factory.get_driver(db_lb.provider)
with db_api.get_lock_session() as lock_session:
self._test_lb_status(lock_session, id)
# Prepare the data for the driver data model
lb_dict = load_balancer.to_dict(render_unsets=False)
lb_dict['id'] = id
vip_dict = lb_dict.pop('vip', {})
lb_dict = driver_utils.lb_dict_to_provider_dict(lb_dict)
if 'qos_policy_id' in vip_dict:
lb_dict['vip_qos_policy_id'] = vip_dict['qos_policy_id']
# Dispatch to the driver
LOG.info("Sending update Load Balancer %s to provider "
"%s", id, driver.name)
driver_utils.call_provider(
driver.name, driver.loadbalancer_update,
driver_dm.LoadBalancer.from_dict(lb_dict))
db_lb_dict = load_balancer.to_dict(render_unsets=False)
if 'vip' in db_lb_dict:
db_vip_dict = db_lb_dict.pop('vip')
self.repositories.vip.update(lock_session, id, **db_vip_dict)
if db_lb_dict:
self.repositories.load_balancer.update(lock_session, id,
**db_lb_dict)
# Force SQL alchemy to query the DB, otherwise we get inconsistent
# results
context.session.expire_all()
db_lb = self._get_db_lb(context.session, id)
result = self._convert_db_to_type(db_lb, lb_types.LoadBalancerResponse)
return lb_types.LoadBalancerRootResponse(loadbalancer=result)
@ -464,6 +506,9 @@ class LoadBalancersController(base.BaseController):
self._auth_validate_action(context, db_lb.project_id,
constants.RBAC_DELETE)
# Load the driver early as it also provides validation
driver = driver_factory.get_driver(db_lb.provider)
with db_api.get_lock_session() as lock_session:
if (db_lb.listeners or db_lb.pools) and not cascade:
msg = _("Cannot delete Load Balancer %s - "
@ -473,14 +518,10 @@ class LoadBalancersController(base.BaseController):
self._test_lb_status(lock_session, id,
lb_status=constants.PENDING_DELETE)
try:
LOG.info("Sending deleted Load Balancer %s to the handler", id)
self.handler.delete(db_lb, cascade)
except Exception:
with excutils.save_and_reraise_exception(reraise=False):
self.repositories.load_balancer.update(
context.session, id,
provisioning_status=constants.ERROR)
LOG.info("Sending delete Load Balancer %s to provider %s",
id, driver.name)
driver_utils.call_provider(driver.name, driver.loadbalancer_delete,
id, cascade)
@pecan.expose()
def _lookup(self, id, *remainder):
@ -579,13 +620,12 @@ class FailoverController(LoadBalancersController):
self._auth_validate_action(context, db_lb.project_id,
constants.RBAC_PUT_FAILOVER)
self._test_lb_status(context.session, self.lb_id)
try:
LOG.info("Sending failover request for lb %s to the handler",
self.lb_id)
self.handler.failover(db_lb)
except Exception:
with excutils.save_and_reraise_exception(reraise=False):
self.repositories.load_balancer.update(
context.session, self.lb_id,
provisioning_status=constants.ERROR)
# Load the driver early as it also provides validation
driver = driver_factory.get_driver(db_lb.provider)
with db_api.get_lock_session() as lock_session:
self._test_lb_status(lock_session, self.lb_id)
LOG.info("Sending failover request for load balancer %s to the "
"provider %s", self.lb_id, driver.name)
driver_utils.call_provider(
driver.name, driver.loadbalancer_failover, self.lb_id)

View File

@ -231,6 +231,7 @@ class PoolsController(base.BaseController):
hm['project_id'] = db_pool.project_id
new_hm = health_monitor.HealthMonitorController()._graph_create(
lock_session, hm)
db_pool.health_monitor = new_hm
# Now check quotas for members
if members and self.repositories.check_quota_met(
@ -245,7 +246,8 @@ class PoolsController(base.BaseController):
new_members.append(
member.MembersController(db_pool.id)._graph_create(
lock_session, m))
return db_pool, new_hm, new_members
db_pool.members = new_members
return db_pool
@wsme_pecan.wsexpose(pool_types.PoolRootResponse, wtypes.text,
body=pool_types.PoolRootPut, status_code=200)

View File

@ -120,9 +120,7 @@ class LoadBalancerPOST(BaseLoadBalancerType):
project_id = wtypes.wsattr(wtypes.StringType(max_length=36))
listeners = wtypes.wsattr([listener.ListenerSingleCreate], default=[])
pools = wtypes.wsattr([pool.PoolSingleCreate], default=[])
# TODO(johnsom) This should be dynamic based on the loaded providers
# once providers are implemented.
provider = wtypes.wsattr(wtypes.Enum(str, *constants.SUPPORTED_PROVIDERS))
provider = wtypes.wsattr(wtypes.StringType(max_length=64))
# TODO(johnsom) This should be dynamic based on the loaded flavors
# once flavors are implemented.
flavor_id = wtypes.wsattr(wtypes.Enum(str, *constants.SUPPORTED_FLAVORS))

View File

@ -102,6 +102,12 @@ api_opts = [
help=_("Allow users to create TLS Terminated listeners?")),
cfg.BoolOpt('allow_ping_health_monitors', default=True,
help=_("Allow users to create PING type Health Monitors?")),
cfg.ListOpt('enabled_provider_drivers',
help=_('List of enabled provider drivers. Must match the '
'driver name in the octavia.api.drivers entrypoint.'),
default=['amphora', 'octavia']),
cfg.StrOpt('default_provider_driver', default='amphora',
help=_('Default provider driver.')),
]
# Options only used by the amphora agent

View File

@ -193,8 +193,10 @@ DELTAS = 'deltas'
HEALTH_MON = 'health_mon'
LISTENER = 'listener'
LISTENERS = 'listeners'
LISTENER_ID = 'listener_id'
LOADBALANCER = 'loadbalancer'
LOADBALANCER_ID = 'loadbalancer_id'
LOAD_BALANCER_ID = 'load_balancer_id'
SERVER_GROUP_ID = 'server_group_id'
ANTI_AFFINITY = 'anti-affinity'
SOFT_ANTI_AFFINITY = 'soft-anti-affinity'
@ -220,6 +222,10 @@ ADDED_PORTS = 'added_ports'
PORTS = 'ports'
MEMBER_PORTS = 'member_ports'
LOADBALANCER_TOPOLOGY = 'topology'
HEALTH_MONITOR_ID = 'health_monitor_id'
L7POLICY_ID = 'l7policy_id'
L7RULE_ID = 'l7rule_id'
LOAD_BALANCER_UPDATES = 'load_balancer_updates'
CERT_ROTATE_AMPHORA_FLOW = 'octavia-cert-rotate-amphora-flow'
CREATE_AMPHORA_FLOW = 'octavia-create-amphora-flow'
@ -489,7 +495,6 @@ RBAC_GET_STATUS = 'get_status'
# PROVIDERS
# TODO(johnsom) When providers are implemented, this should be removed.
OCTAVIA = 'octavia'
SUPPORTED_PROVIDERS = OCTAVIA,
# FLAVORS
# TODO(johnsom) When flavors are implemented, this should be removed.

View File

@ -419,7 +419,7 @@ class LoadBalancer(BaseDataModel):
provisioning_status=None, operating_status=None, enabled=None,
topology=None, vip=None, listeners=None, amphorae=None,
pools=None, vrrp_group=None, server_group_id=None,
created_at=None, updated_at=None):
created_at=None, updated_at=None, provider=None):
self.id = id
self.project_id = project_id
@ -437,6 +437,7 @@ class LoadBalancer(BaseDataModel):
self.server_group_id = server_group_id
self.created_at = created_at
self.updated_at = updated_at
self.provider = provider
def update(self, update_dict):
for key, value in update_dict.items():

View File

@ -340,3 +340,30 @@ class InvalidLimit(APIException):
class MissingVIPSecurityGroup(OctaviaException):
message = _('VIP security group is missing for load balancer: %(lb_id)s')
class ProviderNotEnabled(APIException):
msg = _("Provider '%(prov)s' is not enabled.")
code = 400
class ProviderNotFound(APIException):
msg = _("Provider '%(prov)s' was not found.")
code = 501
class ProviderDriverError(APIException):
msg = _("Provider '%(prov)s' reports error: %(user_msg)s")
code = 500
class ProviderNotImplementedError(APIException):
msg = _("Provider '%(prov)s' does not support a requested action: "
"%(user_msg)s")
code = 501
class ProviderUnsupportedOptionError(APIException):
msg = _("Provider '%(prov)s' does not support a requested option: "
"%(user_msg)s")
code = 501

View File

@ -0,0 +1,37 @@
# Copyright 2018 Rackspace, US Inc.
#
# 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.
"""Add provider column
Revision ID: 0f242cf02c74
Revises: 0fd2c131923f
Create Date: 2018-04-23 16:22:26.971048
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '0f242cf02c74'
down_revision = '0fd2c131923f'
def upgrade():
op.add_column(
u'load_balancer',
sa.Column(u'provider', sa.String(64), nullable=True)
)
op.execute("UPDATE load_balancer set provider='amphora' where provider "
"is null")

View File

@ -343,6 +343,7 @@ class LoadBalancer(base_models.BASE, base_models.IdMixin,
backref=orm.backref("load_balancer",
uselist=False))
server_group_id = sa.Column(sa.String(36), nullable=True)
provider = sa.Column(sa.String(64), nullable=True)
class VRRPGroup(base_models.BASE):

View File

@ -79,6 +79,12 @@ class BaseAPITest(base_db_test.OctaviaDBTestBase):
self.conf.config(group="controller_worker",
network_driver='network_noop_driver')
self.conf.config(group='api_settings', auth_strategy=constants.NOAUTH)
self.conf.config(group='api_settings',
default_provider_driver='noop_driver')
# We still need to test with the "octavia" alias
self.conf.config(group='api_settings',
enabled_provider_drivers='amphora, noop_driver, '
'octavia')
self.lb_repo = repositories.LoadBalancerRepository()
self.listener_repo = repositories.ListenerRepository()
self.listener_stats_repo = repositories.ListenerStatisticsRepository()

View File

@ -128,10 +128,11 @@ class TestAmphora(base.BaseAPITest):
self.put(self.AMPHORA_FAILOVER_PATH.format(
amphora_id=new_amp.id), body={}, status=404)
def test_failover_bad_amp_id(self):
@mock.patch('oslo_messaging.RPCClient.cast')
def test_failover_bad_amp_id(self, mock_cast):
self.put(self.AMPHORA_FAILOVER_PATH.format(
amphora_id='asdf'), body={}, status=404)
self.assertFalse(self.handler_mock().amphora.failover.called)
self.assertFalse(mock_cast.called)
def test_get_authorized(self):
self.conf = self.useFixture(oslo_fixture.Config(cfg.CONF))

View File

@ -20,9 +20,11 @@ from oslo_config import cfg
from oslo_config import fixture as oslo_fixture
from oslo_utils import uuidutils
from octavia.api.drivers import exceptions as provider_exceptions
from octavia.common import constants
import octavia.common.context
from octavia.common import data_models
from octavia.common import exceptions
from octavia.network import base as network_base
from octavia.network import data_models as network_models
from octavia.tests.functional.api.v2 import base
@ -314,13 +316,19 @@ class TestLoadBalancer(base.BaseAPITest):
'vip_port_id': port.id, 'admin_state_up': False,
'project_id': self.project_id}
body = self._build_body(lb_json)
# This test needs the provider driver to not supply the VIP port
# so mocking noop to not supply a VIP port.
with mock.patch(
"octavia.network.drivers.noop_driver.driver.NoopManager"
".get_network") as mock_get_network, mock.patch(
"octavia.network.drivers.noop_driver.driver.NoopManager"
".get_port") as mock_get_port:
".get_port") as mock_get_port, mock.patch(
"octavia.api.drivers.noop_driver.driver.NoopManager."
"create_vip_port") as mock_provider:
mock_get_network.return_value = network
mock_get_port.return_value = port
mock_provider.side_effect = (provider_exceptions.
NotImplementedError())
response = self.post(self.LBS_PATH, body)
api_lb = response.json.get(self.root_tag)
self._assert_request_matches_response(lb_json, api_lb)
@ -466,17 +474,23 @@ class TestLoadBalancer(base.BaseAPITest):
'vip_network_id': network.id, 'vip_port_id': port.id,
'admin_state_up': False, 'project_id': self.project_id}
body = self._build_body(lb_json)
# This test needs the provider driver to not supply the VIP port
# so mocking noop to not supply a VIP port.
with mock.patch(
"octavia.network.drivers.noop_driver.driver.NoopManager"
".get_network") as mock_get_network, mock.patch(
"octavia.network.drivers.noop_driver.driver.NoopManager"
".get_port") as mock_get_port, mock.patch(
"octavia.network.drivers.noop_driver.driver.NoopManager"
".allocate_vip") as mock_allocate_vip:
".allocate_vip") as mock_allocate_vip, mock.patch(
"octavia.api.drivers.noop_driver.driver.NoopManager."
"create_vip_port") as mock_provider:
mock_get_network.return_value = network
mock_get_port.return_value = port
mock_allocate_vip.side_effect = TestNeutronException(
"octavia_msg", "neutron_msg", 409)
mock_provider.side_effect = (provider_exceptions.
NotImplementedError())
response = self.post(self.LBS_PATH, body, status=409)
# Make sure the faultstring contains the neutron error and not
# the octavia error message
@ -786,6 +800,9 @@ class TestLoadBalancer(base.BaseAPITest):
}
lb_json.update(optionals)
body = self._build_body(lb_json)
with mock.patch('oslo_messaging.get_rpc_transport'):
with mock.patch('oslo_messaging.Target'):
with mock.patch('oslo_messaging.RPCClient'):
response = self.post(self.LBS_PATH, body)
api_lb = response.json.get(self.root_tag)
self._assert_request_matches_response(lb_json, api_lb)
@ -800,8 +817,7 @@ class TestLoadBalancer(base.BaseAPITest):
lb_json.update(optionals)
body = self._build_body(lb_json)
response = self.post(self.LBS_PATH, body, status=400)
self.assertIn("Invalid input for field/attribute provider. Value: "
"'BOGUS'. Value should be one of:",
self.assertIn("Provider 'BOGUS' is not enabled.",
response.json.get('faultstring'))
def test_create_flavor_bogus(self, **optionals):
@ -1259,7 +1275,7 @@ class TestLoadBalancer(base.BaseAPITest):
lb_json)
api_lb = response.json.get(self.root_tag)
self.assertIsNotNone(api_lb.get('vip_subnet_id'))
self.assertEqual('lb1', api_lb.get('name'))
self.assertEqual('lb2', api_lb.get('name'))
self.assertEqual(project_id, api_lb.get('project_id'))
self.assertEqual('desc1', api_lb.get('description'))
self.assertFalse(api_lb.get('admin_state_up'))
@ -1360,7 +1376,7 @@ class TestLoadBalancer(base.BaseAPITest):
api_lb = response.json.get(self.root_tag)
self.conf.config(group='api_settings', auth_strategy=auth_strategy)
self.assertIsNotNone(api_lb.get('vip_subnet_id'))
self.assertEqual('lb1', api_lb.get('name'))
self.assertEqual('lb2', api_lb.get('name'))
self.assertEqual(project_id, api_lb.get('project_id'))
self.assertEqual('desc1', api_lb.get('description'))
self.assertFalse(api_lb.get('admin_state_up'))
@ -1820,28 +1836,33 @@ class TestLoadBalancer(base.BaseAPITest):
lb_id=lb_dict.get('id')) + "/failover")
self.app.put(path, status=404)
def test_create_with_bad_handler(self):
self.handler_mock().load_balancer.create.side_effect = Exception()
api_lb = self.create_load_balancer(
uuidutils.generate_uuid()).get(self.root_tag)
self.assert_correct_status(
lb_id=api_lb.get('id'),
lb_prov_status=constants.ERROR,
lb_op_status=constants.OFFLINE)
@mock.patch('octavia.api.drivers.utils.call_provider')
def test_create_with_bad_provider(self, mock_provider):
mock_provider.side_effect = exceptions.ProviderDriverError(
prov='bad_driver', user_msg='broken')
lb_json = {'name': 'test-lb',
'vip_subnet_id': uuidutils.generate_uuid(),
'project_id': self.project_id}
response = self.post(self.LBS_PATH, self._build_body(lb_json),
status=500)
self.assertIn('Provider \'bad_driver\' reports error: broken',
response.json.get('faultstring'))
def test_update_with_bad_handler(self):
@mock.patch('octavia.api.drivers.utils.call_provider')
def test_update_with_bad_provider(self, mock_provider):
api_lb = self.create_load_balancer(
uuidutils.generate_uuid()).get(self.root_tag)
self.set_lb_status(lb_id=api_lb.get('id'))
new_listener = {'name': 'new_name'}
self.handler_mock().load_balancer.update.side_effect = Exception()
self.put(self.LB_PATH.format(lb_id=api_lb.get('id')),
self._build_body(new_listener))
self.assert_correct_status(
lb_id=api_lb.get('id'),
lb_prov_status=constants.ERROR)
mock_provider.side_effect = exceptions.ProviderDriverError(
prov='bad_driver', user_msg='broken')
response = self.put(self.LB_PATH.format(lb_id=api_lb.get('id')),
self._build_body(new_listener), status=500)
self.assertIn('Provider \'bad_driver\' reports error: broken',
response.json.get('faultstring'))
def test_delete_with_bad_handler(self):
@mock.patch('octavia.api.drivers.utils.call_provider')
def test_delete_with_bad_provider(self, mock_provider):
api_lb = self.create_load_balancer(
uuidutils.generate_uuid()).get(self.root_tag)
self.set_lb_status(lb_id=api_lb.get('id'))
@ -1854,11 +1875,95 @@ class TestLoadBalancer(base.BaseAPITest):
self.assertIsNone(api_lb.pop('updated_at'))
self.assertIsNotNone(response.pop('updated_at'))
self.assertEqual(api_lb, response)
self.handler_mock().load_balancer.delete.side_effect = Exception()
self.delete(self.LB_PATH.format(lb_id=api_lb.get('id')))
self.assert_correct_status(
lb_id=api_lb.get('id'),
lb_prov_status=constants.ERROR)
mock_provider.side_effect = exceptions.ProviderDriverError(
prov='bad_driver', user_msg='broken')
self.delete(self.LB_PATH.format(lb_id=api_lb.get('id')), status=500)
@mock.patch('octavia.api.drivers.utils.call_provider')
def test_create_with_provider_not_implemented(self, mock_provider):
mock_provider.side_effect = exceptions.ProviderNotImplementedError(
prov='bad_driver', user_msg='broken')
lb_json = {'name': 'test-lb',
'vip_subnet_id': uuidutils.generate_uuid(),
'project_id': self.project_id}
response = self.post(self.LBS_PATH, self._build_body(lb_json),
status=501)
self.assertIn('Provider \'bad_driver\' does not support a requested '
'action: broken', response.json.get('faultstring'))
@mock.patch('octavia.api.drivers.utils.call_provider')
def test_update_with_provider_not_implemented(self, mock_provider):
api_lb = self.create_load_balancer(
uuidutils.generate_uuid()).get(self.root_tag)
self.set_lb_status(lb_id=api_lb.get('id'))
new_listener = {'name': 'new_name'}
mock_provider.side_effect = exceptions.ProviderNotImplementedError(
prov='bad_driver', user_msg='broken')
response = self.put(self.LB_PATH.format(lb_id=api_lb.get('id')),
self._build_body(new_listener), status=501)
self.assertIn('Provider \'bad_driver\' does not support a requested '
'action: broken', response.json.get('faultstring'))
@mock.patch('octavia.api.drivers.utils.call_provider')
def test_delete_with_provider_not_implemented(self, mock_provider):
api_lb = self.create_load_balancer(
uuidutils.generate_uuid()).get(self.root_tag)
self.set_lb_status(lb_id=api_lb.get('id'))
# Set status to ACTIVE/ONLINE because set_lb_status did it in the db
api_lb['provisioning_status'] = constants.ACTIVE
api_lb['operating_status'] = constants.ONLINE
response = self.get(self.LB_PATH.format(
lb_id=api_lb.get('id'))).json.get(self.root_tag)
self.assertIsNone(api_lb.pop('updated_at'))
self.assertIsNotNone(response.pop('updated_at'))
self.assertEqual(api_lb, response)
mock_provider.side_effect = exceptions.ProviderNotImplementedError(
prov='bad_driver', user_msg='broken')
self.delete(self.LB_PATH.format(lb_id=api_lb.get('id')), status=501)
@mock.patch('octavia.api.drivers.utils.call_provider')
def test_create_with_provider_unsupport_option(self, mock_provider):
mock_provider.side_effect = exceptions.ProviderUnsupportedOptionError(
prov='bad_driver', user_msg='broken')
lb_json = {'name': 'test-lb',
'vip_subnet_id': uuidutils.generate_uuid(),
'project_id': self.project_id}
response = self.post(self.LBS_PATH, self._build_body(lb_json),
status=501)
self.assertIn('Provider \'bad_driver\' does not support a requested '
'option: broken', response.json.get('faultstring'))
@mock.patch('octavia.api.drivers.utils.call_provider')
def test_update_with_provider_unsupport_option(self, mock_provider):
api_lb = self.create_load_balancer(
uuidutils.generate_uuid()).get(self.root_tag)
self.set_lb_status(lb_id=api_lb.get('id'))
new_listener = {'name': 'new_name'}
mock_provider.side_effect = exceptions.ProviderUnsupportedOptionError(
prov='bad_driver', user_msg='broken')
response = self.put(self.LB_PATH.format(lb_id=api_lb.get('id')),
self._build_body(new_listener), status=501)
self.assertIn('Provider \'bad_driver\' does not support a requested '
'option: broken', response.json.get('faultstring'))
@mock.patch('octavia.api.drivers.utils.call_provider')
def test_delete_with_provider_unsupport_option(self, mock_provider):
api_lb = self.create_load_balancer(
uuidutils.generate_uuid()).get(self.root_tag)
self.set_lb_status(lb_id=api_lb.get('id'))
# Set status to ACTIVE/ONLINE because set_lb_status did it in the db
api_lb['provisioning_status'] = constants.ACTIVE
api_lb['operating_status'] = constants.ONLINE
response = self.get(self.LB_PATH.format(
lb_id=api_lb.get('id'))).json.get(self.root_tag)
self.assertIsNone(api_lb.pop('updated_at'))
self.assertIsNotNone(response.pop('updated_at'))
self.assertEqual(api_lb, response)
mock_provider.side_effect = exceptions.ProviderUnsupportedOptionError(
prov='bad_driver', user_msg='broken')
self.delete(self.LB_PATH.format(lb_id=api_lb.get('id')), status=501)
class TestLoadBalancerGraph(base.BaseAPITest):
@ -1952,6 +2057,7 @@ class TestLoadBalancerGraph(base.BaseAPITest):
'vip_subnet_id': uuidutils.generate_uuid(),
'vip_port_id': uuidutils.generate_uuid(),
'vip_address': '198.51.100.10',
'provider': 'noop_driver',
'listeners': create_listeners,
'pools': create_pools or []
}
@ -1968,7 +2074,7 @@ class TestLoadBalancerGraph(base.BaseAPITest):
'vip_network_id': mock.ANY,
'vip_qos_policy_id': None,
'flavor_id': '',
'provider': 'octavia'
'provider': 'noop_driver'
}
expected_lb.update(create_lb)
expected_lb['listeners'] = expected_listeners
@ -2241,7 +2347,11 @@ class TestLoadBalancerGraph(base.BaseAPITest):
api_lb = response.json.get(self.root_tag)
self._assert_graphs_equal(expected_lb, api_lb)
def test_with_one_listener_sni_containers(self):
# TODO(johnsom) Fix this when there is a noop certificate manager
@mock.patch('octavia.common.tls_utils.cert_parser.load_certificates_data')
def test_with_one_listener_sni_containers(self, mock_cert_data):
mock_cert_data.return_value = {'tls_cert': 'cert 1',
'sni_certs': ['cert 2', 'cert 3']}
create_sni_containers, expected_sni_containers = (
self._get_sni_container_bodies())
create_listener, expected_listener = self._get_listener_bodies(
@ -2399,7 +2509,12 @@ class TestLoadBalancerGraph(base.BaseAPITest):
body = self._build_body(create_lb)
return body, expected_lb
def test_with_one_of_everything(self):
# TODO(johnsom) Fix this when there is a noop certificate manager
@mock.patch('octavia.common.tls_utils.cert_parser.load_certificates_data')
def test_with_one_of_everything(self, mock_cert_data):
mock_cert_data.return_value = {'tls_cert': 'cert 1',
'sni_certs': ['cert 2', 'cert 3']}
body, expected_lb = self._test_with_one_of_everything_helper()
response = self.post(self.LBS_PATH, body)
api_lb = response.json.get(self.root_tag)
@ -2481,7 +2596,12 @@ class TestLoadBalancerGraph(base.BaseAPITest):
self.start_quota_mock(data_models.HealthMonitor)
self.post(self.LBS_PATH, body, status=403)
def test_create_over_quota_sanity_check(self):
# TODO(johnsom) Fix this when there is a noop certificate manager
@mock.patch('octavia.common.tls_utils.cert_parser.load_certificates_data')
def test_create_over_quota_sanity_check(self, mock_cert_data):
mock_cert_data.return_value = {'tls_cert': 'cert 1',
'sni_certs': ['cert 2', 'cert 3']}
# This one should create, as we don't check quotas on L7Policies
body, _ = self._test_with_one_of_everything_helper()
self.start_quota_mock(data_models.L7Policy)

View File

@ -127,6 +127,7 @@ class AllRepositoriesTest(base.OctaviaDBTestBase):
'operating_status': constants.OFFLINE,
'topology': constants.TOPOLOGY_ACTIVE_STANDBY,
'vrrp_group': None,
'provider': 'amphora',
'server_group_id': uuidutils.generate_uuid(),
'project_id': uuidutils.generate_uuid(),
'id': uuidutils.generate_uuid()}

View File

@ -32,6 +32,7 @@ class TestProviderDataModels(base.TestCase):
self.vip_port_id = uuidutils.generate_uuid()
self.vip_subnet_id = uuidutils.generate_uuid()
self.listener_id = uuidutils.generate_uuid()
self.vip_qos_policy_id = uuidutils.generate_uuid()
self.ref_listener = data_models.Listener(
admin_state_up=True,
@ -59,7 +60,8 @@ class TestProviderDataModels(base.TestCase):
vip_address=self.vip_address,
vip_network_id=self.vip_network_id,
vip_port_id=self.vip_port_id,
vip_subnet_id=self.vip_subnet_id)
vip_subnet_id=self.vip_subnet_id,
vip_qos_policy_id=self.vip_qos_policy_id)
self.ref_lb_dict = {'project_id': self.project_id,
'flavor': {'cake': 'chocolate'},
@ -67,37 +69,38 @@ class TestProviderDataModels(base.TestCase):
'admin_state_up': False,
'loadbalancer_id': self.loadbalancer_id,
'vip_port_id': self.vip_port_id,
'listeners': None,
'vip_address': self.vip_address,
'description': 'One great load balancer',
'vip_subnet_id': self.vip_subnet_id,
'name': 'favorite_lb'}
'name': 'favorite_lb',
'vip_qos_policy_id': self.vip_qos_policy_id}
self.ref_lb_dict_with_listener = {
'admin_state_up': False,
'description': 'One great load balancer',
'flavor': {'cake': 'chocolate'},
'listeners': [{'admin_state_up': True,
self.ref_listener = {'admin_state_up': True,
'connection_limit': 5000,
'default_pool': None,
'default_pool_id': None,
'default_tls_container': 'a_pkcs12_bundle',
'description': 'The listener',
'insert_headers': {'X-Forwarded-For': 'true'},
'l7policies': None,
'listener_id': self.listener_id,
'loadbalancer_id': self.loadbalancer_id,
'name': 'super_listener',
'protocol': 'avian',
'protocol_port': 42,
'sni_containers': 'another_pkcs12_bundle'}],
'sni_containers': 'another_pkcs12_bundle'}
self.ref_lb_dict_with_listener = {
'admin_state_up': False,
'description': 'One great load balancer',
'flavor': {'cake': 'chocolate'},
'listeners': [self.ref_listener],
'loadbalancer_id': self.loadbalancer_id,
'name': 'favorite_lb',
'project_id': self.project_id,
'vip_address': self.vip_address,
'vip_network_id': self.vip_network_id,
'vip_port_id': self.vip_port_id,
'vip_subnet_id': self.vip_subnet_id}
'vip_subnet_id': self.vip_subnet_id,
'vip_qos_policy_id': self.vip_qos_policy_id}
def test_equality(self):
second_ref_lb = deepcopy(self.ref_lb)
@ -126,12 +129,67 @@ class TestProviderDataModels(base.TestCase):
self.assertEqual(self.ref_lb_dict, ref_lb_converted_to_dict)
def test_to_dict_partial(self):
ref_lb = data_models.LoadBalancer(loadbalancer_id=self.loadbalancer_id)
ref_lb_dict = {'loadbalancer_id': self.loadbalancer_id}
ref_lb_converted_to_dict = ref_lb.to_dict()
self.assertEqual(ref_lb_dict, ref_lb_converted_to_dict)
def test_to_dict_render_unsets(self):
ref_lb_converted_to_dict = self.ref_lb.to_dict(render_unsets=True)
new_ref_lib_dict = deepcopy(self.ref_lb_dict)
new_ref_lib_dict['pools'] = None
new_ref_lib_dict['listeners'] = None
self.assertEqual(new_ref_lib_dict, ref_lb_converted_to_dict)
def test_to_dict_recursive(self):
ref_lb_converted_to_dict = self.ref_lb.to_dict(recurse=True)
self.assertEqual(self.ref_lb_dict_with_listener,
ref_lb_converted_to_dict)
def test_to_dict_recursive_partial(self):
ref_lb = data_models.LoadBalancer(
loadbalancer_id=self.loadbalancer_id,
listeners=[self.ref_listener])
ref_lb_dict_with_listener = {
'loadbalancer_id': self.loadbalancer_id,
'listeners': [self.ref_listener]}
ref_lb_converted_to_dict = ref_lb.to_dict(recurse=True)
self.assertEqual(ref_lb_dict_with_listener, ref_lb_converted_to_dict)
def test_to_dict_recursive_render_unset(self):
ref_lb = data_models.LoadBalancer(
admin_state_up=False,
description='One great load balancer',
flavor={'cake': 'chocolate'},
listeners=[self.ref_listener],
loadbalancer_id=self.loadbalancer_id,
project_id=self.project_id,
vip_address=self.vip_address,
vip_network_id=self.vip_network_id,
vip_port_id=self.vip_port_id,
vip_subnet_id=self.vip_subnet_id,
vip_qos_policy_id=self.vip_qos_policy_id)
ref_lb_dict_with_listener = deepcopy(self.ref_lb_dict_with_listener)
ref_lb_dict_with_listener['pools'] = None
ref_lb_dict_with_listener['name'] = None
ref_lb_converted_to_dict = ref_lb.to_dict(recurse=True,
render_unsets=True)
self.assertEqual(ref_lb_dict_with_listener,
ref_lb_converted_to_dict)
def test_from_dict(self):
lb_object = data_models.LoadBalancer.from_dict(self.ref_lb_dict)

View File

@ -30,7 +30,7 @@ class TestProviderBase(base.TestCase):
def test_create_vip_port(self):
self.assertRaises(exceptions.NotImplementedError,
self.driver.create_vip_port,
False, False)
False, False, False)
def test_loadbalancer_create(self):
self.assertRaises(exceptions.NotImplementedError,

View File

@ -37,6 +37,7 @@ class TestNoopProviderDriver(base.TestCase):
self.healthmonitor_id = uuidutils.generate_uuid()
self.l7policy_id = uuidutils.generate_uuid()
self.l7rule_id = uuidutils.generate_uuid()
self.project_id = uuidutils.generate_uuid()
self.ref_vip = data_models.VIP(
vip_address=self.vip_address,
@ -75,7 +76,6 @@ class TestNoopProviderDriver(base.TestCase):
description='Olympic swimming pool',
healthmonitor=self.ref_healthmonitor,
lb_algorithm='A_Fast_One',
listener_id=self.listener_id,
loadbalancer_id=self.loadbalancer_id,
members=[self.ref_member],
name='Osborn',
@ -128,7 +128,7 @@ class TestNoopProviderDriver(base.TestCase):
listeners=[self.ref_listener],
loadbalancer_id=self.loadbalancer_id,
name='favorite_lb',
project_id=uuidutils.generate_uuid(),
project_id=self.project_id,
vip_address=self.vip_address,
vip_network_id=self.vip_network_id,
vip_port_id=self.vip_port_id,
@ -140,6 +140,7 @@ class TestNoopProviderDriver(base.TestCase):
def test_create_vip_port(self):
vip_dict = self.driver.create_vip_port(self.loadbalancer_id,
self.project_id,
self.ref_vip.to_dict())
self.assertEqual(self.ref_vip.to_dict(), vip_dict)

View File

@ -0,0 +1,640 @@
# Copyright 2018 Rackspace, US Inc.
#
# 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 copy
import mock
from oslo_utils import uuidutils
from octavia.api.drivers import data_models as driver_dm
from octavia.api.drivers import exceptions as driver_exceptions
from octavia.api.drivers import utils
from octavia.common import constants
from octavia.common import data_models
from octavia.common import exceptions
from octavia.tests.unit import base
class TestUtils(base.TestCase):
def setUp(self):
super(TestUtils, self).setUp()
hm1_id = uuidutils.generate_uuid()
hm2_id = uuidutils.generate_uuid()
l7policy1_id = uuidutils.generate_uuid()
l7policy2_id = uuidutils.generate_uuid()
l7rule1_id = uuidutils.generate_uuid()
l7rule2_id = uuidutils.generate_uuid()
listener1_id = uuidutils.generate_uuid()
listener2_id = uuidutils.generate_uuid()
member1_id = uuidutils.generate_uuid()
member2_id = uuidutils.generate_uuid()
member3_id = uuidutils.generate_uuid()
member4_id = uuidutils.generate_uuid()
pool1_id = uuidutils.generate_uuid()
pool2_id = uuidutils.generate_uuid()
self.lb_id = uuidutils.generate_uuid()
self.project_id = uuidutils.generate_uuid()
self.ip_address = '192.0.2.30'
self.port_id = uuidutils.generate_uuid()
self.network_id = uuidutils.generate_uuid()
self.subnet_id = uuidutils.generate_uuid()
self.qos_policy_id = uuidutils.generate_uuid()
self.sni_containers = [{'tls_container_id': '2'},
{'tls_container_id': '3'}]
_common_test_dict = {'provisioning_status': constants.ACTIVE,
'operating_status': constants.ONLINE,
'project_id': self.project_id,
'created_at': 'then',
'updated_at': 'now',
'enabled': True}
# Setup Health Monitors
self.test_hm1_dict = {'id': hm1_id,
'type': constants.HEALTH_MONITOR_PING,
'delay': 1, 'timeout': 3, 'fall_threshold': 1,
'rise_threshold': 2, 'http_method': 'GET',
'url_path': '/', 'expected_codes': '200',
'name': 'hm1', 'pool_id': pool1_id}
self.test_hm1_dict.update(_common_test_dict)
self.test_hm2_dict = copy.deepcopy(self.test_hm1_dict)
self.test_hm2_dict['id'] = hm2_id
self.test_hm2_dict['name'] = 'hm2'
self.db_hm1 = data_models.HealthMonitor(**self.test_hm1_dict)
self.db_hm2 = data_models.HealthMonitor(**self.test_hm2_dict)
self.provider_hm1_dict = {'admin_state_up': True,
'delay': 1, 'expected_codes': '200',
'healthmonitor_id': hm1_id,
'http_method': 'GET',
'max_retries': 2,
'max_retries_down': 1,
'name': 'hm1',
'pool_id': pool1_id,
'timeout': 3,
'type': constants.HEALTH_MONITOR_PING,
'url_path': '/'}
self.provider_hm2_dict = copy.deepcopy(self.provider_hm1_dict)
self.provider_hm2_dict['healthmonitor_id'] = hm2_id
self.provider_hm2_dict['name'] = 'hm2'
self.provider_hm1 = driver_dm.HealthMonitor(**self.provider_hm1_dict)
self.provider_hm2 = driver_dm.HealthMonitor(**self.provider_hm2_dict)
# Setup Members
self.test_member1_dict = {'id': member1_id,
'pool_id': pool1_id,
'ip_address': '192.0.2.16',
'protocol_port': 80, 'weight': 0,
'backup': False,
'subnet_id': self.subnet_id,
'pool': None,
'name': 'member1',
'monitor_address': '192.0.2.26',
'monitor_port': 81}
self.test_member1_dict.update(_common_test_dict)
self.test_member2_dict = copy.deepcopy(self.test_member1_dict)
self.test_member2_dict['id'] = member2_id
self.test_member2_dict['ip_address'] = '192.0.2.17'
self.test_member2_dict['monitor_address'] = '192.0.2.27'
self.test_member2_dict['name'] = 'member2'
self.test_member3_dict = copy.deepcopy(self.test_member1_dict)
self.test_member3_dict['id'] = member3_id
self.test_member3_dict['ip_address'] = '192.0.2.18'
self.test_member3_dict['monitor_address'] = '192.0.2.28'
self.test_member3_dict['name'] = 'member3'
self.test_member3_dict['pool_id'] = pool2_id
self.test_member4_dict = copy.deepcopy(self.test_member1_dict)
self.test_member4_dict['id'] = member4_id
self.test_member4_dict['ip_address'] = '192.0.2.19'
self.test_member4_dict['monitor_address'] = '192.0.2.29'
self.test_member4_dict['name'] = 'member4'
self.test_member4_dict['pool_id'] = pool2_id
self.test_pool1_members_dict = [self.test_member1_dict,
self.test_member2_dict]
self.test_pool2_members_dict = [self.test_member3_dict,
self.test_member4_dict]
self.db_member1 = data_models.Member(**self.test_member1_dict)
self.db_member2 = data_models.Member(**self.test_member2_dict)
self.db_member3 = data_models.Member(**self.test_member3_dict)
self.db_member4 = data_models.Member(**self.test_member4_dict)
self.db_pool1_members = [self.db_member1, self.db_member2]
self.db_pool2_members = [self.db_member3, self.db_member4]
self.provider_member1_dict = {'address': '192.0.2.16',
'admin_state_up': True,
'member_id': member1_id,
'monitor_address': '192.0.2.26',
'monitor_port': 81,
'name': 'member1',
'pool_id': pool1_id,
'protocol_port': 80,
'subnet_id': self.subnet_id,
'weight': 0,
'backup': False}
self.provider_member2_dict = copy.deepcopy(self.provider_member1_dict)
self.provider_member2_dict['member_id'] = member2_id
self.provider_member2_dict['address'] = '192.0.2.17'
self.provider_member2_dict['monitor_address'] = '192.0.2.27'
self.provider_member2_dict['name'] = 'member2'
self.provider_member3_dict = copy.deepcopy(self.provider_member1_dict)
self.provider_member3_dict['member_id'] = member3_id
self.provider_member3_dict['address'] = '192.0.2.18'
self.provider_member3_dict['monitor_address'] = '192.0.2.28'
self.provider_member3_dict['name'] = 'member3'
self.provider_member3_dict['pool_id'] = pool2_id
self.provider_member4_dict = copy.deepcopy(self.provider_member1_dict)
self.provider_member4_dict['member_id'] = member4_id
self.provider_member4_dict['address'] = '192.0.2.19'
self.provider_member4_dict['monitor_address'] = '192.0.2.29'
self.provider_member4_dict['name'] = 'member4'
self.provider_member4_dict['pool_id'] = pool2_id
self.provider_pool1_members_dict = [self.provider_member1_dict,
self.provider_member2_dict]
self.provider_pool2_members_dict = [self.provider_member3_dict,
self.provider_member4_dict]
self.provider_member1 = driver_dm.Member(**self.provider_member1_dict)
self.provider_member2 = driver_dm.Member(**self.provider_member2_dict)
self.provider_member3 = driver_dm.Member(**self.provider_member3_dict)
self.provider_member4 = driver_dm.Member(**self.provider_member4_dict)
self.provider_pool1_members = [self.provider_member1,
self.provider_member2]
self.provider_pool2_members = [self.provider_member3,
self.provider_member4]
# Setup test pools
self.test_pool1_dict = {'id': pool1_id,
'name': 'pool1', 'description': 'Pool 1',
'load_balancer_id': self.lb_id,
'protocol': 'avian',
'lb_algorithm': 'round_robin',
'members': self.test_pool1_members_dict,
'health_monitor': self.test_hm1_dict,
'session_persistence': {'type': 'SOURCE'},
'listeners': [],
'l7policies': []}
self.test_pool1_dict.update(_common_test_dict)
self.test_pool2_dict = copy.deepcopy(self.test_pool1_dict)
self.test_pool2_dict['id'] = pool2_id
self.test_pool2_dict['name'] = 'pool2'
self.test_pool2_dict['description'] = 'Pool 2'
self.test_pool2_dict['members'] = self.test_pool2_members_dict
self.test_pools = [self.test_pool1_dict, self.test_pool2_dict]
self.db_pool1 = data_models.Pool(**self.test_pool1_dict)
self.db_pool1.health_monitor = self.db_hm1
self.db_pool1.members = self.db_pool1_members
self.db_pool2 = data_models.Pool(**self.test_pool2_dict)
self.db_pool2.health_monitor = self.db_hm2
self.db_pool2.members = self.db_pool2_members
self.test_db_pools = [self.db_pool1, self.db_pool2]
self.provider_pool1_dict = {
'admin_state_up': True,
'description': 'Pool 1',
'healthmonitor': self.provider_hm1_dict,
'lb_algorithm': 'round_robin',
'loadbalancer_id': self.lb_id,
'members': self.provider_pool1_members_dict,
'name': 'pool1',
'pool_id': pool1_id,
'protocol': 'avian',
'session_persistence': {'type': 'SOURCE'}}
self.provider_pool2_dict = copy.deepcopy(self.provider_pool1_dict)
self.provider_pool2_dict['pool_id'] = pool2_id
self.provider_pool2_dict['name'] = 'pool2'
self.provider_pool2_dict['description'] = 'Pool 2'
self.provider_pool2_dict['members'] = self.provider_pool2_members_dict
self.provider_pool2_dict['healthmonitor'] = self.provider_hm2_dict
self.provider_pool1 = driver_dm.Pool(**self.provider_pool1_dict)
self.provider_pool1.members = self.provider_pool1_members
self.provider_pool1.healthmonitor = self.provider_hm1
self.provider_pool2 = driver_dm.Pool(**self.provider_pool2_dict)
self.provider_pool2.members = self.provider_pool2_members
self.provider_pool2.healthmonitor = self.provider_hm2
self.provider_pools = [self.provider_pool1, self.provider_pool2]
# Setup L7Rules
self.test_l7rule1_dict = {'id': l7rule1_id,
'l7policy_id': l7policy1_id,
'type': 'o',
'compare_type': 'fake_type',
'key': 'fake_key',
'value': 'fake_value',
'l7policy': None,
'invert': False}
self.test_l7rule1_dict.update(_common_test_dict)
self.test_l7rule2_dict = copy.deepcopy(self.test_l7rule1_dict)
self.test_l7rule2_dict['id'] = l7rule2_id
self.test_l7rules = [self.test_l7rule1_dict, self.test_l7rule2_dict]
self.db_l7Rule1 = data_models.L7Rule(**self.test_l7rule1_dict)
self.db_l7Rule2 = data_models.L7Rule(**self.test_l7rule2_dict)
self.db_l7Rules = [self.db_l7Rule1, self.db_l7Rule2]
self.provider_l7rule1_dict = {'admin_state_up': True,
'compare_type': 'fake_type',
'invert': False,
'key': 'fake_key',
'l7policy_id': l7policy1_id,
'l7rule_id': l7rule1_id,
'type': 'o',
'value': 'fake_value'}
self.provider_l7rule2_dict = copy.deepcopy(self.provider_l7rule1_dict)
self.provider_l7rule2_dict['l7rule_id'] = l7rule2_id
self.provider_l7rules_dicts = [self.provider_l7rule1_dict,
self.provider_l7rule2_dict]
self.provider_l7rule1 = driver_dm.L7Rule(**self.provider_l7rule1_dict)
self.provider_l7rule2 = driver_dm.L7Rule(**self.provider_l7rule2_dict)
self.provider_rules = [self.provider_l7rule1, self.provider_l7rule2]
# Setup L7Policies
self.test_l7policy1_dict = {'id': l7policy1_id,
'name': 'l7policy_1',
'description': 'L7policy 1',
'listener_id': listener1_id,
'action': 'go',
'redirect_pool_id': pool1_id,
'redirect_url': '/index.html',
'position': 1,
'listener': None,
'redirect_pool': None,
'l7rules': self.test_l7rules}
self.test_l7policy1_dict.update(_common_test_dict)
self.test_l7policy2_dict = copy.deepcopy(self.test_l7policy1_dict)
self.test_l7policy2_dict['id'] = l7policy2_id
self.test_l7policy2_dict['name'] = 'l7policy_2'
self.test_l7policy2_dict['description'] = 'L7policy 2'
self.test_l7policies = [self.test_l7policy1_dict,
self.test_l7policy2_dict]
self.db_l7policy1 = data_models.L7Policy(**self.test_l7policy1_dict)
self.db_l7policy2 = data_models.L7Policy(**self.test_l7policy2_dict)
self.db_l7policy1.l7rules = self.db_l7Rules
self.db_l7policy2.l7rules = self.db_l7Rules
self.db_l7policies = [self.db_l7policy1, self.db_l7policy2]
self.provider_l7policy1_dict = {'action': 'go',
'admin_state_up': True,
'description': 'L7policy 1',
'l7policy_id': l7policy1_id,
'listener_id': listener1_id,
'name': 'l7policy_1',
'position': 1,
'redirect_pool_id': pool1_id,
'redirect_url': '/index.html',
'rules': self.provider_l7rules_dicts}
self.provider_l7policy2_dict = copy.deepcopy(
self.provider_l7policy1_dict)
self.provider_l7policy2_dict['l7policy_id'] = l7policy2_id
self.provider_l7policy2_dict['name'] = 'l7policy_2'
self.provider_l7policy2_dict['description'] = 'L7policy 2'
self.provider_l7policies_dict = [self.provider_l7policy1_dict,
self.provider_l7policy2_dict]
self.provider_l7policy1 = driver_dm.L7Policy(
**self.provider_l7policy1_dict)
self.provider_l7policy1.rules = self.provider_rules
self.provider_l7policy2 = driver_dm.L7Policy(
**self.provider_l7policy2_dict)
self.provider_l7policy2.rules = self.provider_rules
self.provider_l7policies = [self.provider_l7policy1,
self.provider_l7policy2]
# Setup Listeners
self.test_listener1_dict = {'id': listener1_id,
'name': 'listener_1',
'description': 'Listener 1',
'default_pool_id': pool1_id,
'load_balancer_id': self.lb_id,
'protocol': 'avian',
'protocol_port': 90,
'connection_limit': 10000,
'tls_certificate_id': '1',
'stats': None,
'default_pool': self.test_pool1_dict,
'load_balancer': None,
'sni_containers': self.sni_containers,
'peer_port': 55,
'l7policies': self.test_l7policies,
'insert_headers': {},
'pools': None,
'timeout_client_data': 1000,
'timeout_member_connect': 2000,
'timeout_member_data': 3000,
'timeout_tcp_inspect': 4000}
self.test_listener1_dict.update(_common_test_dict)
self.test_listener2_dict = copy.deepcopy(self.test_listener1_dict)
self.test_listener2_dict['id'] = listener2_id
self.test_listener2_dict['name'] = 'listener_2'
self.test_listener2_dict['description'] = 'Listener 1'
self.test_listener2_dict['default_pool_id'] = pool2_id
self.test_listener2_dict['default_pool'] = self.test_pool2_dict
del self.test_listener2_dict['l7policies']
del self.test_listener2_dict['sni_containers']
self.test_listeners = [self.test_listener1_dict,
self.test_listener2_dict]
self.db_listener1 = data_models.Listener(**self.test_listener1_dict)
self.db_listener2 = data_models.Listener(**self.test_listener2_dict)
self.db_listener1.default_pool = self.db_pool1
self.db_listener2.default_pool = self.db_pool2
self.db_listener1.l7policies = self.db_l7policies
self.db_listener1.sni_containers = [
data_models.SNI(tls_container_id='2'),
data_models.SNI(tls_container_id='3')]
self.test_db_listeners = [self.db_listener1, self.db_listener2]
self.provider_listener1_dict = {
'admin_state_up': True,
'connection_limit': 10000,
'default_pool': self.provider_pool1_dict,
'default_pool_id': pool1_id,
'default_tls_container': 'cert 1',
'description': 'Listener 1',
'insert_headers': {},
'l7policies': self.provider_l7policies_dict,
'listener_id': listener1_id,
'loadbalancer_id': self.lb_id,
'name': 'listener_1',
'protocol': 'avian',
'protocol_port': 90,
'sni_containers': ['cert 2', 'cert 3'],
'timeout_client_data': 1000,
'timeout_member_connect': 2000,
'timeout_member_data': 3000,
'timeout_tcp_inspect': 4000}
self.provider_listener2_dict = copy.deepcopy(
self.provider_listener1_dict)
self.provider_listener2_dict['listener_id'] = listener2_id
self.provider_listener2_dict['name'] = 'listener_2'
self.provider_listener2_dict['description'] = 'Listener 1'
self.provider_listener2_dict['default_pool_id'] = pool2_id
self.provider_listener2_dict['default_pool'] = self.provider_pool2_dict
del self.provider_listener2_dict['l7policies']
self.provider_listener1 = driver_dm.Listener(
**self.provider_listener1_dict)
self.provider_listener2 = driver_dm.Listener(
**self.provider_listener2_dict)
self.provider_listener1.default_pool = self.provider_pool1
self.provider_listener2.default_pool = self.provider_pool2
self.provider_listener1.l7policies = self.provider_l7policies
self.provider_listeners = [self.provider_listener1,
self.provider_listener2]
def test_call_provider(self):
mock_driver_method = mock.MagicMock()
# Test happy path
utils.call_provider("provider_name", mock_driver_method,
"arg1", foo="arg2")
mock_driver_method.assert_called_with("arg1", foo="arg2")
# Test driver raising DriverError
mock_driver_method.side_effect = driver_exceptions.DriverError
self.assertRaises(exceptions.ProviderDriverError,
utils.call_provider, "provider_name",
mock_driver_method)
# Test driver raising NotImplementedError
mock_driver_method.side_effect = driver_exceptions.NotImplementedError
self.assertRaises(exceptions.ProviderNotImplementedError,
utils.call_provider, "provider_name",
mock_driver_method)
# Test driver raising UnsupportedOptionError
mock_driver_method.side_effect = (
driver_exceptions.UnsupportedOptionError)
self.assertRaises(exceptions.ProviderUnsupportedOptionError,
utils.call_provider, "provider_name",
mock_driver_method)
# Test driver raising DriverError
mock_driver_method.side_effect = Exception
self.assertRaises(exceptions.ProviderDriverError,
utils.call_provider, "provider_name",
mock_driver_method)
def test_base_to_provider_dict(self):
test_dict = {'provisioning_status': constants.ACTIVE,
'operating_status': constants.ONLINE,
'provider': 'octavia',
'created_at': 'now',
'updated_at': 'then',
'enabled': True,
'project_id': 1}
result_dict = utils._base_to_provider_dict(test_dict,
include_project_id=True)
self.assertEqual({'admin_state_up': True, 'project_id': 1},
result_dict)
result_dict = utils._base_to_provider_dict(test_dict,
include_project_id=False)
self.assertEqual({'admin_state_up': True},
result_dict)
@mock.patch('octavia.common.tls_utils.cert_parser.load_certificates_data')
def test_lb_dict_to_provider_dict(self, mock_load_cert):
mock_load_cert.return_value = {'tls_cert': 'cert 1',
'sni_certs': ['cert 2', 'cert 3']}
test_lb_dict = {'name': 'lb1', 'project_id': self.project_id,
'vip_subnet_id': self.subnet_id,
'vip_port_id': self.port_id,
'vip_address': self.ip_address,
'vip_network_id': self.network_id,
'vip_qos_policy_id': self.qos_policy_id,
'provider': 'noop_driver',
'id': self.lb_id,
'listeners': [],
'pools': [],
'description': '', 'admin_state_up': True,
'provisioning_status': constants.PENDING_CREATE,
'operating_status': constants.OFFLINE,
'flavor_id': '',
'provider': 'noop_driver'}
ref_prov_lb_dict = {'vip_address': self.ip_address,
'admin_state_up': True,
'loadbalancer_id': self.lb_id,
'vip_subnet_id': self.subnet_id,
'listeners': self.provider_listeners,
'description': '',
'project_id': self.project_id,
'flavor_id': '',
'vip_port_id': self.port_id,
'vip_qos_policy_id': self.qos_policy_id,
'vip_network_id': self.network_id,
'pools': self.provider_pools,
'name': 'lb1'}
vip = data_models.Vip(ip_address=self.ip_address,
network_id=self.network_id,
port_id=self.port_id, subnet_id=self.subnet_id,
qos_policy_id=self.qos_policy_id)
provider_lb_dict = utils.lb_dict_to_provider_dict(
test_lb_dict, vip=vip, db_pools=self.test_db_pools,
db_listeners=self.test_db_listeners)
self.assertEqual(ref_prov_lb_dict, provider_lb_dict)
@mock.patch('octavia.common.tls_utils.cert_parser.load_certificates_data')
def test_db_listeners_to_provider_listeners(self, mock_load_cert):
mock_load_cert.return_value = {'tls_cert': 'cert 1',
'sni_certs': ['cert 2', 'cert 3']}
provider_listeners = utils.db_listeners_to_provider_listeners(
self.test_db_listeners)
self.assertEqual(self.provider_listeners, provider_listeners)
@mock.patch('octavia.common.tls_utils.cert_parser.load_certificates_data')
def test_listener_dict_to_provider_dict(self, mock_load_cert):
mock_load_cert.return_value = {'tls_cert': 'cert 1',
'sni_certs': ['cert 2', 'cert 3']}
provider_listener = utils.listener_dict_to_provider_dict(
self.test_listener1_dict)
self.assertEqual(self.provider_listener1_dict, provider_listener)
def test_db_pool_to_provider_pool(self):
provider_pool = utils.db_pool_to_provider_pool(self.db_pool1)
self.assertEqual(self.provider_pool1, provider_pool)
def test_db_pools_to_provider_pools(self):
provider_pools = utils.db_pools_to_provider_pools(self.test_db_pools)
self.assertEqual(self.provider_pools, provider_pools)
def test_pool_dict_to_provider_dict(self):
provider_pool_dict = utils.pool_dict_to_provider_dict(
self.test_pool1_dict)
self.assertEqual(self.provider_pool1_dict, provider_pool_dict)
def test_db_HM_to_provider_HM(self):
provider_hm = utils.db_HM_to_provider_HM(self.db_hm1)
self.assertEqual(self.provider_hm1, provider_hm)
def test_hm_dict_to_provider_dict(self):
provider_hm_dict = utils.hm_dict_to_provider_dict(self.test_hm1_dict)
self.assertEqual(self.provider_hm1_dict, provider_hm_dict)
def test_db_members_to_provider_members(self):
provider_members = utils.db_members_to_provider_members(
self.db_pool1_members)
self.assertEqual(self.provider_pool1_members, provider_members)
def test_member_dict_to_provider_dict(self):
provider_member_dict = utils.member_dict_to_provider_dict(
self.test_member1_dict)
self.assertEqual(self.provider_member1_dict, provider_member_dict)
def test_db_l7policies_to_provider_l7policies(self):
provider_rules = utils.db_l7policies_to_provider_l7policies(
self.db_l7policies)
self.assertEqual(self.provider_l7policies, provider_rules)
def test_l7policy_dict_to_provider_dict(self):
provider_l7policy_dict = utils.l7policy_dict_to_provider_dict(
self.test_l7policy1_dict)
self.assertEqual(self.provider_l7policy1_dict, provider_l7policy_dict)
def test_db_l7rules_to_provider_l7rules(self):
provider_rules = utils.db_l7rules_to_provider_l7rules(self.db_l7Rules)
self.assertEqual(self.provider_rules, provider_rules)
def test_l7rule_dict_to_provider_dict(self):
provider_rules_dict = utils.l7rule_dict_to_provider_dict(
self.test_l7rule1_dict)
self.assertEqual(self.provider_l7rule1_dict, provider_rules_dict)
def test_vip_dict_to_provider_dict(self):
test_vip_dict = {'ip_address': self.ip_address,
'network_id': self.network_id,
'port_id': self.port_id,
'subnet_id': self.subnet_id,
'qos_policy_id': self.qos_policy_id}
provider_vip_dict = {'vip_address': self.ip_address,
'vip_network_id': self.network_id,
'vip_port_id': self.port_id,
'vip_subnet_id': self.subnet_id,
'vip_qos_policy_id': self.qos_policy_id}
new_vip_dict = utils.vip_dict_to_provider_dict(test_vip_dict)
self.assertEqual(provider_vip_dict, new_vip_dict)
def test_provider_vip_dict_to_vip_obj(self):
provider_vip_dict = {'vip_address': self.ip_address,
'vip_network_id': self.network_id,
'vip_port_id': self.port_id,
'vip_subnet_id': self.subnet_id,
'vip_qos_policy_id': self.qos_policy_id}
ref_vip = data_models.Vip(ip_address=self.ip_address,
network_id=self.network_id,
port_id=self.port_id,
subnet_id=self.subnet_id,
qos_policy_id=self.qos_policy_id)
new_provider_vip = utils.provider_vip_dict_to_vip_obj(
provider_vip_dict)
self.assertEqual(ref_vip, new_provider_vip)

View File

@ -71,6 +71,9 @@ console_scripts =
haproxy-vrrp-check = octavia.cmd.haproxy_vrrp_check:main
octavia.api.drivers =
noop_driver = octavia.api.drivers.noop_driver.driver:NoopProviderDriver
amphora = octavia.api.drivers.amphora_driver.driver:AmphoraProviderDriver
# octavia is an alias for backward compatibility
octavia = octavia.api.drivers.amphora_driver.driver:AmphoraProviderDriver
octavia.api.handlers =
simulated_handler = octavia.api.handlers.controller_simulator.handler:SimulatedControllerHandler
queue_producer = octavia.api.handlers.queue.producer:ProducerHandler

View File

@ -113,6 +113,8 @@ Load balancer
+-----------------+--------+-----------------------------------------------+
| name | string | Human-readable name of the resource. |
+-----------------+--------+-----------------------------------------------+
| pools | list | A list of `Pool object`_. |
+-----------------+--------+-----------------------------------------------+
| project_id | string | ID of the project owning this resource. |
+-----------------+--------+-----------------------------------------------+
| vip_address | string | The IP address of the Virtual IP (VIP). |
@ -121,6 +123,8 @@ Load balancer
+-----------------+--------+-----------------------------------------------+
| vip_port_id | string | The ID of the VIP port. |
+-----------------+--------+-----------------------------------------------+
|vip_qos_policy_id| string | The ID of the qos policy for the VIP. |
+-----------------+--------+-----------------------------------------------+
| vip_subnet_id | string | The ID of the subnet for the VIP. |
+-----------------+--------+-----------------------------------------------+
@ -145,12 +149,16 @@ Load balancer
+-----------------+--------+-----------------------------------------------+
| Name | Type | Description |
+=================+========+===============================================+
| project_id | string | ID of the project owning this resource. |
+-----------------+--------+-----------------------------------------------+
| vip_address | string | The IP address of the Virtual IP (VIP). |
+-----------------+--------+-----------------------------------------------+
| vip_network_id | string | The ID of the network for the VIP. |
+-----------------+--------+-----------------------------------------------+
| vip_port_id | string | The ID of the VIP port. |
+-----------------+--------+-----------------------------------------------+
|vip_qos_policy_id| string | The ID of the qos policy for the VIP. |
+-----------------+--------+-----------------------------------------------+
| vip_subnet_id | string | The ID of the subnet for the VIP. |
+-----------------+--------+-----------------------------------------------+
@ -364,6 +372,21 @@ Listener
+-----------------------+--------+------------------------------------------+
| sni_containers | object | A pkcs12 format set of certificates. |
+-----------------------+--------+------------------------------------------+
| timeout_client_data | int | Frontend client inactivity timeout in |
| | | milliseconds. |
+-----------------------+--------+------------------------------------------+
| timeout_member_connect| int | Backend member connection timeout in |
| | | milliseconds. |
+-----------------------+--------+------------------------------------------+
| timeout_member_data | int | Backend member inactivity timeout in |
| | | milliseconds. |
+-----------------------+--------+------------------------------------------+
| timeout_tcp_inspect | int | Time, in milliseconds, to wait for |
| | | additional TCP packets for content |
| | | inspection. |
+-----------------------+--------+------------------------------------------+
.. _Supported HTTP Header Insertions:
@ -533,11 +556,7 @@ Pool
| | | ROUND_ROBIN, LEAST_CONNECTIONS, or |
| | | SOURCE_IP. |
+-----------------------+--------+------------------------------------------+
| listener_id | string | ID of listener. Required if |
| | | loadbalancer_id not specified. |
+-----------------------+--------+------------------------------------------+
| loadbalancer_id | string | ID of load balancer. Required if |
| | | listener_id not specified. |
| loadbalancer_id | string | ID of load balancer. |
+-----------------------+--------+------------------------------------------+
| members | list | A list of `Member objects`_. |
+-----------------------+--------+------------------------------------------+
@ -684,6 +703,10 @@ Member
+-----------------------+--------+------------------------------------------+
| admin_state_up | bool | Admin state: True if up, False if down. |
+-----------------------+--------+------------------------------------------+
| backup | bool | Is the member a backup? Backup members |
| | | only receive traffic when all non-backup |
| | | members are down. |
+-----------------------+--------+------------------------------------------+
| member_id | string | ID of member to create. |
+-----------------------+--------+------------------------------------------+
| monitor_address | string | An alternate IP address used for health |