# Copyright 2015 VMware, Inc. # All Rights Reserved # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import xml.etree.ElementTree as et import netaddr from neutron_lib import constants from neutron_lib import exceptions as n_exc from oslo_log import log as logging 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 from vmware_nsx.plugins.nsx_v.vshield import vcns as nsxv_api LOG = logging.getLogger(__name__) MEMBER_ID_PFX = 'member-' RESOURCE_ID_PFX = 'lbaas-' LBAAS_FW_SECTION_NAME = 'LBaaS FW Rules' def get_member_id(member_id): return MEMBER_ID_PFX + member_id def get_lb_resource_id(lb_id): return (RESOURCE_ID_PFX + lb_id)[:36] 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']) 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_lb_edge_name(context, lb_id): """Look for the resource name of the edge hosting the LB. For older loadbalancers this may be a router edge """ binding = nsxv_db.get_nsxv_lbaas_loadbalancer_binding( context.session, lb_id) if binding: edge_binding = nsxv_db.get_nsxv_router_binding_by_edge( context.session, binding['edge_id']) if edge_binding: return edge_binding['router_id'] # fallback return get_lb_resource_id(lb_id) def get_lb_interface(context, plugin, lb_id, subnet_id): filters = {'fixed_ips': {'subnet_id': [subnet_id]}, 'device_id': [lb_id], 'device_owner': [constants.DEVICE_OWNER_NEUTRON_PREFIX + 'LB']} lb_ports = plugin.get_ports(context.elevated(), filters=filters) return lb_ports def create_lb_interface(context, plugin, lb_id, subnet_id, tenant_id, vip_addr=None, subnet=None): if not subnet: subnet = plugin.get_subnet(context, subnet_id) network_id = subnet.get('network_id') 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']) resource_id = get_lb_edge_name(context, lb_id) address_groups = [{'primaryAddress': ip_addr, 'subnetPrefixLength': str(net.prefixlen), 'subnetMask': str(net.netmask)}] if vip_addr: address_groups[0]['secondaryAddresses'] = { 'type': 'secondary_addresses', 'ipAddress': [vip_addr]} edge_utils.update_internal_interface( plugin.nsx_v, context, resource_id, network_id, address_groups) def delete_lb_interface(context, plugin, lb_id, subnet_id): resource_id = get_lb_edge_name(context, lb_id) subnet = plugin.get_subnet(context, subnet_id) network_id = subnet.get('network_id') lb_ports = get_lb_interface(context, plugin, lb_id, subnet_id) for lb_port in lb_ports: plugin.delete_port(context, lb_port['id']) edge_utils.delete_interface(plugin.nsx_v, context, resource_id, network_id, dist=False) def get_lbaas_edge_id(context, plugin, lb_id, vip_addr, subnet_id, tenant_id, appliance_size): subnet = plugin.get_subnet(context, subnet_id) network_id = subnet.get('network_id') availability_zone = plugin.get_network_az_by_net_id(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, appliance_size=appliance_size) create_lb_interface(context, plugin, lb_id, subnet_id, tenant_id, vip_addr=vip_addr, subnet=subnet) gw_ip = subnet.get('gateway_ip') if gw_ip or subnet['host_routes']: routes = [{'cidr': r['destination'], 'nexthop': r['nexthop']} for r in subnet['host_routes']] plugin.nsx_v.update_routes(edge_id, gw_ip, routes) return edge_id def find_address_in_same_subnet(ip_addr, address_groups): """ Lookup an address group with a matching subnet to ip_addr. If found, return address_group. """ for address_group in address_groups['addressGroups']: net_addr = '%(primaryAddress)s/%(subnetPrefixLength)s' % address_group if netaddr.IPAddress(ip_addr) in netaddr.IPNetwork(net_addr): return address_group def add_address_to_address_groups(ip_addr, address_groups): """ Add ip_addr as a secondary IP address to an address group which belongs to the same subnet. """ address_group = find_address_in_same_subnet( ip_addr, address_groups) if address_group: sec_addr = address_group.get('secondaryAddresses') if not sec_addr: sec_addr = { 'type': 'secondary_addresses', 'ipAddress': [ip_addr]} else: sec_addr['ipAddress'].append(ip_addr) address_group['secondaryAddresses'] = sec_addr return True return False def del_address_from_address_groups(ip_addr, address_groups): """ Delete ip_addr from secondary address list in address groups. """ address_group = find_address_in_same_subnet(ip_addr, address_groups) if address_group: sec_addr = address_group.get('secondaryAddresses') if sec_addr and ip_addr in sec_addr['ipAddress']: sec_addr['ipAddress'].remove(ip_addr) return True return False def vip_as_secondary_ip(vcns, edge_id, vip, handler): with locking.LockManager.get_lock(edge_id): r = vcns.get_interfaces(edge_id)[1] vnics = r.get('vnics', []) for vnic in vnics: if vnic['type'] == 'trunk': for sub_interface in vnic.get('subInterfaces', {}).get( 'subInterfaces', []): address_groups = sub_interface.get('addressGroups') if handler(vip, address_groups): vcns.update_interface(edge_id, vnic) return True else: address_groups = vnic.get('addressGroups') if handler(vip, address_groups): vcns.update_interface(edge_id, vnic) return True return False def add_vip_as_secondary_ip(vcns, edge_id, vip): """ Edge appliance requires that a VIP will be configured as a primary or a secondary IP address on an interface. To do so, we locate an interface which is connected to the same subnet that vip belongs to. This can be a regular interface, on a sub-interface on a trunk. """ if not vip_as_secondary_ip(vcns, edge_id, vip, add_address_to_address_groups): msg = _('Failed to add VIP %(vip)s as secondary IP on ' 'Edge %(edge_id)s') % {'vip': vip, 'edge_id': edge_id} raise n_exc.BadRequest(resource='edge-lbaas', msg=msg) def del_vip_as_secondary_ip(vcns, edge_id, vip): """ While removing vip, delete the secondary interface from Edge config. """ if not vip_as_secondary_ip(vcns, edge_id, vip, del_address_from_address_groups): msg = _('Failed to delete VIP %(vip)s as secondary IP on ' 'Edge %(edge_id)s') % {'vip': vip, 'edge_id': edge_id} raise n_exc.BadRequest(resource='edge-lbaas', msg=msg) def extract_resource_id(location_uri): """ Edge assigns an ID for each resource that is being created: it is postfixes the uri specified in the Location header. This ID should be used while updating/deleting this resource. """ uri_elements = location_uri.split('/') return uri_elements[-1] def set_lb_firewall_default_rule(vcns, edge_id, action): with locking.LockManager.get_lock(edge_id): vcns.update_firewall_default_policy(edge_id, {'action': action}) def add_vip_fw_rule(vcns, edge_id, vip_id, ip_address): fw_rule = { 'firewallRules': [ {'action': 'accept', 'destination': { 'ipAddress': [ip_address]}, 'enabled': True, 'name': vip_id}]} with locking.LockManager.get_lock(edge_id): h = vcns.add_firewall_rule(edge_id, fw_rule)[0] fw_rule_id = extract_resource_id(h['location']) return fw_rule_id def del_vip_fw_rule(vcns, edge_id, vip_fw_rule_id): with locking.LockManager.get_lock(edge_id): vcns.delete_firewall_rule(edge_id, vip_fw_rule_id) def get_edge_ip_addresses(vcns, edge_id): edge_ips = [] r = vcns.get_interfaces(edge_id)[1] vnics = r.get('vnics', []) for vnic in vnics: if vnic['type'] == 'trunk': for sub_interface in vnic.get('subInterfaces', {}).get( 'subInterfaces', []): address_groups = sub_interface.get('addressGroups') for address_group in address_groups['addressGroups']: edge_ips.append(address_group['primaryAddress']) else: address_groups = vnic.get('addressGroups') for address_group in address_groups['addressGroups']: edge_ips.append(address_group['primaryAddress']) return edge_ips def update_pool_fw_rule(vcns, pool_id, edge_id, section_id, member_ips): edge_ips = get_edge_ip_addresses(vcns, edge_id) with locking.LockManager.get_lock('lbaas-fw-section'): section_uri = '%s/%s/%s' % (nsxv_api.FIREWALL_PREFIX, 'layer3sections', section_id) xml_section = vcns.get_section(section_uri)[1] section = et.fromstring(xml_section) pool_rule = None for rule in section.iter('rule'): if rule.find('name').text == pool_id: pool_rule = rule if member_ips: pool_rule.find('sources').find('source').find( 'value').text = (','.join(edge_ips)) pool_rule.find('destinations').find( 'destination').find('value').text = ','.join( member_ips) else: section.remove(pool_rule) break if member_ips and pool_rule is None: pool_rule = et.SubElement(section, 'rule') et.SubElement(pool_rule, 'name').text = pool_id et.SubElement(pool_rule, 'action').text = 'allow' sources = et.SubElement(pool_rule, 'sources') sources.attrib['excluded'] = 'false' source = et.SubElement(sources, 'source') et.SubElement(source, 'type').text = 'Ipv4Address' et.SubElement(source, 'value').text = ','.join(edge_ips) destinations = et.SubElement(pool_rule, 'destinations') destinations.attrib['excluded'] = 'false' destination = et.SubElement(destinations, 'destination') et.SubElement(destination, 'type').text = 'Ipv4Address' et.SubElement(destination, 'value').text = ','.join(member_ips) vcns.update_section(section_uri, et.tostring(section, encoding="us-ascii"), None) def get_lbaas_fw_section_id(vcns): # Avoid concurrent creation of section by multiple neutron # instances with locking.LockManager.get_lock('lbaas-fw-section'): fw_section_id = vcns.get_section_id(LBAAS_FW_SECTION_NAME) if not fw_section_id: section = et.Element('section') section.attrib['name'] = LBAAS_FW_SECTION_NAME sect = vcns.create_section('ip', et.tostring(section))[1] fw_section_id = et.fromstring(sect).attrib['id'] return fw_section_id def enable_edge_acceleration(vcns, edge_id): with locking.LockManager.get_lock(edge_id): # Query the existing load balancer config in case metadata lb is set _, config = vcns.get_loadbalancer_config(edge_id) config['accelerationEnabled'] = True config['enabled'] = True config['featureType'] = 'loadbalancer_4.0' vcns.enable_service_loadbalancer(edge_id, config) def is_lb_on_router_edge(context, core_plugin, edge_id): binding = nsxv_db.get_nsxv_router_binding_by_edge( context.session, edge_id) router_id = binding['router_id'] if router_id.startswith(RESOURCE_ID_PFX): # New lbaas edge return False # verify that this is a router (and an exclusive one) try: router = core_plugin.get_router(context, router_id) if router.get('router_type') == 'exclusive': return True except Exception: pass LOG.error("Edge %(edge)s router %(rtr)s is not an lbaas edge, but also " "not an exclusive router", {'edge': edge_id, 'rtr': router_id}) return False