From 8451309333f56a83dec5dd73c9e93ea5b0c1bad1 Mon Sep 17 00:00:00 2001 From: Adit Sarfaty Date: Sun, 3 Jul 2016 15:40:02 +0300 Subject: [PATCH] NSX|v service insertion handle upgrade When the service insertion is first enabled, and a security group is created, the plugin should do 2 things to allow the user to start working with service insertion immediately: 1. Add all the current compute ports (VMs) to service insertion security group. 2. Depending on the configuration, the driver will create any->any flow classifier entry, and any->any redirect rule at the backend so all the traffic will be redirected to the security partner. DocImpact: new nsxv configuration: service_insertion_redirect_all In True the plugin will create a rule to redirect all the traffic to the security partner. Change-Id: I2d45f4db821e205ccb09f02e2579d05c938c2658 --- vmware_nsx/common/config.py | 4 + vmware_nsx/plugins/nsx_v/plugin.py | 37 ++++++++++ .../services/flowclassifier/nsx_v/driver.py | 74 +++++++++++++++++-- .../services/flowclassifier/nsx_v/utils.py | 1 + vmware_nsx/tests/unit/nsx_v/test_plugin.py | 27 +++++++ .../flowclassifier/test_nsxv_driver.py | 25 ++++++- 6 files changed, 160 insertions(+), 8 deletions(-) diff --git a/vmware_nsx/common/config.py b/vmware_nsx/common/config.py index 3834eead5c..b4ff8f11a3 100644 --- a/vmware_nsx/common/config.py +++ b/vmware_nsx/common/config.py @@ -597,6 +597,10 @@ nsxv_opts = [ help=_("(Optional) The profile id of the redirect firewall " "rules that will be used for the Service Insertion " "feature.")), + cfg.BoolOpt('service_insertion_redirect_all', default=False, + help=_("(Optional) If set to True, the plugin will create " + "a redirect rule to send all the traffic to the " + "security partner")), ] # Register the configuration options diff --git a/vmware_nsx/plugins/nsx_v/plugin.py b/vmware_nsx/plugins/nsx_v/plugin.py index 0fd83c3b4a..b37d934c1e 100644 --- a/vmware_nsx/plugins/nsx_v/plugin.py +++ b/vmware_nsx/plugins/nsx_v/plugin.py @@ -224,11 +224,48 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, self.metadata_proxy_handler = ( nsx_v_md_proxy.NsxVMetadataProxyHandler(self)) + # Service insertion driver register self._si_handler = fc_utils.NsxvServiceInsertionHandler(self) + registry.subscribe(self.add_vms_to_service_insertion, + fc_utils.SERVICE_INSERTION_RESOURCE, + events.AFTER_CREATE) def init_complete(self, resource, event, trigger, **kwargs): self.init_is_complete = True + def add_vms_to_service_insertion(self, sg_id): + def _add_vms_to_service_insertion(*args, **kwargs): + + """Adding existing VMs to the service insertion security group + + Adding all current compute ports with port security to the service + insertion security group in order to classify their traffic by the + security redirect rules + """ + sg_id = args[0] + context = n_context.get_admin_context() + filters = {'device_owner': ['compute:None']} + ports = self.get_ports(context, filters=filters) + for port in ports: + # Only add compute ports with device-id, vnic & port security + if (validators.is_attr_set(port.get(ext_vnic_idx.VNIC_INDEX)) + and validators.is_attr_set(port.get('device_id')) + and port[psec.PORTSECURITY]): + try: + vnic_idx = port[ext_vnic_idx.VNIC_INDEX] + device_id = port['device_id'] + vnic_id = self._get_port_vnic_id(vnic_idx, device_id) + self._add_member_to_security_group(sg_id, vnic_id) + except Exception as e: + LOG.info(_LI('Could not add port %(port)s to service ' + 'insertion security group. Exception ' + '%(err)s'), + {'port': port['id'], 'err': e}) + + # Doing this in a separate thread to not slow down the init process + # in case there are many compute ports + c_utils.spawn_n(_add_vms_to_service_insertion, sg_id) + def _start_rpc_listeners(self): self.conn = n_rpc.create_connection() qos_topic = resources_rpc.resource_type_versioned_topic( diff --git a/vmware_nsx/services/flowclassifier/nsx_v/driver.py b/vmware_nsx/services/flowclassifier/nsx_v/driver.py index fe2afb24d0..98537585e4 100644 --- a/vmware_nsx/services/flowclassifier/nsx_v/driver.py +++ b/vmware_nsx/services/flowclassifier/nsx_v/driver.py @@ -16,8 +16,14 @@ import xml.etree.ElementTree as et +from networking_sfc.extensions import flowclassifier from networking_sfc.services.flowclassifier.common import exceptions as exc from networking_sfc.services.flowclassifier.drivers import base as fc_driver +from neutron.callbacks import events +from neutron.callbacks import registry +from neutron.callbacks import resources +from neutron import context as n_context +from neutron import manager from oslo_config import cfg from oslo_log import helpers as log_helpers from oslo_log import log as logging @@ -26,6 +32,7 @@ from vmware_nsx._i18n import _, _LE from vmware_nsx.common import config # noqa from vmware_nsx.common import exceptions as nsx_exc from vmware_nsx.common import locking +from vmware_nsx.common import nsxv_constants from vmware_nsx.plugins.nsx_v.vshield import vcns as nsxv_api from vmware_nsx.plugins.nsx_v.vshield import vcns_driver from vmware_nsx.services.flowclassifier.nsx_v import utils as fc_utils @@ -47,9 +54,15 @@ class NsxvFlowClassifierDriver(fc_driver.FlowClassifierDriverBase): self.init_security_group() self.init_security_group_in_profile() - #TODO(asarfaty) - Add a new config for any->any redirect: - # create any->any flow classifier entry (and backed rule) - # if not exist yet + # register an event to the end of the init to handle the first upgrade + # TODO(asarfaty): This registry will call the callback on each + # spawned thread, but we need it to be called only once. + # So it should be replaces with events.BEFORE_SPAWN after approved + # in neutron + if self._is_new_security_group: + registry.subscribe(self.init_complete, + resources.PROCESS, + events.AFTER_INIT) def init_profile_id(self): """Init the service insertion profile ID @@ -78,6 +91,7 @@ class NsxvFlowClassifierDriver(fc_driver.FlowClassifierDriverBase): # check if this group exist, and create it if not. sg_name = fc_utils.SERVICE_INSERTION_SG_NAME sg_id = self._nsxv.vcns.get_security_group_id(sg_name) + self._is_new_security_group = False if not sg_id: description = ("OpenStack Service Insertion Security Group, " "managed by Neutron nsx-v plugin.") @@ -85,10 +99,7 @@ class NsxvFlowClassifierDriver(fc_driver.FlowClassifierDriverBase): "description": description}} h, sg_id = ( self._nsxv.vcns.create_security_group(sg)) - - # TODO(asarfaty) - if the security group was just created - # also add all the current compute ports with port-security - # to this security group (for upgrades scenarios) + self._is_new_security_group = True self._security_group_id = sg_id @@ -110,6 +121,55 @@ class NsxvFlowClassifierDriver(fc_driver.FlowClassifierDriverBase): self._profile_id, et.tostring(profile_binding, encoding="us-ascii")) + def init_complete(self, resource, event, trigger, **kwargs): + # This callback is called for each process. + # Until fixing it, lock is used to keep things in order + with locking.LockManager.get_lock('service_insertion_init_complete'): + if self._is_new_security_group: + # add existing VMs to the new security group + # This code must run after init is done + core_plugin = manager.NeutronManager.get_plugin() + core_plugin.add_vms_to_service_insertion( + self._security_group_id) + + # Add the first flow classifier entry + if cfg.CONF.nsxv.service_insertion_redirect_all: + self.add_any_any_redirect_rule() + + def add_any_any_redirect_rule(self): + """Add an any->any flow classifier entry + + Add 1 flow classifier entry that will redirect all the traffic to the + security partner + The user will be able to delete/change it later + """ + context = n_context.get_admin_context() + fc_plugin = manager.NeutronManager.get_service_plugins().get( + flowclassifier.FLOW_CLASSIFIER_EXT) + + # first check that there is no other flow classifier entry defined: + fcs = fc_plugin.get_flow_classifiers(context) + if len(fcs) > 0: + return + + # Create any->any rule + fc = {'name': 'redirect_all', + 'description': 'Redirect all traffic', + 'tenant_id': nsxv_constants.INTERNAL_TENANT_ID, + 'l7_parameters': {}, + 'ethertype': 'IPv4', + 'protocol': None, + 'source_port_range_min': None, + 'source_port_range_max': None, + 'destination_port_range_min': None, + 'destination_port_range_max': None, + 'source_ip_prefix': None, + 'destination_ip_prefix': None, + 'logical_source_port': None, + 'logical_destination_port': None + } + fc_plugin.create_flow_classifier(context, {'flow_classifier': fc}) + def get_redirect_fw_section_id(self): if not self._redirect_section_id: # try to find it diff --git a/vmware_nsx/services/flowclassifier/nsx_v/utils.py b/vmware_nsx/services/flowclassifier/nsx_v/utils.py index e6bed765c7..cf22219501 100644 --- a/vmware_nsx/services/flowclassifier/nsx_v/utils.py +++ b/vmware_nsx/services/flowclassifier/nsx_v/utils.py @@ -20,6 +20,7 @@ from oslo_log import log as logging LOG = logging.getLogger(__name__) SERVICE_INSERTION_SG_NAME = 'Service Insertion Security Group' +SERVICE_INSERTION_RESOURCE = 'Service Insertion' class NsxvServiceInsertionHandler(object): diff --git a/vmware_nsx/tests/unit/nsx_v/test_plugin.py b/vmware_nsx/tests/unit/nsx_v/test_plugin.py index d093d8651f..2ab4f3e07b 100644 --- a/vmware_nsx/tests/unit/nsx_v/test_plugin.py +++ b/vmware_nsx/tests/unit/nsx_v/test_plugin.py @@ -3859,6 +3859,33 @@ class TestNSXPortSecurity(test_psec.TestPortSecurity, self.fc2.remove_member_from_security_group.assert_any_call( p._si_handler.sg_id, vnic_index) + def test_service_insertion_notify(self): + # create a compute ports with/without port security + device_id = _uuid() + # create 2 compute ports with port security + port1 = self._create_compute_port('net1', device_id, True) + self._add_vnic_to_port(port1['port']['id'], False, 1) + port2 = self._create_compute_port('net2', device_id, True) + self._add_vnic_to_port(port2['port']['id'], False, 2) + # create 1 compute port without port security + port3 = self._create_compute_port('net3', device_id, False) + self._add_vnic_to_port(port3['port']['id'], True, 3) + + # init the plugin mocks + p = manager.NeutronManager.get_plugin() + self.fc2.add_member_to_security_group = ( + mock.Mock().add_member_to_security_group) + + # call the function (that should be called from the flow classifier + # driver) and verify it adds all relevant ports to the group + # Since it uses spawn_n, we should mock it. + orig_spawn = c_utils.spawn_n + c_utils.spawn_n = mock.Mock(side_effect=lambda f, x: f(x, None)) + p.add_vms_to_service_insertion(sg_id='aaa') + # back to normal + c_utils.spawn_n = orig_spawn + self.assertEqual(2, self.fc2.add_member_to_security_group.call_count) + class TestSharedRouterTestCase(L3NatTest, L3NatTestCaseBase, test_l3_plugin.L3NatTestCaseMixin, diff --git a/vmware_nsx/tests/unit/services/flowclassifier/test_nsxv_driver.py b/vmware_nsx/tests/unit/services/flowclassifier/test_nsxv_driver.py index 29e7b8cad7..f32863082f 100644 --- a/vmware_nsx/tests/unit/services/flowclassifier/test_nsxv_driver.py +++ b/vmware_nsx/tests/unit/services/flowclassifier/test_nsxv_driver.py @@ -24,6 +24,7 @@ from neutron.api import extensions as api_ext from neutron.common import config from neutron import context from neutron.extensions import portbindings +from neutron import manager from networking_sfc.db import flowclassifier_db as fdb from networking_sfc.extensions import flowclassifier @@ -84,6 +85,9 @@ class TestNsxvFlowClassifierDriver( self._profile_id = 'serviceprofile-1' cfg.CONF.set_override('service_insertion_profile_id', self._profile_id, 'nsxv') + cfg.CONF.set_override('service_insertion_redirect_all', + True, 'nsxv') + self.driver = nsx_v_driver.NsxvFlowClassifierDriver() self.driver.initialize() @@ -110,9 +114,28 @@ class TestNsxvFlowClassifierDriver( super(TestNsxvFlowClassifierDriver, self).tearDown() def test_driver_init(self): - self.assertEqual(self.driver._profile_id, self._profile_id) + self.assertEqual(self._profile_id, self.driver._profile_id) self.assertEqual(self.driver._security_group_id, '0') + mock_nsxv_plugin = mock.Mock() + fc_plugin = manager.NeutronManager.get_service_plugins().get( + flowclassifier.FLOW_CLASSIFIER_EXT) + with mock.patch.object(manager.NeutronManager, 'get_plugin', + return_value=mock_nsxv_plugin): + with mock.patch.object( + mock_nsxv_plugin, + 'add_vms_to_service_insertion') as fake_add: + with mock.patch.object( + fc_plugin, + 'create_flow_classifier') as fake_create: + self.driver.init_complete(None, None, {}) + # check that the plugin was called to add vms to the + # security group + self.assertTrue(fake_add.called) + # check that redirect_all flow classifier entry + # was created + self.assertTrue(fake_create.called) + def test_create_flow_classifier_precommit(self): with self.flow_classifier(flow_classifier=self._fc) as fc: fc_context = fc_ctx.FlowClassifierContext(