
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 <avladu@cloudbasesolutions.com>
209 lines
7.6 KiB
Python
209 lines
7.6 KiB
Python
# 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())
|