From f99a8daabf368ad98765b46e62c4020a87ea7ea3 Mon Sep 17 00:00:00 2001 From: Alessandro Pilotti Date: Mon, 20 Aug 2018 02:58:27 +0300 Subject: [PATCH] Add NetLBFO network teaming On Windows two main options are available to create network adapter teams: NetLBFO (starting with Windows Server 2012) and SET (starting with Windows Server 2016, requiring a VMSwitch). This patch adds support for for NetLBFO. Partially-Implements: blueprint json-network-config Change-Id: Id80d24db0239acb8cb9d3ba5f9eaca1191957f1a Co-Authored-By: Adrian Vladu --- cloudbaseinit/osutils/base.py | 8 + cloudbaseinit/osutils/windows.py | 40 +++ cloudbaseinit/tests/osutils/test_windows.py | 55 ++++ .../tests/utils/windows/test_netlbfo.py | 262 ++++++++++++++++++ cloudbaseinit/utils/network_team.py | 34 +++ cloudbaseinit/utils/windows/netlbfo.py | 208 ++++++++++++++ 6 files changed, 607 insertions(+) create mode 100644 cloudbaseinit/tests/utils/windows/test_netlbfo.py create mode 100644 cloudbaseinit/utils/network_team.py create mode 100644 cloudbaseinit/utils/windows/netlbfo.py diff --git a/cloudbaseinit/osutils/base.py b/cloudbaseinit/osutils/base.py index 8af0656f..8d50f521 100644 --- a/cloudbaseinit/osutils/base.py +++ b/cloudbaseinit/osutils/base.py @@ -88,6 +88,14 @@ class BaseOSUtils(object): broadcast, gateway, dnsnameservers): raise NotImplementedError() + def create_network_team(self, team_name, mode, load_balancing_algorithm, + members, mac_address, primary_nic_name=None, + primary_nic_vlan_id=None, lacp_timer=None): + raise NotImplementedError() + + def add_network_team_nic(self, team_name, nic_name, vlan_id): + raise NotImplementedError() + def set_config_value(self, name, value, section=None): raise NotImplementedError() diff --git a/cloudbaseinit/osutils/windows.py b/cloudbaseinit/osutils/windows.py index e4ece759..7d1210c2 100644 --- a/cloudbaseinit/osutils/windows.py +++ b/cloudbaseinit/osutils/windows.py @@ -37,6 +37,7 @@ import winerror from cloudbaseinit import exception from cloudbaseinit.osutils import base +from cloudbaseinit.utils import classloader from cloudbaseinit.utils.windows import disk from cloudbaseinit.utils.windows import network from cloudbaseinit.utils.windows import privilege @@ -415,6 +416,11 @@ class WindowsUtils(base.BaseOSUtils): _FW_SCOPE_ALL = 0 _FW_SCOPE_LOCAL_SUBNET = 1 + VER_NT_WORKSTATION = 1 + + def __init__(self): + self._network_team_manager = None + def reboot(self): with privilege.acquire_privilege(win32security.SE_SHUTDOWN_NAME): ret_val = advapi32.InitiateSystemShutdownExW( @@ -867,6 +873,37 @@ class WindowsUtils(base.BaseOSUtils): except wmi.x_wmi as exc: raise exception.CloudbaseInitException(exc.com_error) + def _get_network_team_manager(self): + if self._network_team_manager: + return self._network_team_manager + + team_managers = [ + "cloudbaseinit.utils.windows.netlbfo.NetLBFOTeamManager", + ] + + cl = classloader.ClassLoader() + for class_name in team_managers: + try: + cls = cl.load_class(class_name) + if cls.is_available(): + self._network_team_manager = cls() + return self._network_team_manager + except Exception as ex: + LOG.exception(ex) + raise exception.ItemNotFoundException( + "No network team manager available") + + def create_network_team(self, team_name, mode, load_balancing_algorithm, + members, mac_address, primary_nic_name=None, + primary_nic_vlan_id=None, lacp_timer=None): + self._get_network_team_manager().create_team( + team_name, mode, load_balancing_algorithm, members, mac_address, + primary_nic_name, primary_nic_vlan_id, lacp_timer) + + def add_network_team_nic(self, team_name, nic_name, vlan_id): + self._get_network_team_manager().add_team_nic( + team_name, nic_name, vlan_id) + def _get_config_key_name(self, section): key_name = self._config_key if section: @@ -1187,6 +1224,9 @@ class WindowsUtils(base.BaseOSUtils): "suite_mask": vi.wSuiteMask, "product_type": vi.wProductType} + def is_client_os(self): + return self.get_os_version()["product_type"] == self.VER_NT_WORKSTATION + def check_os_version(self, major, minor, build=0): vi = Win32_OSVERSIONINFOEX_W() vi.dwOSVersionInfoSize = ctypes.sizeof(Win32_OSVERSIONINFOEX_W) diff --git a/cloudbaseinit/tests/osutils/test_windows.py b/cloudbaseinit/tests/osutils/test_windows.py index f740528b..e3684d9f 100644 --- a/cloudbaseinit/tests/osutils/test_windows.py +++ b/cloudbaseinit/tests/osutils/test_windows.py @@ -61,6 +61,7 @@ class TestWindowsUtils(testutils.CloudbaseInitTestBase): self._win32service_mock = mock.MagicMock() self._winerror_mock = mock.MagicMock() self._winerror_mock.ERROR_SERVICE_DOES_NOT_EXIST = 0x424 + self._mi_mock = mock.MagicMock() self._wmi_mock = mock.MagicMock() self._wmi_mock.x_wmi = WMIError self._moves_mock = mock.MagicMock() @@ -81,6 +82,7 @@ class TestWindowsUtils(testutils.CloudbaseInitTestBase): 'win32netcon': self._win32netcon_mock, 'win32service': self._win32service_mock, 'winerror': self._winerror_mock, + 'mi': self._mi_mock, 'wmi': self._wmi_mock, 'six.moves': self._moves_mock, 'six.moves.xmlrpc_client': self._xmlrpc_client_mock, @@ -1328,6 +1330,14 @@ class TestWindowsUtils(testutils.CloudbaseInitTestBase): def test_check_os_version_fail(self): self._test_check_os_version(ret_val=mock.Mock(), fail=True) + @mock.patch('cloudbaseinit.osutils.windows.WindowsUtils' + '.get_os_version') + def test_is_client_os(self, mock_get_os_version): + mock_get_os_version.return_value = { + "product_type": self._winutils.VER_NT_WORKSTATION} + + self.assertEqual(True, self._winutils.is_client_os()) + def _test_get_volume_label(self, ret_val): label = mock.MagicMock() max_label_size = 261 @@ -2630,3 +2640,48 @@ class TestWindowsUtils(testutils.CloudbaseInitTestBase): self._win32api_mock.GetFileVersionInfo.assert_called_once_with( mock_path, '\\') self.assertIsNotNone(res) + + @mock.patch('cloudbaseinit.utils.windows.netlbfo.NetLBFOTeamManager') + def test_get_network_team_manager(self, mock_netlbfo_team_manager): + mock_netlbfo_team_manager.is_available.return_value = True + self.assertEqual( + mock_netlbfo_team_manager.return_value, + self._winutils._get_network_team_manager()) + + @mock.patch('cloudbaseinit.utils.windows.netlbfo.NetLBFOTeamManager') + def test_get_network_team_manager_not_found(self, + mock_netlbfo_team_manager): + mock_netlbfo_team_manager.is_available.return_value = False + self.assertRaises( + exception.ItemNotFoundException, + self._winutils._get_network_team_manager) + + @mock.patch('cloudbaseinit.osutils.windows.WindowsUtils.' + '_get_network_team_manager') + def test_create_network_team(self, mock_get_network_team_manager): + mock_team_manager = mock_get_network_team_manager.return_value + + self._winutils.create_network_team( + mock.sentinel.team_name, mock.sentinel.mode, + mock.sentinel.lb_algo, mock.sentinel.members, + mock.sentinel.mac, mock.sentinel.primary_name, + mock.sentinel.vlan_id, mock.sentinel.lacp_timer) + + mock_team_manager.create_team.assert_called_once_with( + mock.sentinel.team_name, mock.sentinel.mode, + mock.sentinel.lb_algo, mock.sentinel.members, + mock.sentinel.mac, mock.sentinel.primary_name, + mock.sentinel.vlan_id, mock.sentinel.lacp_timer) + + @mock.patch('cloudbaseinit.osutils.windows.WindowsUtils.' + '_get_network_team_manager') + def test_add_network_team_nic(self, mock_get_network_team_manager): + mock_team_manager = mock_get_network_team_manager.return_value + + self._winutils.add_network_team_nic( + mock.sentinel.team_name, mock.sentinel.nic_name, + mock.sentinel.vlan_id) + + mock_team_manager.add_team_nic.assert_called_once_with( + mock.sentinel.team_name, mock.sentinel.nic_name, + mock.sentinel.vlan_id) diff --git a/cloudbaseinit/tests/utils/windows/test_netlbfo.py b/cloudbaseinit/tests/utils/windows/test_netlbfo.py new file mode 100644 index 00000000..7e9f81e9 --- /dev/null +++ b/cloudbaseinit/tests/utils/windows/test_netlbfo.py @@ -0,0 +1,262 @@ +# Copyright (c) 2017 Cloudbase Solutions Srl +# +# 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 importlib +import unittest + +try: + import unittest.mock as mock +except ImportError: + import mock + +from cloudbaseinit import exception +from cloudbaseinit.models import network as network_model + +MODPATH = "cloudbaseinit.utils.windows.netlbfo" + + +class NetLBFOTest(unittest.TestCase): + + def setUp(self): + self._wmi_mock = mock.MagicMock() + self._mi_mock = mock.MagicMock() + self._module_patcher = mock.patch.dict( + 'sys.modules', { + 'wmi': self._wmi_mock, + 'mi': self._mi_mock}) + self._module_patcher.start() + self._netlbfo = importlib.import_module(MODPATH) + + def tearDown(self): + self._module_patcher.stop() + + @mock.patch(MODPATH + '.NetLBFOTeamManager._get_primary_adapter_name') + @mock.patch(MODPATH + '.NetLBFOTeamManager._add_team_member') + @mock.patch(MODPATH + '.NetLBFOTeamManager._set_primary_nic_vlan_id') + @mock.patch(MODPATH + '.NetLBFOTeamManager._wait_for_nic') + @mock.patch(MODPATH + '.NetLBFOTeamManager.delete_team') + def _test_create_team(self, mock_delete_team, mock_wait_for_nic, + mock_set_primary_nic_vlan_id, mock_add_team_member, + mock_get_primary_adapter_name, mode_not_found=False, + lb_algo_not_found=False, add_team_member_fail=False): + mock_get_primary_adapter_name.return_value = mock.sentinel.pri_nic_name + + lacp_timer = network_model.BOND_LACP_RATE_FAST + members = [mock.sentinel.pri_nic_name, mock.sentinel.other_member] + + conn = self._wmi_mock.WMI.return_value + mock_team = mock.Mock() + conn.MSFT_NetLbfoTeam.new.return_value = mock_team + + if mode_not_found: + mode = "fake mode" + else: + mode = network_model.BOND_TYPE_8023AD + + if lb_algo_not_found: + lb_algo = "fake lb algo" + else: + lb_algo = network_model.BOND_LB_ALGO_L2 + + if add_team_member_fail: + ex = exception.CloudbaseInitException + mock_add_team_member.side_effect = ex + + if mode_not_found or lb_algo_not_found: + self.assertRaises( + exception.ItemNotFoundException, + self._netlbfo.NetLBFOTeamManager().create_team, + mock.sentinel.team_name, mode, lb_algo, members, + mock.sentinel.mac, mock.sentinel.pri_nic_name, + mock.sentinel.vlan_id, lacp_timer) + return + elif add_team_member_fail: + self.assertRaises( + exception.CloudbaseInitException, + self._netlbfo.NetLBFOTeamManager().create_team, + mock.sentinel.team_name, mode, lb_algo, members, + mock.sentinel.mac, mock.sentinel.pri_nic_name, + mock.sentinel.vlan_id, lacp_timer) + else: + self._netlbfo.NetLBFOTeamManager().create_team( + mock.sentinel.team_name, mode, lb_algo, members, + mock.sentinel.mac, mock.sentinel.pri_nic_name, + mock.sentinel.vlan_id, lacp_timer) + + custom_options = [ + { + u'name': u'TeamMembers', + u'value_type': + self._mi_mock.MI_ARRAY | self._mi_mock.MI_STRING, + u'value': [mock.sentinel.pri_nic_name] + }, + { + u'name': u'TeamNicName', + u'value_type': self._mi_mock.MI_STRING, + u'value': mock.sentinel.pri_nic_name + } + ] + + operation_options = {u'custom_options': custom_options} + mock_team.put.assert_called_once_with( + operation_options=operation_options) + + mock_add_team_member.assert_called_once_with( + conn, mock.sentinel.team_name, mock.sentinel.other_member) + + if not add_team_member_fail: + mock_set_primary_nic_vlan_id.assert_called_once_with( + conn, mock.sentinel.team_name, mock.sentinel.vlan_id) + + mock_wait_for_nic.assert_called_once_with( + mock.sentinel.pri_nic_name) + else: + mock_delete_team.assert_called_once_with(mock.sentinel.team_name) + + def test_create_team(self): + self._test_create_team() + + def test_create_team_mode_not_found(self): + self._test_create_team(mode_not_found=True) + + def test_create_team_mode_lb_algo_not_found(self): + self._test_create_team(lb_algo_not_found=True) + + def test_create_team_add_team_member_fail(self): + self._test_create_team(add_team_member_fail=True) + + def test_delete_team(self): + conn = self._wmi_mock.WMI.return_value + mock_team = mock.Mock() + conn.MSFT_NetLbfoTeam.return_value = [mock_team] + + self._netlbfo.NetLBFOTeamManager().delete_team(mock.sentinel.team_name) + + conn.MSFT_NetLbfoTeam.assert_called_once_with( + name=mock.sentinel.team_name) + mock_team.Delete_.assert_called_once_with() + + @mock.patch(MODPATH + '.NetLBFOTeamManager._wait_for_nic') + def test_add_team_nic(self, mock_wait_for_nic): + conn = self._wmi_mock.WMI.return_value + mock_team_nic = mock.Mock() + conn.MSFT_NetLbfoTeamNIC.new.return_value = mock_team_nic + + self._netlbfo.NetLBFOTeamManager().add_team_nic( + mock.sentinel.team_name, mock.sentinel.nic_name, + mock.sentinel.vlan_id) + + self.assertEqual(mock.sentinel.team_name, mock_team_nic.Team) + self.assertEqual(mock.sentinel.nic_name, mock_team_nic.Name) + self.assertEqual(mock.sentinel.vlan_id, mock_team_nic.VlanID) + mock_team_nic.put.assert_called_once_with() + mock_wait_for_nic.assert_called_once_with(mock_team_nic.Name) + + @mock.patch('cloudbaseinit.osutils.factory.get_os_utils') + def test_is_available(self, mock_get_os_utils): + os_utils = mock_get_os_utils.return_value + os_utils.check_os_version.return_value = True + os_utils.is_client_os.return_value = False + with mock.patch('sys.platform', 'win32'): + self.assertEqual( + True, self._netlbfo.NetLBFOTeamManager.is_available()) + + @mock.patch('time.sleep') + def test_wait_for_nic(self, mock_sleep): + conn = self._wmi_mock.WMI.return_value + conn.Win32_NetworkAdapter.side_effect = [ + [], [mock.sentinel.net_adapter]] + + self._netlbfo.NetLBFOTeamManager()._wait_for_nic( + mock.sentinel.nic_name) + + conn.Win32_NetworkAdapter.assert_has_calls([ + mock.call(NetConnectionID=mock.sentinel.nic_name), + mock.call(NetConnectionID=mock.sentinel.nic_name)]) + mock_sleep.assert_called_once_with(1) + + def test_set_primary_nic_vlan_id(self): + conn = mock.Mock() + mock_team_nic = mock.Mock() + conn.MSFT_NetLbfoTeamNIC.return_value = [mock_team_nic] + + self._netlbfo.NetLBFOTeamManager()._set_primary_nic_vlan_id( + conn, mock.sentinel.team_name, mock.sentinel.vlan_id) + + custom_options = [{ + u'name': u'VlanID', + u'value_type': self._mi_mock.MI_UINT32, + u'value': mock.sentinel.vlan_id + }] + operation_options = {u'custom_options': custom_options} + mock_team_nic.put.assert_called_once_with( + operation_options=operation_options) + + def test_add_team_member(self): + conn = mock.Mock() + mock_team_member = mock.Mock() + conn.MSFT_NetLbfoTeamMember.new.return_value = mock_team_member + + self._netlbfo.NetLBFOTeamManager()._add_team_member( + conn, mock.sentinel.team_name, mock.sentinel.team_member) + + custom_options = [{ + u'name': u'Name', + u'value_type': self._mi_mock.MI_STRING, + u'value': mock.sentinel.team_member + }] + operation_options = {u'custom_options': custom_options} + mock_team_member.put.assert_called_once_with( + operation_options=operation_options) + self.assertEqual(mock.sentinel.team_name, mock_team_member.Team) + + def _test_get_primary_adapter_name(self, mac_not_found=False, + member_not_found=False): + mock_members = [mock.sentinel.team_member] + conn = self._wmi_mock.WMI.return_value + + if mac_not_found: + conn.Win32_NetworkAdapter.return_value = [] + else: + conn.Win32_NetworkAdapter.return_value = [ + mock.sentinel.net_adapter] + + if member_not_found: + net_conn_id = mock.sentinel.something_else + else: + net_conn_id = mock.sentinel.team_member + mock.sentinel.net_adapter.NetConnectionID = net_conn_id + + if mac_not_found or member_not_found: + self.assertRaises( + exception.ItemNotFoundException, + self._netlbfo.NetLBFOTeamManager()._get_primary_adapter_name, + mock_members, mock.sentinel.mac) + else: + self.assertEqual( + mock.sentinel.team_member, + self._netlbfo.NetLBFOTeamManager()._get_primary_adapter_name( + mock_members, mock.sentinel.mac)) + + conn.Win32_NetworkAdapter.assert_called_once_with( + MACAddress=mock.sentinel.mac) + + def test_get_primary_adapter_name(self): + self._test_get_primary_adapter_name() + + def test_get_primary_adapter_name_mac_not_found(self): + self._test_get_primary_adapter_name(mac_not_found=True) + + def test_get_primary_adapter_name_member_not_found(self): + self._test_get_primary_adapter_name(member_not_found=True) diff --git a/cloudbaseinit/utils/network_team.py b/cloudbaseinit/utils/network_team.py new file mode 100644 index 00000000..3fdc4b17 --- /dev/null +++ b/cloudbaseinit/utils/network_team.py @@ -0,0 +1,34 @@ +# Copyright 2018 Cloudbase Solutions Srl +# +# 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 abc + +import six + + +@six.add_metaclass(abc.ABCMeta) +class BaseNetworkTeamManager(object): + @abc.abstractmethod + def create_team(self, team_name, mode, load_balancing_algorithm, + members, mac_address, primary_nic_name=None, + primary_nic_vlan_id=None, lacp_timer=None): + pass + + @abc.abstractmethod + def add_team_nic(self, team_name, nic_name, vlan_id): + pass + + @abc.abstractmethod + def delete_team(self, name): + pass diff --git a/cloudbaseinit/utils/windows/netlbfo.py b/cloudbaseinit/utils/windows/netlbfo.py new file mode 100644 index 00000000..c904ce65 --- /dev/null +++ b/cloudbaseinit/utils/windows/netlbfo.py @@ -0,0 +1,208 @@ +# Copyright 2018 Cloudbase Solutions Srl +# +# 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 sys +import time + +import mi +from oslo_log import log as oslo_logging +import wmi + +from cloudbaseinit import exception +from cloudbaseinit.models import network as network_model +from cloudbaseinit.osutils import factory as osutils_factory +from cloudbaseinit.utils import network_team + +LBFO_TEAM_MODE_STATIC = 0 +LBFO_TEAM_MODE_SWITCH_INDEPENDENT = 1 +LBFO_TEAM_MODE_LACP = 2 + +LBFO_TEAM_ALGORITHM_TRANSPORT_PORTS = 0 +LBFO_TEAM_ALGORITHM_IP_ADDRESSES = 2 +LBFO_TEAM_ALGORITHM_MAC_ADDRESSES = 3 +LBFO_TEAM_ALGORITHM_HYPERV_PORT = 4 +LBFO_TEAM_ALGORITHM_DYNAMIC = 5 + +LBFO_TEST_LACP_TIMER_SLOW = 0 +LBFO_TEST_LACP_TIMER_FAST = 1 + +NETWORK_MODEL_TEAM_MODE_MAP = { + network_model.BOND_TYPE_8023AD: LBFO_TEAM_MODE_LACP, + network_model.BOND_TYPE_BALANCE_RR: LBFO_TEAM_MODE_STATIC, + network_model.BOND_TYPE_ACTIVE_BACKUP: LBFO_TEAM_MODE_SWITCH_INDEPENDENT, + network_model.BOND_TYPE_BALANCE_XOR: LBFO_TEAM_MODE_STATIC, + network_model.BOND_TYPE_BALANCE_TLB: LBFO_TEAM_MODE_SWITCH_INDEPENDENT, + network_model.BOND_TYPE_BALANCE_ALB: LBFO_TEAM_MODE_SWITCH_INDEPENDENT, +} + +NETWORK_MODEL_LB_ALGO_MAP = { + network_model.BOND_LB_ALGO_L2: LBFO_TEAM_ALGORITHM_MAC_ADDRESSES, + network_model.BOND_LB_ALGO_L2_L3: LBFO_TEAM_ALGORITHM_IP_ADDRESSES, + network_model.BOND_LB_ALGO_L3_L4: LBFO_TEAM_ALGORITHM_TRANSPORT_PORTS, + network_model.BOND_LB_ENCAP_L2_L3: LBFO_TEAM_ALGORITHM_IP_ADDRESSES, + network_model.BOND_LB_ENCAP_L3_L4: LBFO_TEAM_ALGORITHM_TRANSPORT_PORTS, +} + +NETWORK_MODEL_LACP_RATE_MAP = { + network_model.BOND_LACP_RATE_FAST: LBFO_TEST_LACP_TIMER_FAST, + network_model.BOND_LACP_RATE_SLOW: LBFO_TEST_LACP_TIMER_SLOW, +} + +LOG = oslo_logging.getLogger(__name__) + + +class NetLBFOTeamManager(network_team.BaseNetworkTeamManager): + @staticmethod + def _get_primary_adapter_name(members, mac_address): + conn = wmi.WMI(moniker='root/cimv2') + adapters = conn.Win32_NetworkAdapter(MACAddress=mac_address) + if not adapters: + raise exception.ItemNotFoundException( + "No adapter with MAC address \"%s\" found" % mac_address) + primary_adapter_name = adapters[0].NetConnectionID + + if primary_adapter_name not in members: + raise exception.ItemNotFoundException( + "Adapter \"%s\" not found in members" % primary_adapter_name) + return primary_adapter_name + + @staticmethod + def _add_team_member(conn, team_name, member): + team_member = conn.MSFT_NetLbfoTeamMember.new() + team_member.Team = team_name + custom_options = [{ + u'name': u'Name', + u'value_type': mi.MI_STRING, + u'value': member + }] + operation_options = {u'custom_options': custom_options} + team_member.put(operation_options=operation_options) + + @staticmethod + def _set_primary_nic_vlan_id(conn, team_name, vlan_id): + team_nic = conn.MSFT_NetLbfoTeamNIC(Team=team_name, Primary=True)[0] + + custom_options = [{ + u'name': u'VlanID', + u'value_type': mi.MI_UINT32, + u'value': vlan_id + }] + + operation_options = {u'custom_options': custom_options} + team_nic.put(operation_options=operation_options) + + def create_team(self, team_name, mode, load_balancing_algorithm, + members, mac_address, primary_nic_name=None, + primary_nic_vlan_id=None, lacp_timer=None): + conn = wmi.WMI(moniker='root/standardcimv2') + + primary_adapter_name = self._get_primary_adapter_name( + members, mac_address) + + teaming_mode = NETWORK_MODEL_TEAM_MODE_MAP.get(mode) + if teaming_mode is None: + raise exception.ItemNotFoundException( + "Unsupported teaming mode: %s" % mode) + + if load_balancing_algorithm is None: + lb_algo = LBFO_TEAM_ALGORITHM_DYNAMIC + else: + lb_algo = NETWORK_MODEL_LB_ALGO_MAP.get( + load_balancing_algorithm) + if lb_algo is None: + raise exception.ItemNotFoundException( + "Unsupported LB algorithm: %s" % load_balancing_algorithm) + + team = conn.MSFT_NetLbfoTeam.new() + team.Name = team_name + team.TeamingMode = teaming_mode + team.LoadBalancingAlgorithm = lb_algo + + if lacp_timer is not None and team.TeamingMode == LBFO_TEAM_MODE_LACP: + team.LacpTimer = NETWORK_MODEL_LACP_RATE_MAP[lacp_timer] + + nic_name = primary_nic_name or team_name + custom_options = [ + { + u'name': u'TeamMembers', + u'value_type': mi.MI_ARRAY | mi.MI_STRING, + u'value': [primary_adapter_name] + }, + { + u'name': u'TeamNicName', + u'value_type': mi.MI_STRING, + u'value': nic_name + } + ] + + operation_options = {u'custom_options': custom_options} + team.put(operation_options=operation_options) + + try: + for member in members: + if member != primary_adapter_name: + self._add_team_member(conn, team_name, member) + + if primary_nic_vlan_id is not None: + self._set_primary_nic_vlan_id( + conn, team_name, primary_nic_vlan_id) + + if primary_nic_name != team_name or primary_nic_vlan_id is None: + # TODO(alexpilotti): query the new nic name + # When the nic name equals the bond name and a VLAN ID is set, + # the nick name is changed. + self._wait_for_nic(nic_name) + except Exception as ex: + self.delete_team(team_name) + raise ex + + @staticmethod + def _wait_for_nic(nic_name): + conn = wmi.WMI(moniker='//./root/cimv2') + max_count = 100 + i = 0 + while True: + if len(conn.Win32_NetworkAdapter(NetConnectionID=nic_name)): + break + else: + i += 1 + if i >= max_count: + raise exception.ItemNotFoundException( + "Cannot find team NIC: %s" % nic_name) + LOG.debug("Waiting for team NIC: %s", nic_name) + time.sleep(1) + + def add_team_nic(self, team_name, nic_name, vlan_id): + conn = wmi.WMI(moniker='root/standardcimv2') + team_nic = conn.MSFT_NetLbfoTeamNIC.new() + team_nic.Team = team_name + team_nic.Name = nic_name + team_nic.VlanID = vlan_id + team_nic.put() + # Ensure that the NIC is visible in the OS before returning + self._wait_for_nic(nic_name) + + def delete_team(self, team_name): + conn = wmi.WMI(moniker='root/standardcimv2') + teams = conn.MSFT_NetLbfoTeam(name=team_name) + if not teams: + raise exception.ItemNotFoundException( + "Team not found: %s" % team_name) + teams[0].Delete_() + + @classmethod + def is_available(cls): + osutils = osutils_factory.get_os_utils() + return (sys.platform == 'win32' and osutils.check_os_version(6, 2) and + not osutils.is_client_os())