diff --git a/neutron/plugins/hyperv/agent/hyperv_neutron_agent.py b/neutron/plugins/hyperv/agent/hyperv_neutron_agent.py index 0f993d4673a..242967469dc 100644 --- a/neutron/plugins/hyperv/agent/hyperv_neutron_agent.py +++ b/neutron/plugins/hyperv/agent/hyperv_neutron_agent.py @@ -241,7 +241,10 @@ class HyperVNeutronAgent(object): vswitch_name = self._get_vswitch_name(network_type, physical_network) - if network_type in [p_const.TYPE_VLAN, p_const.TYPE_FLAT]: + if network_type == p_const.TYPE_VLAN: + self._utils.set_switch_external_port_trunk_vlan(vswitch_name, + segmentation_id, constants.TRUNK_ENDPOINT_MODE) + elif network_type == p_const.TYPE_FLAT: #Nothing to do pass elif network_type == p_const.TYPE_LOCAL: diff --git a/neutron/plugins/hyperv/agent/utils.py b/neutron/plugins/hyperv/agent/utils.py index 0fb79b5534c..e012df625a5 100644 --- a/neutron/plugins/hyperv/agent/utils.py +++ b/neutron/plugins/hyperv/agent/utils.py @@ -41,6 +41,10 @@ WMI_JOB_STATE_COMPLETED = 7 class HyperVUtils(object): _ETHERNET_SWITCH_PORT = 'Msvm_SwitchPort' + _SWITCH_LAN_ENDPOINT = 'Msvm_SwitchLanEndpoint' + _VIRTUAL_SWITCH = 'Msvm_VirtualSwitch' + _BINDS_TO = 'Msvm_BindsTo' + _VLAN_ENDPOINT_SET_DATA = 'Msvm_VLANEndpointSettingData' _wmi_namespace = '//./root/virtualization' @@ -207,17 +211,21 @@ class HyperVUtils(object): vswitch_name) return vswitch[0] - def _get_vswitch_external_port(self, vswitch): - vswitch_ports = vswitch.associators( - wmi_result_class=self._ETHERNET_SWITCH_PORT) - for vswitch_port in vswitch_ports: - lan_endpoints = vswitch_port.associators( + def _get_vswitch_external_port(self, vswitch_name): + ext_ports = self._conn.Msvm_ExternalEthernetPort() + for ext_port in ext_ports: + lan_endpoint_list = ext_port.associators( wmi_result_class='Msvm_SwitchLanEndpoint') - if lan_endpoints: - ext_port = lan_endpoints[0].associators( - wmi_result_class='Msvm_ExternalEthernetPort') - if ext_port: - return vswitch_port + if lan_endpoint_list: + vswitch_port_list = lan_endpoint_list[0].associators( + wmi_result_class=self._ETHERNET_SWITCH_PORT) + if vswitch_port_list: + vswitch_port = vswitch_port_list[0] + vswitch_list = vswitch_port.associators( + wmi_result_class='Msvm_VirtualSwitch') + if (vswitch_list and + vswitch_list[0].ElementName == vswitch_name): + return vswitch_port def set_vswitch_port_vlan_id(self, vlan_id, switch_port_name): vlan_endpoint_settings = self._conn.Msvm_VLANEndpointSettingData( @@ -226,6 +234,21 @@ class HyperVUtils(object): vlan_endpoint_settings.AccessVLAN = vlan_id vlan_endpoint_settings.put() + def set_switch_external_port_trunk_vlan(self, vswitch_name, vlan_id, + desired_endpoint_mode): + vswitch_external_port = self._get_vswitch_external_port(vswitch_name) + if vswitch_external_port: + vlan_endpoint = vswitch_external_port.associators( + wmi_association_class=self._BINDS_TO)[0] + if vlan_endpoint.DesiredEndpointMode != desired_endpoint_mode: + vlan_endpoint.DesiredEndpointMode = desired_endpoint_mode + vlan_endpoint.put() + vlan_endpoint_settings = vlan_endpoint.associators( + wmi_result_class=self._VLAN_ENDPOINT_SET_DATA)[0] + if vlan_id not in vlan_endpoint_settings.TrunkedVLANList: + vlan_endpoint_settings.TrunkedVLANList += (vlan_id,) + vlan_endpoint_settings.put() + def _get_switch_port_path_by_name(self, switch_port_name): vswitch = self._conn.Msvm_SwitchPort(ElementName=switch_port_name) if vswitch: diff --git a/neutron/plugins/hyperv/agent/utilsv2.py b/neutron/plugins/hyperv/agent/utilsv2.py index 0ef1ce4d891..7dad23b741a 100644 --- a/neutron/plugins/hyperv/agent/utilsv2.py +++ b/neutron/plugins/hyperv/agent/utilsv2.py @@ -134,21 +134,6 @@ class HyperVUtilsV2(utils.HyperVUtils): vswitch_name) return vswitch[0] - def _get_vswitch_external_port(self, vswitch): - vswitch_ports = vswitch.associators( - wmi_result_class=self._ETHERNET_SWITCH_PORT) - for vswitch_port in vswitch_ports: - lan_endpoints = vswitch_port.associators( - wmi_result_class=self._LAN_ENDPOINT) - if len(lan_endpoints): - lan_endpoints = lan_endpoints[0].associators( - wmi_result_class=self._LAN_ENDPOINT) - if len(lan_endpoints): - ext_port = lan_endpoints[0].associators( - wmi_result_class=self._EXTERNAL_PORT) - if ext_port: - return vswitch_port - def set_vswitch_port_vlan_id(self, vlan_id, switch_port_name): port_alloc, found = self._get_switch_port_allocation(switch_port_name) if not found: @@ -171,6 +156,10 @@ class HyperVUtilsV2(utils.HyperVUtils): port_alloc.path_(), [vlan_settings.GetText_(1)]) self._check_job_status(ret_val, job_path) + def set_switch_external_port_trunk_vlan(self, vswitch_name, vlan_id, + desired_endpoint_mode): + pass + def _get_vlan_setting_data_from_port_alloc(self, port_alloc): return self._get_first_item(port_alloc.associators( wmi_result_class=self._PORT_VLAN_SET_DATA)) diff --git a/neutron/plugins/hyperv/common/constants.py b/neutron/plugins/hyperv/common/constants.py index bf3d3787806..9dd005b1936 100644 --- a/neutron/plugins/hyperv/common/constants.py +++ b/neutron/plugins/hyperv/common/constants.py @@ -18,3 +18,4 @@ TUNNEL = 'tunnel' # Special vlan_id value in ovs_vlan_allocations table indicating flat network FLAT_VLAN_ID = -1 +TRUNK_ENDPOINT_MODE = 5 diff --git a/neutron/tests/unit/hyperv/test_hyperv_neutron_agent.py b/neutron/tests/unit/hyperv/test_hyperv_neutron_agent.py index 1de90d12fec..9fbac60d9fe 100644 --- a/neutron/tests/unit/hyperv/test_hyperv_neutron_agent.py +++ b/neutron/tests/unit/hyperv/test_hyperv_neutron_agent.py @@ -21,8 +21,11 @@ Unit tests for Windows Hyper-V virtual switch neutron driver import mock from oslo.config import cfg +from neutron.plugins.common import constants as p_const from neutron.plugins.hyperv.agent import hyperv_neutron_agent +from neutron.plugins.hyperv.agent import utils from neutron.plugins.hyperv.agent import utilsfactory +from neutron.plugins.hyperv.common import constants from neutron.tests import base cfg.CONF.import_opt('enable_metrics_collection', @@ -54,6 +57,7 @@ class TestHyperVNeutronAgent(base.BaseTestCase): 'neutron.agent.firewall.NoopFirewallDriver', group='SECURITYGROUP') self.agent = hyperv_neutron_agent.HyperVNeutronAgent() + self.agent._utils = mock.MagicMock() self.agent.plugin_rpc = mock.Mock() self.agent.sec_groups_agent = mock.MagicMock() self.agent.context = mock.Mock() @@ -214,3 +218,54 @@ class TestHyperVNeutronAgent(base.BaseTestCase): self.assertTrue(common_config.init.called) self.assertTrue(common_config.setup_logging.called) plugin.assert_has_calls([mock.call().daemon_loop()]) + + @mock.patch.object(hyperv_neutron_agent.HyperVNeutronAgent, + "_get_vswitch_name") + def test_provision_network_exception(self, mock_get_vswitch_name): + self.assertRaises(utils.HyperVException, self.agent._provision_network, + mock.sentinel.FAKE_PORT_ID, + mock.sentinel.FAKE_NET_UUID, + mock.sentinel.FAKE_NETWORK_TYPE, + mock.sentinel.FAKE_PHYSICAL_NETWORK, + mock.sentinel.FAKE_SEGMENTATION_ID) + mock_get_vswitch_name.assert_called_once_with( + mock.sentinel.FAKE_NETWORK_TYPE, + mock.sentinel.FAKE_PHYSICAL_NETWORK) + + @mock.patch.object(hyperv_neutron_agent.HyperVNeutronAgent, + "_get_vswitch_name") + def test_provision_network_vlan(self, mock_get_vswitch_name): + vswitch_name = mock_get_vswitch_name.return_value + self.agent._provision_network(mock.sentinel.FAKE_PORT_ID, + mock.sentinel.FAKE_NET_UUID, + p_const.TYPE_VLAN, + mock.sentinel.FAKE_PHYSICAL_NETWORK, + mock.sentinel.FAKE_SEGMENTATION_ID) + mock_get_vswitch_name.assert_called_once_with(p_const.TYPE_VLAN, + mock.sentinel.FAKE_PHYSICAL_NETWORK) + set_switch = self.agent._utils.set_switch_external_port_trunk_vlan + set_switch.assert_called_once_with(vswitch_name, + mock.sentinel.FAKE_SEGMENTATION_ID, + constants.TRUNK_ENDPOINT_MODE) + + @mock.patch.object(hyperv_neutron_agent.HyperVNeutronAgent, + "_get_vswitch_name") + def test_provision_network_flat(self, mock_get_vswitch_name): + self.agent._provision_network(mock.sentinel.FAKE_PORT_ID, + mock.sentinel.FAKE_NET_UUID, + p_const.TYPE_FLAT, + mock.sentinel.FAKE_PHYSICAL_NETWORK, + mock.sentinel.FAKE_SEGMENTATION_ID) + mock_get_vswitch_name.assert_called_once_with(p_const.TYPE_FLAT, + mock.sentinel.FAKE_PHYSICAL_NETWORK) + + @mock.patch.object(hyperv_neutron_agent.HyperVNeutronAgent, + "_get_vswitch_name") + def test_provision_network_local(self, mock_get_vswitch_name): + self.agent._provision_network(mock.sentinel.FAKE_PORT_ID, + mock.sentinel.FAKE_NET_UUID, + p_const.TYPE_LOCAL, + mock.sentinel.FAKE_PHYSICAL_NETWORK, + mock.sentinel.FAKE_SEGMENTATION_ID) + mock_get_vswitch_name.assert_called_once_with(p_const.TYPE_LOCAL, + mock.sentinel.FAKE_PHYSICAL_NETWORK) diff --git a/neutron/tests/unit/hyperv/test_hyperv_utils.py b/neutron/tests/unit/hyperv/test_hyperv_utils.py new file mode 100644 index 00000000000..fce4b792367 --- /dev/null +++ b/neutron/tests/unit/hyperv/test_hyperv_utils.py @@ -0,0 +1,107 @@ +# 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. + +""" +Unit tests for the Hyper-V Utils class. +""" + +import mock + +from neutron.plugins.hyperv.agent import utils +from neutron.plugins.hyperv.common import constants +from neutron.tests import base + + +class HyperVUtilsTestCase(base.BaseTestCase): + + FAKE_VLAN_ID = 500 + + def setUp(self): + super(HyperVUtilsTestCase, self).setUp() + self.utils = utils.HyperVUtils() + self.utils._wmi_conn = mock.MagicMock() + + def test_get_vswitch_external_port(self): + ext_port = mock.MagicMock() + self.utils._conn.Msvm_ExternalEthernetPort.return_value = [ext_port] + lan_endpoint = mock.MagicMock() + ext_port.associators.return_value = [lan_endpoint] + vswitch_port = mock.MagicMock() + lan_endpoint.associators.return_value = [vswitch_port] + vswitch = mock.MagicMock() + vswitch.ElementName = mock.sentinel.FAKE_VSWITCH_NAME + vswitch_port.associators.return_value = [vswitch] + + result = self.utils._get_vswitch_external_port( + mock.sentinel.FAKE_VSWITCH_NAME) + + self.assertEqual(vswitch_port, result) + + ext_port.associators.assert_called_once_with( + wmi_result_class=self.utils._SWITCH_LAN_ENDPOINT) + lan_endpoint.associators.assert_called_once_with( + wmi_result_class=self.utils._ETHERNET_SWITCH_PORT) + vswitch_port.associators.assert_called_once_with( + wmi_result_class=self.utils._VIRTUAL_SWITCH) + + @mock.patch.object(utils.HyperVUtils, "_get_vswitch_external_port") + def _check_set_switch_ext_port_trunk_vlan(self, + mock_get_vswitch_external_port, desired_endpoint_mode, + trunked_list): + vswitch_external_port = mock_get_vswitch_external_port.return_value + vlan_endpoint = mock.MagicMock() + vswitch_external_port.associators.return_value = [vlan_endpoint] + vlan_endpoint_settings = mock.MagicMock() + vlan_endpoint_settings.TrunkedVLANList = trunked_list + vlan_endpoint.associators.return_value = [vlan_endpoint_settings] + + self.utils.set_switch_external_port_trunk_vlan( + mock.sentinel.FAKE_VSWITCH_NAME, self.FAKE_VLAN_ID, + desired_endpoint_mode) + + mock_get_vswitch_external_port.assert_called_once_with( + mock.sentinel.FAKE_VSWITCH_NAME) + vswitch_external_port.associators.assert_called_once_with( + wmi_association_class=self.utils._BINDS_TO) + vlan_endpoint.associators.assert_called_once_with( + wmi_result_class=self.utils._VLAN_ENDPOINT_SET_DATA) + + self.assertEqual(desired_endpoint_mode, + vlan_endpoint.DesiredEndpointMode) + self.assertIn(self.FAKE_VLAN_ID, + vlan_endpoint_settings.TrunkedVLANList) + + @mock.patch.object(utils.HyperVUtils, "_get_vswitch_external_port") + def test_set_switch_ext_port_trunk_vlan_internal(self, + mock_get_vswitch_external_port): + mock_get_vswitch_external_port.return_value = None + + self.utils.set_switch_external_port_trunk_vlan( + mock.sentinel.FAKE_VSWITCH_NAME, self.FAKE_VLAN_ID, + constants.TRUNK_ENDPOINT_MODE) + + mock_get_vswitch_external_port.assert_called_once_with( + mock.sentinel.FAKE_VSWITCH_NAME) + + def test_set_switch_ext_port_trunk_vlan_trunked_missing(self): + self._check_set_switch_ext_port_trunk_vlan( + desired_endpoint_mode=constants.TRUNK_ENDPOINT_MODE, + trunked_list=[]) + + def test_set_switch_ext_port_trunk_vlan_trunked_added(self): + self._check_set_switch_ext_port_trunk_vlan( + desired_endpoint_mode=constants.TRUNK_ENDPOINT_MODE, + trunked_list=[self.FAKE_VLAN_ID]) diff --git a/neutron/tests/unit/hyperv/test_hyperv_utilsv2.py b/neutron/tests/unit/hyperv/test_hyperv_utilsv2.py index 332f45daed5..8eeab4177a2 100644 --- a/neutron/tests/unit/hyperv/test_hyperv_utilsv2.py +++ b/neutron/tests/unit/hyperv/test_hyperv_utilsv2.py @@ -180,19 +180,6 @@ class TestHyperVUtilsV2(base.BaseTestCase): self.assertRaises(utils.HyperVException, self._utils._get_vswitch, self._FAKE_VSWITCH_NAME) - def test_get_vswitch_external_port(self): - mock_vswitch = mock.MagicMock() - mock_sw_port = mock.MagicMock() - mock_vswitch.associators.return_value = [mock_sw_port] - mock_le = mock_sw_port.associators.return_value - mock_le.__len__.return_value = 1 - mock_le1 = mock_le[0].associators.return_value - mock_le1.__len__.return_value = 1 - - vswitch_port = self._utils._get_vswitch_external_port(mock_vswitch) - - self.assertEqual(mock_sw_port, vswitch_port) - def test_set_vswitch_port_vlan_id(self): mock_port_alloc = mock.MagicMock() self._utils._get_switch_port_allocation = mock.MagicMock(return_value=(