
Python 2 reached its EOL long time ago so we no longer need to use six to guarantee compatibility with Python 2. Also set the minimum python version to avoid installation in old Python 3 versions which already reached EOL. Change-Id: I8f3fb493fd09943660fdae24bf592fbc3ff7ea2b
446 lines
16 KiB
Python
446 lines
16 KiB
Python
# Copyright 2017 FUJITSU LIMITED
|
|
#
|
|
# 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 re
|
|
import socket
|
|
import struct
|
|
|
|
from scciclient.irmc import scci
|
|
from scciclient.irmc.viom import elcm
|
|
|
|
|
|
ONBOARD = 'onboard'
|
|
ADDON = 'addon'
|
|
_CARD_DICT = {
|
|
ONBOARD: elcm.OnboardCard,
|
|
ADDON: elcm.AddOnCard}
|
|
|
|
LAN = 'LAN'
|
|
FC = 'FC'
|
|
CNA = 'CNA'
|
|
_ADAPTER_DICT = {
|
|
LAN: elcm.LANAdapter,
|
|
FC: elcm.FCAdapter,
|
|
CNA: elcm.CNAAdapter
|
|
}
|
|
_POSSIBLE_CARD_TYPE = _ADAPTER_DICT.keys()
|
|
|
|
_CNA_LAN_FUNC_IDX = 1
|
|
_CNA_FCOE_FUNC_IDX = 2
|
|
_CNA_ISCSI_FUNC_IDX = 3
|
|
|
|
|
|
class _PortHandler(object, metaclass=abc.ABCMeta):
|
|
"""VIOM Configurator with physical port information"""
|
|
|
|
def __init__(self, slot_type, card_type, slot_idx, card_idx, port_idx):
|
|
self.slot_type = slot_type
|
|
self.card_type = card_type
|
|
self.slot_idx = slot_idx
|
|
self.card_idx = card_idx
|
|
self.port_idx = port_idx
|
|
|
|
def need_padding(self):
|
|
return False
|
|
|
|
def create_card(self):
|
|
return _CARD_DICT[self.slot_type](
|
|
self.card_idx, _ADAPTER_DICT[self.card_type]())
|
|
|
|
def create_lan_port(self, mac=None, port_enable=True):
|
|
raise NotImplementedError()
|
|
|
|
def set_lan_port(self, port, mac=None):
|
|
raise NotImplementedError()
|
|
|
|
def set_iscsi_port(self, port, iscsi_boot):
|
|
raise NotImplementedError()
|
|
|
|
def create_iscsi_port(self, iscsi_boot):
|
|
raise NotImplementedError()
|
|
|
|
def set_fc_port(self, port, fc_boot, wwnn=None, wwpn=None):
|
|
raise NotImplementedError()
|
|
|
|
def create_fc_port(self, fc_boot, wwnn=None, wwpn=None):
|
|
raise NotImplementedError()
|
|
|
|
|
|
class _LANPortHandler(_PortHandler):
|
|
"""Configurator for LAN card."""
|
|
|
|
def need_padding(self):
|
|
return True if self.slot_type == ONBOARD else False
|
|
|
|
def create_lan_port(self, mac=None, port_enable=True):
|
|
return elcm.LANPort(self.port_idx,
|
|
port_enable=port_enable,
|
|
use_virtual_addresses=bool(mac),
|
|
mac=mac)
|
|
|
|
def set_lan_port(self, port, mac=None):
|
|
port.port_enable = True
|
|
port.mac = mac
|
|
port.use_virtual_addresses = bool(mac)
|
|
|
|
def set_iscsi_port(self, port, iscsi_boot):
|
|
port.boot = iscsi_boot
|
|
|
|
def create_iscsi_port(self, iscsi_boot):
|
|
return elcm.LANPort(self.port_idx, boot=iscsi_boot, port_enable=True)
|
|
|
|
|
|
class _FCPortHandler(_PortHandler):
|
|
"""Configurator for FC card."""
|
|
|
|
def set_fc_port(self, port, fc_boot, wwnn=None, wwpn=None):
|
|
port.boot = fc_boot
|
|
port.port_enable = True
|
|
port.use_virtual_addresses = bool(wwnn or wwpn)
|
|
port.wwnn = wwnn
|
|
port.wwpn = wwpn
|
|
|
|
def create_fc_port(self, fc_boot, wwnn=None, wwpn=None):
|
|
return elcm.FCPort(self.port_idx, boot=fc_boot, wwnn=wwnn, wwpn=wwpn,
|
|
use_virtual_addresses=bool(wwnn or wwpn),
|
|
port_enable=True)
|
|
|
|
|
|
class _CNAPortHandler(_PortHandler):
|
|
"""Configurator for CNA card."""
|
|
|
|
def _create_port(self, function):
|
|
cna_port = elcm.CNAPort(self.port_idx)
|
|
if not isinstance(function, elcm.LANFunction):
|
|
# LanFunction is must
|
|
cna_port.add_function(
|
|
elcm.LANFunction(_CNA_LAN_FUNC_IDX, function_enable=False,
|
|
boot=None))
|
|
cna_port.add_function(function)
|
|
return cna_port
|
|
|
|
def create_lan_port(self, mac=None, port_enable=True):
|
|
function = elcm.LANFunction(_CNA_LAN_FUNC_IDX,
|
|
function_enable=port_enable,
|
|
use_virtual_addresses=bool(mac),
|
|
mac=mac)
|
|
return self._create_port(function)
|
|
|
|
def set_lan_port(self, port, mac=None):
|
|
function = port.get_function(_CNA_LAN_FUNC_IDX)
|
|
# Lan Function is always created when port is created.
|
|
function.function_enable = True
|
|
function.mac = mac
|
|
function.use_virtual_addresses = bool(mac)
|
|
|
|
def set_iscsi_port(self, port, iscsi_boot):
|
|
function = port.get_function(_CNA_ISCSI_FUNC_IDX)
|
|
if function:
|
|
function.boot = iscsi_boot
|
|
else:
|
|
function = elcm.ISCSIFunction(_CNA_ISCSI_FUNC_IDX, boot=iscsi_boot,
|
|
function_enable=True)
|
|
port.add_function(function)
|
|
|
|
def create_iscsi_port(self, iscsi_boot):
|
|
function = elcm.ISCSIFunction(_CNA_ISCSI_FUNC_IDX, boot=iscsi_boot)
|
|
return self._create_port(function)
|
|
|
|
def set_fc_port(self, port, fc_boot, wwnn=None, wwpn=None):
|
|
function = port.get_function(_CNA_FCOE_FUNC_IDX)
|
|
if function:
|
|
function.boot = fc_boot
|
|
function.use_virtual_addresses = bool(wwnn or wwpn)
|
|
function.wwnn = wwnn
|
|
function.wwpn = wwpn
|
|
else:
|
|
function = elcm.FCoEFunction(
|
|
_CNA_FCOE_FUNC_IDX, boot=fc_boot, function_enable=True,
|
|
use_virtual_addresses=bool(wwnn or wwpn), wwnn=wwnn, wwpn=wwpn)
|
|
port.add_function(function)
|
|
|
|
def create_fc_port(self, fc_boot, wwnn=None, wwpn=None):
|
|
function = elcm.FCoEFunction(
|
|
_CNA_FCOE_FUNC_IDX, boot=fc_boot, function_enable=True,
|
|
use_virtual_addresses=bool(wwnn or wwpn), wwnn=wwnn, wwpn=wwpn)
|
|
return self._create_port(function)
|
|
|
|
|
|
_PORT_HANDLERS = {
|
|
LAN: _LANPortHandler,
|
|
FC: _FCPortHandler,
|
|
CNA: _CNAPortHandler,
|
|
}
|
|
|
|
|
|
def _parse_physical_port_id(port_id):
|
|
|
|
message = ('Physical port ID should follow the format: '
|
|
'<card-type><slot-idx>-<port-idx> like CNA1-1. '
|
|
'<card-type> must be chosen from CNA, FC, or LAN. '
|
|
'<slot-idx> should be 0 for onboard slot or 1-9 for addon '
|
|
'slot. <port-idx> should be 1-9.')
|
|
|
|
m = re.match('^([a-zA-Z]+)([0-9])-([1-9])$', port_id)
|
|
if not m:
|
|
raise scci.SCCIInvalidInputError(message)
|
|
|
|
card_type = m.group(1).upper()
|
|
if card_type not in _POSSIBLE_CARD_TYPE:
|
|
raise scci.SCCIInvalidInputError(message)
|
|
|
|
slot_idx = 0
|
|
if int(m.group(2)) == 0:
|
|
slot_type = ONBOARD
|
|
card_idx = 1
|
|
else:
|
|
slot_type = ADDON
|
|
card_idx = int(m.group(2))
|
|
port_idx = int(m.group(3))
|
|
|
|
return _PORT_HANDLERS[card_type](slot_type, card_type, slot_idx, card_idx,
|
|
port_idx)
|
|
|
|
|
|
def _create_iscsi_boot(initiator_iqn,
|
|
initiator_dhcp=False, initiator_ip=None,
|
|
initiator_netmask=None,
|
|
target_dhcp=False, target_iqn=None, target_ip=None,
|
|
target_port=None, target_lun=None, boot_prio=1,
|
|
chap_user=None, chap_secret=None,
|
|
mutual_chap_secret=None):
|
|
iscsi_initiator = elcm.ISCSIInitiator(dhcp_usage=initiator_dhcp,
|
|
iqn=initiator_iqn,
|
|
ip=initiator_ip,
|
|
subnet=initiator_netmask)
|
|
if chap_user and chap_secret:
|
|
auth_method = 'MutualCHAP' if mutual_chap_secret else 'CHAP'
|
|
else:
|
|
auth_method = 'None'
|
|
iscsi_target = elcm.ISCSITarget(dhcp_usage=target_dhcp,
|
|
iqn=target_iqn,
|
|
ip=target_ip,
|
|
port=target_port,
|
|
lun=target_lun,
|
|
auth_method=auth_method,
|
|
chap_user=chap_user,
|
|
chap_secret=chap_secret,
|
|
mutual_chap_secret=mutual_chap_secret)
|
|
iscsi_boot = elcm.ISCSIBoot(iscsi_initiator,
|
|
iscsi_target,
|
|
boot_prio=boot_prio)
|
|
return iscsi_boot
|
|
|
|
|
|
def _convert_netmask(mask):
|
|
"""Convert netmask from CIDR format(integer) to doted decimal string."""
|
|
if mask not in range(0, 33):
|
|
raise scci.SCCIInvalidInputError(
|
|
'Netmask value is invalid.')
|
|
|
|
return socket.inet_ntoa(struct.pack(
|
|
'!L', int('1' * mask + '0' * (32 - mask), 2)))
|
|
|
|
|
|
class VIOMConfiguration(object):
|
|
"""VIOM Configurator for volume boot"""
|
|
|
|
def __init__(self, irmc_info, identification):
|
|
self.client = elcm.ELCMVIOMClient(irmc_info)
|
|
self.root = elcm.VIOMTable()
|
|
self.root.set_manage_table(
|
|
elcm.ManageTable(identification=identification))
|
|
|
|
def apply(self, reboot=False):
|
|
"""Apply the configuration to iRMC."""
|
|
self.root.use_virtual_addresses = True
|
|
self.root.manage.manage = True
|
|
self.root.mode = 'new'
|
|
self.root.init_boot = reboot
|
|
|
|
self.client.set_profile(self.root.get_json())
|
|
|
|
def terminate(self, reboot=False):
|
|
"""Delete VIOM configuration from iRMC."""
|
|
self.root.manage.manage = False
|
|
self.root.mode = 'delete'
|
|
self.root.init_boot = reboot
|
|
self.client.set_profile(self.root.get_json())
|
|
|
|
def _find_card(self, port_handler):
|
|
slot = self.root.get_slot(port_handler.slot_idx, create=False)
|
|
if not slot:
|
|
return None
|
|
if port_handler.slot_type == ONBOARD:
|
|
return slot.get_onboard_card(port_handler.card_idx)
|
|
else:
|
|
return slot.get_addon_card(port_handler.card_idx)
|
|
|
|
def _get_card(self, port_handler):
|
|
card = self._find_card(port_handler)
|
|
if card:
|
|
return card
|
|
card = port_handler.create_card()
|
|
self.root.get_slot(port_handler.slot_idx).add_card(card)
|
|
return card
|
|
|
|
def _find_port(self, port_handler):
|
|
card = self._find_card(port_handler)
|
|
if not card:
|
|
return None
|
|
return card.get_port(port_handler.port_idx)
|
|
|
|
def _add_port(self, port_handler, port):
|
|
self._pad_former_ports(port_handler)
|
|
card = self._get_card(port_handler)
|
|
card.add_port(port)
|
|
|
|
def set_lan_port(self, port_id, mac=None):
|
|
"""Set LAN port information to configuration.
|
|
|
|
:param port_id: Physical port ID.
|
|
:param mac: virtual MAC address if virtualization is necessary.
|
|
"""
|
|
port_handler = _parse_physical_port_id(port_id)
|
|
port = self._find_port(port_handler)
|
|
if port:
|
|
port_handler.set_lan_port(port, mac)
|
|
else:
|
|
self._add_port(port_handler, port_handler.create_lan_port(mac))
|
|
|
|
def set_iscsi_volume(self, port_id,
|
|
initiator_iqn, initiator_dhcp=False,
|
|
initiator_ip=None, initiator_netmask=None,
|
|
target_dhcp=False, target_iqn=None, target_ip=None,
|
|
target_port=3260, target_lun=0, boot_prio=1,
|
|
chap_user=None, chap_secret=None,
|
|
mutual_chap_secret=None):
|
|
"""Set iSCSI volume information to configuration.
|
|
|
|
:param port_id: Physical port ID.
|
|
:param initiator_iqn: IQN of initiator.
|
|
:param initiator_dhcp: True if DHCP is used in the iSCSI network.
|
|
:param initiator_ip: IP address of initiator. None if DHCP is used.
|
|
:param initiator_netmask: Netmask of initiator as integer. None if
|
|
DHCP is used.
|
|
:param target_dhcp: True if DHCP is used for iSCSI target.
|
|
:param target_iqn: IQN of target. None if DHCP is used.
|
|
:param target_ip: IP address of target. None if DHCP is used.
|
|
:param target_port: Port number of target. None if DHCP is used.
|
|
:param target_lun: LUN number of target. None if DHCP is used,
|
|
:param boot_prio: Boot priority of the volume. 1 indicates the highest
|
|
priority.
|
|
"""
|
|
|
|
initiator_netmask = (_convert_netmask(initiator_netmask)
|
|
if initiator_netmask else None)
|
|
|
|
port_handler = _parse_physical_port_id(port_id)
|
|
iscsi_boot = _create_iscsi_boot(
|
|
initiator_iqn,
|
|
initiator_dhcp=initiator_dhcp,
|
|
initiator_ip=initiator_ip,
|
|
initiator_netmask=initiator_netmask,
|
|
target_dhcp=target_dhcp,
|
|
target_iqn=target_iqn,
|
|
target_ip=target_ip,
|
|
target_port=target_port,
|
|
target_lun=target_lun,
|
|
boot_prio=boot_prio,
|
|
chap_user=chap_user,
|
|
chap_secret=chap_secret,
|
|
mutual_chap_secret=mutual_chap_secret)
|
|
|
|
port = self._find_port(port_handler)
|
|
if port:
|
|
port_handler.set_iscsi_port(port, iscsi_boot)
|
|
else:
|
|
port = port_handler.create_iscsi_port(iscsi_boot)
|
|
self._add_port(port_handler, port)
|
|
|
|
def set_fc_volume(self, port_id,
|
|
target_wwn, target_lun=0, boot_prio=1,
|
|
initiator_wwnn=None, initiator_wwpn=None):
|
|
"""Set FibreChannel volume information to configuration.
|
|
|
|
:param port_id: Physical port ID.
|
|
:param target_wwn: WWN of target.
|
|
:param target_lun: LUN number of target.
|
|
:param boot_prio: Boot priority of the volume. 1 indicates the highest
|
|
priority.
|
|
:param initiator_wwnn: Virtual WWNN for initiator if necessary.
|
|
:param initiator_wwpn: Virtual WWPN for initiator if necessary.
|
|
"""
|
|
port_handler = _parse_physical_port_id(port_id)
|
|
fc_target = elcm.FCTarget(target_wwn, target_lun)
|
|
fc_boot = elcm.FCBoot(boot_prio=boot_prio, boot_enable=True)
|
|
fc_boot.add_target(fc_target)
|
|
|
|
port = self._find_port(port_handler)
|
|
if port:
|
|
port_handler.set_fc_port(port, fc_boot,
|
|
wwnn=initiator_wwnn, wwpn=initiator_wwpn)
|
|
else:
|
|
port = port_handler.create_fc_port(fc_boot,
|
|
wwnn=initiator_wwnn,
|
|
wwpn=initiator_wwpn)
|
|
self._add_port(port_handler, port)
|
|
|
|
def dump_json(self):
|
|
"""Create JSON profile based on current configuration.
|
|
|
|
:returns: JSON data created from current configurtion. It can be
|
|
logged by a caller.
|
|
"""
|
|
return self.root.get_json()
|
|
|
|
def _pad_former_ports(self, port_handler):
|
|
"""Create ports with former port index.
|
|
|
|
:param port_handler: Port information to be registered.
|
|
|
|
Depending on slot type and card type, it is necessary to register
|
|
LAN ports with former index to VIOM table.
|
|
"""
|
|
if not port_handler.need_padding():
|
|
return
|
|
for port_idx in range(1, port_handler.port_idx):
|
|
pad_handler = port_handler.__class__(
|
|
port_handler.slot_type,
|
|
port_handler.card_type,
|
|
port_handler.slot_idx,
|
|
port_handler.card_idx,
|
|
port_idx)
|
|
if not self._find_port(pad_handler):
|
|
self._add_port(pad_handler,
|
|
pad_handler.create_lan_port())
|
|
|
|
|
|
def validate_physical_port_id(port_id):
|
|
"""Validate physical port ID.
|
|
|
|
Physical port ID is required for configure interfaces with VIOM API. The
|
|
format is:
|
|
<Card type><Slot Idx>-<Port Idx>
|
|
|
|
* Card type is chosen from CNA, FC or LAN.
|
|
* Slot Idx should be 0, which indecates on-board slot or 1-9, which
|
|
specify add-on slot index.
|
|
* Port Idx should be 1-9, which indicate port number.
|
|
|
|
:param port_id: Physical port ID following the format.
|
|
"""
|
|
_parse_physical_port_id(port_id)
|