diff --git a/quantum/db/routedserviceinsertion_db.py b/quantum/db/routedserviceinsertion_db.py new file mode 100644 index 00000000000..1d7dc517f9c --- /dev/null +++ b/quantum/db/routedserviceinsertion_db.py @@ -0,0 +1,88 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2013 VMware, Inc. 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. +# +# @author: Kaiwei Fan, VMware, Inc + +import sqlalchemy as sa +from sqlalchemy import event +from sqlalchemy.orm import exc + +from quantum.common import exceptions as qexception +from quantum.db import model_base +from quantum.extensions import routedserviceinsertion as rsi + + +class ServiceRouterBinding(model_base.BASEV2): + resource_id = sa.Column(sa.String(36), + primary_key=True) + resource_type = sa.Column(sa.String(36), + primary_key=True) + router_id = sa.Column(sa.String(36), + sa.ForeignKey('routers.id'), + nullable=False) + + +class AttributeException(qexception.QuantumException): + message = _("Resource type '%(resource_type)s' is longer " + "than %(maxlen)d characters") + + +@event.listens_for(ServiceRouterBinding.resource_type, 'set', retval=True) +def validate_resource_type(target, value, oldvalue, initiator): + """Make sure the resource type fit the resource_type column.""" + maxlen = ServiceRouterBinding.resource_type.property.columns[0].type.length + if len(value) > maxlen: + raise AttributeException(resource_type=value, maxlen=maxlen) + return value + + +class RoutedServiceInsertionDbMixin(object): + """Mixin class to add router service insertion.""" + + def _process_create_resource_router_id(self, context, resource, model): + with context.session.begin(subtransactions=True): + db = ServiceRouterBinding( + resource_id=resource['id'], + resource_type=model.__tablename__, + router_id=resource[rsi.ROUTER_ID]) + context.session.add(db) + return self._make_resource_router_id_dict(db, model) + + def _extend_resource_router_id_dict(self, context, resource, model): + binding = self._get_resource_router_id_binding( + context, resource['resource_id'], model) + resource[rsi.ROUTER_ID] = binding['router_id'] + + def _get_resource_router_id_binding(self, context, resource_id, model): + query = self._model_query(context, ServiceRouterBinding) + query = query.filter( + ServiceRouterBinding.resource_id == resource_id, + ServiceRouterBinding.resource_type == model.__tablename__) + return query.first() + + def _make_resource_router_id_dict(self, resource_router_binding, model, + fields=None): + resource = {'resource_id': resource_router_binding['resource_id'], + 'resource_type': model.__tablename__, + rsi.ROUTER_ID: resource_router_binding[rsi.ROUTER_ID]} + return self._fields(resource, fields) + + def _delete_resource_router_id_binding(self, context, resource_id, model): + with context.session.begin(subtransactions=True): + binding = self._get_resource_router_id_binding( + context, resource_id, model) + if binding: + context.session.delete(binding) diff --git a/quantum/db/routerservicetype_db.py b/quantum/db/routerservicetype_db.py new file mode 100644 index 00000000000..d764769feef --- /dev/null +++ b/quantum/db/routerservicetype_db.py @@ -0,0 +1,61 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2013 VMware, Inc. 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. +# +# @author: Kaiwei Fan, VMware, Inc + +from sqlalchemy.orm import exc +import sqlalchemy as sa + +from quantum.db import model_base +from quantum.extensions import routerservicetype as rst + + +class RouterServiceTypeBinding(model_base.BASEV2): + router_id = sa.Column(sa.String(36), + sa.ForeignKey('routers.id', ondelete="CASCADE"), + primary_key=True) + service_type_id = sa.Column(sa.String(36), + sa.ForeignKey('servicetypes.id'), + nullable=False) + + +class RouterServiceTypeDbMixin(object): + """Mixin class to add router service type.""" + + def _process_create_router_service_type_id(self, context, router): + with context.session.begin(subtransactions=True): + db = RouterServiceTypeBinding( + router_id=router['id'], + service_type_id=router[rst.SERVICE_TYPE_ID]) + context.session.add(db) + return self._make_router_service_type_id_dict(db) + + def _extend_router_service_type_id_dict(self, context, router): + rsbind = self._get_router_service_type_id_binding( + context, router['id']) + if rsbind: + router[rst.SERVICE_TYPE_ID] = rsbind['service_type_id'] + + def _get_router_service_type_id_binding(self, context, router_id): + query = self._model_query(context, RouterServiceTypeBinding) + query = query.filter( + RouterServiceTypeBinding.router_id == router_id) + return query.first() + + def _make_router_service_type_id_dict(self, router_service_type): + res = {'router_id': router_service_type['router_id'], + 'service_type_id': router_service_type[rst.SERVICE_TYPE_ID]} + return self._fields(res, None) diff --git a/quantum/extensions/l3.py b/quantum/extensions/l3.py index 2b251f6017f..587b5ccd391 100644 --- a/quantum/extensions/l3.py +++ b/quantum/extensions/l3.py @@ -214,9 +214,14 @@ class L3(extensions.ExtensionDescriptor): return exts + def update_attributes_map(self, attributes): + super(L3, self).update_attributes_map( + attributes, extension_attrs_map=RESOURCE_ATTRIBUTE_MAP) + def get_extended_resources(self, version): if version == "2.0": - return EXTENDED_ATTRIBUTES_2_0 + return dict(EXTENDED_ATTRIBUTES_2_0.items() + + RESOURCE_ATTRIBUTE_MAP.items()) else: return {} diff --git a/quantum/extensions/loadbalancer.py b/quantum/extensions/loadbalancer.py index 6c5a2a17511..d33307e091c 100644 --- a/quantum/extensions/loadbalancer.py +++ b/quantum/extensions/loadbalancer.py @@ -318,6 +318,16 @@ class Loadbalancer(extensions.ExtensionDescriptor): def get_plugin_interface(cls): return LoadBalancerPluginBase + def update_attributes_map(self, attributes): + super(Loadbalancer, 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 {} + class LoadBalancerPluginBase(ServicePluginBase): __metaclass__ = abc.ABCMeta diff --git a/quantum/extensions/routedserviceinsertion.py b/quantum/extensions/routedserviceinsertion.py new file mode 100644 index 00000000000..b3962534748 --- /dev/null +++ b/quantum/extensions/routedserviceinsertion.py @@ -0,0 +1,67 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2013 VMware, Inc. 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. +# +# @author: Kaiwei Fan, VMware, Inc + + +ROUTER_ID = 'router_id' +EXTENDED_ATTRIBUTES_2_0 = { + 'vips': { + ROUTER_ID: {'allow_post': True, 'allow_put': False, + 'validate': {'type:uuid_or_none': None}, + 'default': None, 'is_visible': True}, + }, + 'pools': { + ROUTER_ID: {'allow_post': True, 'allow_put': False, + 'validate': {'type:uuid_or_none': None}, + 'default': None, 'is_visible': True}, + }, + 'health_monitors': { + ROUTER_ID: {'allow_post': True, 'allow_put': False, + 'validate': {'type:uuid_or_none': None}, + 'default': None, 'is_visible': True}, + }, +} + + +class Routedserviceinsertion(object): + """Extension class supporting routed service type.""" + + @classmethod + def get_name(cls): + return "Routed Service Insertion" + + @classmethod + def get_alias(cls): + return "routed-service-insertion" + + @classmethod + def get_description(cls): + return "Provides routed service type" + + @classmethod + def get_namespace(cls): + return "" + + @classmethod + def get_updated(cls): + return "2013-01-29T00:00:00-00:00" + + def get_extended_resources(self, version): + if version == "2.0": + return EXTENDED_ATTRIBUTES_2_0 + else: + return {} diff --git a/quantum/extensions/routerservicetype.py b/quantum/extensions/routerservicetype.py new file mode 100644 index 00000000000..17f032362b2 --- /dev/null +++ b/quantum/extensions/routerservicetype.py @@ -0,0 +1,57 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2013 VMware, Inc. 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. +# +# @author: Kaiwei Fan, VMware, Inc + + +SERVICE_TYPE_ID = 'service_type_id' +EXTENDED_ATTRIBUTES_2_0 = { + 'routers': { + SERVICE_TYPE_ID: {'allow_post': True, 'allow_put': False, + 'validate': {'type:uuid_or_none': None}, + 'default': None, 'is_visible': True}, + } +} + + +class Routerservicetype(object): + """Extension class supporting router service type.""" + + @classmethod + def get_name(cls): + return "Router Service Type" + + @classmethod + def get_alias(cls): + return "router-service-type" + + @classmethod + def get_description(cls): + return "Provides router service type" + + @classmethod + def get_namespace(cls): + return "" + + @classmethod + def get_updated(cls): + return "2013-01-29T00:00:00-00:00" + + def get_extended_resources(self, version): + if version == "2.0": + return EXTENDED_ATTRIBUTES_2_0 + else: + return {} diff --git a/quantum/tests/unit/test_routerserviceinsertion.py b/quantum/tests/unit/test_routerserviceinsertion.py new file mode 100644 index 00000000000..633629f27ec --- /dev/null +++ b/quantum/tests/unit/test_routerserviceinsertion.py @@ -0,0 +1,435 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 VMware, Inc. 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 unittest2 as unittest +import webob.exc as webexc + +import quantum +from quantum.api import extensions +from quantum.api.v2 import router +from quantum.common import config +from quantum.db.loadbalancer import loadbalancer_db as lb_db +from quantum.db import db_base_plugin_v2 +from quantum.db import l3_db +from quantum.db import routedserviceinsertion_db as rsi_db +from quantum.db import routerservicetype_db as rst_db +from quantum.db import servicetype_db as st_db +from quantum.extensions import routedserviceinsertion as rsi +from quantum.extensions import routerservicetype as rst +from quantum.openstack.common import cfg +from quantum.plugins.common import constants +from quantum.tests.unit import test_api_v2 +from quantum.tests.unit import testlib_api +from quantum import wsgi + +_uuid = test_api_v2._uuid +_get_path = test_api_v2._get_path +extensions_path = ':'.join(quantum.extensions.__path__) + + +class RouterServiceInsertionTestPlugin( + rst_db.RouterServiceTypeDbMixin, + rsi_db.RoutedServiceInsertionDbMixin, + st_db.ServiceTypeManager, + lb_db.LoadBalancerPluginDb, + l3_db.L3_NAT_db_mixin, + db_base_plugin_v2.QuantumDbPluginV2): + + supported_extension_aliases = [ + "router", "router-service-type", "routed-service-insertion", + "service-type", "lbaas" + ] + + def create_router(self, context, router): + with context.session.begin(subtransactions=True): + r = super(RouterServiceInsertionTestPlugin, self).create_router( + context, router) + service_type_id = router['router'].get(rst.SERVICE_TYPE_ID) + if service_type_id is not None: + r[rst.SERVICE_TYPE_ID] = service_type_id + self._process_create_router_service_type_id( + context, r) + return r + + def get_router(self, context, id, fields=None): + with context.session.begin(subtransactions=True): + r = super(RouterServiceInsertionTestPlugin, self).get_router( + context, id, fields) + rsbind = self._get_router_service_type_id_binding(context, id) + if rsbind: + r[rst.SERVICE_TYPE_ID] = rsbind['service_type_id'] + return r + + def delete_router(self, context, id): + with context.session.begin(subtransactions=True): + super(RouterServiceInsertionTestPlugin, self).delete_router( + context, id) + rsbind = self._get_router_service_type_id_binding(context, id) + if rsbind: + raise Exception('Router service-type binding is not deleted') + + def create_resource(self, res, context, resource, model): + with context.session.begin(subtransactions=True): + method_name = "create_{0}".format(res) + method = getattr(super(RouterServiceInsertionTestPlugin, self), + method_name) + o = method(context, resource) + router_id = resource[res].get(rsi.ROUTER_ID) + if router_id is not None: + o[rsi.ROUTER_ID] = router_id + self._process_create_resource_router_id( + context, o, model) + return o + + def get_resource(self, res, context, id, fields, model): + method_name = "get_{0}".format(res) + method = getattr(super(RouterServiceInsertionTestPlugin, self), + method_name) + o = method(context, id, fields) + if fields is None or rsi.ROUTER_ID in fields: + rsbind = self._get_resource_router_id_binding( + context, id, model) + if rsbind: + o[rsi.ROUTER_ID] = rsbind['router_id'] + return o + + def delete_resource(self, res, context, id, model): + method_name = "delete_{0}".format(res) + with context.session.begin(subtransactions=True): + method = getattr(super(RouterServiceInsertionTestPlugin, self), + method_name) + method(context, id) + self._delete_resource_router_id_binding(context, id, model) + if self._get_resource_router_id_binding(context, id, model): + raise Exception("{0}-router binding is not deleted".format(res)) + + def create_pool(self, context, pool): + return self.create_resource('pool', context, pool, lb_db.Pool) + + def get_pool(self, context, id, fields=None): + return self.get_resource('pool', context, id, fields, lb_db.Pool) + + def delete_pool(self, context, id): + return self.delete_resource('pool', context, id, lb_db.Pool) + + def create_health_monitor(self, context, health_monitor): + return self.create_resource('health_monitor', context, health_monitor, + lb_db.HealthMonitor) + + def get_health_monitor(self, context, id, fields=None): + return self.get_resource('health_monitor', context, id, fields, + lb_db.HealthMonitor) + + def delete_health_monitor(self, context, id): + return self.delete_resource('health_monitor', context, id, + lb_db.HealthMonitor) + + def create_vip(self, context, vip): + return self.create_resource('vip', context, vip, lb_db.Vip) + + def get_vip(self, context, id, fields=None): + return self.get_resource( + 'vip', context, id, fields, lb_db.Vip) + + def delete_vip(self, context, id): + return self.delete_resource('vip', context, id, lb_db.Vip) + + def stats(self, context, pool_id): + pass + + +class RouterServiceInsertionTestCase(unittest.TestCase): + def setUp(self): + plugin = ( + "quantum.tests.unit.test_routerserviceinsertion." + "RouterServiceInsertionTestPlugin" + ) + + # point config file to: quantum/tests/etc/quantum.conf.test + args = ['--config-file', test_api_v2.etcdir('quantum.conf.test')] + config.parse(args=args) + + #just stubbing core plugin with LoadBalancer plugin + cfg.CONF.set_override('core_plugin', plugin) + cfg.CONF.set_override('service_plugins', [plugin]) + cfg.CONF.set_override('quota_router', -1, group='QUOTAS') + + # Ensure 'stale' patched copies of the plugin are never returned + quantum.manager.QuantumManager._instance = None + # Ensure existing ExtensionManager is not used + + ext_mgr = extensions.PluginAwareExtensionManager( + extensions_path, + {constants.LOADBALANCER: RouterServiceInsertionTestPlugin()} + ) + extensions.PluginAwareExtensionManager._instance = ext_mgr + router.APIRouter() + + app = config.load_paste_app('extensions_test_app') + self._api = extensions.ExtensionMiddleware(app, ext_mgr=ext_mgr) + + self._tenant_id = "8c70909f-b081-452d-872b-df48e6c355d1" + + res = self._do_request('GET', _get_path('service-types')) + self._service_type_id = res['service_types'][0]['id'] + + def tearDown(self): + self._api = None + cfg.CONF.reset() + + def _do_request(self, method, path, data=None, params=None, action=None): + content_type = 'application/json' + body = None + if data is not None: # empty dict is valid + body = wsgi.Serializer().serialize(data, content_type) + + req = testlib_api.create_request( + path, body, content_type, + method, query_string=params) + res = req.get_response(self._api) + if res.status_code >= 400: + raise webexc.HTTPClientError(detail=res.body, code=res.status_code) + if res.status_code != webexc.HTTPNoContent.code: + return res.json + + def _router_create(self, service_type_id=None): + data = { + "router": { + "tenant_id": self._tenant_id, + "name": "test", + "admin_state_up": True, + "service_type_id": service_type_id, + } + } + + res = self._do_request('POST', _get_path('routers'), data) + return res['router'] + + def test_router_create_no_service_type_id(self): + router = self._router_create() + self.assertEqual(router.get('service_type_id'), None) + + def test_router_create_with_service_type_id(self): + router = self._router_create(self._service_type_id) + self.assertEqual(router['service_type_id'], self._service_type_id) + + def test_router_get(self): + router = self._router_create(self._service_type_id) + res = self._do_request('GET', + _get_path('routers/{0}'.format(router['id']))) + self.assertEqual(res['router']['service_type_id'], + self._service_type_id) + + def _test_router_update(self, update_service_type_id): + router = self._router_create(self._service_type_id) + router_id = router['id'] + new_name = _uuid() + data = { + "router": { + "name": new_name, + "admin_state_up": router['admin_state_up'], + } + } + if update_service_type_id: + data["router"]["service_type_id"] = _uuid() + with self.assertRaises(webexc.HTTPClientError) as ctx_manager: + res = self._do_request( + 'PUT', _get_path('routers/{0}'.format(router_id)), data) + self.assertEqual(ctx_manager.exception.code, 400) + else: + res = self._do_request( + 'PUT', _get_path('routers/{0}'.format(router_id)), data) + res = self._do_request( + 'GET', _get_path('routers/{0}'.format(router['id']))) + self.assertEqual(res['router']['name'], new_name) + + def test_router_update_with_service_type_id(self): + self._test_router_update(True) + + def test_router_update_without_service_type_id(self): + self._test_router_update(False) + + def test_router_delete(self): + router = self._router_create(self._service_type_id) + self._do_request( + 'DELETE', _get_path('routers/{0}'.format(router['id']))) + + def _test_lb_setup(self): + self._subnet_id = _uuid() + router = self._router_create(self._service_type_id) + self._router_id = router['id'] + + def _test_pool_setup(self): + self._test_lb_setup() + + def _test_health_monitor_setup(self): + self._test_lb_setup() + + def _test_vip_setup(self): + self._test_pool_setup() + pool = self._pool_create(self._router_id) + self._pool_id = pool['id'] + + def _create_resource(self, res, data): + resp = self._do_request('POST', _get_path('lb/{0}s'.format(res)), data) + return resp[res] + + def _pool_create(self, router_id=None): + data = { + "pool": { + "tenant_id": self._tenant_id, + "name": "test", + "protocol": "HTTP", + "subnet_id": self._subnet_id, + "lb_method": "ROUND_ROBIN", + "router_id": router_id + } + } + + return self._create_resource('pool', data) + + def _pool_update_attrs(self, pool): + uattr = {} + fields = [ + 'name', 'description', 'lb_method', + 'health_monitors', 'admin_state_up' + ] + for field in fields: + uattr[field] = pool[field] + return uattr + + def _health_monitor_create(self, router_id=None): + data = { + "health_monitor": { + "tenant_id": self._tenant_id, + "type": "HTTP", + "delay": 1, + "timeout": 1, + "max_retries": 1, + "router_id": router_id + } + } + + return self._create_resource('health_monitor', data) + + def _health_monitor_update_attrs(self, hm): + uattr = {} + fields = ['delay', 'timeout', 'max_retries'] + for field in fields: + uattr[field] = hm[field] + return uattr + + def _vip_create(self, router_id=None): + data = { + "vip": { + "tenant_id": self._tenant_id, + "name": "test", + "protocol": "HTTP", + "port": 80, + "subnet_id": self._subnet_id, + "pool_id": self._pool_id, + "address": "192.168.1.101", + "connection_limit": 100, + "admin_state_up": True, + "router_id": router_id + } + } + + return self._create_resource('vip', data) + + def _vip_update_attrs(self, vip): + uattr = {} + fields = [ + 'name', 'description', 'pool_id', 'connection_limit', + 'admin_state_up' + ] + for field in fields: + uattr[field] = vip[field] + return uattr + + def _test_resource_create(self, res): + getattr(self, "_test_{0}_setup".format(res))() + obj = getattr(self, "_{0}_create".format(res))() + obj = getattr(self, "_{0}_create".format(res))(self._router_id) + self.assertEqual(obj['router_id'], self._router_id) + + def _test_resource_update(self, res, update_router_id, + update_attr, update_value): + getattr(self, "_test_{0}_setup".format(res))() + obj = getattr(self, "_{0}_create".format(res))(self._router_id) + uattrs = getattr(self, "_{0}_update_attrs".format(res))(obj) + uattrs[update_attr] = update_value + data = {res: uattrs} + if update_router_id: + uattrs['router_id'] = self._router_id + with self.assertRaises(webexc.HTTPClientError) as ctx_manager: + newobj = self._do_request( + 'PUT', + _get_path('lb/{0}s/{1}'.format(res, obj['id'])), data) + self.assertEqual(ctx_manager.exception.code, 400) + else: + newobj = self._do_request( + 'PUT', + _get_path('lb/{0}s/{1}'.format(res, obj['id'])), data) + updated = self._do_request( + 'GET', + _get_path('lb/{0}s/{1}'.format(res, obj['id']))) + self.assertEqual(updated[res][update_attr], update_value) + + def _test_resource_delete(self, res): + getattr(self, "_test_{0}_setup".format(res))() + obj = getattr(self, "_{0}_create".format(res))() + self._do_request( + 'DELETE', _get_path('lb/{0}s/{1}'.format(res, obj['id']))) + obj = getattr(self, "_{0}_create".format(res))(self._router_id) + self._do_request( + 'DELETE', _get_path('lb/{0}s/{1}'.format(res, obj['id']))) + + def test_pool_create(self): + self._test_resource_create('pool') + + def test_pool_update_with_router_id(self): + self._test_resource_update('pool', True, 'name', _uuid()) + + def test_pool_update_without_router_id(self): + self._test_resource_update('pool', False, 'name', _uuid()) + + def test_pool_delete(self): + self._test_resource_delete('pool') + + def test_health_monitor_create(self): + self._test_resource_create('health_monitor') + + def test_health_monitor_update_with_router_id(self): + self._test_resource_update('health_monitor', True, 'timeout', 2) + + def test_health_monitor_update_without_router_id(self): + self._test_resource_update('health_monitor', False, 'timeout', 2) + + def test_health_monitor_delete(self): + self._test_resource_delete('health_monitor') + + def test_vip_create(self): + self._test_resource_create('vip') + + def test_vip_update_with_router_id(self): + self._test_resource_update('vip', True, 'name', _uuid()) + + def test_vip_update_without_router_id(self): + self._test_resource_update('vip', False, 'name', _uuid()) + + def test_vip_delete(self): + self._test_resource_delete('vip')