From 006231cf7d712a7f2663ae910ffe9cd15628feb2 Mon Sep 17 00:00:00 2001 From: Gary Kotton Date: Sun, 1 Oct 2017 06:10:09 -0700 Subject: [PATCH] NSX-V: add support for mac learning extension Enable a port to set the mac learning property. This will configure the port group to be in 'forget transmit' and 'promiscuous'. This will enable an embedded hypervisor to be run with the NSX-V Mac learning can only be configured if there is no port security and security groups configured Change-Id: I9cb36d877669fbd682cb7a9c0a88a172a31aeebb --- vmware_nsx/db/maclearning.py | 10 +++++ vmware_nsx/dvs/dvs.py | 49 ++++++++++++++++++++++ vmware_nsx/plugins/nsx_v/plugin.py | 67 +++++++++++++++++++++++++++++- 3 files changed, 124 insertions(+), 2 deletions(-) diff --git a/vmware_nsx/db/maclearning.py b/vmware_nsx/db/maclearning.py index fe780d71e8..362156a9c1 100644 --- a/vmware_nsx/db/maclearning.py +++ b/vmware_nsx/db/maclearning.py @@ -66,3 +66,13 @@ class MacLearningDbMixin(object): mac_learning_enabled=enabled) context.session.add(state) return self._make_mac_learning_state_dict(state) + + def get_mac_learning_state(self, context, port_id): + try: + query = model_query.query_with_hooks( + context, nsx_models.MacLearningState) + state = query.filter( + nsx_models.MacLearningState.port_id == port_id).one() + return state.mac_learning_enabled + except exc.NoResultFound: + return None diff --git a/vmware_nsx/dvs/dvs.py b/vmware_nsx/dvs/dvs.py index 4d65606c31..7d4792c74a 100644 --- a/vmware_nsx/dvs/dvs.py +++ b/vmware_nsx/dvs/dvs.py @@ -444,6 +444,36 @@ class DvsManager(VCManagerBase): port_conf = pg_spec.defaultPortConfig port_conf.vlan = self._get_trunk_vlan_spec() + def update_port_group_security_policy(self, pg_spec, status): + policy = pg_spec.policy + policy.securityPolicyOverrideAllowed = status + + def _update_port_security_policy(self, dvs_moref, port, status): + client_factory = self._session.vim.client.factory + ps = client_factory.create('ns0:DVPortConfigSpec') + ps.key = port.portKey + ps.operation = 'edit' + policy = client_factory.create('ns0:DVSSecurityPolicy') + bp = client_factory.create('ns0:BoolPolicy') + bp.inherited = False + bp.value = status + policy.allowPromiscuous = bp + policy.forgedTransmits = bp + policy.inherited = False + setting = client_factory.create('ns0:VMwareDVSPortSetting') + setting.securityPolicy = policy + ps.setting = setting + task = self._session.invoke_api(self._session.vim, + 'ReconfigureDVPort_Task', + dvs_moref, + port=ps) + try: + self._session.wait_for_task(task) + LOG.info("Updated port security status") + except Exception as e: + LOG.error("Failed to update port %s. Reason: %s", + port.key, e) + class VMManager(VCManagerBase): """Management class for VMs related VC tasks.""" @@ -567,6 +597,25 @@ class VMManager(VCManagerBase): "config.hardware.device") return hardware_devices + def _get_device_port(self, device_id, mac_address): + vm_moref = self.get_vm_moref_obj(device_id) + hardware_devices = self.get_vm_interfaces_info(vm_moref) + if not hardware_devices: + return + if hardware_devices.__class__.__name__ == "ArrayOfVirtualDevice": + hardware_devices = hardware_devices.VirtualDevice + for device in hardware_devices: + if hasattr(device, 'macAddress'): + if device.macAddress == mac_address: + return device.backing.port + + def update_port_security_policy(self, dvs_id, net_id, net_moref, + device_id, mac_address, status): + dvs_moref = self._get_dvs_moref_by_id(dvs_id) + port = self._get_device_port(device_id, mac_address) + if port: + self._update_port_security_policy(dvs_moref, port, status) + class ClusterManager(VCManagerBase): """Management class for Cluster related VC tasks.""" diff --git a/vmware_nsx/plugins/nsx_v/plugin.py b/vmware_nsx/plugins/nsx_v/plugin.py index 776852b558..155dcd1cc7 100644 --- a/vmware_nsx/plugins/nsx_v/plugin.py +++ b/vmware_nsx/plugins/nsx_v/plugin.py @@ -104,6 +104,7 @@ from vmware_nsx.db import ( routertype as rt_rtr) from vmware_nsx.db import db as nsx_db from vmware_nsx.db import extended_security_group as extended_secgroup +from vmware_nsx.db import maclearning as mac_db from vmware_nsx.db import nsx_portbindings_db as pbin_db from vmware_nsx.db import nsxv_db from vmware_nsx.db import vnic_index_db @@ -113,6 +114,7 @@ from vmware_nsx.extensions import ( vnicindex as ext_vnic_idx) from vmware_nsx.extensions import dhcp_mtu as ext_dhcp_mtu from vmware_nsx.extensions import dns_search_domain as ext_dns_search_domain +from vmware_nsx.extensions import maclearning as mac_ext from vmware_nsx.extensions import nsxpolicy from vmware_nsx.extensions import providersecuritygroup as provider_sg from vmware_nsx.extensions import routersize @@ -163,7 +165,8 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, vnic_index_db.VnicIndexDbMixin, dns_db.DNSDbMixin, nsxpolicy.NsxPolicyPluginBase, vlantransparent_db.Vlantransparent_db_mixin, - nsx_com_az.NSXAvailabilityZonesPluginCommon): + nsx_com_az.NSXAvailabilityZonesPluginCommon, + mac_db.MacLearningDbMixin): supported_extension_aliases = ["agent", "allowed-address-pairs", @@ -193,7 +196,8 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, "router_availability_zone", "l3-flavors", "flavors", - "dhcp-mtu"] + "dhcp-mtu", + "mac-learning"] __native_bulk_support = True __native_pagination_support = True @@ -1778,6 +1782,20 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, self._process_port_create_extra_dhcp_opts( context, port_data, dhcp_opts) + # MAC learning - only update DB. Can only update NSX when the port + # exists - this is done via update + if validators.is_attr_set(port_data.get(mac_ext.MAC_LEARNING)): + if (((has_ip and port_security) or + has_security_groups or provider_sg_specified) and + port_data.get(mac_ext.MAC_LEARNING) is True): + err_msg = _("Security features are not supported for " + "mac learning.") + raise n_exc.InvalidInput(error_message=err_msg) + self._create_mac_learning_state(context, port_data) + elif mac_ext.MAC_LEARNING in port_data: + # This is due to the fact that the default is + # ATTR_NOT_SPECIFIED + port_data.pop(mac_ext.MAC_LEARNING) try: # Configure NSX - this should not be done in the DB transaction @@ -1909,6 +1927,30 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, self.edge_manager.update_dhcp_edge_service( context, network_id, address_groups=address_groups) + def _nsx_update_mac_learning(self, context, port): + net_id = port['network_id'] + # default dvs for this network + az = self.get_network_az_by_net_id(context, net_id) + az_dvs_id = az.dvs_id + + # get the network moref/s from the db + net_mappings = nsx_db.get_nsx_network_mappings( + context.session, net_id) + for mapping in net_mappings: + dvs_id = mapping.dvs_id or az_dvs_id + try: + self._vcm.update_port_groups_config( + dvs_id, net_id, mapping.nsx_id, + self._vcm.update_port_group_security_policy, True) + except Exception as e: + LOG.error("Unable to update network security override " + "policy: %s", e) + return + self._vcm.update_port_security_policy( + dvs_id, net_id, mapping.nsx_id, + port['device_id'], port['mac_address'], + port[mac_ext.MAC_LEARNING]) + def _update_port(self, context, id, port, original_port, is_compute_port, device_id): attrs = port[port_def.RESOURCE_NAME] @@ -1995,6 +2037,13 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, err_msg = _("Security features are not supported for " "ports with direct/direct-physical VNIC type.") raise n_exc.InvalidInput(error_message=err_msg) + if (mac_ext.MAC_LEARNING in port_data and + port_data[mac_ext.MAC_LEARNING] is True and + has_port_security): + err_msg = _("Security features are not supported for " + "mac_learning.") + raise n_exc.InvalidInput(error_message=err_msg) + old_mac_learning_state = original_port.get(mac_ext.MAC_LEARNING) with db_api.context_manager.writer.using(context): ret_port = super(NsxVPluginV2, self).update_port( @@ -2037,6 +2086,11 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, self._update_extra_dhcp_opts_on_port(context, id, port, ret_port) + new_mac_learning_state = ret_port.get(mac_ext.MAC_LEARNING) + if (new_mac_learning_state is not None and + old_mac_learning_state != new_mac_learning_state): + self._update_mac_learning_state(context, id, + new_mac_learning_state) if comp_owner_update: # Create dhcp bindings, the port is now owned by an instance @@ -2180,6 +2234,15 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, self._update_security_groups_port_mapping( context.session, id, vnic_id, curr_sgids, new_sgids) + # update mac learning on NSX + if self._vcm: + mac_learning = self.get_mac_learning_state(context, id) + if mac_learning is not None: + try: + self._nsx_update_mac_learning(context, ret_port) + except Exception as e: + LOG.error("Unable to update mac learning for port %s, " + "reason: %s", id, e) return ret_port def delete_port(self, context, id, l3_port_check=True,