From 52f0750d80e8742efc253b48c6bc121ae9853cc1 Mon Sep 17 00:00:00 2001 From: berlin Date: Fri, 29 Nov 2013 17:45:33 +0800 Subject: [PATCH] Add session persistence support for NVP advanced LBaaS Change-Id: I2042894755cdaf54b2bc39e58028746aa7c1e8ea Closes-Bug: #1256243 --- neutron/plugins/vmware/plugins/service.py | 4 +- .../vshield/edge_loadbalancer_driver.py | 109 ++++++++++++++---- neutron/plugins/vmware/vshield/vcns.py | 6 + .../tests/unit/vmware/vshield/fake_vcns.py | 11 ++ .../unit/vmware/vshield/test_lbaas_plugin.py | 12 ++ .../vshield/test_loadbalancer_driver.py | 91 +++++++++++++++ 6 files changed, 211 insertions(+), 22 deletions(-) diff --git a/neutron/plugins/vmware/plugins/service.py b/neutron/plugins/vmware/plugins/service.py index 786ea5d6c..8414600ad 100644 --- a/neutron/plugins/vmware/plugins/service.py +++ b/neutron/plugins/vmware/plugins/service.py @@ -1240,6 +1240,8 @@ class NsxAdvancedPlugin(sr_db.ServiceRouter_mixin, def update_vip(self, context, id, vip): edge_id = self._get_edge_id_by_vip_id(context, id) old_vip = self.get_vip(context, id) + session_persistence_update = bool( + vip['vip'].get('session_persistence')) vip['vip']['status'] = service_constants.PENDING_UPDATE v = super(NsxAdvancedPlugin, self).update_vip(context, id, vip) v[rsi.ROUTER_ID] = self._get_resource_router_id_binding( @@ -1262,7 +1264,7 @@ class NsxAdvancedPlugin(sr_db.ServiceRouter_mixin, self.vcns_driver.create_vip(context, edge_id, v) return v try: - self.vcns_driver.update_vip(context, v) + self.vcns_driver.update_vip(context, v, session_persistence_update) except Exception: with excutils.save_and_reraise_exception(): LOG.exception(_("Failed to update vip with id: %s!"), id) diff --git a/neutron/plugins/vmware/vshield/edge_loadbalancer_driver.py b/neutron/plugins/vmware/vshield/edge_loadbalancer_driver.py index fb5fa248a..63f0bccd9 100644 --- a/neutron/plugins/vmware/vshield/edge_loadbalancer_driver.py +++ b/neutron/plugins/vmware/vshield/edge_loadbalancer_driver.py @@ -37,6 +37,13 @@ PROTOCOL_MAP = { lb_constants.PROTOCOL_HTTP: 'http', lb_constants.PROTOCOL_HTTPS: 'tcp' } +SESSION_PERSISTENCE_METHOD_MAP = { + lb_constants.SESSION_PERSISTENCE_SOURCE_IP: 'sourceip', + lb_constants.SESSION_PERSISTENCE_APP_COOKIE: 'cookie', + lb_constants.SESSION_PERSISTENCE_HTTP_COOKIE: 'cookie'} +SESSION_PERSISTENCE_COOKIE_MAP = { + lb_constants.SESSION_PERSISTENCE_APP_COOKIE: 'app', + lb_constants.SESSION_PERSISTENCE_HTTP_COOKIE: 'insert'} class EdgeLbDriver(): @@ -51,9 +58,11 @@ class EdgeLbDriver(): pool_vseid = poolid_map['pool_vseid'] return { 'name': vip.get('name'), + 'description': vip.get('description'), 'ipAddress': vip.get('address'), 'protocol': vip.get('protocol'), 'port': vip.get('protocol_port'), + 'connectionLimit': max(0, vip.get('connection_limit')), 'defaultPoolId': pool_vseid, 'applicationProfileId': app_profileid } @@ -75,15 +84,18 @@ class EdgeLbDriver(): def _convert_lb_pool(self, context, edge_id, pool, members): vsepool = { 'name': pool.get('name'), + 'description': pool.get('description'), 'algorithm': BALANCE_MAP.get( pool.get('lb_method'), 'round-robin'), + 'transparent': True, 'member': [], 'monitorId': [] } for member in members: vsepool['member'].append({ 'ipAddress': member['address'], + 'weight': member['weight'], 'port': member['protocol_port'] }) ##TODO(linb) right now, vse only accept at most one monitor per pool @@ -121,23 +133,45 @@ class EdgeLbDriver(): 'id': monitor_vse['name'] } - def _convert_app_profile(self, name, app_profile): - #TODO(linb): convert the session_persistence to - #corresponding app_profile - return { - "insertXForwardedFor": False, - "name": name, - "persistence": { - "method": "sourceip" - }, - "serverSslEnabled": False, - "sslPassthrough": False, - "template": "HTTP" + def _convert_app_profile(self, name, sess_persist, protocol): + vcns_app_profile = { + 'insertXForwardedFor': False, + 'name': name, + 'serverSslEnabled': False, + 'sslPassthrough': False, + 'template': protocol, } + # Since SSL Termination is not supported right now, so just use + # sslPassthrough mehtod if the protocol is HTTPS. + if protocol == lb_constants.PROTOCOL_HTTPS: + vcns_app_profile['sslPassthrough'] = True + + if sess_persist.get('type'): + # If protocol is not HTTP, only sourceip is supported + if (protocol != lb_constants.PROTOCOL_HTTP and + sess_persist['type'] != ( + lb_constants.SESSION_PERSISTENCE_SOURCE_IP)): + msg = (_("Invalid %(protocol)s persistence method: %(type)s") % + {'protocol': protocol, + 'type': sess_persist['type']}) + raise vcns_exc.VcnsBadRequest(resource='sess_persist', msg=msg) + persistence = { + 'method': SESSION_PERSISTENCE_METHOD_MAP.get( + sess_persist['type'])} + if sess_persist['type'] in SESSION_PERSISTENCE_COOKIE_MAP: + if sess_persist.get('cookie_name'): + persistence['cookieName'] = sess_persist['cookie_name'] + else: + persistence['cookieName'] = 'default_cookie_name' + persistence['cookieMode'] = SESSION_PERSISTENCE_COOKIE_MAP.get( + sess_persist['type']) + vcns_app_profile['persistence'] = persistence + return vcns_app_profile def create_vip(self, context, edge_id, vip): app_profile = self._convert_app_profile( - vip['name'], vip.get('session_persistence')) + vip['name'], (vip.get('session_persistence') or {}), + vip.get('protocol')) try: header, response = self.vcns.create_app_profile( edge_id, app_profile) @@ -156,6 +190,7 @@ class EdgeLbDriver(): with excutils.save_and_reraise_exception(): LOG.exception(_("Failed to create vip on vshield edge: %s"), edge_id) + self.vcns.delete_app_profile(edge_id, app_profileid) objuri = header['location'] vip_vseid = objuri[objuri.rfind("/") + 1:] @@ -168,6 +203,18 @@ class EdgeLbDriver(): } vcns_db.add_vcns_edge_vip_binding(context.session, map_info) + def _get_vip_binding(self, session, id): + vip_binding = vcns_db.get_vcns_edge_vip_binding(session, id) + if not vip_binding: + msg = (_("vip_binding not found with id: %(id)s " + "edge_id: %(edge_id)s") % { + 'id': id, + 'edge_id': vip_binding[vcns_const.EDGE_ID]}) + LOG.error(msg) + raise vcns_exc.VcnsNotFound( + resource='router_service_binding', msg=msg) + return vip_binding + def get_vip(self, context, id): vip_binding = vcns_db.get_vcns_edge_vip_binding(context.session, id) edge_id = vip_binding[vcns_const.EDGE_ID] @@ -179,33 +226,53 @@ class EdgeLbDriver(): LOG.exception(_("Failed to get vip on edge")) return self._restore_lb_vip(context, edge_id, response) - def update_vip(self, context, vip): - vip_binding = vcns_db.get_vcns_edge_vip_binding( - context.session, vip['id']) + def update_vip(self, context, vip, session_persistence_update=True): + vip_binding = self._get_vip_binding(context.session, vip['id']) edge_id = vip_binding[vcns_const.EDGE_ID] vip_vseid = vip_binding.get('vip_vseid') - app_profileid = vip_binding.get('app_profileid') + if session_persistence_update: + app_profileid = vip_binding.get('app_profileid') + app_profile = self._convert_app_profile( + vip['name'], vip.get('session_persistence', {}), + vip.get('protocol')) + try: + self.vcns.update_app_profile( + edge_id, app_profileid, app_profile) + except vcns_exc.VcnsApiException: + with excutils.save_and_reraise_exception(): + LOG.exception(_("Failed to update app profile on " + "edge: %s") % edge_id) vip_new = self._convert_lb_vip(context, edge_id, vip, app_profileid) try: self.vcns.update_vip(edge_id, vip_vseid, vip_new) except vcns_exc.VcnsApiException: with excutils.save_and_reraise_exception(): - LOG.exception(_("Failed to update vip on edge: %s"), edge_id) + LOG.exception(_("Failed to update vip on edge: %s") % edge_id) def delete_vip(self, context, id): - vip_binding = vcns_db.get_vcns_edge_vip_binding( - context.session, id) + vip_binding = self._get_vip_binding(context.session, id) edge_id = vip_binding[vcns_const.EDGE_ID] vip_vseid = vip_binding['vip_vseid'] app_profileid = vip_binding['app_profileid'] try: self.vcns.delete_vip(edge_id, vip_vseid) + except vcns_exc.ResourceNotFound: + LOG.exception(_("vip not found on edge: %s") % edge_id) + except vcns_exc.VcnsApiException: + with excutils.save_and_reraise_exception(): + LOG.exception(_("Failed to delete vip on edge: %s") % edge_id) + + try: self.vcns.delete_app_profile(edge_id, app_profileid) + except vcns_exc.ResourceNotFound: + LOG.exception(_("app profile not found on edge: %s") % edge_id) except vcns_exc.VcnsApiException: with excutils.save_and_reraise_exception(): - LOG.exception(_("Failed to delete vip on edge: %s"), edge_id) + LOG.exception(_("Failed to delete app profile on edge: %s") % + edge_id) + vcns_db.delete_vcns_edge_vip_binding(context.session, id) def create_pool(self, context, edge_id, pool, members): diff --git a/neutron/plugins/vmware/vshield/vcns.py b/neutron/plugins/vmware/vshield/vcns.py index 157a4405b..11f0c1e2f 100644 --- a/neutron/plugins/vmware/vshield/vcns.py +++ b/neutron/plugins/vmware/vshield/vcns.py @@ -260,6 +260,12 @@ class Vcns(object): APP_PROFILE_RESOURCE) return self.do_request(HTTP_POST, uri, app_profile) + def update_app_profile(self, edge_id, app_profileid, app_profile): + uri = self._build_uri_path( + edge_id, LOADBALANCER_SERVICE, + APP_PROFILE_RESOURCE, app_profileid) + return self.do_request(HTTP_PUT, uri, app_profile) + def delete_app_profile(self, edge_id, app_profileid): uri = self._build_uri_path( edge_id, LOADBALANCER_SERVICE, diff --git a/neutron/tests/unit/vmware/vshield/fake_vcns.py b/neutron/tests/unit/vmware/vshield/fake_vcns.py index b346aab9d..2e097e89c 100644 --- a/neutron/tests/unit/vmware/vshield/fake_vcns.py +++ b/neutron/tests/unit/vmware/vshield/fake_vcns.py @@ -510,6 +510,17 @@ class FakeVcns(object): response = "" return self.return_helper(header, response) + def update_app_profile(self, edge_id, app_profileid, app_profile): + header = {'status': 404} + response = "" + if not self._fake_app_profiles_dict.get(edge_id) or ( + not self._fake_app_profiles_dict[edge_id].get(app_profileid)): + return self.return_helper(header, response) + header = {'status': 204} + self._fake_app_profiles_dict[edge_id][app_profileid].update( + app_profile) + return self.return_helper(header, response) + def delete_app_profile(self, edge_id, app_profileid): header = {'status': 404} response = "" diff --git a/neutron/tests/unit/vmware/vshield/test_lbaas_plugin.py b/neutron/tests/unit/vmware/vshield/test_lbaas_plugin.py index 99338f600..1b1b67be9 100644 --- a/neutron/tests/unit/vmware/vshield/test_lbaas_plugin.py +++ b/neutron/tests/unit/vmware/vshield/test_lbaas_plugin.py @@ -15,6 +15,7 @@ import contextlib +import testtools from webob import exc as web_exc from neutron.api.v2 import attributes @@ -83,6 +84,8 @@ class TestLoadbalancerPlugin( self.fc2.delete_health_monitor) instance.return_value.create_app_profile.side_effect = ( self.fc2.create_app_profile) + instance.return_value.update_app_profile.side_effect = ( + self.fc2.update_app_profile) instance.return_value.delete_app_profile.side_effect = ( self.fc2.delete_app_profile) @@ -189,6 +192,15 @@ class TestLoadbalancerPlugin( expected ) + def test_create_vip_with_session_persistence(self): + self.test_create_vip(session_persistence={'type': 'HTTP_COOKIE'}) + + def test_create_vip_with_invalid_persistence_method(self): + with testtools.ExpectedException(web_exc.HTTPClientError): + self.test_create_vip( + protocol='TCP', + session_persistence={'type': 'HTTP_COOKIE'}) + def test_update_vip(self): name = 'new_vip' router_id = self._create_and_get_router() diff --git a/neutron/tests/unit/vmware/vshield/test_loadbalancer_driver.py b/neutron/tests/unit/vmware/vshield/test_loadbalancer_driver.py index 6b922e340..7ff53f324 100644 --- a/neutron/tests/unit/vmware/vshield/test_loadbalancer_driver.py +++ b/neutron/tests/unit/vmware/vshield/test_loadbalancer_driver.py @@ -22,6 +22,7 @@ from neutron.openstack.common import uuidutils from neutron.plugins.vmware.dbexts import vcns_db from neutron.plugins.vmware.vshield.common import exceptions as vcns_exc from neutron.plugins.vmware.vshield import vcns_driver +from neutron.services.loadbalancer import constants as lb_constants from neutron.tests.unit.db.loadbalancer import test_db_loadbalancer from neutron.tests.unit.vmware import get_fake_conf from neutron.tests.unit.vmware import VCNS_NAME @@ -68,6 +69,8 @@ class VcnsDriverTestCase(test_db_loadbalancer.LoadBalancerPluginDbTestCase): self.fc2.delete_health_monitor) instance.return_value.create_app_profile.side_effect = ( self.fc2.create_app_profile) + instance.return_value.update_app_profile.side_effect = ( + self.fc2.update_app_profile) instance.return_value.delete_app_profile.side_effect = ( self.fc2.delete_app_profile) self.pool_id = None @@ -106,6 +109,94 @@ class TestEdgeLbDriver(VcnsDriverTestCase): for k, v in vip_get.iteritems(): self.assertEqual(vip_create[k], v) + def test_convert_app_profile(self): + app_profile_name = 'app_profile_name' + sess_persist1 = {'type': "SOURCE_IP"} + sess_persist2 = {'type': "HTTP_COOKIE"} + sess_persist3 = {'type': "APP_COOKIE", + 'cookie_name': "app_cookie_name"} + # protocol is HTTP and type is SOURCE_IP + expect_vcns_app_profile1 = { + 'insertXForwardedFor': False, + 'name': app_profile_name, + 'serverSslEnabled': False, + 'sslPassthrough': False, + 'template': lb_constants.PROTOCOL_HTTP, + 'persistence': {'method': 'sourceip'}} + vcns_app_profile = self.driver._convert_app_profile( + app_profile_name, sess_persist1, lb_constants.PROTOCOL_HTTP) + for k, v in expect_vcns_app_profile1.iteritems(): + self.assertEqual(vcns_app_profile[k], v) + # protocol is HTTP and type is HTTP_COOKIE and APP_COOKIE + expect_vcns_app_profile2 = { + 'insertXForwardedFor': False, + 'name': app_profile_name, + 'serverSslEnabled': False, + 'sslPassthrough': False, + 'template': lb_constants.PROTOCOL_HTTP, + 'persistence': {'method': 'cookie', + 'cookieName': 'default_cookie_name', + 'cookieMode': 'insert'}} + vcns_app_profile = self.driver._convert_app_profile( + app_profile_name, sess_persist2, lb_constants.PROTOCOL_HTTP) + for k, v in expect_vcns_app_profile2.iteritems(): + self.assertEqual(vcns_app_profile[k], v) + expect_vcns_app_profile3 = { + 'insertXForwardedFor': False, + 'name': app_profile_name, + 'serverSslEnabled': False, + 'sslPassthrough': False, + 'template': lb_constants.PROTOCOL_HTTP, + 'persistence': {'method': 'cookie', + 'cookieName': sess_persist3['cookie_name'], + 'cookieMode': 'app'}} + vcns_app_profile = self.driver._convert_app_profile( + app_profile_name, sess_persist3, lb_constants.PROTOCOL_HTTP) + for k, v in expect_vcns_app_profile3.iteritems(): + self.assertEqual(vcns_app_profile[k], v) + # protocol is HTTPS and type is SOURCE_IP + expect_vcns_app_profile1 = { + 'insertXForwardedFor': False, + 'name': app_profile_name, + 'serverSslEnabled': False, + 'sslPassthrough': True, + 'template': lb_constants.PROTOCOL_HTTPS, + 'persistence': {'method': 'sourceip'}} + vcns_app_profile = self.driver._convert_app_profile( + app_profile_name, sess_persist1, lb_constants.PROTOCOL_HTTPS) + for k, v in expect_vcns_app_profile1.iteritems(): + self.assertEqual(vcns_app_profile[k], v) + # protocol is HTTPS, and type isn't SOURCE_IP + self.assertRaises(vcns_exc.VcnsBadRequest, + self.driver._convert_app_profile, + app_profile_name, + sess_persist2, lb_constants.PROTOCOL_HTTPS) + self.assertRaises(vcns_exc.VcnsBadRequest, + self.driver._convert_app_profile, + app_profile_name, + sess_persist3, lb_constants.PROTOCOL_HTTPS) + # protocol is TCP and type is SOURCE_IP + expect_vcns_app_profile1 = { + 'insertXForwardedFor': False, + 'name': app_profile_name, + 'serverSslEnabled': False, + 'sslPassthrough': False, + 'template': lb_constants.PROTOCOL_TCP, + 'persistence': {'method': 'sourceip'}} + vcns_app_profile = self.driver._convert_app_profile( + app_profile_name, sess_persist1, lb_constants.PROTOCOL_TCP) + for k, v in expect_vcns_app_profile1.iteritems(): + self.assertEqual(vcns_app_profile[k], v) + # protocol is TCP, and type isn't SOURCE_IP + self.assertRaises(vcns_exc.VcnsBadRequest, + self.driver._convert_app_profile, + app_profile_name, + sess_persist2, lb_constants.PROTOCOL_TCP) + self.assertRaises(vcns_exc.VcnsBadRequest, + self.driver._convert_app_profile, + app_profile_name, + sess_persist3, lb_constants.PROTOCOL_TCP) + def test_update_vip(self): ctx = context.get_admin_context() with self.pool(no_delete=True) as pool: -- 2.45.2