From 7e9169b2a1ac1a3c0e01e38583e79006637fb09e Mon Sep 17 00:00:00 2001 From: Hironori Shiina Date: Wed, 26 Apr 2017 16:21:50 +0000 Subject: [PATCH] Add configuration client for VIOM configuration This patch adds configuration client, which makes it easier to set a VIOM(AdapterConfig) profile to iRMC than creating a VIOM profile directly. This client provides features to set up volume boot to a server, which is necessary for OpenStack Ironic iRMC driver. Change-Id: I6e946f5344c67a676a5f8e7cead2a8eb62150f8b --- scciclient/irmc/viom/client.py | 447 +++++++++++ scciclient/tests/irmc/viom/test_client.py | 894 ++++++++++++++++++++++ 2 files changed, 1341 insertions(+) create mode 100644 scciclient/irmc/viom/client.py create mode 100644 scciclient/tests/irmc/viom/test_client.py diff --git a/scciclient/irmc/viom/client.py b/scciclient/irmc/viom/client.py new file mode 100644 index 0000000..3d33d10 --- /dev/null +++ b/scciclient/irmc/viom/client.py @@ -0,0 +1,447 @@ +# 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 six +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 + + +@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: ' + '- 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) diff --git a/scciclient/tests/irmc/viom/test_client.py b/scciclient/tests/irmc/viom/test_client.py new file mode 100644 index 0000000..cab815e --- /dev/null +++ b/scciclient/tests/irmc/viom/test_client.py @@ -0,0 +1,894 @@ +# 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 mock +import testtools + +from scciclient.irmc import scci +from scciclient.irmc.viom import client as viom_client +from scciclient.irmc.viom import elcm as viom_elcm + + +class VIOMConfigurationTestCase(testtools.TestCase): + + def setUp(self): + super(VIOMConfigurationTestCase, self).setUp() + + self.irmc_info = { + 'irmc_address': '10.124.196.159', + 'irmc_username': 'admin', + 'irmc_password': 'admin0', + 'irmc_port': 80, + 'irmc_auth_method': 'basic', + 'irmc_client_timeout': 60, + } + self.identification = 'viom_identification' + self.configurator = viom_client.VIOMConfiguration( + self.irmc_info, self.identification) + + @mock.patch.object(viom_elcm.ELCMVIOMClient, 'set_profile') + def _test_terminate(self, mock_set, reboot=None): + if reboot is None: + self.configurator.terminate() + reboot = False + else: + self.configurator.terminate(reboot=reboot) + expected_json = { + 'VIOMManage': { + 'Manage': False, + 'Identification': 'viom_identification', + }, + 'InitBoot': reboot, + 'Mode': 'delete', + } + mock_set.assert_called_once_with(expected_json) + + def test_terminate(self): + self._test_terminate() + + def test_terminate_reboot_true(self): + self._test_terminate(reboot=True) + + def test_terminate_reboot_false(self): + self._test_terminate(reboot=False) + + @staticmethod + def _create_json_for_apply(slot_json, init_boot=False): + return { + 'UseVirtualAddresses': True, + 'Mode': 'new', + 'InitBoot': init_boot, + 'VIOMManage': { + 'Manage': True, + 'Identification': 'viom_identification' + }, + 'Slots': { + 'Slot': [slot_json] + } + } + + @staticmethod + def _create_json_before_apply(slot_json): + return { + 'VIOMManage': { + 'Identification': 'viom_identification' + }, + 'Slots': { + 'Slot': [slot_json] + } + } + + @staticmethod + def _create_json_slot_onbaord_lan(lan_port): + lan_ports = [] + for port_idx in range(1, lan_port['@PortIdx']): + lan_ports.append( + {'@PortIdx': port_idx, + 'BootPriority': 1, + 'BootProtocol': 'None', + 'PortEnable': True, + 'UseVirtualAddresses': False}) + lan_ports.append(lan_port) + return { + '@SlotIdx': 0, + 'OnboardControllers': { + 'OnboardController': [ + {'@OnboardControllerIdx': 1, + 'LANAdapter': { + 'Ports': { + 'Port': lan_ports + } + }} + ] + } + } + + @staticmethod + def _create_json_slot_with_cna_lan(card_idx, port_idx, lan_function): + return { + '@SlotIdx': 0, + 'AddOnCards': { + 'AddOnCard': [ + {'@AddOnCardIdx': card_idx, + 'CNAAdapter': { + 'Ports': { + 'Port': [ + {'@PortIdx': port_idx, + 'PortEnable': True, + 'Functions': { + 'Function': [ + {'@FunctionIdx': 1, + 'LANFunction': lan_function}, + ] + }} + ] + } + }} + ] + } + } + + @staticmethod + def _create_json_slot_with_cna_iscsi(card_idx, port_idx, iscsi_function, + lan_function=None): + if not lan_function: + lan_function = { + 'FunctionEnable': False, + 'BootProtocol': 'None', + 'BootPriority': 1, + } + return { + '@SlotIdx': 0, + 'AddOnCards': { + 'AddOnCard': [ + {'@AddOnCardIdx': card_idx, + 'CNAAdapter': { + 'Ports': { + 'Port': [ + {'@PortIdx': port_idx, + 'PortEnable': True, + 'Functions': { + 'Function': [ + {'@FunctionIdx': 1, + 'LANFunction': lan_function}, + {'@FunctionIdx': 3, + 'ISCSIFunction': iscsi_function} + ] + }} + ] + } + }} + ] + } + } + + @staticmethod + def _create_json_slot_with_fc(card_idx, fc_port): + return { + '@SlotIdx': 0, + 'AddOnCards': { + 'AddOnCard': [ + {'@AddOnCardIdx': card_idx, + 'FCAdapter': { + 'Ports': { + 'Port': [ + fc_port + ] + } + }} + ] + } + } + + @staticmethod + def _create_json_slot_with_cna_fcoe(card_idx, port_idx, fcoe_function, + lan_function=None): + if not lan_function: + lan_function = { + 'FunctionEnable': False, + 'BootProtocol': 'None', + 'BootPriority': 1, + } + return { + '@SlotIdx': 0, + 'AddOnCards': { + 'AddOnCard': [ + {'@AddOnCardIdx': card_idx, + 'CNAAdapter': { + 'Ports': { + 'Port': [ + {'@PortIdx': port_idx, + 'PortEnable': True, + 'Functions': { + 'Function': [ + {'@FunctionIdx': 1, + 'LANFunction': lan_function}, + {'@FunctionIdx': 2, + 'FCoEFunction': fcoe_function} + ] + }} + ] + } + }} + ] + } + } + + @mock.patch.object(viom_elcm.ELCMVIOMClient, 'set_profile') + def test_set_lan_port_to_onboard(self, mock_set): + self.configurator.set_lan_port('LAN0-1') + port = { + '@PortIdx': 1, + 'PortEnable': True, + 'BootProtocol': 'None', + 'BootPriority': 1, + 'UseVirtualAddresses': False, + } + slot = VIOMConfigurationTestCase._create_json_slot_onbaord_lan(port) + self.assertEqual( + VIOMConfigurationTestCase._create_json_before_apply(slot), + self.configurator.dump_json()) + self.configurator.apply() + mock_set.assert_called_once_with( + VIOMConfigurationTestCase._create_json_for_apply(slot)) + + @mock.patch.object(viom_elcm.ELCMVIOMClient, 'set_profile') + def test_set_lan_port_to_onboard_with_virtual_mac(self, mock_set): + self.configurator.set_lan_port('LAN0-9', mac='aa:bb:cc:dd:ee') + port = { + '@PortIdx': 9, + 'PortEnable': True, + 'BootProtocol': 'None', + 'BootPriority': 1, + 'UseVirtualAddresses': True, + 'VirtualAddress': { + 'MAC': 'aa:bb:cc:dd:ee' + } + } + slot = VIOMConfigurationTestCase._create_json_slot_onbaord_lan(port) + self.assertEqual( + VIOMConfigurationTestCase._create_json_before_apply(slot), + self.configurator.dump_json()) + self.configurator.apply(reboot=True) + mock_set.assert_called_once_with( + VIOMConfigurationTestCase._create_json_for_apply(slot, + init_boot=True)) + + @mock.patch.object(viom_elcm.ELCMVIOMClient, 'set_profile') + def test_set_lan_port_to_cna(self, mock_set): + self.configurator.set_lan_port('CNA5-6') + function = { + 'FunctionEnable': True, + 'BootProtocol': 'None', + 'BootPriority': 1, + 'UseVirtualAddresses': False, + } + slot = VIOMConfigurationTestCase._create_json_slot_with_cna_lan( + 5, 6, function) + self.assertEqual( + VIOMConfigurationTestCase._create_json_before_apply(slot), + self.configurator.dump_json()) + self.configurator.apply() + mock_set.assert_called_once_with( + VIOMConfigurationTestCase._create_json_for_apply(slot)) + + @mock.patch.object(viom_elcm.ELCMVIOMClient, 'set_profile') + def test_set_lan_port_to_cna_with_virtual_mac(self, mock_set): + self.configurator.set_lan_port('CNA9-1', mac='12:34:56:78:90') + function = { + 'FunctionEnable': True, + 'BootProtocol': 'None', + 'BootPriority': 1, + 'UseVirtualAddresses': True, + 'VirtualAddress': { + 'MAC': '12:34:56:78:90' + } + } + slot = VIOMConfigurationTestCase._create_json_slot_with_cna_lan( + 9, 1, function) + self.assertEqual( + VIOMConfigurationTestCase._create_json_before_apply(slot), + self.configurator.dump_json()) + self.configurator.apply() + mock_set.assert_called_once_with( + VIOMConfigurationTestCase._create_json_for_apply(slot)) + + @mock.patch.object(viom_elcm.ELCMVIOMClient, 'set_profile') + def test_set_iscsi_volume_to_onboard_lan(self, mock_set): + self.configurator.set_iscsi_volume( + 'LAN0-9', + 'iqn-2017-04.com.fujitsu:01', + initiator_ip='192.168.11.11', + initiator_netmask=24, + target_iqn='iqn-2017-04.com.fujitsu:11', + target_ip='192.168.22.22') + port = { + '@PortIdx': 9, + 'PortEnable': True, + 'BootProtocol': 'ISCSI', + 'BootPriority': 1, + 'ISCSIBootEnvironment': { + 'ISCSIInitiator': { + 'DHCPUsage': False, + 'Name': 'iqn-2017-04.com.fujitsu:01', + 'IPv4Address': '192.168.11.11', + 'SubnetMask': '255.255.255.0', + 'VLANId': 0, + }, + 'ISCSITarget': { + 'DHCPUsage': False, + 'Name': 'iqn-2017-04.com.fujitsu:11', + 'IPv4Address': '192.168.22.22', + 'PortNumber': 3260, + 'BootLUN': 0, + 'AuthenticationMethod': 'None' + } + } + } + slot = VIOMConfigurationTestCase._create_json_slot_onbaord_lan(port) + self.assertEqual( + VIOMConfigurationTestCase._create_json_before_apply(slot), + self.configurator.dump_json()) + self.configurator.apply() + mock_set.assert_called_once_with( + VIOMConfigurationTestCase._create_json_for_apply(slot)) + + @mock.patch.object(viom_elcm.ELCMVIOMClient, 'set_profile') + def test_set_iscsi_volume_to_cna_card(self, mock_set): + self.configurator.set_iscsi_volume( + 'CNA1-2', + 'iqn-2017-04.com.fujitsu:01', + initiator_ip='192.168.11.11', + initiator_netmask=16, + target_iqn='iqn-2017-04.com.fujitsu:11', + target_ip='192.168.22.22') + iscsi_function = { + 'FunctionEnable': True, + 'BootProtocol': 'ISCSI', + 'BootPriority': 1, + 'ISCSIBootEnvironment': { + 'ISCSIInitiator': { + 'DHCPUsage': False, + 'Name': 'iqn-2017-04.com.fujitsu:01', + 'IPv4Address': '192.168.11.11', + 'SubnetMask': '255.255.0.0', + 'VLANId': 0, + }, + 'ISCSITarget': { + 'DHCPUsage': False, + 'Name': 'iqn-2017-04.com.fujitsu:11', + 'IPv4Address': '192.168.22.22', + 'PortNumber': 3260, + 'BootLUN': 0, + 'AuthenticationMethod': 'None' + } + } + } + slot = VIOMConfigurationTestCase._create_json_slot_with_cna_iscsi( + 1, 2, iscsi_function) + self.assertEqual( + VIOMConfigurationTestCase._create_json_before_apply(slot), + self.configurator.dump_json()) + self.configurator.apply() + mock_set.assert_called_once_with( + VIOMConfigurationTestCase._create_json_for_apply(slot)) + + @mock.patch.object(viom_elcm.ELCMVIOMClient, 'set_profile') + def test_set_iscsi_volume_to_cna_card_chap(self, mock_set): + self.configurator.set_iscsi_volume( + 'CNA9-9', + 'iqn-2017-04.com.fujitsu:01', + initiator_ip='192.168.11.11', + initiator_netmask=30, + target_iqn='iqn-2017-04.com.fujitsu:11', + target_ip='192.168.22.22', + chap_user='chap-user', + chap_secret='chap-secret') + iscsi_function = { + 'FunctionEnable': True, + 'BootProtocol': 'ISCSI', + 'BootPriority': 1, + 'ISCSIBootEnvironment': { + 'ISCSIInitiator': { + 'DHCPUsage': False, + 'Name': 'iqn-2017-04.com.fujitsu:01', + 'IPv4Address': '192.168.11.11', + 'SubnetMask': '255.255.255.252', + 'VLANId': 0, + }, + 'ISCSITarget': { + 'DHCPUsage': False, + 'Name': 'iqn-2017-04.com.fujitsu:11', + 'IPv4Address': '192.168.22.22', + 'PortNumber': 3260, + 'BootLUN': 0, + 'AuthenticationMethod': 'CHAP', + 'ChapUserName': 'chap-user', + 'ChapSecret': 'chap-secret' + } + } + } + slot = VIOMConfigurationTestCase._create_json_slot_with_cna_iscsi( + 9, 9, iscsi_function) + self.assertEqual( + VIOMConfigurationTestCase._create_json_before_apply(slot), + self.configurator.dump_json()) + self.configurator.apply() + mock_set.assert_called_once_with( + VIOMConfigurationTestCase._create_json_for_apply(slot)) + + @mock.patch.object(viom_elcm.ELCMVIOMClient, 'set_profile') + def test_set_iscsi_volume_to_cna_card_mutual_chap(self, mock_set): + self.configurator.set_iscsi_volume( + 'CNA2-1', + 'iqn-2017-04.com.fujitsu:01', + initiator_ip='192.168.11.11', + initiator_netmask=8, + target_iqn='iqn-2017-04.com.fujitsu:11', + target_ip='192.168.22.22', + boot_prio=2, + target_lun=3, + chap_user='chap-user', + chap_secret='chap-secret', + mutual_chap_secret='mutual-secret') + iscsi_function = { + 'FunctionEnable': True, + 'BootProtocol': 'ISCSI', + 'BootPriority': 2, + 'ISCSIBootEnvironment': { + 'ISCSIInitiator': { + 'DHCPUsage': False, + 'Name': 'iqn-2017-04.com.fujitsu:01', + 'IPv4Address': '192.168.11.11', + 'SubnetMask': '255.0.0.0', + 'VLANId': 0, + }, + 'ISCSITarget': { + 'DHCPUsage': False, + 'Name': 'iqn-2017-04.com.fujitsu:11', + 'IPv4Address': '192.168.22.22', + 'PortNumber': 3260, + 'BootLUN': 3, + 'AuthenticationMethod': 'MutualCHAP', + 'ChapUserName': 'chap-user', + 'ChapSecret': 'chap-secret', + 'MutualChapSecret': 'mutual-secret', + } + } + } + slot = VIOMConfigurationTestCase._create_json_slot_with_cna_iscsi( + 2, 1, iscsi_function) + self.assertEqual( + VIOMConfigurationTestCase._create_json_before_apply(slot), + self.configurator.dump_json()) + self.configurator.apply() + mock_set.assert_called_once_with( + VIOMConfigurationTestCase._create_json_for_apply(slot)) + + @mock.patch.object(viom_elcm.ELCMVIOMClient, 'set_profile') + def test_set_iscsi_volume_to_cna_card_initiator_dhcp(self, mock_set): + self.configurator.set_iscsi_volume( + 'CNA1-2', + 'iqn-2017-04.com.fujitsu:01', + initiator_dhcp=True, + target_iqn='iqn-2017-04.com.fujitsu:11', + target_ip='192.168.22.22') + iscsi_function = { + 'FunctionEnable': True, + 'BootProtocol': 'ISCSI', + 'BootPriority': 1, + 'ISCSIBootEnvironment': { + 'ISCSIInitiator': { + 'DHCPUsage': True, + 'Name': 'iqn-2017-04.com.fujitsu:01', + }, + 'ISCSITarget': { + 'DHCPUsage': False, + 'Name': 'iqn-2017-04.com.fujitsu:11', + 'IPv4Address': '192.168.22.22', + 'PortNumber': 3260, + 'BootLUN': 0, + 'AuthenticationMethod': 'None' + } + } + } + slot = VIOMConfigurationTestCase._create_json_slot_with_cna_iscsi( + 1, 2, iscsi_function) + self.assertEqual( + VIOMConfigurationTestCase._create_json_before_apply(slot), + self.configurator.dump_json()) + self.configurator.apply() + mock_set.assert_called_once_with( + VIOMConfigurationTestCase._create_json_for_apply(slot)) + + @mock.patch.object(viom_elcm.ELCMVIOMClient, 'set_profile') + def test_set_iscsi_volume_to_cna_card_target_dhcp(self, mock_set): + self.configurator.set_iscsi_volume( + 'CNA1-2', + 'iqn-2017-04.com.fujitsu:01', + initiator_ip='192.168.11.11', + initiator_netmask=16, + target_dhcp=True) + iscsi_function = { + 'FunctionEnable': True, + 'BootProtocol': 'ISCSI', + 'BootPriority': 1, + 'ISCSIBootEnvironment': { + 'ISCSIInitiator': { + 'DHCPUsage': False, + 'Name': 'iqn-2017-04.com.fujitsu:01', + 'IPv4Address': '192.168.11.11', + 'SubnetMask': '255.255.0.0', + 'VLANId': 0, + }, + 'ISCSITarget': { + 'DHCPUsage': True, + 'AuthenticationMethod': 'None' + } + } + } + slot = VIOMConfigurationTestCase._create_json_slot_with_cna_iscsi( + 1, 2, iscsi_function) + self.assertEqual( + VIOMConfigurationTestCase._create_json_before_apply(slot), + self.configurator.dump_json()) + self.configurator.apply() + mock_set.assert_called_once_with( + VIOMConfigurationTestCase._create_json_for_apply(slot)) + + @mock.patch.object(viom_elcm.ELCMVIOMClient, 'set_profile') + def test_set_lan_and_iscsi_volume_to_cna_card(self, mock_set): + self.configurator.set_lan_port('CNA1-2') + self.configurator.set_iscsi_volume( + 'CNA1-2', + 'iqn-2017-04.com.fujitsu:01', + initiator_ip='192.168.11.11', + initiator_netmask=1, + target_iqn='iqn-2017-04.com.fujitsu:11', + target_ip='192.168.22.22') + lan_function = { + 'FunctionEnable': True, + 'BootProtocol': 'None', + 'BootPriority': 1, + 'UseVirtualAddresses': False, + } + iscsi_function = { + 'FunctionEnable': True, + 'BootProtocol': 'ISCSI', + 'BootPriority': 1, + 'ISCSIBootEnvironment': { + 'ISCSIInitiator': { + 'DHCPUsage': False, + 'Name': 'iqn-2017-04.com.fujitsu:01', + 'IPv4Address': '192.168.11.11', + 'SubnetMask': '128.0.0.0', + 'VLANId': 0, + }, + 'ISCSITarget': { + 'DHCPUsage': False, + 'Name': 'iqn-2017-04.com.fujitsu:11', + 'IPv4Address': '192.168.22.22', + 'PortNumber': 3260, + 'BootLUN': 0, + 'AuthenticationMethod': 'None' + } + } + } + slot = VIOMConfigurationTestCase._create_json_slot_with_cna_iscsi( + 1, 2, iscsi_function, lan_function=lan_function) + self.assertEqual( + VIOMConfigurationTestCase._create_json_before_apply(slot), + self.configurator.dump_json()) + self.configurator.apply() + mock_set.assert_called_once_with( + VIOMConfigurationTestCase._create_json_for_apply(slot)) + + @mock.patch.object(viom_elcm.ELCMVIOMClient, 'set_profile') + def test_set_fc_volume_to_fc_card(self, mock_set): + self.configurator.set_fc_volume('FC1-1', '11:22:33:44:55') + port = { + '@PortIdx': 1, + 'UseVirtualAddresses': False, + 'PortEnable': True, + 'BootProtocol': 'FC', + 'BootPriority': 1, + 'FCBootEnvironment': { + 'FCTargets': { + 'FCTarget': [ + {'@FCTargetIdx': 1, + 'TargetWWPN': '11:22:33:44:55', + 'TargetLUN': 0} + ] + }, + 'FCLinkSpeed': 'auto', + 'FCTopology': 'auto_loop', + 'SANBootEnable': True, + } + } + slot = VIOMConfigurationTestCase._create_json_slot_with_fc(1, port) + self.assertEqual( + VIOMConfigurationTestCase._create_json_before_apply(slot), + self.configurator.dump_json()) + self.configurator.apply() + mock_set.assert_called_once_with( + VIOMConfigurationTestCase._create_json_for_apply(slot)) + + @mock.patch.object(viom_elcm.ELCMVIOMClient, 'set_profile') + def test_set_fc_volume_to_fc_card_with_virtual_wwn(self, mock_set): + self.configurator.set_fc_volume('FC2-1', '11:22:33:44:55', + boot_prio=3, target_lun=2, + initiator_wwnn='aa:bb:cc:dd:ee', + initiator_wwpn='12:34:56:78:90') + port = { + '@PortIdx': 1, + 'UseVirtualAddresses': True, + 'VirtualAddress': { + 'WWNN': 'aa:bb:cc:dd:ee', + 'WWPN': '12:34:56:78:90' + }, + 'PortEnable': True, + 'BootProtocol': 'FC', + 'BootPriority': 3, + 'FCBootEnvironment': { + 'FCTargets': { + 'FCTarget': [ + {'@FCTargetIdx': 1, + 'TargetWWPN': '11:22:33:44:55', + 'TargetLUN': 2} + ] + }, + 'FCLinkSpeed': 'auto', + 'FCTopology': 'auto_loop', + 'SANBootEnable': True, + } + } + slot = VIOMConfigurationTestCase._create_json_slot_with_fc(2, port) + self.assertEqual( + VIOMConfigurationTestCase._create_json_before_apply(slot), + self.configurator.dump_json()) + self.configurator.apply() + mock_set.assert_called_once_with( + VIOMConfigurationTestCase._create_json_for_apply(slot)) + + @mock.patch.object(viom_elcm.ELCMVIOMClient, 'set_profile') + def test_set_fc_volume_to_cna_card(self, mock_set): + self.configurator.set_fc_volume('CNA2-1', '11:22:33:44:55') + fcoe_function = { + 'UseVirtualAddresses': False, + 'FunctionEnable': True, + 'BootProtocol': 'FC', + 'BootPriority': 1, + 'FCBootEnvironment': { + 'FCTargets': { + 'FCTarget': [ + {'@FCTargetIdx': 1, + 'TargetWWPN': '11:22:33:44:55', + 'TargetLUN': 0} + ] + }, + 'FCLinkSpeed': 'auto', + 'FCTopology': 'auto_loop', + 'SANBootEnable': True, + } + } + slot = VIOMConfigurationTestCase._create_json_slot_with_cna_fcoe( + 2, 1, fcoe_function) + self.assertEqual( + VIOMConfigurationTestCase._create_json_before_apply(slot), + self.configurator.dump_json()) + self.configurator.apply() + mock_set.assert_called_once_with( + VIOMConfigurationTestCase._create_json_for_apply(slot)) + + @mock.patch.object(viom_elcm.ELCMVIOMClient, 'set_profile') + def test_set_fc_volume_to_cna_card_with_virtual_wwn(self, mock_set): + self.configurator.set_fc_volume('CNA9-9', '11:22:33:44:55', + boot_prio=2, target_lun=3, + initiator_wwnn='aa:bb:cc:dd:ee', + initiator_wwpn='12:34:56:78:90') + fcoe_function = { + 'UseVirtualAddresses': True, + 'VirtualAddress': { + 'WWNN': 'aa:bb:cc:dd:ee', + 'WWPN': '12:34:56:78:90' + }, + 'FunctionEnable': True, + 'BootProtocol': 'FC', + 'BootPriority': 2, + 'FCBootEnvironment': { + 'FCTargets': { + 'FCTarget': [ + {'@FCTargetIdx': 1, + 'TargetWWPN': '11:22:33:44:55', + 'TargetLUN': 3} + ] + }, + 'FCLinkSpeed': 'auto', + 'FCTopology': 'auto_loop', + 'SANBootEnable': True, + } + } + slot = VIOMConfigurationTestCase._create_json_slot_with_cna_fcoe( + 9, 9, fcoe_function) + self.assertEqual( + VIOMConfigurationTestCase._create_json_before_apply(slot), + self.configurator.dump_json()) + self.configurator.apply() + mock_set.assert_called_once_with( + VIOMConfigurationTestCase._create_json_for_apply(slot)) + + @mock.patch.object(viom_elcm.ELCMVIOMClient, 'set_profile') + def test_set_lan_and_fc_volume_to_cna_card(self, mock_set): + self.configurator.set_lan_port('CNA2-1') + self.configurator.set_fc_volume('CNA2-1', '11:22:33:44:55') + lan_function = { + 'FunctionEnable': True, + 'BootProtocol': 'None', + 'BootPriority': 1, + 'UseVirtualAddresses': False, + } + fcoe_function = { + 'UseVirtualAddresses': False, + 'FunctionEnable': True, + 'BootProtocol': 'FC', + 'BootPriority': 1, + 'FCBootEnvironment': { + 'FCTargets': { + 'FCTarget': [ + {'@FCTargetIdx': 1, + 'TargetWWPN': '11:22:33:44:55', + 'TargetLUN': 0} + ] + }, + 'FCLinkSpeed': 'auto', + 'FCTopology': 'auto_loop', + 'SANBootEnable': True, + } + } + slot = VIOMConfigurationTestCase._create_json_slot_with_cna_fcoe( + 2, 1, fcoe_function, lan_function=lan_function) + self.assertEqual( + VIOMConfigurationTestCase._create_json_before_apply(slot), + self.configurator.dump_json()) + self.configurator.apply() + mock_set.assert_called_once_with( + VIOMConfigurationTestCase._create_json_for_apply(slot)) + + def test_set_lan_port_to_onboard_overwrite(self): + self.configurator.set_lan_port('LAN0-9') + self.test_set_lan_port_to_onboard_with_virtual_mac() + + def test_set_lan_port_to_cna_overwrite(self): + self.configurator.set_lan_port('CNA5-6', mac='12:34:56:78:90') + self.test_set_lan_port_to_cna() + + def test_set_iscsi_volume_to_onboard_lan_overwrite(self): + self.configurator.set_iscsi_volume( + 'LAN0-9', + 'iqn-initiator', + initiator_ip='192.168.99.99', + initiator_netmask=32, + target_iqn='iqn-target', + target_ip='192.168.88.88') + self.test_set_iscsi_volume_to_onboard_lan() + + def test_set_iscsi_volume_to_cna_card_overwrite(self): + self.configurator.set_iscsi_volume( + 'CNA1-2', + 'iqn-initiator', + initiator_ip='192.168.99.99', + initiator_netmask=16, + target_iqn='iqn-target', + target_ip='192.168.88.88') + self.test_set_iscsi_volume_to_cna_card() + + def test_set_fc_volume_to_fc_card_overwrite(self): + self.configurator.set_fc_volume('FC2-1', '11:22:33:44:55') + self.test_set_fc_volume_to_fc_card_with_virtual_wwn() + + def test_set_fc_volume_to_cna_card_overwrite(self): + self.configurator.set_fc_volume('CNA2-1', '11:22:33:44:55', + boot_prio=2, target_lun=3, + initiator_wwnn='aa:bb:cc:dd:ee', + initiator_wwpn='12:34:56:78:90') + self.test_set_fc_volume_to_cna_card() + + +class PhysicalPortIDParseTestCase(testtools.TestCase): + + def _validate_handler(self, handler, handler_class, slot_type, card_type, + slot_idx, card_idx, port_idx): + self.assertTrue(isinstance(handler, handler_class)) + self.assertEqual(slot_type, handler.slot_type) + self.assertEqual(card_type, handler.card_type) + self.assertEqual(slot_idx, handler.slot_idx) + self.assertEqual(card_idx, handler.card_idx) + self.assertEqual(port_idx, handler.port_idx) + + def test_lan_onboard(self): + handler = viom_client._parse_physical_port_id('LAN0-1') + self._validate_handler(handler, viom_client._LANPortHandler, + viom_client.ONBOARD, viom_client.LAN, + 0, 1, 1) + + def test_lan_onboard_lower(self): + handler = viom_client._parse_physical_port_id('lan0-2') + self._validate_handler(handler, viom_client._LANPortHandler, + viom_client.ONBOARD, viom_client.LAN, + 0, 1, 2) + + def test_lan_onboard_cammel(self): + handler = viom_client._parse_physical_port_id('Lan0-9') + self._validate_handler(handler, viom_client._LANPortHandler, + viom_client.ONBOARD, viom_client.LAN, + 0, 1, 9) + + def test_lan_addon(self): + handler = viom_client._parse_physical_port_id('LAN1-3') + self._validate_handler(handler, viom_client._LANPortHandler, + viom_client.ADDON, viom_client.LAN, + 0, 1, 3) + + def test_fc_addon(self): + handler = viom_client._parse_physical_port_id('FC2-5') + self._validate_handler(handler, viom_client._FCPortHandler, + viom_client.ADDON, viom_client.FC, + 0, 2, 5) + + def test_cna_addon(self): + handler = viom_client._parse_physical_port_id('CNA9-2') + self._validate_handler(handler, viom_client._CNAPortHandler, + viom_client.ADDON, viom_client.CNA, + 0, 9, 2) + + def test_unkown_card(self): + self.assertRaises(scci.SCCIInvalidInputError, + viom_client._parse_physical_port_id, 'HCA1-1') + + def test_slot_out_of_range(self): + self.assertRaises(scci.SCCIInvalidInputError, + viom_client._parse_physical_port_id, 'CNA10-9') + + def test_port_out_of_range_min(self): + self.assertRaises(scci.SCCIInvalidInputError, + viom_client._parse_physical_port_id, 'FC0-0') + + def test_port_out_of_range_max(self): + self.assertRaises(scci.SCCIInvalidInputError, + viom_client._parse_physical_port_id, 'FC9-10') + + def test_public_validation(self): + self.assertIsNone(viom_client.validate_physical_port_id('LAN0-2')) + + def test_public_validation_error(self): + self.assertRaises(scci.SCCIInvalidInputError, + viom_client.validate_physical_port_id, 'CNA1-0') + + +class ConvertNetMaskTestCase(testtools.TestCase): + def test_convert_zero(self): + self.assertEqual('0.0.0.0', + viom_client._convert_netmask(0)) + + def test_convert_max(self): + self.assertEqual('255.255.255.255', + viom_client._convert_netmask(32)) + + def test_convert_nagative(self): + self.assertRaises(scci.SCCIInvalidInputError, + viom_client._convert_netmask, -1) + + def test_convert_too_large(self): + self.assertRaises(scci.SCCIInvalidInputError, + viom_client._convert_netmask, 33)