diff --git a/vmware_nsx/services/lbaas/nsx_v/implementation/member_mgr.py b/vmware_nsx/services/lbaas/nsx_v/implementation/member_mgr.py index f49433f5ef..10c19d8387 100644 --- a/vmware_nsx/services/lbaas/nsx_v/implementation/member_mgr.py +++ b/vmware_nsx/services/lbaas/nsx_v/implementation/member_mgr.py @@ -45,6 +45,19 @@ class EdgeMemberManagerFromDict(base_mgr.EdgeLoadbalancerBaseManager): lb_id = member['pool']['loadbalancer']['id'] return lb_id + def _get_pool_member_ips(self, pool, operation, address): + member_ips = [member['address'] for member in pool['members']] + if operation == 'add' and address not in member_ips: + member_ips.append(address) + elif operation == 'del' and address in member_ips: + member_ips.remove(address) + return member_ips + + def _get_lbaas_fw_section_id(self): + if not self._fw_section_id: + self._fw_section_id = lb_common.get_lbaas_fw_section_id(self.vcns) + return self._fw_section_id + def create(self, context, member, completor): lb_id = self._get_pool_lb_id(member) lb_binding = nsxv_db.get_nsxv_lbaas_loadbalancer_binding( @@ -61,11 +74,10 @@ class EdgeMemberManagerFromDict(base_mgr.EdgeLoadbalancerBaseManager): raise n_exc.BadRequest(resource='edge-lbaas', msg=msg) edge_pool_id = pool_binding['edge_pool_id'] + old_lb = lb_common.is_lb_on_router_edge( + context, self.core_plugin, edge_id) with locking.LockManager.get_lock(edge_id): - if (not cfg.CONF.nsxv.use_routers_as_lbaas_platform and - not lb_common.is_lb_on_router_edge(context.elevated(), - self.core_plugin, - edge_id)): + if not cfg.CONF.nsxv.use_routers_as_lbaas_platform and not old_lb: # Verify that Edge appliance is connected to the member's # subnet (only if this is a dedicated loadbalancer edge) if not lb_common.get_lb_interface( @@ -93,6 +105,16 @@ class EdgeMemberManagerFromDict(base_mgr.EdgeLoadbalancerBaseManager): self.vcns.update_pool(edge_id, edge_pool_id, edge_pool) completor(success=True) + if old_lb: + member_ips = self._get_pool_member_ips(member['pool'], + 'add', + member['address']) + lb_common.update_pool_fw_rule( + self.vcns, member['pool_id'], + edge_id, + self._get_lbaas_fw_section_id(), + member_ips) + except nsxv_exc.VcnsApiException: with excutils.save_and_reraise_exception(): completor(success=False) @@ -150,6 +172,9 @@ class EdgeMemberManagerFromDict(base_mgr.EdgeLoadbalancerBaseManager): context.session, lb_id, member['pool_id']) edge_id = lb_binding['edge_id'] + old_lb = lb_common.is_lb_on_router_edge( + context, self.core_plugin, edge_id) + with locking.LockManager.get_lock(edge_id): if not cfg.CONF.nsxv.use_routers_as_lbaas_platform: # we should remove LB subnet interface if no members are @@ -182,6 +207,16 @@ class EdgeMemberManagerFromDict(base_mgr.EdgeLoadbalancerBaseManager): try: self.vcns.update_pool(edge_id, edge_pool_id, edge_pool) + if old_lb: + member_ips = self._get_pool_member_ips(member['pool'], + 'del', + member['address']) + lb_common.update_pool_fw_rule( + self.vcns, member['pool_id'], + edge_id, + self._get_lbaas_fw_section_id(), + member_ips) + completor(success=True) except nsxv_exc.VcnsApiException: diff --git a/vmware_nsx/services/lbaas/nsx_v/implementation/pool_mgr.py b/vmware_nsx/services/lbaas/nsx_v/implementation/pool_mgr.py index 8800a93a60..96b38ca6eb 100644 --- a/vmware_nsx/services/lbaas/nsx_v/implementation/pool_mgr.py +++ b/vmware_nsx/services/lbaas/nsx_v/implementation/pool_mgr.py @@ -34,6 +34,7 @@ class EdgePoolManagerFromDict(base_mgr.EdgeLoadbalancerBaseManager): @log_helpers.log_method_call def __init__(self, vcns_driver): super(EdgePoolManagerFromDict, self).__init__(vcns_driver) + self._fw_section_id = None def create(self, context, pool, completor): @@ -191,6 +192,20 @@ class EdgePoolManagerFromDict(base_mgr.EdgeLoadbalancerBaseManager): listener_mgr.update_app_profile( self.vcns, context, listener, edge_id) + old_lb = lb_common.is_lb_on_router_edge( + context, self.core_plugin, lb_binding['edge_id']) + + if old_lb: + lb_common.update_pool_fw_rule(self.vcns, pool['id'], + edge_id, + self._get_lbaas_fw_section_id(), + []) + except nsxv_exc.VcnsApiException: completor(success=False) LOG.error('Failed to delete pool %s', pool['id']) + + def _get_lbaas_fw_section_id(self): + if not self._fw_section_id: + self._fw_section_id = lb_common.get_lbaas_fw_section_id(self.vcns) + return self._fw_section_id diff --git a/vmware_nsx/services/lbaas/nsx_v/lbaas_common.py b/vmware_nsx/services/lbaas/nsx_v/lbaas_common.py index f2dff9e439..330c2e0f92 100644 --- a/vmware_nsx/services/lbaas/nsx_v/lbaas_common.py +++ b/vmware_nsx/services/lbaas/nsx_v/lbaas_common.py @@ -13,22 +13,25 @@ # License for the specific language governing permissions and limitations # under the License. -import netaddr +import xml.etree.ElementTree as et -from oslo_log import log as logging +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): @@ -300,6 +303,64 @@ def get_edge_ip_addresses(vcns, edge_id): 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 diff --git a/vmware_nsx/shell/admin/plugins/nsxv/resources/lbaas.py b/vmware_nsx/shell/admin/plugins/nsxv/resources/lbaas.py new file mode 100644 index 0000000000..5edfbf952f --- /dev/null +++ b/vmware_nsx/shell/admin/plugins/nsxv/resources/lbaas.py @@ -0,0 +1,80 @@ +# Copyright 2018 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 logging +import xml.etree.ElementTree as et + +from neutron_lbaas.db.loadbalancer import models as nlbaas_v2 +from neutron_lib.callbacks import registry + +from vmware_nsx.common import locking +from vmware_nsx.plugins.nsx_v.vshield import vcns as nsxv_api +from vmware_nsx.shell.admin.plugins.common import constants +from vmware_nsx.shell.admin.plugins.common.utils import output_header +from vmware_nsx.shell.admin.plugins.nsxv.resources import utils as utils +from vmware_nsx.shell.resources import Operations + +LBAAS_FW_SECTION_NAME = 'LBaaS FW Rules' +LOG = logging.getLogger(__name__) + + +@output_header +def sync_lbaas_dfw_rules(resource, event, trigger, **kwargs): + vcns = utils.get_nsxv_client() + 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'] + + if not fw_section_id: + LOG.error('No LBaaS FW Section id found') + return + + neutron_db = utils.NeutronDbClient() + pools = neutron_db.context.session.query(nlbaas_v2.PoolV2).all() + pool_ids = [pool['id'] for pool in pools] + + section_uri = '%s/%s/%s' % (nsxv_api.FIREWALL_PREFIX, + 'layer3sections', + fw_section_id) + + xml_section_data = vcns.get_section(section_uri) + if xml_section_data: + xml_section = xml_section_data[1] + else: + LOG.info('LBaaS XML section was not found!') + return + + section = et.fromstring(xml_section) + + for rule in section.findall('.//rule'): + if rule.find('name').text in pool_ids: + LOG.info('Rule %s found and valid', rule.find('name').text) + else: + section.remove(rule) + LOG.info('Rule %s is stale and removed', + rule.find('name').text) + + vcns.update_section(section_uri, + et.tostring(section, encoding="us-ascii"), + None) + + +registry.subscribe(sync_lbaas_dfw_rules, + constants.LBAAS, + Operations.NSX_UPDATE.value) diff --git a/vmware_nsx/shell/resources.py b/vmware_nsx/shell/resources.py index 6749cb5aca..4ced2c917c 100644 --- a/vmware_nsx/shell/resources.py +++ b/vmware_nsx/shell/resources.py @@ -215,7 +215,9 @@ nsxv_resources = { Operations.DELETE.value]), constants.BGP_NEIGHBOUR: Resource(constants.BGP_NEIGHBOUR, [Operations.CREATE.value, - Operations.DELETE.value]) + Operations.DELETE.value]), + constants.LBAAS: Resource(constants.LBAAS, + [Operations.NSX_UPDATE.value]), } 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 42f1af7b67..77a4a7fe5c 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 @@ -611,11 +611,14 @@ class TestEdgeLbaasV2Pool(BaseTestEdgeLbaasV2): ) as mock_del_pool, \ mock.patch.object(nsxv_db, 'del_nsxv_lbaas_pool_binding' ) as mock_del_binding,\ + mock.patch.object(lb_common, 'is_lb_on_router_edge' + ) as mock_lb_router, \ mock.patch.object(self.edge_driver.vcns, 'update_app_profile' ): mock_get_lb_binding.return_value = LB_BINDING mock_get_pool_binding.return_value = POOL_BINDING mock_get_listener_binding.return_value = LISTENER_BINDING + mock_lb_router.return_value = False self.edge_driver.pool.delete(self.context, self.pool) @@ -709,12 +712,15 @@ class TestEdgeLbaasV2Member(BaseTestEdgeLbaasV2): ) as mock_get_pool, \ mock.patch.object(self.core_plugin, 'get_ports' ) as mock_get_ports, \ + mock.patch.object(lb_common, 'is_lb_on_router_edge' + ) as mock_lb_router, \ mock.patch.object(lb_common, 'delete_lb_interface' ) as mock_del_lb_iface, \ mock.patch.object(self.edge_driver.vcns, 'update_pool' ) as mock_update_pool: mock_get_lb_binding.return_value = LB_BINDING mock_get_pool_binding.return_value = POOL_BINDING + mock_lb_router.return_value = False edge_pool_def = EDGE_POOL_DEF.copy() edge_pool_def['member'] = [EDGE_MEMBER_DEF] mock_get_pool.return_value = (None, edge_pool_def)