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()
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,})$)")
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
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
"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': {
--- /dev/null
+# 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
# service type constants:
CORE = "CORE"
DUMMY = "DUMMY"
+LOADBALANCER = "LOADBALANCER"
COMMON_PREFIXES = {
CORE: "",
DUMMY: "/dummy_svc",
+ LOADBALANCER: "/lb",
}
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 = []
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):
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])
--- /dev/null
+# 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)