Add lbaasv2 extension to Neutron for REST refactor

Change-Id: I9f4b9e1f151281d4a467b3b90980a32529a78334
Co-Authored-By: Brandon Logan <brandon.logan@rackspace.com>
This commit is contained in:
Doug Wiegley 2014-12-09 17:52:14 -07:00
parent e43865a379
commit 2bf7bac997

View File

@ -0,0 +1,566 @@
# Copyright 2014 OpenStack Foundation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import abc
from oslo.config import cfg
import six
from neutron.api import extensions
from neutron.api.v2 import attributes as attr
from neutron.api.v2 import base
from neutron.api.v2 import resource_helper
from neutron.common import exceptions as qexception
from neutron import manager
from neutron.plugins.common import constants
from neutron.services import service_base
# TODO(dougw) - stop hard-coding these constants when this extension moves
# to the neutron-lbaas repo
#from neutron.services.loadbalancer import constants as lb_const
LB_METHOD_ROUND_ROBIN = 'ROUND_ROBIN'
LB_METHOD_LEAST_CONNECTIONS = 'LEAST_CONNECTIONS'
LB_METHOD_SOURCE_IP = 'SOURCE_IP'
SUPPORTED_LB_ALGORITHMS = (LB_METHOD_LEAST_CONNECTIONS, LB_METHOD_ROUND_ROBIN,
LB_METHOD_SOURCE_IP)
PROTOCOL_TCP = 'TCP'
PROTOCOL_HTTP = 'HTTP'
PROTOCOL_HTTPS = 'HTTPS'
SUPPORTED_PROTOCOLS = (PROTOCOL_TCP, PROTOCOL_HTTPS, PROTOCOL_HTTP)
HEALTH_MONITOR_PING = 'PING'
HEALTH_MONITOR_TCP = 'TCP'
HEALTH_MONITOR_HTTP = 'HTTP'
HEALTH_MONITOR_HTTPS = 'HTTPS'
SUPPORTED_HEALTH_MONITOR_TYPES = (HEALTH_MONITOR_HTTP, HEALTH_MONITOR_HTTPS,
HEALTH_MONITOR_PING, HEALTH_MONITOR_TCP)
SESSION_PERSISTENCE_SOURCE_IP = 'SOURCE_IP'
SESSION_PERSISTENCE_HTTP_COOKIE = 'HTTP_COOKIE'
SESSION_PERSISTENCE_APP_COOKIE = 'APP_COOKIE'
SUPPORTED_SP_TYPES = (SESSION_PERSISTENCE_SOURCE_IP,
SESSION_PERSISTENCE_HTTP_COOKIE,
SESSION_PERSISTENCE_APP_COOKIE)
# Loadbalancer Exceptions
# This exception is only for a workaround when having v1 and v2 lbaas extension
# and plugins enabled
class RequiredAttributeNotSpecified(qexception.BadRequest):
message = _("Required attribute %(attr_name)s not specified")
class EntityNotFound(qexception.NotFound):
message = _("%(name)s %(id)s could not be found")
class DelayOrTimeoutInvalid(qexception.BadRequest):
message = _("Delay must be greater than or equal to timeout")
class EntityInUse(qexception.InUse):
message = _("%(entity_using)s %(id)s is using this %(entity_in_use)s")
class LoadBalancerListenerProtocolPortExists(qexception.Conflict):
message = _("Load Balancer %(lb_id)s already has a listener with "
"protocol_port of %(protocol_port)s")
class ListenerPoolProtocolMismatch(qexception.Conflict):
message = _("Listener protocol %(listener_proto)s and pool protocol "
"%(pool_proto)s are not compatible.")
class AttributeIDImmutable(qexception.NeutronException):
message = _("Cannot change %(attribute)s if one already exists")
class StateInvalid(qexception.NeutronException):
message = _("Invalid state %(state)s of loadbalancer resource %(id)s")
class MemberNotFoundForPool(qexception.NotFound):
message = _("Member %(member_id)s could not be found in pool %(pool_id)s")
class MemberExists(qexception.Conflict):
message = _("Member with address %(address)s and protocol_port %(port)s "
"already present in pool %(pool)s")
class MemberAddressTypeSubnetTypeMismatch(qexception.NeutronException):
message = _("Member with address %(address)s and subnet %(subnet_id) "
" have mismatched IP versions")
class DriverError(qexception.NeutronException):
message = _("An error happened in the driver")
class LBConfigurationUnsupported(qexception.NeutronException):
message = _("Load balancer %(load_balancer_id)s configuration is not"
"supported by driver %(driver_name)s")
RESOURCE_ATTRIBUTE_MAP = {
'loadbalancers': {
'id': {'allow_post': False, 'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True,
'primary_key': True},
'name': {'allow_post': True, 'allow_put': True,
'validate': {'type:string': None},
'default': '',
'is_visible': True},
'tenant_id': {'allow_post': True, 'allow_put': False,
'validate': {'type:string': None},
'required_by_policy': True,
'is_visible': True},
'description': {'allow_post': True, 'allow_put': True,
'validate': {'type:string': None},
'is_visible': True, 'default': ''},
'vip_subnet_id': {'allow_post': True, 'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True},
'vip_address': {'allow_post': True, 'allow_put': False,
'default': attr.ATTR_NOT_SPECIFIED,
'validate': {'type:ip_address_or_none': None},
'is_visible': True},
'admin_state_up': {'allow_post': True, 'allow_put': True,
'default': True,
'convert_to': attr.convert_to_boolean,
'is_visible': True},
'status': {'allow_post': False, 'allow_put': False,
'is_visible': True}
},
'listeners': {
'id': {'allow_post': False, 'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True,
'primary_key': True},
'tenant_id': {'allow_post': True, 'allow_put': False,
'validate': {'type:string': None},
'required_by_policy': True,
'is_visible': True},
'name': {'allow_post': True, 'allow_put': True,
'validate': {'type:string': None},
'default': '',
'is_visible': True},
'description': {'allow_post': True, 'allow_put': True,
'validate': {'type:string': None},
'is_visible': True, 'default': ''},
'loadbalancer_id': {'allow_post': True, 'allow_put': True,
'validate': {'type:uuid_or_none': None},
'default': attr.ATTR_NOT_SPECIFIED,
'is_visible': True},
'default_pool_id': {'allow_post': True, 'allow_put': True,
'validate': {'type:uuid_or_none': None},
'default': attr.ATTR_NOT_SPECIFIED,
'is_visible': True},
'connection_limit': {'allow_post': True, 'allow_put': True,
'default': -1,
'convert_to': attr.convert_to_int,
'is_visible': True},
'protocol': {'allow_post': True, 'allow_put': False,
'validate': {'type:values': SUPPORTED_PROTOCOLS},
'is_visible': True},
'protocol_port': {'allow_post': True, 'allow_put': False,
'validate': {'type:range': [0, 65535]},
'convert_to': attr.convert_to_int,
'is_visible': True},
'admin_state_up': {'allow_post': True, 'allow_put': True,
'default': True,
'convert_to': attr.convert_to_boolean,
'is_visible': True},
'status': {'allow_post': False, 'allow_put': False,
'is_visible': True}
},
'pools': {
'id': {'allow_post': False, 'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True,
'primary_key': True},
'tenant_id': {'allow_post': True, 'allow_put': False,
'validate': {'type:string': None},
'required_by_policy': True,
'is_visible': True},
'name': {'allow_post': True, 'allow_put': True,
'validate': {'type:string': None},
'is_visible': True, 'default': ''},
'description': {'allow_post': True, 'allow_put': True,
'validate': {'type:string': None},
'is_visible': True, 'default': ''},
'healthmonitor_id': {'allow_post': True, 'allow_put': True,
'validate': {'type:string_or_none': None},
'is_visible': True,
'default': attr.ATTR_NOT_SPECIFIED},
'protocol': {'allow_post': True, 'allow_put': False,
'validate': {'type:values': SUPPORTED_PROTOCOLS},
'is_visible': True},
'lb_algorithm': {'allow_post': True, 'allow_put': True,
'validate': {
'type:values': SUPPORTED_LB_ALGORITHMS},
# TODO(brandon-logan) remove when old API is removed
# because this is a required attribute)
'default': attr.ATTR_NOT_SPECIFIED,
'is_visible': True},
'session_persistence': {
'allow_post': True, 'allow_put': True,
'convert_to': attr.convert_none_to_empty_dict,
'default': {},
'validate': {
'type:dict_or_empty': {
'type': {
'type:values': SUPPORTED_SP_TYPES,
'required': True},
'cookie_name': {'type:string': None,
'required': False}}},
'is_visible': True},
'members': {'allow_post': False, 'allow_put': False,
'is_visible': True},
'admin_state_up': {'allow_post': True, 'allow_put': True,
'default': True,
'convert_to': attr.convert_to_boolean,
'is_visible': True},
'status': {'allow_post': False, 'allow_put': False,
'is_visible': True}
},
'healthmonitors': {
'id': {'allow_post': False, 'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True,
'primary_key': True},
'tenant_id': {'allow_post': True, 'allow_put': False,
'validate': {'type:string': None},
'required_by_policy': True,
'is_visible': True},
'type': {'allow_post': True, 'allow_put': False,
'validate': {
'type:values': SUPPORTED_HEALTH_MONITOR_TYPES},
'is_visible': True},
'delay': {'allow_post': True, 'allow_put': True,
'validate': {'type:non_negative': None},
'convert_to': attr.convert_to_int,
'is_visible': True},
'timeout': {'allow_post': True, 'allow_put': True,
'validate': {'type:non_negative': None},
'convert_to': attr.convert_to_int,
'is_visible': True},
'max_retries': {'allow_post': True, 'allow_put': True,
'validate': {'type:range': [1, 10]},
'convert_to': attr.convert_to_int,
'is_visible': True},
'http_method': {'allow_post': True, 'allow_put': True,
'validate': {'type:string': None},
'default': 'GET',
'is_visible': True},
'url_path': {'allow_post': True, 'allow_put': True,
'validate': {'type:string': None},
'default': '/',
'is_visible': True},
'expected_codes': {
'allow_post': True,
'allow_put': True,
'validate': {
'type:regex': '^(\d{3}(\s*,\s*\d{3})*)$|^(\d{3}-\d{3})$'
},
'default': '200',
'is_visible': True
},
'admin_state_up': {'allow_post': True, 'allow_put': True,
'default': True,
'convert_to': attr.convert_to_boolean,
'is_visible': True},
'status': {'allow_post': False, 'allow_put': False,
'is_visible': True}
}
}
SUB_RESOURCE_ATTRIBUTE_MAP = {
'members': {
'parent': {'collection_name': 'pools',
'member_name': 'pool'},
'parameters': {
'id': {'allow_post': False, 'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True,
'primary_key': True},
'tenant_id': {'allow_post': True, 'allow_put': False,
'validate': {'type:string': None},
'required_by_policy': True,
'is_visible': True},
'address': {'allow_post': True, 'allow_put': False,
'validate': {'type:ip_address': None},
'is_visible': True},
'protocol_port': {'allow_post': True, 'allow_put': False,
'validate': {'type:range': [0, 65535]},
'convert_to': attr.convert_to_int,
'is_visible': True},
'weight': {'allow_post': True, 'allow_put': True,
'default': 1,
'validate': {'type:range': [0, 256]},
'convert_to': attr.convert_to_int,
'is_visible': True},
'admin_state_up': {'allow_post': True, 'allow_put': True,
'default': True,
'convert_to': attr.convert_to_boolean,
'is_visible': True},
'status': {'allow_post': False, 'allow_put': False,
'is_visible': True},
'subnet_id': {'allow_post': True, 'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True},
}
}
}
lbaasv2_quota_opts = [
cfg.IntOpt('quota_loadbalancer',
default=10,
help=_('Number of LoadBalancers allowed per tenant. '
'A negative value means unlimited.')),
cfg.IntOpt('quota_listener',
default=-1,
help=_('Number of Loadbalancer Listeners allowed per tenant. '
'A negative value means unlimited.')),
cfg.IntOpt('quota_pool',
default=10,
help=_('Number of pools allowed per tenant. '
'A negative value means unlimited.')),
cfg.IntOpt('quota_member',
default=-1,
help=_('Number of pool members allowed per tenant. '
'A negative value means unlimited.')),
cfg.IntOpt('quota_healthmonitor',
default=-1,
help=_('Number of health monitors allowed per tenant. '
'A negative value means unlimited.'))
]
cfg.CONF.register_opts(lbaasv2_quota_opts, 'QUOTAS')
class Loadbalancerv2(extensions.ExtensionDescriptor):
@classmethod
def get_name(cls):
return "LoadBalancing service v2"
@classmethod
def get_alias(cls):
return "lbaasv2"
@classmethod
def get_description(cls):
return "Extension for LoadBalancing service v2"
@classmethod
def get_namespace(cls):
return "http://wiki.openstack.org/neutron/LBaaS/API_2.0"
@classmethod
def get_updated(cls):
return "2014-06-18T10:00:00-00:00"
@classmethod
def get_resources(cls):
plural_mappings = resource_helper.build_plural_mappings(
{}, RESOURCE_ATTRIBUTE_MAP)
action_map = {'loadbalancer': {'stats': 'GET'}}
plural_mappings['members'] = 'member'
attr.PLURALS.update(plural_mappings)
resources = resource_helper.build_resource_info(
plural_mappings,
RESOURCE_ATTRIBUTE_MAP,
constants.LOADBALANCERV2,
action_map=action_map,
register_quota=True)
plugin = manager.NeutronManager.get_service_plugins()[
constants.LOADBALANCERV2]
for collection_name in SUB_RESOURCE_ATTRIBUTE_MAP:
# Special handling needed for sub-resources with 'y' ending
# (e.g. proxies -> proxy)
resource_name = collection_name[:-1]
parent = SUB_RESOURCE_ATTRIBUTE_MAP[collection_name].get('parent')
params = SUB_RESOURCE_ATTRIBUTE_MAP[collection_name].get(
'parameters')
controller = base.create_resource(collection_name, resource_name,
plugin, params,
allow_bulk=True,
parent=parent,
allow_pagination=True,
allow_sorting=True)
resource = extensions.ResourceExtension(
collection_name,
controller, parent,
path_prefix=constants.COMMON_PREFIXES[
constants.LOADBALANCERV2],
attr_map=params)
resources.append(resource)
return resources
@classmethod
def get_plugin_interface(cls):
return LoadBalancerPluginBaseV2
def update_attributes_map(self, attributes, extension_attrs_map=None):
super(Loadbalancerv2, self).update_attributes_map(
attributes, extension_attrs_map=RESOURCE_ATTRIBUTE_MAP)
def get_extended_resources(self, version):
if version == "2.0":
return RESOURCE_ATTRIBUTE_MAP
else:
return {}
@six.add_metaclass(abc.ABCMeta)
class LoadBalancerPluginBaseV2(service_base.ServicePluginBase):
def get_plugin_name(self):
return constants.LOADBALANCERV2
def get_plugin_type(self):
return constants.LOADBALANCERV2
def get_plugin_description(self):
return 'LoadBalancer service plugin v2'
@abc.abstractmethod
def get_loadbalancers(self, context, filters=None, fields=None):
pass
@abc.abstractmethod
def get_loadbalancer(self, context, id, fields=None):
pass
@abc.abstractmethod
def create_loadbalancer(self, context, loadbalancer):
pass
@abc.abstractmethod
def update_loadbalancer(self, context, id, loadbalancer):
pass
@abc.abstractmethod
def delete_loadbalancer(self, context, id):
pass
@abc.abstractmethod
def create_listener(self, context, listener):
pass
@abc.abstractmethod
def get_listener(self, context, id, fields=None):
pass
@abc.abstractmethod
def get_listeners(self, context, filters=None, fields=None):
pass
@abc.abstractmethod
def update_listener(self, context, id, listener):
pass
@abc.abstractmethod
def delete_listener(self, context, id):
pass
@abc.abstractmethod
def get_pools(self, context, filters=None, fields=None):
pass
@abc.abstractmethod
def get_pool(self, context, id, fields=None):
pass
@abc.abstractmethod
def create_pool(self, context, pool):
pass
@abc.abstractmethod
def update_pool(self, context, id, pool):
pass
@abc.abstractmethod
def delete_pool(self, context, id):
pass
@abc.abstractmethod
def stats(self, context, loadbalancer_id):
pass
@abc.abstractmethod
def get_pool_members(self, context, pool_id,
filters=None,
fields=None):
pass
@abc.abstractmethod
def get_pool_member(self, context, id, pool_id,
fields=None):
pass
@abc.abstractmethod
def create_pool_member(self, context, member,
pool_id):
pass
@abc.abstractmethod
def update_pool_member(self, context, member, id,
pool_id):
pass
@abc.abstractmethod
def delete_pool_member(self, context, id, pool_id):
pass
@abc.abstractmethod
def get_healthmonitors(self, context, filters=None, fields=None):
pass
@abc.abstractmethod
def get_healthmonitor(self, context, id, fields=None):
pass
@abc.abstractmethod
def create_healthmonitor(self, context, healthmonitor):
pass
@abc.abstractmethod
def update_healthmonitor(self, context, id, healthmonitor):
pass
@abc.abstractmethod
def delete_healthmonitor(self, context, id):
pass
@abc.abstractmethod
def get_members(self, context, filters=None, fields=None):
pass
@abc.abstractmethod
def get_member(self, context, id, fields=None):
pass