Files
python-scciclient/scciclient/irmc/viom/client.py
Takashi Kajinami c3ca521025 Remove six
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
2025-02-26 13:01:52 +00:00

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)