]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Add session persistence support for NVP advanced LBaaS
authorberlin <linb@vmware.com>
Fri, 29 Nov 2013 09:45:33 +0000 (17:45 +0800)
committerGerrit Code Review <review@openstack.org>
Tue, 18 Mar 2014 01:11:13 +0000 (01:11 +0000)
Change-Id: I2042894755cdaf54b2bc39e58028746aa7c1e8ea
Closes-Bug: #1256243

neutron/plugins/vmware/plugins/service.py
neutron/plugins/vmware/vshield/edge_loadbalancer_driver.py
neutron/plugins/vmware/vshield/vcns.py
neutron/tests/unit/vmware/vshield/fake_vcns.py
neutron/tests/unit/vmware/vshield/test_lbaas_plugin.py
neutron/tests/unit/vmware/vshield/test_loadbalancer_driver.py

index 786ea5d6cbc1e13f2c87f90e511591d17f5396aa..8414600ad2c1013da9d8c9612d31dc32fbac1405 100644 (file)
@@ -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)
index fb5fa248a826b2570f83d70f8c750f33be94b2e0..63f0bccd9544a24afdcb15718b6255b4087a2bd0 100644 (file)
@@ -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):
index 157a4405b610f4c8217a518f447c9e575cef1cd9..11f0c1e2f4b4c09fc579d24f2f9f6387d15c8f9b 100644 (file)
@@ -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,
index b346aab9d33136847871ca3e217c0aa5ee5ffc87..2e097e89c66537809288a9069c46626fc3835652 100644 (file)
@@ -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 = ""
index 99338f6007a1f80960cea029f3b2303212d9aa66..1b1b67be907fc20fcb5f535ec4ee211f1b1add95 100644 (file)
@@ -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()
index 6b922e340e3122b7c281125adcaf58d2b027ab81..7ff53f324837cbecdf63a4b95b76c79929d8eb5f 100644 (file)
@@ -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: