NSXv: Remove router dependency for LBaaS

Use of exclusive router edge appliance to host LBaaS instances is
inconsistent with upstream behavior.
This patch allocates a dedicated edge appliance per each LB instance.

Change-Id: I699df5289e339c6b335d6e60928faca39a0dcd59
This commit is contained in:
Kobi Samoray 2017-02-02 16:30:50 +02:00
parent fdda07fb32
commit e2016b2027
5 changed files with 136 additions and 48 deletions

View File

@ -23,6 +23,7 @@ DHCP_EDGE_PREFIX = 'dhcp-'
ROUTER_EDGE_PREFIX = 'router-'
PLR_EDGE_PREFIX = 'plr-'
BACKUP_ROUTER_PREFIX = 'backup-'
LB_EDGE_PREFIX = 'lb-'
EDGE_NAME_LEN = 20
# Interface
@ -59,7 +60,8 @@ SUFFIX_LENGTH = 8
#Edge size
SERVICE_SIZE_MAPPING = {
'router': nsxv_constants.COMPACT,
'dhcp': nsxv_constants.COMPACT
'dhcp': nsxv_constants.COMPACT,
'lb': nsxv_constants.COMPACT
}
ALLOWED_EDGE_SIZES = (nsxv_constants.COMPACT,
nsxv_constants.LARGE,

View File

@ -794,6 +794,15 @@ class EdgeManager(object):
appliance_size=vcns_const.SERVICE_SIZE_MAPPING['dhcp'],
availability_zone=availability_zone)
def allocate_lb_edge_appliance(
self, context, resource_id, availability_zone,
appliance_size=vcns_const.SERVICE_SIZE_MAPPING['lb']):
return self._allocate_edge_appliance(
context, resource_id, resource_id,
appliance_size=appliance_size,
availability_zone=availability_zone)
def _free_dhcp_edge_appliance(self, context, network_id):
router_id = (vcns_const.DHCP_EDGE_PREFIX + network_id)[:36]
@ -835,6 +844,8 @@ class EdgeManager(object):
self.update_syslog_by_flavor(context,
lrouter['id'], lrouter['flavor_id'], edge_id)
return edge_id
def delete_lrouter(self, context, router_id, dist=False):
self._free_edge_appliance(context, router_id)

View File

@ -15,11 +15,12 @@
import netaddr
from neutron_lib import constants
from neutron_lib import exceptions as n_exc
from vmware_nsx._i18n import _
from vmware_nsx.common import locking
from vmware_nsx.db import nsxv_db
from vmware_nsx.plugins.nsx_v.vshield import edge_utils
MEMBER_ID_PFX = 'member-'
@ -28,25 +29,49 @@ def get_member_id(member_id):
return MEMBER_ID_PFX + member_id
def get_lbaas_edge_id_for_subnet(context, plugin, subnet_id, tenant_id):
"""
Grab the id of an Edge appliance that is connected to subnet_id.
"""
subnet = plugin.get_subnet(context, subnet_id)
net_id = subnet.get('network_id')
filters = {'network_id': [net_id],
'device_owner': ['network:router_interface'],
'tenant_id': [tenant_id]}
attached_routers = plugin.get_ports(context.elevated(),
filters=filters,
fields=['device_id'])
def get_lb_resource_id(lb_id):
return ('lbaas-' + lb_id)[:36]
for attached_router in attached_routers:
router = plugin.get_router(context, attached_router['device_id'])
if router.get('router_type') == 'exclusive':
rtr_bindings = nsxv_db.get_nsxv_router_binding(context.session,
router['id'])
return rtr_bindings['edge_id']
def get_lbaas_edge_id(context, plugin, lb_id, vip_addr, subnet_id, tenant_id):
subnet = plugin.get_subnet(context, subnet_id)
network_id = subnet.get('network_id')
availability_zone = plugin.get_network_az(context, network_id)
resource_id = get_lb_resource_id(lb_id)
edge_id = plugin.edge_manager.allocate_lb_edge_appliance(
context, resource_id, availability_zone=availability_zone)
port_dict = {'name': 'lb_if-' + lb_id,
'admin_state_up': True,
'network_id': network_id,
'tenant_id': tenant_id,
'fixed_ips': [{'subnet_id': subnet['id']}],
'device_owner': constants.DEVICE_OWNER_NEUTRON_PREFIX + 'LB',
'device_id': lb_id,
'mac_address': constants.ATTR_NOT_SPECIFIED
}
port = plugin.base_create_port(context, {'port': port_dict})
ip_addr = port['fixed_ips'][0]['ip_address']
net = netaddr.IPNetwork(subnet['cidr'])
address_groups = [{'primaryAddress': ip_addr,
'subnetPrefixLength': str(net.prefixlen),
'subnetMask': str(net.netmask),
'secondaryAddresses': {
'type': 'secondary_addresses',
'ipAddress': [vip_addr]}
}]
edge_utils.update_internal_interface(
plugin.nsx_v, context, resource_id,
network_id, address_groups)
gw_ip = subnet.get('gateway_ip')
if gw_ip:
plugin.nsx_v.update_routes(edge_id, gw_ip, [])
return edge_id
def find_address_in_same_subnet(ip_addr, address_groups):

