]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Add lbaasv2 extension to Neutron for REST refactor
authorDoug Wiegley <dougw@a10networks.com>
Wed, 10 Dec 2014 00:52:14 +0000 (17:52 -0700)
committerDoug Wiegley <dougw@a10networks.com>
Wed, 10 Dec 2014 03:27:10 +0000 (20:27 -0700)
Change-Id: I9f4b9e1f151281d4a467b3b90980a32529a78334
Co-Authored-By: Brandon Logan <brandon.logan@rackspace.com>
neutron/extensions/loadbalancerv2.py [new file with mode: 0644]

diff --git a/neutron/extensions/loadbalancerv2.py b/neutron/extensions/loadbalancerv2.py
new file mode 100644 (file)
index 0000000..6b2f8ee
--- /dev/null
@@ -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