The patch introduces an API extension for LBaaS service
- API extension - abstract base class for plugin - some new validators were added to quantum/api/v2/attributes.py Implements: blueprint lbaas-restapi-tenant Change-Id: Ic2fd4debc4969389b395ce7352ab208c6854018b
This commit is contained in:
parent
b8607bb0c8
commit
1703047903
@ -233,6 +233,50 @@ def _validate_uuid(data, valid_values=None):
|
||||
return msg
|
||||
|
||||
|
||||
def _validate_uuid_or_none(data, valid_values=None):
|
||||
if data is not None:
|
||||
return _validate_uuid(data)
|
||||
|
||||
|
||||
def _validate_uuid_list(data, valid_values=None):
|
||||
if not isinstance(data, list):
|
||||
msg = _("'%s' is not a list") % data
|
||||
LOG.debug("validate_uuid_list: %s", msg)
|
||||
return msg
|
||||
|
||||
for item in data:
|
||||
msg = _validate_uuid(item)
|
||||
if msg:
|
||||
LOG.debug("validate_uuid_list: %s", msg)
|
||||
return msg
|
||||
|
||||
if len(set(data)) != len(data):
|
||||
msg = _("Duplicate items in the list: %s") % ', '.join(data)
|
||||
LOG.debug("validate_uuid_list: %s", msg)
|
||||
return msg
|
||||
|
||||
|
||||
def _validate_dict(data, valid_values=None):
|
||||
if not isinstance(data, dict):
|
||||
msg = _("'%s' is not a dictionary") % data
|
||||
LOG.debug("validate_dict: %s", msg)
|
||||
return msg
|
||||
|
||||
|
||||
def _validate_non_negative(data, valid_values=None):
|
||||
try:
|
||||
data = int(data)
|
||||
except (ValueError, TypeError):
|
||||
msg = _("'%s' is not an integer") % data
|
||||
LOG.debug("validate_non_negative: %s", msg)
|
||||
return msg
|
||||
|
||||
if data < 0:
|
||||
msg = _("'%s' should be non-negative") % data
|
||||
LOG.debug("validate_non_negative: %s", msg)
|
||||
return msg
|
||||
|
||||
|
||||
def convert_to_boolean(data):
|
||||
if isinstance(data, basestring):
|
||||
val = data.lower()
|
||||
@ -294,6 +338,15 @@ def convert_none_to_empty_list(value):
|
||||
return [] if value is None else value
|
||||
|
||||
|
||||
def convert_to_list(data):
|
||||
if data is None:
|
||||
return []
|
||||
elif hasattr(data, '__iter__'):
|
||||
return list(data)
|
||||
else:
|
||||
return [data]
|
||||
|
||||
|
||||
HOSTNAME_PATTERN = ("(?=^.{1,254}$)(^(?:(?!\d+\.|-)[a-zA-Z0-9_\-]"
|
||||
"{1,63}(?<!-)\.?)+(?:[a-zA-Z]{2,})$)")
|
||||
|
||||
@ -306,18 +359,22 @@ UUID_PATTERN = '-'.join([HEX_ELEM + '{8}', HEX_ELEM + '{4}',
|
||||
MAC_PATTERN = "^%s[aceACE02468](:%s{2}){5}$" % (HEX_ELEM, HEX_ELEM)
|
||||
|
||||
# Dictionary that maintains a list of validation functions
|
||||
validators = {'type:fixed_ips': _validate_fixed_ips,
|
||||
validators = {'type:dict': _validate_dict,
|
||||
'type:fixed_ips': _validate_fixed_ips,
|
||||
'type:hostroutes': _validate_hostroutes,
|
||||
'type:ip_address': _validate_ip_address,
|
||||
'type:ip_address_or_none': _validate_ip_address_or_none,
|
||||
'type:ip_pools': _validate_ip_pools,
|
||||
'type:mac_address': _validate_mac_address,
|
||||
'type:nameservers': _validate_nameservers,
|
||||
'type:non_negative': _validate_non_negative,
|
||||
'type:range': _validate_range,
|
||||
'type:regex': _validate_regex,
|
||||
'type:string': _validate_string,
|
||||
'type:subnet': _validate_subnet,
|
||||
'type:uuid': _validate_uuid,
|
||||
'type:uuid_or_none': _validate_uuid_or_none,
|
||||
'type:uuid_list': _validate_uuid_list,
|
||||
'type:values': _validate_values}
|
||||
|
||||
# Note: a default of ATTR_NOT_SPECIFIED indicates that an
|
||||
|
@ -165,8 +165,14 @@ class Controller(object):
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name in self._member_actions:
|
||||
def _handle_action(request, id, body=None):
|
||||
return getattr(self._plugin, name)(request.context, id, body)
|
||||
def _handle_action(request, id, **kwargs):
|
||||
if 'body' in kwargs:
|
||||
body = kwargs.pop('body')
|
||||
return getattr(self._plugin, name)(request.context, id,
|
||||
body, **kwargs)
|
||||
else:
|
||||
return getattr(self._plugin, name)(request.context, id,
|
||||
**kwargs)
|
||||
return _handle_action
|
||||
else:
|
||||
raise AttributeError
|
||||
|
@ -86,13 +86,6 @@ class RouterExternalGatewayInUseByFloatingIp(qexception.InUse):
|
||||
"more floating IPs.")
|
||||
|
||||
|
||||
def _validate_uuid_or_none(data, valid_values=None):
|
||||
if data is None:
|
||||
return None
|
||||
return attr._validate_uuid(data)
|
||||
|
||||
attr.validators['type:uuid_or_none'] = _validate_uuid_or_none
|
||||
|
||||
# Attribute Map
|
||||
RESOURCE_ATTRIBUTE_MAP = {
|
||||
'routers': {
|
||||
|
383
quantum/extensions/loadbalancer.py
Normal file
383
quantum/extensions/loadbalancer.py
Normal file
@ -0,0 +1,383 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 OpenStack LLC.
|
||||
# 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 quantum.api import extensions
|
||||
from quantum.api.v2 import attributes as attr
|
||||
from quantum.api.v2 import base
|
||||
from quantum import manager
|
||||
from quantum.plugins.common import constants
|
||||
from quantum.plugins.services.service_base import ServicePluginBase
|
||||
|
||||
|
||||
RESOURCE_ATTRIBUTE_MAP = {
|
||||
'vips': {
|
||||
'id': {'allow_post': False, 'allow_put': False,
|
||||
'validate': {'type:uuid': None},
|
||||
'is_visible': 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},
|
||||
'description': {'allow_post': True, 'allow_put': True,
|
||||
'validate': {'type:string': None},
|
||||
'is_visible': True, 'default': ''},
|
||||
'subnet_id': {'allow_post': True, 'allow_put': False,
|
||||
'validate': {'type:uuid': None},
|
||||
'is_visible': True},
|
||||
'address': {'allow_post': True, 'allow_put': False,
|
||||
'default': attr.ATTR_NOT_SPECIFIED,
|
||||
'validate': {'type:ip_address_or_none': None},
|
||||
'is_visible': True},
|
||||
'port': {'allow_post': True, 'allow_put': False,
|
||||
'validate': {'type:range': [0, 65535]},
|
||||
'convert_to': attr.convert_to_int,
|
||||
'is_visible': True},
|
||||
'protocol': {'allow_post': True, 'allow_put': False,
|
||||
'validate': {'type:string': None},
|
||||
'is_visible': True},
|
||||
'pool_id': {'allow_post': True, 'allow_put': True,
|
||||
'validate': {'type:uuid': None},
|
||||
'is_visible': True},
|
||||
'session_persistence': {'allow_post': True, 'allow_put': True,
|
||||
'default': {},
|
||||
'validate': {'type:dict': None},
|
||||
'is_visible': True},
|
||||
'connection_limit': {'allow_post': True, 'allow_put': True,
|
||||
'default': -1,
|
||||
'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},
|
||||
'tenant_id': {'allow_post': True, 'allow_put': False,
|
||||
'validate': {'type:string': None},
|
||||
'required_by_policy': True,
|
||||
'is_visible': True},
|
||||
'vip_id': {'allow_post': False, 'allow_put': False,
|
||||
'is_visible': True},
|
||||
'name': {'allow_post': True, 'allow_put': True,
|
||||
'validate': {'type:string': None},
|
||||
'is_visible': True},
|
||||
'description': {'allow_post': True, 'allow_put': True,
|
||||
'validate': {'type:string': None},
|
||||
'is_visible': True, 'default': ''},
|
||||
'subnet_id': {'allow_post': True, 'allow_put': False,
|
||||
'validate': {'type:uuid': None},
|
||||
'is_visible': True},
|
||||
'protocol': {'allow_post': True, 'allow_put': False,
|
||||
'validate': {'type:string': None},
|
||||
'is_visible': True},
|
||||
'lb_method': {'allow_post': True, 'allow_put': True,
|
||||
'validate': {'type:string': None},
|
||||
'is_visible': True},
|
||||
'members': {'allow_post': False, 'allow_put': False,
|
||||
'is_visible': True},
|
||||
'health_monitors': {'allow_post': True, 'allow_put': True,
|
||||
'default': None,
|
||||
'validate': {'type:uuid_list': None},
|
||||
'convert_to': attr.convert_to_list,
|
||||
'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}
|
||||
},
|
||||
'members': {
|
||||
'id': {'allow_post': False, 'allow_put': False,
|
||||
'validate': {'type:uuid': None},
|
||||
'is_visible': True},
|
||||
'tenant_id': {'allow_post': True, 'allow_put': False,
|
||||
'validate': {'type:string': None},
|
||||
'required_by_policy': True,
|
||||
'is_visible': True},
|
||||
'pool_id': {'allow_post': True, 'allow_put': True,
|
||||
'validate': {'type:uuid': None},
|
||||
'is_visible': True},
|
||||
'address': {'allow_post': True, 'allow_put': False,
|
||||
'validate': {'type:ip_address': None},
|
||||
'is_visible': True},
|
||||
'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}
|
||||
},
|
||||
'health_monitors': {
|
||||
'id': {'allow_post': False, 'allow_put': False,
|
||||
'validate': {'type:uuid': None},
|
||||
'is_visible': 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': ['PING', 'TCP', 'HTTP', 'HTTPS']},
|
||||
'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,
|
||||
'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 = {
|
||||
'health_monitors': {
|
||||
'parent': {'collection_name': 'pools',
|
||||
'member_name': 'pool'},
|
||||
'parameters': {'id': {'allow_post': True, 'allow_put': False,
|
||||
'validate': {'type:uuid': None},
|
||||
'is_visible': True},
|
||||
'tenant_id': {'allow_post': True, 'allow_put': False,
|
||||
'validate': {'type:string': None},
|
||||
'required_by_policy': True,
|
||||
'is_visible': True},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Loadbalancer(extensions.ExtensionDescriptor):
|
||||
|
||||
@classmethod
|
||||
def get_name(cls):
|
||||
return "LoadBalancing service"
|
||||
|
||||
@classmethod
|
||||
def get_alias(cls):
|
||||
return "lbaas"
|
||||
|
||||
@classmethod
|
||||
def get_description(cls):
|
||||
return "Extension for LoadBalancing service"
|
||||
|
||||
@classmethod
|
||||
def get_namespace(cls):
|
||||
return "http://wiki.openstack.org/Quantum/LBaaS/API_1.0"
|
||||
|
||||
@classmethod
|
||||
def get_updated(cls):
|
||||
return "2012-10-07T10:00:00-00:00"
|
||||
|
||||
@classmethod
|
||||
def get_resources(cls):
|
||||
resources = []
|
||||
plugin = manager.QuantumManager.get_service_plugins()[
|
||||
constants.LOADBALANCER]
|
||||
for collection_name in RESOURCE_ATTRIBUTE_MAP:
|
||||
# Special handling needed for resources with 'y' ending
|
||||
# (e.g. proxies -> proxy)
|
||||
resource_name = collection_name[:-1]
|
||||
params = RESOURCE_ATTRIBUTE_MAP[collection_name]
|
||||
|
||||
member_actions = {}
|
||||
if resource_name == 'pool':
|
||||
member_actions = {'stats': 'GET'}
|
||||
|
||||
controller = base.create_resource(collection_name,
|
||||
resource_name,
|
||||
plugin, params,
|
||||
member_actions=member_actions)
|
||||
|
||||
resource = extensions.ResourceExtension(
|
||||
collection_name,
|
||||
controller,
|
||||
path_prefix=constants.COMMON_PREFIXES[constants.LOADBALANCER],
|
||||
member_actions=member_actions)
|
||||
resources.append(resource)
|
||||
|
||||
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)
|
||||
|
||||
resource = extensions.ResourceExtension(
|
||||
collection_name,
|
||||
controller, parent,
|
||||
path_prefix=constants.COMMON_PREFIXES[constants.LOADBALANCER])
|
||||
resources.append(resource)
|
||||
|
||||
return resources
|
||||
|
||||
@classmethod
|
||||
def get_plugin_interface(cls):
|
||||
return LoadBalancerPluginBase
|
||||
|
||||
|
||||
class LoadBalancerPluginBase(ServicePluginBase):
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
def get_plugin_type(self):
|
||||
return constants.LOADBALANCER
|
||||
|
||||
def get_plugin_description(self):
|
||||
return 'LoadBalancer service plugin'
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_vips(self, context, filters=None, fields=None):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_vip(self, context, id, fields=None):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def create_vip(self, context, vip):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def update_vip(self, context, id, vip):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def delete_vip(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, pool_id):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def create_pool_health_monitor(self, context, health_monitor, pool_id):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_pool_health_monitor(self, context, id, pool_id, fields=None):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def delete_pool_health_monitor(self, context, id, pool_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
|
||||
|
||||
@abc.abstractmethod
|
||||
def create_member(self, context, member):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def update_member(self, context, id, member):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def delete_member(self, context, id):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_health_monitors(self, context, filters=None, fields=None):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_health_monitor(self, context, id, fields=None):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def create_health_monitor(self, context, health_monitor):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def update_health_monitor(self, context, id, health_monitor):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def delete_health_monitor(self, context, id):
|
||||
pass
|
@ -18,9 +18,11 @@
|
||||
# service type constants:
|
||||
CORE = "CORE"
|
||||
DUMMY = "DUMMY"
|
||||
LOADBALANCER = "LOADBALANCER"
|
||||
|
||||
|
||||
COMMON_PREFIXES = {
|
||||
CORE: "",
|
||||
DUMMY: "/dummy_svc",
|
||||
LOADBALANCER: "/lb",
|
||||
}
|
||||
|
@ -17,8 +17,10 @@
|
||||
|
||||
import abc
|
||||
|
||||
from quantum.api import extensions
|
||||
|
||||
class ServicePluginBase(object):
|
||||
|
||||
class ServicePluginBase(extensions.PluginInterface):
|
||||
""" defines base interface for any Advanced Service plugin """
|
||||
__metaclass__ = abc.ABCMeta
|
||||
supported_extension_aliases = []
|
||||
|
@ -371,6 +371,69 @@ class TestAttributes(unittest2.TestCase):
|
||||
msg = attributes._validate_uuid('00000000-ffff-ffff-ffff-000000000000')
|
||||
self.assertIsNone(msg)
|
||||
|
||||
def test_validate_uuid_list(self):
|
||||
# check not a list
|
||||
uuids = [None,
|
||||
123,
|
||||
'e5069610-744b-42a7-8bd8-ceac1a229cd4',
|
||||
'12345678123456781234567812345678',
|
||||
{'uuid': 'e5069610-744b-42a7-8bd8-ceac1a229cd4'}]
|
||||
for uuid in uuids:
|
||||
msg = attributes._validate_uuid_list(uuid)
|
||||
error = "'%s' is not a list" % uuid
|
||||
self.assertEquals(msg, error)
|
||||
|
||||
# check invalid uuid in a list
|
||||
invalid_uuid_lists = [[None],
|
||||
[123],
|
||||
[123, 'e5069610-744b-42a7-8bd8-ceac1a229cd4'],
|
||||
['123', '12345678123456781234567812345678'],
|
||||
['t5069610-744b-42a7-8bd8-ceac1a229cd4'],
|
||||
['e5069610-744b-42a7-8bd8-ceac1a229cd44'],
|
||||
['e50696100-744b-42a7-8bd8-ceac1a229cd4'],
|
||||
['e5069610-744bb-42a7-8bd8-ceac1a229cd4']]
|
||||
for uuid_list in invalid_uuid_lists:
|
||||
msg = attributes._validate_uuid_list(uuid_list)
|
||||
error = "'%s' is not a valid UUID" % uuid_list[0]
|
||||
self.assertEquals(msg, error)
|
||||
|
||||
# check duplicate items in a list
|
||||
duplicate_uuids = ['e5069610-744b-42a7-8bd8-ceac1a229cd4',
|
||||
'f3eeab00-8367-4524-b662-55e64d4cacb5',
|
||||
'e5069610-744b-42a7-8bd8-ceac1a229cd4']
|
||||
msg = attributes._validate_uuid_list(duplicate_uuids)
|
||||
error = "Duplicate items in the list: %s" % ', '.join(duplicate_uuids)
|
||||
self.assertEquals(msg, error)
|
||||
|
||||
# check valid uuid lists
|
||||
valid_uuid_lists = [['e5069610-744b-42a7-8bd8-ceac1a229cd4'],
|
||||
['f3eeab00-8367-4524-b662-55e64d4cacb5'],
|
||||
['e5069610-744b-42a7-8bd8-ceac1a229cd4',
|
||||
'f3eeab00-8367-4524-b662-55e64d4cacb5']]
|
||||
for uuid_list in valid_uuid_lists:
|
||||
msg = attributes._validate_uuid_list(uuid_list)
|
||||
self.assertEquals(msg, None)
|
||||
|
||||
def test_validate_dict(self):
|
||||
for value in (None, True, '1', []):
|
||||
self.assertEquals(attributes._validate_dict(value),
|
||||
"'%s' is not a dictionary" % value)
|
||||
|
||||
msg = attributes._validate_dict({})
|
||||
self.assertIsNone(msg)
|
||||
|
||||
msg = attributes._validate_dict({'key': 'value'})
|
||||
self.assertIsNone(msg)
|
||||
|
||||
def test_validate_non_negative(self):
|
||||
for value in (-1, '-2'):
|
||||
self.assertEquals(attributes._validate_non_negative(value),
|
||||
"'%s' should be non-negative" % value)
|
||||
|
||||
for value in (0, 1, '2', True, False):
|
||||
msg = attributes._validate_non_negative(value)
|
||||
self.assertIsNone(msg)
|
||||
|
||||
|
||||
class TestConvertToBoolean(unittest2.TestCase):
|
||||
|
||||
@ -457,3 +520,22 @@ class TestConvertKvp(unittest2.TestCase):
|
||||
def test_convert_kvp_str_to_list_succeeds_for_two_equals(self):
|
||||
result = attributes.convert_kvp_str_to_list('a=a=a')
|
||||
self.assertEqual(['a', 'a=a'], result)
|
||||
|
||||
|
||||
class TestConvertToList(unittest2.TestCase):
|
||||
|
||||
def test_convert_to_empty_list(self):
|
||||
for item in (None, [], (), {}):
|
||||
self.assertEquals(attributes.convert_to_list(item), [])
|
||||
|
||||
def test_convert_to_list_string(self):
|
||||
for item in ('', 'foo'):
|
||||
self.assertEquals(attributes.convert_to_list(item), [item])
|
||||
|
||||
def test_convert_to_list_iterable(self):
|
||||
for item in ([None], [1, 2, 3], (1, 2, 3), set([1, 2, 3]), ['foo']):
|
||||
self.assertEquals(attributes.convert_to_list(item), list(item))
|
||||
|
||||
def test_convert_to_list_non_iterable(self):
|
||||
for item in (True, False, 1, 1.2, object()):
|
||||
self.assertEquals(attributes.convert_to_list(item), [item])
|
||||
|
477
quantum/tests/unit/test_loadbalancer_plugin.py
Normal file
477
quantum/tests/unit/test_loadbalancer_plugin.py
Normal file
@ -0,0 +1,477 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 OpenStack LLC.
|
||||
# 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 spec
|
||||
|
||||
import copy
|
||||
import mock
|
||||
from webob import exc
|
||||
import webtest
|
||||
import unittest2
|
||||
|
||||
from quantum.api import extensions
|
||||
from quantum.common import config
|
||||
from quantum.extensions import loadbalancer
|
||||
from quantum import manager
|
||||
from quantum.openstack.common import cfg
|
||||
from quantum.openstack.common import uuidutils
|
||||
from quantum.plugins.common import constants
|
||||
from quantum.tests.unit import test_api_v2
|
||||
from quantum.tests.unit import test_extensions
|
||||
|
||||
|
||||
_uuid = uuidutils.generate_uuid
|
||||
_get_path = test_api_v2._get_path
|
||||
|
||||
|
||||
class LoadBalancerTestExtensionManager(object):
|
||||
|
||||
def get_resources(self):
|
||||
return loadbalancer.Loadbalancer.get_resources()
|
||||
|
||||
def get_actions(self):
|
||||
return []
|
||||
|
||||
def get_request_extensions(self):
|
||||
return []
|
||||
|
||||
|
||||
class LoadBalancerExtensionTestCase(unittest2.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
plugin = 'quantum.extensions.loadbalancer.LoadBalancerPluginBase'
|
||||
# Ensure 'stale' patched copies of the plugin are never returned
|
||||
manager.QuantumManager._instance = None
|
||||
|
||||
# Ensure existing ExtensionManager is not used
|
||||
extensions.PluginAwareExtensionManager._instance = None
|
||||
|
||||
# Create the default configurations
|
||||
args = ['--config-file', test_api_v2.etcdir('quantum.conf.test')]
|
||||
config.parse(args)
|
||||
|
||||
#just stubbing core plugin with LoadBalancer plugin
|
||||
cfg.CONF.set_override('core_plugin', plugin)
|
||||
cfg.CONF.set_override('service_plugins', [plugin])
|
||||
|
||||
self._plugin_patcher = mock.patch(plugin, autospec=True)
|
||||
self.plugin = self._plugin_patcher.start()
|
||||
instance = self.plugin.return_value
|
||||
instance.get_plugin_type.return_value = constants.LOADBALANCER
|
||||
|
||||
ext_mgr = LoadBalancerTestExtensionManager()
|
||||
self.ext_mdw = test_extensions.setup_extensions_middleware(ext_mgr)
|
||||
self.api = webtest.TestApp(self.ext_mdw)
|
||||
|
||||
def tearDown(self):
|
||||
self._plugin_patcher.stop()
|
||||
self.api = None
|
||||
self.plugin = None
|
||||
cfg.CONF.reset()
|
||||
|
||||
def test_vip_create(self):
|
||||
vip_id = _uuid()
|
||||
data = {'vip': {'name': 'vip1',
|
||||
'description': 'descr_vip1',
|
||||
'subnet_id': _uuid(),
|
||||
'address': '127.0.0.1',
|
||||
'port': 80,
|
||||
'protocol': 'HTTP',
|
||||
'pool_id': _uuid(),
|
||||
'session_persistence': {'type': 'dummy'},
|
||||
'connection_limit': 100,
|
||||
'admin_state_up': True,
|
||||
'tenant_id': _uuid()}}
|
||||
return_value = copy.copy(data['vip'])
|
||||
return_value.update({'status': "ACTIVE", 'id': vip_id})
|
||||
|
||||
instance = self.plugin.return_value
|
||||
instance.create_vip.return_value = return_value
|
||||
res = self.api.post_json(_get_path('lb/vips'), data)
|
||||
instance.create_vip.assert_called_with(mock.ANY,
|
||||
vip=data)
|
||||
self.assertEqual(res.status_int, exc.HTTPCreated.code)
|
||||
self.assertTrue('vip' in res.json)
|
||||
self.assertEqual(res.json['vip'], return_value)
|
||||
|
||||
def test_vip_list(self):
|
||||
vip_id = _uuid()
|
||||
return_value = [{'name': 'vip1',
|
||||
'admin_state_up': True,
|
||||
'tenant_id': _uuid(),
|
||||
'id': vip_id}]
|
||||
|
||||
instance = self.plugin.return_value
|
||||
instance.get_vips.return_value = return_value
|
||||
|
||||
res = self.api.get(_get_path('lb/vips'))
|
||||
|
||||
instance.get_vips.assert_called_with(mock.ANY, fields=mock.ANY,
|
||||
filters=mock.ANY)
|
||||
self.assertEqual(res.status_int, exc.HTTPOk.code)
|
||||
|
||||
def test_vip_update(self):
|
||||
vip_id = _uuid()
|
||||
update_data = {'vip': {'admin_state_up': False}}
|
||||
return_value = {'name': 'vip1',
|
||||
'admin_state_up': False,
|
||||
'tenant_id': _uuid(),
|
||||
'status': "ACTIVE",
|
||||
'id': vip_id}
|
||||
|
||||
instance = self.plugin.return_value
|
||||
instance.update_vip.return_value = return_value
|
||||
|
||||
res = self.api.put_json(_get_path('lb/vips', id=vip_id),
|
||||
update_data)
|
||||
|
||||
instance.update_vip.assert_called_with(mock.ANY, vip_id,
|
||||
vip=update_data)
|
||||
self.assertEqual(res.status_int, exc.HTTPOk.code)
|
||||
self.assertTrue('vip' in res.json)
|
||||
self.assertEqual(res.json['vip'], return_value)
|
||||
|
||||
def test_vip_get(self):
|
||||
vip_id = _uuid()
|
||||
return_value = {'name': 'vip1',
|
||||
'admin_state_up': False,
|
||||
'tenant_id': _uuid(),
|
||||
'status': "ACTIVE",
|
||||
'id': vip_id}
|
||||
|
||||
instance = self.plugin.return_value
|
||||
instance.get_vip.return_value = return_value
|
||||
|
||||
res = self.api.get(_get_path('lb/vips', id=vip_id))
|
||||
|
||||
instance.get_vip.assert_called_with(mock.ANY, vip_id,
|
||||
fields=mock.ANY)
|
||||
self.assertEqual(res.status_int, exc.HTTPOk.code)
|
||||
self.assertTrue('vip' in res.json)
|
||||
self.assertEqual(res.json['vip'], return_value)
|
||||
|
||||
def test_vip_delete(self):
|
||||
vip_id = _uuid()
|
||||
|
||||
res = self.api.delete(_get_path('lb/vips', id=vip_id))
|
||||
|
||||
instance = self.plugin.return_value
|
||||
instance.delete_vip.assert_called_with(mock.ANY, vip_id)
|
||||
self.assertEqual(res.status_int, exc.HTTPNoContent.code)
|
||||
|
||||
def test_pool_create(self):
|
||||
pool_id = _uuid()
|
||||
hm_id = _uuid()
|
||||
data = {'pool': {'name': 'pool1',
|
||||
'description': 'descr_pool1',
|
||||
'subnet_id': _uuid(),
|
||||
'protocol': 'HTTP',
|
||||
'lb_method': 'ROUND_ROBIN',
|
||||
'health_monitors': [hm_id],
|
||||
'admin_state_up': True,
|
||||
'tenant_id': _uuid()}}
|
||||
return_value = copy.copy(data['pool'])
|
||||
return_value.update({'status': "ACTIVE", 'id': pool_id})
|
||||
|
||||
instance = self.plugin.return_value
|
||||
instance.create_pool.return_value = return_value
|
||||
res = self.api.post_json(_get_path('lb/pools'), data)
|
||||
instance.create_pool.assert_called_with(mock.ANY,
|
||||
pool=data)
|
||||
self.assertEqual(res.status_int, exc.HTTPCreated.code)
|
||||
self.assertTrue('pool' in res.json)
|
||||
self.assertEqual(res.json['pool'], return_value)
|
||||
|
||||
def test_pool_list(self):
|
||||
pool_id = _uuid()
|
||||
return_value = [{'name': 'pool1',
|
||||
'admin_state_up': True,
|
||||
'tenant_id': _uuid(),
|
||||
'id': pool_id}]
|
||||
|
||||
instance = self.plugin.return_value
|
||||
instance.get_pools.return_value = return_value
|
||||
|
||||
res = self.api.get(_get_path('lb/pools'))
|
||||
|
||||
instance.get_pools.assert_called_with(mock.ANY, fields=mock.ANY,
|
||||
filters=mock.ANY)
|
||||
self.assertEqual(res.status_int, exc.HTTPOk.code)
|
||||
|
||||
def test_pool_update(self):
|
||||
pool_id = _uuid()
|
||||
update_data = {'pool': {'admin_state_up': False}}
|
||||
return_value = {'name': 'pool1',
|
||||
'admin_state_up': False,
|
||||
'tenant_id': _uuid(),
|
||||
'status': "ACTIVE",
|
||||
'id': pool_id}
|
||||
|
||||
instance = self.plugin.return_value
|
||||
instance.update_pool.return_value = return_value
|
||||
|
||||
res = self.api.put_json(_get_path('lb/pools', id=pool_id),
|
||||
update_data)
|
||||
|
||||
instance.update_pool.assert_called_with(mock.ANY, pool_id,
|
||||
pool=update_data)
|
||||
self.assertEqual(res.status_int, exc.HTTPOk.code)
|
||||
self.assertTrue('pool' in res.json)
|
||||
self.assertEqual(res.json['pool'], return_value)
|
||||
|
||||
def test_pool_get(self):
|
||||
pool_id = _uuid()
|
||||
return_value = {'name': 'pool1',
|
||||
'admin_state_up': False,
|
||||
'tenant_id': _uuid(),
|
||||
'status': "ACTIVE",
|
||||
'id': pool_id}
|
||||
|
||||
instance = self.plugin.return_value
|
||||
instance.get_pool.return_value = return_value
|
||||
|
||||
res = self.api.get(_get_path('lb/pools', id=pool_id))
|
||||
|
||||
instance.get_pool.assert_called_with(mock.ANY, pool_id,
|
||||
fields=mock.ANY)
|
||||
self.assertEqual(res.status_int, exc.HTTPOk.code)
|
||||
self.assertTrue('pool' in res.json)
|
||||
self.assertEqual(res.json['pool'], return_value)
|
||||
|
||||
def test_pool_delete(self):
|
||||
pool_id = _uuid()
|
||||
|
||||
res = self.api.delete(_get_path('lb/pools', id=pool_id))
|
||||
|
||||
instance = self.plugin.return_value
|
||||
instance.delete_pool.assert_called_with(mock.ANY, pool_id)
|
||||
self.assertEqual(res.status_int, exc.HTTPNoContent.code)
|
||||
|
||||
def test_pool_stats(self):
|
||||
pool_id = _uuid()
|
||||
|
||||
stats = {'stats': 'dummy'}
|
||||
instance = self.plugin.return_value
|
||||
instance.stats.return_value = stats
|
||||
|
||||
path = _get_path('lb/pools', id=pool_id,
|
||||
action="stats")
|
||||
res = self.api.get(path)
|
||||
|
||||
instance.stats.assert_called_with(mock.ANY, pool_id)
|
||||
self.assertEqual(res.status_int, exc.HTTPOk.code)
|
||||
self.assertTrue('stats' in res.json)
|
||||
self.assertEqual(res.json['stats'], stats['stats'])
|
||||
|
||||
def test_member_create(self):
|
||||
member_id = _uuid()
|
||||
data = {'member': {'pool_id': _uuid(),
|
||||
'address': '127.0.0.1',
|
||||
'port': 80,
|
||||
'weight': 1,
|
||||
'admin_state_up': True,
|
||||
'tenant_id': _uuid()}}
|
||||
return_value = copy.copy(data['member'])
|
||||
return_value.update({'status': "ACTIVE", 'id': member_id})
|
||||
|
||||
instance = self.plugin.return_value
|
||||
instance.create_member.return_value = return_value
|
||||
res = self.api.post_json(_get_path('lb/members'), data)
|
||||
instance.create_member.assert_called_with(mock.ANY,
|
||||
member=data)
|
||||
self.assertEqual(res.status_int, exc.HTTPCreated.code)
|
||||
self.assertTrue('member' in res.json)
|
||||
self.assertEqual(res.json['member'], return_value)
|
||||
|
||||
def test_member_list(self):
|
||||
member_id = _uuid()
|
||||
return_value = [{'name': 'member1',
|
||||
'admin_state_up': True,
|
||||
'tenant_id': _uuid(),
|
||||
'id': member_id}]
|
||||
|
||||
instance = self.plugin.return_value
|
||||
instance.get_members.return_value = return_value
|
||||
|
||||
res = self.api.get(_get_path('lb/members'))
|
||||
|
||||
instance.get_members.assert_called_with(mock.ANY, fields=mock.ANY,
|
||||
filters=mock.ANY)
|
||||
self.assertEqual(res.status_int, exc.HTTPOk.code)
|
||||
|
||||
def test_member_update(self):
|
||||
member_id = _uuid()
|
||||
update_data = {'member': {'admin_state_up': False}}
|
||||
return_value = {'admin_state_up': False,
|
||||
'tenant_id': _uuid(),
|
||||
'status': "ACTIVE",
|
||||
'id': member_id}
|
||||
|
||||
instance = self.plugin.return_value
|
||||
instance.update_member.return_value = return_value
|
||||
|
||||
res = self.api.put_json(_get_path('lb/members', id=member_id),
|
||||
update_data)
|
||||
|
||||
instance.update_member.assert_called_with(mock.ANY, member_id,
|
||||
member=update_data)
|
||||
self.assertEqual(res.status_int, exc.HTTPOk.code)
|
||||
self.assertTrue('member' in res.json)
|
||||
self.assertEqual(res.json['member'], return_value)
|
||||
|
||||
def test_member_get(self):
|
||||
member_id = _uuid()
|
||||
return_value = {'admin_state_up': False,
|
||||
'tenant_id': _uuid(),
|
||||
'status': "ACTIVE",
|
||||
'id': member_id}
|
||||
|
||||
instance = self.plugin.return_value
|
||||
instance.get_member.return_value = return_value
|
||||
|
||||
res = self.api.get(_get_path('lb/members', id=member_id))
|
||||
|
||||
instance.get_member.assert_called_with(mock.ANY, member_id,
|
||||
fields=mock.ANY)
|
||||
self.assertEqual(res.status_int, exc.HTTPOk.code)
|
||||
self.assertTrue('member' in res.json)
|
||||
self.assertEqual(res.json['member'], return_value)
|
||||
|
||||
def test_member_delete(self):
|
||||
member_id = _uuid()
|
||||
|
||||
res = self.api.delete(_get_path('lb/members', id=member_id))
|
||||
|
||||
instance = self.plugin.return_value
|
||||
instance.delete_member.assert_called_with(mock.ANY, member_id)
|
||||
self.assertEqual(res.status_int, exc.HTTPNoContent.code)
|
||||
|
||||
def test_health_monitor_create(self):
|
||||
health_monitor_id = _uuid()
|
||||
data = {'health_monitor': {'type': 'HTTP',
|
||||
'delay': 2,
|
||||
'timeout': 1,
|
||||
'max_retries': 3,
|
||||
'http_method': 'GET',
|
||||
'url_path': '/path',
|
||||
'expected_codes': '200-300',
|
||||
'admin_state_up': True,
|
||||
'tenant_id': _uuid()}}
|
||||
return_value = copy.copy(data['health_monitor'])
|
||||
return_value.update({'status': "ACTIVE", 'id': health_monitor_id})
|
||||
|
||||
instance = self.plugin.return_value
|
||||
instance.create_health_monitor.return_value = return_value
|
||||
res = self.api.post_json(_get_path('lb/health_monitors'), data)
|
||||
instance.create_health_monitor.assert_called_with(mock.ANY,
|
||||
health_monitor=data)
|
||||
self.assertEqual(res.status_int, exc.HTTPCreated.code)
|
||||
self.assertTrue('health_monitor' in res.json)
|
||||
self.assertEqual(res.json['health_monitor'], return_value)
|
||||
|
||||
def test_health_monitor_list(self):
|
||||
health_monitor_id = _uuid()
|
||||
return_value = [{'type': 'HTTP',
|
||||
'admin_state_up': True,
|
||||
'tenant_id': _uuid(),
|
||||
'id': health_monitor_id}]
|
||||
|
||||
instance = self.plugin.return_value
|
||||
instance.get_health_monitors.return_value = return_value
|
||||
|
||||
res = self.api.get(_get_path('lb/health_monitors'))
|
||||
|
||||
instance.get_health_monitors.assert_called_with(
|
||||
mock.ANY, fields=mock.ANY, filters=mock.ANY)
|
||||
self.assertEqual(res.status_int, exc.HTTPOk.code)
|
||||
|
||||
def test_health_monitor_update(self):
|
||||
health_monitor_id = _uuid()
|
||||
update_data = {'health_monitor': {'admin_state_up': False}}
|
||||
return_value = {'type': 'HTTP',
|
||||
'admin_state_up': False,
|
||||
'tenant_id': _uuid(),
|
||||
'status': "ACTIVE",
|
||||
'id': health_monitor_id}
|
||||
|
||||
instance = self.plugin.return_value
|
||||
instance.update_health_monitor.return_value = return_value
|
||||
|
||||
res = self.api.put_json(_get_path('lb/health_monitors',
|
||||
id=health_monitor_id),
|
||||
update_data)
|
||||
|
||||
instance.update_health_monitor.assert_called_with(
|
||||
mock.ANY, health_monitor_id, health_monitor=update_data)
|
||||
self.assertEqual(res.status_int, exc.HTTPOk.code)
|
||||
self.assertTrue('health_monitor' in res.json)
|
||||
self.assertEqual(res.json['health_monitor'], return_value)
|
||||
|
||||
def test_health_monitor_get(self):
|
||||
health_monitor_id = _uuid()
|
||||
return_value = {'type': 'HTTP',
|
||||
'admin_state_up': False,
|
||||
'tenant_id': _uuid(),
|
||||
'status': "ACTIVE",
|
||||
'id': health_monitor_id}
|
||||
|
||||
instance = self.plugin.return_value
|
||||
instance.get_health_monitor.return_value = return_value
|
||||
|
||||
res = self.api.get(_get_path('lb/health_monitors',
|
||||
id=health_monitor_id))
|
||||
|
||||
instance.get_health_monitor.assert_called_with(
|
||||
mock.ANY, health_monitor_id, fields=mock.ANY)
|
||||
self.assertEqual(res.status_int, exc.HTTPOk.code)
|
||||
self.assertTrue('health_monitor' in res.json)
|
||||
self.assertEqual(res.json['health_monitor'], return_value)
|
||||
|
||||
def test_health_monitor_delete(self):
|
||||
health_monitor_id = _uuid()
|
||||
|
||||
res = self.api.delete(_get_path('lb/health_monitors',
|
||||
id=health_monitor_id))
|
||||
|
||||
instance = self.plugin.return_value
|
||||
instance.delete_health_monitor.assert_called_with(mock.ANY,
|
||||
health_monitor_id)
|
||||
self.assertEqual(res.status_int, exc.HTTPNoContent.code)
|
||||
|
||||
def test_create_pool_health_monitor(self):
|
||||
health_monitor_id = _uuid()
|
||||
data = {'health_monitor': {'id': health_monitor_id,
|
||||
'tenant_id': _uuid()}}
|
||||
|
||||
return_value = copy.copy(data['health_monitor'])
|
||||
instance = self.plugin.return_value
|
||||
instance.create_pool_health_monitor.return_value = return_value
|
||||
res = self.api.post_json('/lb/pools/id1/health_monitors', data)
|
||||
instance.create_pool_health_monitor.assert_called_with(
|
||||
mock.ANY, pool_id='id1', health_monitor=data)
|
||||
self.assertEqual(res.status_int, exc.HTTPCreated.code)
|
||||
self.assertTrue('health_monitor' in res.json)
|
||||
self.assertEqual(res.json['health_monitor'], return_value)
|
||||
|
||||
def test_delete_pool_health_monitor(self):
|
||||
health_monitor_id = _uuid()
|
||||
|
||||
res = self.api.delete('/lb/pools/id1/health_monitors/%s' %
|
||||
health_monitor_id)
|
||||
|
||||
instance = self.plugin.return_value
|
||||
instance.delete_pool_health_monitor.assert_called_with(
|
||||
mock.ANY, health_monitor_id, pool_id='id1')
|
||||
self.assertEqual(res.status_int, exc.HTTPNoContent.code)
|
Loading…
Reference in New Issue
Block a user