View File

@ -13,6 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
from neutron.callbacks import events
from neutron.callbacks import registry
from neutron.callbacks import resources
from neutron_lib import constants
from neutron_lib import exceptions as n_exc
from oslo_log import helpers as log_helpers
from oslo_log import log as logging
@ -31,15 +35,20 @@ class EdgeLoadBalancerManager(base_mgr.EdgeLoadbalancerBaseManager):
@log_helpers.log_method_call
def __init__(self, vcns_driver):
super(EdgeLoadBalancerManager, self).__init__(vcns_driver)
registry.subscribe(
self._handle_subnet_gw_change,
resources.SUBNET_GATEWAY, events.AFTER_UPDATE)
@log_helpers.log_method_call
def create(self, context, lb):
edge_id = lb_common.get_lbaas_edge_id_for_subnet(
context, self.core_plugin, lb.vip_subnet_id, lb.tenant_id)
edge_id = lb_common.get_lbaas_edge_id(
context, self.core_plugin, lb.id, lb.vip_address, lb.vip_subnet_id,
lb.tenant_id)
if not edge_id:
msg = _(
'No suitable Edge found for subnet %s') % lb.vip_subnet_id
msg = _('Failed to allocate Edge on subnet %(sub)s for '
'loadbalancer %(lb)s') % {'sub': lb.vip_subnet_id,
'lb': lb.id}
raise n_exc.BadRequest(resource='edge-lbaas', msg=msg)
try:
@ -47,8 +56,6 @@ class EdgeLoadBalancerManager(base_mgr.EdgeLoadbalancerBaseManager):
context.session, edge_id):
lb_common.enable_edge_acceleration(self.vcns, edge_id)
lb_common.add_vip_as_secondary_ip(self.vcns, edge_id,
lb.vip_address)
edge_fw_rule_id = lb_common.add_vip_fw_rule(
self.vcns, edge_id, lb.id, lb.vip_address)
@ -68,23 +75,44 @@ class EdgeLoadBalancerManager(base_mgr.EdgeLoadbalancerBaseManager):
@log_helpers.log_method_call
def delete(self, context, lb):
# Discard any ports which are associated with LB
filters = {
'device_id': [lb.id],
'device_owner': [constants.DEVICE_OWNER_NEUTRON_PREFIX + 'LB']}
lb_ports = self.core_plugin.get_ports(context.elevated(),
filters=filters)
for lb_port in lb_ports:
self.core_plugin.delete_port(context.elevated(), lb_port['id'])
binding = nsxv_db.get_nsxv_lbaas_loadbalancer_binding(
context.session, lb.id)
if binding:
try:
lb_common.del_vip_fw_rule(self.vcns, binding['edge_id'],
binding['edge_fw_rule_id'])
except nsxv_exc.VcnsApiException as e:
LOG.error(_LE('Failed to delete loadbalancer %(lb)s FW rule. '
'exception is %(exc)s'), {'lb': lb.id, 'exc': e})
try:
lb_common.del_vip_as_secondary_ip(self.vcns,
binding['edge_id'],
lb.vip_address)
except Exception as e:
LOG.error(_LE('Failed to delete loadbalancer %(lb)s interface'
' IP. exception is %(exc)s'),
{'lb': lb.id, 'exc': e})
edge_binding = nsxv_db.get_nsxv_router_binding_by_edge(
context.session, binding['edge_id'])
if edge_binding:
if edge_binding['router_id'].startswith('lbaas-'):
resource_id = lb_common.get_lb_resource_id(lb.id)
self.core_plugin.edge_manager.delete_lrouter(
context, resource_id, dist=False)
else:
# Edge was created on an exclusive router with the old code
try:
lb_common.del_vip_fw_rule(
self.vcns, binding['edge_id'],
binding['edge_fw_rule_id'])
except nsxv_exc.VcnsApiException as e:
LOG.error(_LE('Failed to delete loadbalancer %(lb)s '
'FW rule. exception is %(exc)s'),
{'lb': lb.id, 'exc': e})
try:
lb_common.del_vip_as_secondary_ip(self.vcns,
binding['edge_id'],
lb.vip_address)
except Exception as e:
LOG.error(_LE('Failed to delete loadbalancer %(lb)s '
'interface IP. exception is %(exc)s'),
{'lb': lb.id, 'exc': e})
nsxv_db.del_nsxv_lbaas_loadbalancer_binding(context.session, lb.id)
self.lbv2_driver.load_balancer.successful_completion(context, lb,
@ -104,3 +132,25 @@ class EdgeLoadBalancerManager(base_mgr.EdgeLoadbalancerBaseManager):
'total_connections': 0}
return stats
def _handle_subnet_gw_change(self, *args, **kwargs):
# As the Edge appliance doesn't use DHCP, we should change the
# default gateway here when the subnet GW changes.
context = kwargs.get('context')
subnet_id = kwargs.get('subnet_id')
subnet = self.core_plugin.get_subnet(context.elevated(), subnet_id)
filters = {'fixed_ips': {'subnet_id': [subnet_id]},
'device_owner': [constants.DEVICE_OWNER_LOADBALANCERV2]}
lb_ports = self.core_plugin.get_ports(context.elevated(),
filters=filters)
if lb_ports:
for lb_port in lb_ports:
if lb_port['device_id']:
edge_bind = nsxv_db.get_nsxv_lbaas_loadbalancer_binding(
context.session, lb_port['device_id'])
edge_id = edge_bind['edge_id']
self.core_plugin.nsx_v.update_routes(
edge_id, subnet['gateway_ip'], [])

