c662acf6e0
In LBaaS legacy mode we use the exclusive router edge as platform for neutron LBaaS. The following patch addresses two issues in this mode: - In the legacy code, LBaaS driver maintained a DFW section which allows traffic between the LB and its members. This code wasn't added when we added the legacy mode. - The original code had a bug which failed to cleanup these DFW rules when a pool or members were deleted. The patch adds an admin utility which cleans up such stale DFW rules. Change-Id: I1c95ec6292e6cf50641581a65cbb4bdf8942aa8f
393 lines
14 KiB
Python
393 lines
14 KiB
Python
# 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
|