diff --git a/vmware_nsx/plugins/nsx_v/vshield/common/constants.py b/vmware_nsx/plugins/nsx_v/vshield/common/constants.py index eca7d9a8f9..91ae3f7dc2 100644 --- a/vmware_nsx/plugins/nsx_v/vshield/common/constants.py +++ b/vmware_nsx/plugins/nsx_v/vshield/common/constants.py @@ -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, diff --git a/vmware_nsx/plugins/nsx_v/vshield/edge_utils.py b/vmware_nsx/plugins/nsx_v/vshield/edge_utils.py index 9e9707e8c1..24f9ac2214 100644 --- a/vmware_nsx/plugins/nsx_v/vshield/edge_utils.py +++ b/vmware_nsx/plugins/nsx_v/vshield/edge_utils.py @@ -814,6 +814,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] @@ -855,6 +864,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) diff --git a/vmware_nsx/services/lbaas/nsx_v/lbaas_common.py b/vmware_nsx/services/lbaas/nsx_v/lbaas_common.py index 26213eb9fc..6b71c63907 100644 --- a/vmware_nsx/services/lbaas/nsx_v/lbaas_common.py +++ b/vmware_nsx/services/lbaas/nsx_v/lbaas_common.py @@ -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): diff --git a/vmware_nsx/services/lbaas/nsx_v/v2/loadbalancer_mgr.py b/vmware_nsx/services/lbaas/nsx_v/v2/loadbalancer_mgr.py index e1f4b3ba86..f6ebf969db 100644 --- a/vmware_nsx/services/lbaas/nsx_v/v2/loadbalancer_mgr.py +++ b/vmware_nsx/services/lbaas/nsx_v/v2/loadbalancer_mgr.py @@ -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'], []) diff --git a/vmware_nsx/tests/unit/nsx_v/test_edge_loadbalancer_driver_v2.py b/vmware_nsx/tests/unit/nsx_v/test_edge_loadbalancer_driver_v2.py index bdd480937f..5e41a9733c 100644 --- a/vmware_nsx/tests/unit/nsx_v/test_edge_loadbalancer_driver_v2.py +++ b/vmware_nsx/tests/unit/nsx_v/test_edge_loadbalancer_driver_v2.py @@ -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,