View File

@ -132,10 +132,8 @@ class TestEdgeLbaasV2Loadbalancer(BaseTestEdgeLbaasV2):
return 'load_balancer'
def test_create(self):
with mock.patch.object(lb_common, 'get_lbaas_edge_id_for_subnet'
with mock.patch.object(lb_common, 'get_lbaas_edge_id'
) as mock_get_edge, \
mock.patch.object(lb_common, 'add_vip_as_secondary_ip'
) as mock_vip_sec_ip, \
mock.patch.object(lb_common, 'add_vip_fw_rule'
) as mock_add_vip_fwr, \
mock.patch.object(lb_common, 'enable_edge_acceleration'
@ -151,9 +149,6 @@ class TestEdgeLbaasV2Loadbalancer(BaseTestEdgeLbaasV2):
self.edge_driver.loadbalancer.create(self.context, self.lb)
mock_vip_sec_ip.assert_called_with(self.edge_driver.vcns,
LB_EDGE_ID,
LB_VIP)
mock_add_vip_fwr.assert_called_with(self.edge_driver.vcns,
LB_EDGE_ID,
LB_ID,
@ -187,9 +182,14 @@ class TestEdgeLbaasV2Loadbalancer(BaseTestEdgeLbaasV2):
mock.patch.object(lb_common, 'del_vip_as_secondary_ip'
) as mock_vip_sec_ip, \
mock.patch.object(nsxv_db, 'del_nsxv_lbaas_loadbalancer_binding',
) as mock_del_binding:
) as mock_del_binding, \
mock.patch.object(self.core_plugin, 'get_ports'
) as mock_get_ports, \
mock.patch.object(nsxv_db, 'get_nsxv_router_binding_by_edge'
) as mock_get_r_binding:
mock_get_binding.return_value = LB_BINDING
mock_get_ports.return_value = []
mock_get_r_binding.return_value = {'router_id': 'xxxx'}
self.edge_driver.loadbalancer.delete(self.context, self.lb)
mock_del_fwr.assert_called_with(self.edge_driver.vcns,