From 96d57271b0aa6eeb5cf60d0093d57ae9f2e0fd15 Mon Sep 17 00:00:00 2001 From: Claudiu Belu Date: Wed, 12 Feb 2014 16:52:47 -0800 Subject: [PATCH] Adds Hyper-V Security Groups implementation Implements the security groups API in the Hyper-V agent. To enable security groups on the Hyper-V agent, its config file must contain the following option: [SECURITYGROUP] firewall_driver=neutron.plugins.hyperv.agent.security_groups_driver.HyperVSecurityGroupsDriver Change-Id: I7556001557cd013c10b7f883dbf371afa8d09626 Implements: blueprint hyperv-security-groups --- .../hyperv/agent/hyperv_neutron_agent.py | 50 ++++- .../hyperv/agent/security_groups_driver.py | 136 ++++++++++++ neutron/plugins/hyperv/agent/utilsfactory.py | 30 +-- neutron/plugins/hyperv/agent/utilsv2.py | 193 +++++++++++++++++- .../unit/hyperv/test_hyperv_neutron_agent.py | 1 + .../test_hyperv_security_groups_driver.py | 176 ++++++++++++++++ .../unit/hyperv/test_hyperv_utilsfactory.py | 3 + .../tests/unit/hyperv/test_hyperv_utilsv2.py | 155 ++++++++++++++ 8 files changed, 730 insertions(+), 14 deletions(-) create mode 100644 neutron/plugins/hyperv/agent/security_groups_driver.py create mode 100644 neutron/tests/unit/hyperv/test_hyperv_security_groups_driver.py diff --git a/neutron/plugins/hyperv/agent/hyperv_neutron_agent.py b/neutron/plugins/hyperv/agent/hyperv_neutron_agent.py index 755ed67f1..cb3054f12 100644 --- a/neutron/plugins/hyperv/agent/hyperv_neutron_agent.py +++ b/neutron/plugins/hyperv/agent/hyperv_neutron_agent.py @@ -27,6 +27,7 @@ from oslo.config import cfg from neutron.agent.common import config from neutron.agent import rpc as agent_rpc +from neutron.agent import securitygroups_rpc as sg_rpc from neutron.common import config as logging_config from neutron.common import constants as n_const from neutron.common import topics @@ -70,6 +71,45 @@ CONF.register_opts(agent_opts, "AGENT") config.register_agent_state_opts_helper(cfg.CONF) +class HyperVSecurityAgent(sg_rpc.SecurityGroupAgentRpcMixin): + # Set RPC API version to 1.1 by default. + RPC_API_VERSION = '1.1' + + def __init__(self, context, plugin_rpc): + self.context = context + self.plugin_rpc = plugin_rpc + self.init_firewall() + + if sg_rpc.is_firewall_enabled(): + self._setup_rpc() + + def _setup_rpc(self): + self.topic = topics.AGENT + self.dispatcher = self._create_rpc_dispatcher() + consumers = [[topics.SECURITY_GROUP, topics.UPDATE]] + + self.connection = agent_rpc.create_consumers(self.dispatcher, + self.topic, + consumers) + + def _create_rpc_dispatcher(self): + rpc_callback = HyperVSecurityCallbackMixin(self) + return dispatcher.RpcDispatcher([rpc_callback]) + + +class HyperVSecurityCallbackMixin(sg_rpc.SecurityGroupAgentRpcCallbackMixin): + # Set RPC API version to 1.1 by default. + RPC_API_VERSION = '1.1' + + def __init__(self, sg_agent): + self.sg_agent = sg_agent + + +class HyperVPluginApi(agent_rpc.PluginApi, + sg_rpc.SecurityGroupServerRpcApiMixin): + pass + + class HyperVNeutronAgent(object): # Set RPC API version to 1.0 by default. RPC_API_VERSION = '1.0' @@ -103,7 +143,7 @@ class HyperVNeutronAgent(object): def _setup_rpc(self): self.agent_id = 'hyperv_%s' % platform.node() self.topic = topics.AGENT - self.plugin_rpc = agent_rpc.PluginApi(topics.PLUGIN) + self.plugin_rpc = HyperVPluginApi(topics.PLUGIN) self.state_rpc = agent_rpc.PluginReportStateAPI(topics.PLUGIN) @@ -119,6 +159,9 @@ class HyperVNeutronAgent(object): self.connection = agent_rpc.create_consumers(self.dispatcher, self.topic, consumers) + + self.sec_groups_agent = HyperVSecurityAgent( + self.context, self.plugin_rpc) report_interval = CONF.AGENT.report_interval if report_interval: heartbeat = loopingcall.LoopingCall(self._report_state) @@ -165,6 +208,9 @@ class HyperVNeutronAgent(object): def port_update(self, context, port=None, network_type=None, segmentation_id=None, physical_network=None): LOG.debug(_("port_update received")) + if 'security_groups' in port: + self.sec_groups_agent.refresh_firewall() + self._treat_vif_port( port['id'], port['network_id'], network_type, physical_network, @@ -311,6 +357,8 @@ class HyperVNeutronAgent(object): device_details['physical_network'], device_details['segmentation_id'], device_details['admin_state_up']) + + self.sec_groups_agent.prepare_devices_filter(devices) self.plugin_rpc.update_device_up(self.context, device, self.agent_id, diff --git a/neutron/plugins/hyperv/agent/security_groups_driver.py b/neutron/plugins/hyperv/agent/security_groups_driver.py new file mode 100644 index 000000000..ac3f4c930 --- /dev/null +++ b/neutron/plugins/hyperv/agent/security_groups_driver.py @@ -0,0 +1,136 @@ +#Copyright 2014 Cloudbase Solutions SRL +#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. +# @author: Claudiu Belu, Cloudbase Solutions Srl + +from neutron.agent import firewall +from neutron.openstack.common import log as logging +from neutron.plugins.hyperv.agent import utilsfactory +from neutron.plugins.hyperv.agent import utilsv2 + +LOG = logging.getLogger(__name__) + + +class HyperVSecurityGroupsDriver(firewall.FirewallDriver): + """Security Groups Driver. + + Security Groups implementation for Hyper-V VMs. + """ + + _ACL_PROP_MAP = { + 'direction': {'ingress': utilsv2.HyperVUtilsV2._ACL_DIR_IN, + 'egress': utilsv2.HyperVUtilsV2._ACL_DIR_OUT}, + 'ethertype': {'IPv4': utilsv2.HyperVUtilsV2._ACL_TYPE_IPV4, + 'IPv6': utilsv2.HyperVUtilsV2._ACL_TYPE_IPV6}, + 'default': "ANY", + 'address_default': {'IPv4': '0.0.0.0/0', 'IPv6': '::/0'} + } + + def __init__(self): + self._utils = utilsfactory.get_hypervutils() + self._security_ports = {} + + def prepare_port_filter(self, port): + LOG.debug('Creating port %s rules' % len(port['security_group_rules'])) + + # newly created port, add default rules. + if port['device'] not in self._security_ports: + LOG.debug('Creating default reject rules.') + self._utils.create_default_reject_all_rules(port['id']) + + self._security_ports[port['device']] = port + self._create_port_rules(port['id'], port['security_group_rules']) + + def _create_port_rules(self, port_id, rules): + for rule in rules: + param_map = self._create_param_map(rule) + try: + self._utils.create_security_rule(port_id, **param_map) + except Exception as ex: + LOG.error(_('Hyper-V Exception: %(hyperv_exeption)s while ' + 'adding rule: %(rule)s'), + dict(hyperv_exeption=ex, rule=rule)) + + def _remove_port_rules(self, port_id, rules): + for rule in rules: + param_map = self._create_param_map(rule) + try: + self._utils.remove_security_rule(port_id, **param_map) + except Exception as ex: + LOG.error(_('Hyper-V Exception: %(hyperv_exeption)s while ' + 'removing rule: %(rule)s'), + dict(hyperv_exeption=ex, rule=rule)) + + def _create_param_map(self, rule): + if 'port_range_min' in rule and 'port_range_max' in rule: + local_port = '%s-%s' % (rule['port_range_min'], + rule['port_range_max']) + else: + local_port = self._ACL_PROP_MAP['default'] + + return { + 'direction': self._ACL_PROP_MAP['direction'][rule['direction']], + 'acl_type': self._ACL_PROP_MAP['ethertype'][rule['ethertype']], + 'local_port': local_port, + 'protocol': self._get_rule_prop_or_default(rule, 'protocol'), + 'remote_address': self._get_rule_remote_address(rule) + } + + def apply_port_filter(self, port): + LOG.info('Aplying port filter.') + + def update_port_filter(self, port): + LOG.info('Updating port rules.') + + if port['device'] not in self._security_ports: + self.prepare_port_filter(port) + return + + old_port = self._security_ports[port['device']] + rules = old_port['security_group_rules'] + param_port_rules = port['security_group_rules'] + + new_rules = [r for r in param_port_rules if r not in rules] + remove_rules = [r for r in rules if r not in param_port_rules] + + LOG.info("Creating %s new rules, removing %s old rules." % ( + len(new_rules), len(remove_rules))) + + self._remove_port_rules(old_port['id'], remove_rules) + self._create_port_rules(port['id'], new_rules) + + self._security_ports[port['device']] = port + + def remove_port_filter(self, port): + LOG.info('Removing port filter') + self._security_ports.pop(port['device'], None) + + @property + def ports(self): + return self._security_ports + + def _get_rule_remote_address(self, rule): + if rule['direction'] is 'ingress': + ip_prefix = 'source_ip_prefix' + else: + ip_prefix = 'dest_ip_prefix' + + if ip_prefix in rule: + return rule[ip_prefix] + return self._ACL_PROP_MAP['address_default'][rule['ethertype']] + + def _get_rule_prop_or_default(self, rule, prop): + if prop in rule: + return rule[prop] + return self._ACL_PROP_MAP['default'] diff --git a/neutron/plugins/hyperv/agent/utilsfactory.py b/neutron/plugins/hyperv/agent/utilsfactory.py index 8d594a223..8b6ae7729 100644 --- a/neutron/plugins/hyperv/agent/utilsfactory.py +++ b/neutron/plugins/hyperv/agent/utilsfactory.py @@ -49,18 +49,24 @@ def _check_min_windows_version(major, minor, build=0): return map(int, version_str.split('.')) >= [major, minor, build] -def _get_class(v1_class, v2_class, force_v1_flag): - # V2 classes are supported starting from Hyper-V Server 2012 and - # Windows Server 2012 (kernel version 6.2) - if not force_v1_flag and _check_min_windows_version(6, 2): - cls = v2_class +def get_hypervutils(): + # V1 virtualization namespace features are supported up to + # Windows Server / Hyper-V Server 2012 + # V2 virtualization namespace features are supported starting with + # Windows Server / Hyper-V Server 2012 + # Windows Server / Hyper-V Server 2012 R2 uses the V2 namespace and + # introduces additional features + + force_v1_flag = CONF.hyperv.force_hyperv_utils_v1 + if _check_min_windows_version(6, 3): + if force_v1_flag: + LOG.warning('V1 virtualization namespace no longer supported on ' + 'Windows Server / Hyper-V Server 2012 R2 or above.') + cls = utilsv2.HyperVUtilsV2R2 + elif not force_v1_flag and _check_min_windows_version(6, 2): + cls = utilsv2.HyperVUtilsV2 else: - cls = v1_class + cls = utils.HyperVUtils LOG.debug(_("Loading class: %(module_name)s.%(class_name)s"), {'module_name': cls.__module__, 'class_name': cls.__name__}) - return cls - - -def get_hypervutils(): - return _get_class(utils.HyperVUtils, utilsv2.HyperVUtilsV2, - CONF.hyperv.force_hyperv_utils_v1)() + return cls() diff --git a/neutron/plugins/hyperv/agent/utilsv2.py b/neutron/plugins/hyperv/agent/utilsv2.py index d2a9a7d3e..5b280c185 100644 --- a/neutron/plugins/hyperv/agent/utilsv2.py +++ b/neutron/plugins/hyperv/agent/utilsv2.py @@ -26,17 +26,32 @@ class HyperVUtilsV2(utils.HyperVUtils): _ETHERNET_SWITCH_PORT = 'Msvm_EthernetSwitchPort' _PORT_ALLOC_SET_DATA = 'Msvm_EthernetPortAllocationSettingData' _PORT_VLAN_SET_DATA = 'Msvm_EthernetSwitchPortVlanSettingData' + _PORT_SECURITY_SET_DATA = 'Msvm_EthernetSwitchPortSecuritySettingData' _PORT_ALLOC_ACL_SET_DATA = 'Msvm_EthernetSwitchPortAclSettingData' + _PORT_EXT_ACL_SET_DATA = _PORT_ALLOC_ACL_SET_DATA _LAN_ENDPOINT = 'Msvm_LANEndpoint' _STATE_DISABLED = 3 _OPERATION_MODE_ACCESS = 1 _ACL_DIR_IN = 1 _ACL_DIR_OUT = 2 + _ACL_TYPE_IPV4 = 2 _ACL_TYPE_IPV6 = 3 + + _ACL_ACTION_ALLOW = 1 + _ACL_ACTION_DENY = 2 _ACL_ACTION_METER = 3 + _ACL_APPLICABILITY_LOCAL = 1 + _ACL_APPLICABILITY_REMOTE = 2 + + _ACL_DEFAULT = 'ANY' + _IPV4_ANY = '0.0.0.0/0' + _IPV6_ANY = '::/0' + _TCP_PROTOCOL = 'tcp' + _UDP_PROTOCOL = 'udp' + _MAX_WEIGHT = 65500 _wmi_namespace = '//./root/virtualization/v2' @@ -80,6 +95,12 @@ class HyperVUtilsV2(utils.HyperVUtils): element.path_(), [res_setting_data.GetText_(1)]) self._check_job_status(ret_val, job_path) + def _remove_virt_feature(self, feature_resource): + vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0] + (job_path, ret_val) = vs_man_svc.RemoveFeatureSettings( + FeatureSettings=[feature_resource.path_()]) + self._check_job_status(ret_val, job_path) + def disconnect_switch_port( self, vswitch_name, switch_port_name, delete_port): """Disconnects the switch port.""" @@ -121,7 +142,7 @@ class HyperVUtilsV2(utils.HyperVUtils): port_alloc, found = self._get_switch_port_allocation(switch_port_name) if not found: raise utils.HyperVException( - msg=_('Port Alloc not found: %s') % switch_port_name) + msg=_('Port Allocation not found: %s') % switch_port_name) vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0] vlan_settings = self._get_vlan_setting_data_from_port_alloc(port_alloc) @@ -196,3 +217,173 @@ class HyperVUtilsV2(utils.HyperVUtils): acl.Action = self._ACL_ACTION_METER acl.Applicability = self._ACL_APPLICABILITY_LOCAL self._add_virt_feature(port, acl) + + def create_security_rule(self, switch_port_name, direction, acl_type, + local_port, protocol, remote_address): + port, found = self._get_switch_port_allocation(switch_port_name, False) + if not found: + return + + # Add the ACLs only if they don't already exist + acls = port.associators(wmi_result_class=self._PORT_EXT_ACL_SET_DATA) + weight = self._get_new_weight(acls) + self._bind_security_rule( + port, direction, acl_type, self._ACL_ACTION_ALLOW, local_port, + protocol, remote_address, weight) + + def remove_security_rule(self, switch_port_name, direction, acl_type, + local_port, protocol, remote_address): + port, found = self._get_switch_port_allocation(switch_port_name, False) + if not found: + # Port not found. It happens when the VM was already deleted. + return + + acls = port.associators(wmi_result_class=self._PORT_EXT_ACL_SET_DATA) + filtered_acls = self._filter_security_acls( + acls, self._ACL_ACTION_ALLOW, direction, acl_type, local_port, + protocol, remote_address) + + for acl in filtered_acls: + self._remove_virt_feature(acl) + + def create_default_reject_all_rules(self, switch_port_name): + port, found = self._get_switch_port_allocation(switch_port_name, False) + if not found: + raise utils.HyperVException( + msg=_('Port Allocation not found: %s') % switch_port_name) + + acls = port.associators(wmi_result_class=self._PORT_EXT_ACL_SET_DATA) + filtered_acls = [v for v in acls if v.Action == self._ACL_ACTION_DENY] + + # 2 directions x 2 address types x 2 protocols = 8 ACLs + if len(filtered_acls) >= 8: + return + + for acl in filtered_acls: + self._remove_virt_feature(acl) + + weight = 0 + ipv4_pair = (self._ACL_TYPE_IPV4, self._IPV4_ANY) + ipv6_pair = (self._ACL_TYPE_IPV6, self._IPV6_ANY) + for direction in [self._ACL_DIR_IN, self._ACL_DIR_OUT]: + for acl_type, address in [ipv4_pair, ipv6_pair]: + for protocol in [self._TCP_PROTOCOL, self._UDP_PROTOCOL]: + self._bind_security_rule( + port, direction, acl_type, self._ACL_ACTION_DENY, + self._ACL_DEFAULT, protocol, address, weight) + weight += 1 + + def _bind_security_rule(self, port, direction, acl_type, action, + local_port, protocol, remote_address, weight): + acls = port.associators(wmi_result_class=self._PORT_EXT_ACL_SET_DATA) + filtered_acls = self._filter_security_acls( + acls, action, direction, acl_type, local_port, protocol, + remote_address) + + for acl in filtered_acls: + self._remove_virt_feature(acl) + + acl = self._create_security_acl( + direction, acl_type, action, local_port, protocol, remote_address, + weight) + + self._add_virt_feature(port, acl) + + def _create_acl(self, direction, acl_type, action): + acl = self._get_default_setting_data(self._PORT_ALLOC_ACL_SET_DATA) + acl.set(Direction=direction, + AclType=acl_type, + Action=action, + Applicability=self._ACL_APPLICABILITY_LOCAL) + return acl + + def _create_security_acl(self, direction, acl_type, action, local_port, + protocol, remote_ip_address, weight): + acl = self._create_acl(direction, acl_type, action) + (remote_address, remote_prefix_length) = remote_ip_address.split('/') + acl.set(Applicability=self._ACL_APPLICABILITY_REMOTE, + RemoteAddress=remote_address, + RemoteAddressPrefixLength=remote_prefix_length) + return acl + + def _filter_acls(self, acls, action, direction, acl_type, remote_addr=""): + return [v for v in acls + if v.Action == action and + v.Direction == direction and + v.AclType == acl_type and + v.RemoteAddress == remote_addr] + + def _filter_security_acls(self, acls, acl_action, direction, acl_type, + local_port, protocol, remote_addr=""): + (remote_address, remote_prefix_length) = remote_addr.split('/') + remote_prefix_length = int(remote_prefix_length) + + return [v for v in acls + if v.Direction == direction and + v.Action in [self._ACL_ACTION_ALLOW, self._ACL_ACTION_DENY] and + v.AclType == acl_type and + v.RemoteAddress == remote_address and + v.RemoteAddressPrefixLength == remote_prefix_length] + + def _get_new_weight(self, acls): + return 0 + + +class HyperVUtilsV2R2(HyperVUtilsV2): + _PORT_EXT_ACL_SET_DATA = 'Msvm_EthernetSwitchPortExtendedAclSettingData' + _MAX_WEIGHT = 65500 + + def create_security_rule(self, switch_port_name, direction, acl_type, + local_port, protocol, remote_address): + protocols = [protocol] + if protocol is self._ACL_DEFAULT: + protocols = [self._TCP_PROTOCOL, self._UDP_PROTOCOL] + + for proto in protocols: + super(HyperVUtilsV2R2, self).create_security_rule( + switch_port_name, direction, acl_type, local_port, + proto, remote_address) + + def remove_security_rule(self, switch_port_name, direction, acl_type, + local_port, protocol, remote_address): + protocols = [protocol] + if protocol is self._ACL_DEFAULT: + protocols = ['tcp', 'udp'] + + for proto in protocols: + super(HyperVUtilsV2R2, self).remove_security_rule( + switch_port_name, direction, acl_type, + local_port, proto, remote_address) + + def _create_security_acl(self, direction, acl_type, action, local_port, + protocol, remote_addr, weight): + acl = self._get_default_setting_data(self._PORT_EXT_ACL_SET_DATA) + acl.set(Direction=direction, + Action=action, + LocalPort=str(local_port), + Protocol=protocol, + RemoteIPAddress=remote_addr, + IdleSessionTimeout=0, + Weight=weight) + return acl + + def _filter_security_acls(self, acls, action, direction, acl_type, + local_port, protocol, remote_addr=""): + return [v for v in acls + if v.Action == action and + v.Direction == direction and + v.LocalPort in [str(local_port), self._ACL_DEFAULT] and + v.Protocol in [protocol] and + v.RemoteIPAddress == remote_addr] + + def _get_new_weight(self, acls): + if not acls: + return self._MAX_WEIGHT - 1 + + weights = [a.Weight for a in acls] + min_weight = min(weights) + for weight in range(min_weight, self._MAX_WEIGHT): + if weight not in weights: + return weight + + return min_weight - 1 diff --git a/neutron/tests/unit/hyperv/test_hyperv_neutron_agent.py b/neutron/tests/unit/hyperv/test_hyperv_neutron_agent.py index dc835647d..28455c6b2 100644 --- a/neutron/tests/unit/hyperv/test_hyperv_neutron_agent.py +++ b/neutron/tests/unit/hyperv/test_hyperv_neutron_agent.py @@ -57,6 +57,7 @@ class TestHyperVNeutronAgent(base.BaseTestCase): self.agent = hyperv_neutron_agent.HyperVNeutronAgent() self.agent.plugin_rpc = mock.Mock() + self.agent.sec_groups_agent = mock.MagicMock() self.agent.context = mock.Mock() self.agent.agent_id = mock.Mock() diff --git a/neutron/tests/unit/hyperv/test_hyperv_security_groups_driver.py b/neutron/tests/unit/hyperv/test_hyperv_security_groups_driver.py new file mode 100644 index 000000000..1ac5b3163 --- /dev/null +++ b/neutron/tests/unit/hyperv/test_hyperv_security_groups_driver.py @@ -0,0 +1,176 @@ +# Copyright 2014 Cloudbase Solutions SRL +# 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. +# @author: Claudiu Belu, Cloudbase Solutions Srl + +""" +Unit tests for the Hyper-V Security Groups Driver. +""" + +import mock +from oslo.config import cfg + +from neutron.plugins.hyperv.agent import security_groups_driver as sg_driver +from neutron.plugins.hyperv.agent import utilsfactory +from neutron.tests import base + +CONF = cfg.CONF + + +class TestHyperVSecurityGroupsDriver(base.BaseTestCase): + + _FAKE_DEVICE = 'fake_device' + _FAKE_ID = 'fake_id' + _FAKE_DIRECTION = 'ingress' + _FAKE_ETHERTYPE = 'IPv4' + _FAKE_ETHERTYPE_IPV6 = 'IPv6' + _FAKE_DEST_IP_PREFIX = 'fake_dest_ip_prefix' + _FAKE_SOURCE_IP_PREFIX = 'fake_source_ip_prefix' + _FAKE_PARAM_NAME = 'fake_param_name' + _FAKE_PARAM_VALUE = 'fake_param_value' + + _FAKE_PORT_MIN = 9001 + _FAKE_PORT_MAX = 9011 + + def setUp(self): + super(TestHyperVSecurityGroupsDriver, self).setUp() + self._mock_windows_version = mock.patch.object(utilsfactory, + 'get_hypervutils') + self._mock_windows_version.start() + self.addCleanup(mock.patch.stopall) + self._driver = sg_driver.HyperVSecurityGroupsDriver() + self._driver._utils = mock.MagicMock() + + @mock.patch('neutron.plugins.hyperv.agent.security_groups_driver' + '.HyperVSecurityGroupsDriver._create_port_rules') + def test_prepare_port_filter(self, mock_create_rules): + mock_port = self._get_port() + mock_utils_method = self._driver._utils.create_default_reject_all_rules + self._driver.prepare_port_filter(mock_port) + + self.assertEqual(mock_port, + self._driver._security_ports[self._FAKE_DEVICE]) + mock_utils_method.assert_called_once_with(self._FAKE_ID) + self._driver._create_port_rules.assert_called_once_with( + self._FAKE_ID, mock_port['security_group_rules']) + + def test_update_port_filter(self): + mock_port = self._get_port() + new_mock_port = self._get_port() + new_mock_port['id'] += '2' + new_mock_port['security_group_rules'][0]['ethertype'] += "2" + + self._driver._security_ports[mock_port['device']] = mock_port + self._driver._create_port_rules = mock.MagicMock() + self._driver._remove_port_rules = mock.MagicMock() + self._driver.update_port_filter(new_mock_port) + + self._driver._remove_port_rules.assert_called_once_with( + mock_port['id'], mock_port['security_group_rules']) + self._driver._create_port_rules.assert_called_once_with( + new_mock_port['id'], new_mock_port['security_group_rules']) + self.assertEqual(new_mock_port, + self._driver._security_ports[new_mock_port['device']]) + + @mock.patch('neutron.plugins.hyperv.agent.security_groups_driver' + '.HyperVSecurityGroupsDriver.prepare_port_filter') + def test_update_port_filter_new_port(self, mock_method): + mock_port = self._get_port() + self._driver.prepare_port_filter = mock.MagicMock() + self._driver.update_port_filter(mock_port) + + self._driver.prepare_port_filter.assert_called_once_with(mock_port) + + def test_remove_port_filter(self): + mock_port = self._get_port() + self._driver._security_ports[mock_port['device']] = mock_port + self._driver.remove_port_filter(mock_port) + self.assertFalse(mock_port['device'] in self._driver._security_ports) + + def test_create_port_rules_exception(self): + fake_rule = self._create_security_rule() + self._driver._utils.create_security_rule.side_effect = Exception( + 'Generated Exception for testing.') + self._driver._create_port_rules(self._FAKE_ID, [fake_rule]) + + def test_create_param_map(self): + fake_rule = self._create_security_rule() + self._driver._get_rule_remote_address = mock.MagicMock( + return_value=self._FAKE_SOURCE_IP_PREFIX) + actual = self._driver._create_param_map(fake_rule) + expected = { + 'direction': self._driver._ACL_PROP_MAP[ + 'direction'][self._FAKE_DIRECTION], + 'acl_type': self._driver._ACL_PROP_MAP[ + 'ethertype'][self._FAKE_ETHERTYPE], + 'local_port': '%s-%s' % (self._FAKE_PORT_MIN, self._FAKE_PORT_MAX), + 'protocol': self._driver._ACL_PROP_MAP['default'], + 'remote_address': self._FAKE_SOURCE_IP_PREFIX + } + + self.assertEqual(expected, actual) + + @mock.patch('neutron.plugins.hyperv.agent.security_groups_driver' + '.HyperVSecurityGroupsDriver._create_param_map') + def test_create_port_rules(self, mock_method): + fake_rule = self._create_security_rule() + mock_method.return_value = { + self._FAKE_PARAM_NAME: self._FAKE_PARAM_VALUE} + self._driver._create_port_rules(self._FAKE_ID, [fake_rule]) + + self._driver._utils.create_security_rule.assert_called_once_with( + self._FAKE_ID, fake_param_name=self._FAKE_PARAM_VALUE) + + def test_convert_any_address_to_same_ingress(self): + rule = self._create_security_rule() + actual = self._driver._get_rule_remote_address(rule) + self.assertEqual(self._FAKE_SOURCE_IP_PREFIX, actual) + + def test_convert_any_address_to_same_egress(self): + rule = self._create_security_rule() + rule['direction'] += '2' + actual = self._driver._get_rule_remote_address(rule) + self.assertEqual(self._FAKE_DEST_IP_PREFIX, actual) + + def test_convert_any_address_to_ipv4(self): + rule = self._create_security_rule() + del rule['source_ip_prefix'] + actual = self._driver._get_rule_remote_address(rule) + self.assertEqual(self._driver._ACL_PROP_MAP['address_default']['IPv4'], + actual) + + def test_convert_any_address_to_ipv6(self): + rule = self._create_security_rule() + del rule['source_ip_prefix'] + rule['ethertype'] = self._FAKE_ETHERTYPE_IPV6 + actual = self._driver._get_rule_remote_address(rule) + self.assertEqual(self._driver._ACL_PROP_MAP['address_default']['IPv6'], + actual) + + def _get_port(self): + return { + 'device': self._FAKE_DEVICE, + 'id': self._FAKE_ID, + 'security_group_rules': [self._create_security_rule()] + } + + def _create_security_rule(self): + return { + 'direction': self._FAKE_DIRECTION, + 'ethertype': self._FAKE_ETHERTYPE, + 'dest_ip_prefix': self._FAKE_DEST_IP_PREFIX, + 'source_ip_prefix': self._FAKE_SOURCE_IP_PREFIX, + 'port_range_min': self._FAKE_PORT_MIN, + 'port_range_max': self._FAKE_PORT_MAX + } diff --git a/neutron/tests/unit/hyperv/test_hyperv_utilsfactory.py b/neutron/tests/unit/hyperv/test_hyperv_utilsfactory.py index bc2622844..fef96d734 100644 --- a/neutron/tests/unit/hyperv/test_hyperv_utilsfactory.py +++ b/neutron/tests/unit/hyperv/test_hyperv_utilsfactory.py @@ -34,6 +34,9 @@ CONF = cfg.CONF class TestHyperVUtilsFactory(base.BaseTestCase): + def test_get_hypervutils_v2_r2(self): + self._test_returned_class(utilsv2.HyperVUtilsV2R2, True, '6.3.0') + def test_get_hypervutils_v2(self): self._test_returned_class(utilsv2.HyperVUtilsV2, False, '6.2.0') diff --git a/neutron/tests/unit/hyperv/test_hyperv_utilsv2.py b/neutron/tests/unit/hyperv/test_hyperv_utilsv2.py index 82786c918..b30d4785a 100644 --- a/neutron/tests/unit/hyperv/test_hyperv_utilsv2.py +++ b/neutron/tests/unit/hyperv/test_hyperv_utilsv2.py @@ -41,6 +41,14 @@ class TestHyperVUtilsV2(base.BaseTestCase): _FAKE_CLASS_NAME = "fake_class_name" _FAKE_ELEMENT_NAME = "fake_element_name" + _FAKE_ACL_ACT = 'fake_acl_action' + _FAKE_ACL_DIR = 'fake_acl_dir' + _FAKE_ACL_TYPE = 'fake_acl_type' + _FAKE_LOCAL_PORT = 'fake_local_port' + _FAKE_PROTOCOL = 'fake_port_protocol' + _FAKE_REMOTE_ADDR = '0.0.0.0/0' + _FAKE_WEIGHT = 'fake_weight' + def setUp(self): super(TestHyperVUtilsV2, self).setUp() self._utils = utilsv2.HyperVUtilsV2() @@ -144,6 +152,20 @@ class TestHyperVUtilsV2(base.BaseTestCase): mock_svc.RemoveResourceSettings.assert_called_with( ResourceSettings=[self._FAKE_RES_PATH]) + @mock.patch('neutron.plugins.hyperv.agent.utilsv2.HyperVUtilsV2' + '._check_job_status') + def test_remove_virt_feature(self, mock_check_job_status): + mock_svc = self._utils._conn.Msvm_VirtualSystemManagementService()[0] + mock_svc.RemoveFeatureSettings.return_value = (self._FAKE_JOB_PATH, + self._FAKE_RET_VAL) + mock_res_setting_data = mock.MagicMock() + mock_res_setting_data.path_.return_value = self._FAKE_RES_PATH + + self._utils._remove_virt_feature(mock_res_setting_data) + + mock_svc.RemoveFeatureSettings.assert_called_with( + FeatureSettings=[self._FAKE_RES_PATH]) + def test_disconnect_switch_port_delete_port(self): self._test_disconnect_switch_port(True) @@ -249,3 +271,136 @@ class TestHyperVUtilsV2(base.BaseTestCase): self.assertEqual(4, len(self._utils._add_virt_feature.mock_calls)) self._utils._add_virt_feature.assert_called_with( mock_port, mock_acl) + + @mock.patch('neutron.plugins.hyperv.agent.utilsv2.HyperVUtilsV2' + '._remove_virt_feature') + @mock.patch('neutron.plugins.hyperv.agent.utilsv2.HyperVUtilsV2' + '._bind_security_rule') + def test_create_default_reject_all_rules(self, mock_bind, mock_remove): + (m_port, m_acl) = self._setup_security_rule_test() + m_acl.Action = self._utils._ACL_ACTION_DENY + self._utils.create_default_reject_all_rules(self._FAKE_PORT_NAME) + + calls = [] + ipv4_pair = (self._utils._ACL_TYPE_IPV4, self._utils._IPV4_ANY) + ipv6_pair = (self._utils._ACL_TYPE_IPV6, self._utils._IPV6_ANY) + for direction in [self._utils._ACL_DIR_IN, self._utils._ACL_DIR_OUT]: + for acl_type, address in [ipv4_pair, ipv6_pair]: + for protocol in [self._utils._TCP_PROTOCOL, + self._utils._UDP_PROTOCOL]: + calls.append(mock.call(m_port, direction, acl_type, + self._utils._ACL_ACTION_DENY, + self._utils._ACL_DEFAULT, + protocol, address, mock.ANY)) + + self._utils._remove_virt_feature.assert_called_once_with(m_acl) + self._utils._bind_security_rule.assert_has_calls(calls) + + @mock.patch('neutron.plugins.hyperv.agent.utilsv2.HyperVUtilsV2' + '._remove_virt_feature') + @mock.patch('neutron.plugins.hyperv.agent.utilsv2.HyperVUtilsV2' + '._add_virt_feature') + @mock.patch('neutron.plugins.hyperv.agent.utilsv2.HyperVUtilsV2' + '._create_security_acl') + def test_bind_security_rule(self, mock_create_acl, mock_add, mock_remove): + (m_port, m_acl) = self._setup_security_rule_test() + mock_create_acl.return_value = m_acl + + self._utils._bind_security_rule( + m_port, self._FAKE_ACL_DIR, self._FAKE_ACL_TYPE, + self._FAKE_ACL_ACT, self._FAKE_LOCAL_PORT, self._FAKE_PROTOCOL, + self._FAKE_REMOTE_ADDR, self._FAKE_WEIGHT) + + self._utils._add_virt_feature.assert_called_once_with(m_port, m_acl) + + @mock.patch('neutron.plugins.hyperv.agent.utilsv2.HyperVUtilsV2' + '._remove_virt_feature') + def test_remove_security_rule(self, mock_remove_feature): + mock_acl = self._setup_security_rule_test()[1] + self._utils.remove_security_rule( + self._FAKE_PORT_NAME, self._FAKE_ACL_DIR, self._FAKE_ACL_TYPE, + self._FAKE_LOCAL_PORT, self._FAKE_PROTOCOL, self._FAKE_REMOTE_ADDR) + self._utils._remove_virt_feature.assert_called_once_with(mock_acl) + + def _setup_security_rule_test(self): + mock_port = mock.MagicMock() + mock_acl = mock.MagicMock() + mock_port.associators.return_value = [mock_acl] + + self._utils._get_switch_port_allocation = mock.MagicMock(return_value=( + mock_port, True)) + self._utils._filter_security_acls = mock.MagicMock( + return_value=[mock_acl]) + + return (mock_port, mock_acl) + + def test_filter_acls(self): + mock_acl = mock.MagicMock() + mock_acl.Action = self._FAKE_ACL_ACT + mock_acl.Applicability = self._utils._ACL_APPLICABILITY_LOCAL + mock_acl.Direction = self._FAKE_ACL_DIR + mock_acl.AclType = self._FAKE_ACL_TYPE + mock_acl.RemoteAddress = self._FAKE_REMOTE_ADDR + + acls = [mock_acl, mock_acl] + good_acls = self._utils._filter_acls( + acls, self._FAKE_ACL_ACT, self._FAKE_ACL_DIR, + self._FAKE_ACL_TYPE, self._FAKE_REMOTE_ADDR) + bad_acls = self._utils._filter_acls( + acls, self._FAKE_ACL_ACT, self._FAKE_ACL_DIR, self._FAKE_ACL_TYPE) + + self.assertEqual(acls, good_acls) + self.assertEqual([], bad_acls) + + +class TestHyperVUtilsV2R2(base.BaseTestCase): + _FAKE_ACL_ACT = 'fake_acl_action' + _FAKE_ACL_DIR = 'fake_direction' + _FAKE_ACL_TYPE = 'fake_acl_type' + _FAKE_LOCAL_PORT = 'fake_local_port' + _FAKE_PROTOCOL = 'fake_port_protocol' + _FAKE_REMOTE_ADDR = '10.0.0.0/0' + + def setUp(self): + super(TestHyperVUtilsV2R2, self).setUp() + self._utils = utilsv2.HyperVUtilsV2R2() + + def test_filter_security_acls(self): + self._test_filter_security_acls( + self._FAKE_LOCAL_PORT, self._FAKE_PROTOCOL, self._FAKE_REMOTE_ADDR) + + def test_filter_security_acls_default(self): + default = self._utils._ACL_DEFAULT + self._test_filter_security_acls( + default, default, self._FAKE_REMOTE_ADDR) + + def _test_filter_security_acls(self, local_port, protocol, remote_addr): + mock_acl = mock.MagicMock() + mock_acl.Action = self._utils._ACL_ACTION_ALLOW + mock_acl.Direction = self._FAKE_ACL_DIR + mock_acl.LocalPort = local_port + mock_acl.Protocol = protocol + mock_acl.RemoteIPAddress = remote_addr + + acls = [mock_acl, mock_acl] + good_acls = self._utils._filter_security_acls( + acls, mock_acl.Action, self._FAKE_ACL_DIR, self._FAKE_ACL_TYPE, + local_port, protocol, remote_addr) + bad_acls = self._utils._filter_security_acls( + acls, self._FAKE_ACL_ACT, self._FAKE_ACL_DIR, self._FAKE_ACL_TYPE, + local_port, protocol, remote_addr) + + self.assertEqual(acls, good_acls) + self.assertEqual([], bad_acls) + + def test_get_new_weight(self): + mockacl1 = mock.MagicMock() + mockacl1.Weight = self._utils._MAX_WEIGHT - 1 + mockacl2 = mock.MagicMock() + mockacl2.Weight = self._utils._MAX_WEIGHT - 3 + self.assertEqual(self._utils._MAX_WEIGHT - 2, + self._utils._get_new_weight([mockacl1, mockacl2])) + + def test_get_new_weight_no_acls(self): + self.assertEqual(self._utils._MAX_WEIGHT - 1, + self._utils._get_new_weight([])) -- 2.45.2