Add session persistence support for NVP advanced LBaaS
Change-Id: I2042894755cdaf54b2bc39e58028746aa7c1e8ea Closes-Bug: #1256243
This commit is contained in:
parent
6945f525f4
commit
19f299d6f7
@ -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)
|
||||
|
@ -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)
|
||||
self.vcns.delete_app_profile(edge_id, app_profileid)
|
||||
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)
|
||||
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 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):
|
||||
|
@ -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,
|
||||
|
@ -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 = ""
|
||||
|
@ -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()
|
||||
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user