# 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: ' '- like CNA1-1. ' ' must be chosen from CNA, FC, or LAN. ' ' should be 0 for onboard slot or 1-9 for addon ' 'slot. 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 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)