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
changes/95/563795/13
Michael Johnson 5 years ago
parent dfa7ef2ab3
commit 7b2621fe29
  1. 6
      etc/octavia.conf
  2. 5
      octavia/api/config.py
  3. 11
      octavia/api/drivers/amphora_driver/__init__.py
  4. 163
      octavia/api/drivers/amphora_driver/driver.py
  5. 128
      octavia/api/drivers/data_models.py
  6. 50
      octavia/api/drivers/driver_factory.py
  7. 12
      octavia/api/drivers/noop_driver/driver.py
  8. 163
      octavia/api/drivers/provider_base.py
  9. 353
      octavia/api/drivers/utils.py
  10. 1
      octavia/api/v2/controllers/l7policy.py
  11. 3
      octavia/api/v2/controllers/listener.py
  12. 142
      octavia/api/v2/controllers/load_balancer.py
  13. 4
      octavia/api/v2/controllers/pool.py
  14. 4
      octavia/api/v2/types/load_balancer.py
  15. 6
      octavia/common/config.py
  16. 7
      octavia/common/constants.py
  17. 3
      octavia/common/data_models.py
  18. 27
      octavia/common/exceptions.py
  19. 37
      octavia/db/migration/alembic_migrations/versions/0f242cf02c74_add_provider_column.py
  20. 1
      octavia/db/models.py
  21. 6
      octavia/tests/functional/api/v2/base.py
  22. 5
      octavia/tests/functional/api/v2/test_amphora.py
  23. 182
      octavia/tests/functional/api/v2/test_load_balancer.py
  24. 1
      octavia/tests/functional/db/test_repositories.py
  25. 94
      octavia/tests/unit/api/drivers/test_data_models.py
  26. 2
      octavia/tests/unit/api/drivers/test_provider_base.py
  27. 5
      octavia/tests/unit/api/drivers/test_provider_noop_driver.py
  28. 640
      octavia/tests/unit/api/drivers/test_utils.py
  29. 3
      setup.cfg
  30. 33
      specs/version1.1/enable-provider-driver.rst

@ -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:

@ -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
}

@ -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.

@ -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

@ -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)):
ret[attr] = None
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

@ -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

@ -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)

@ -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.')

@ -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

@ -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,

@ -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)

@ -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