
Update tox.ini to be current. Move some requirements from test-requirements directly to tox.ini. Include a py27 constriants file for running py27 tox tests. Remove lower-constraints.txt, it's not used anymore. Use wallaby jobs. Also fix some pep8 issues. Change-Id: Ia29128ed6c12e1c51c0a34556051a3566c4f9bfd
449 lines
16 KiB
Python
449 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
|
|
|
|
import six
|
|
|
|
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
|
|
|
|
|
|
@six.add_metaclass(abc.ABCMeta)
|
|
class _PortHandler(object):
|
|
"""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)
|