Redfish: Add 'iscsi_boot' capability

This commit adds support to add the new
server capability 'iscsi_boot'

Change-Id: Ia6cc56586143d651ad672206b3d97f2292ed99aa
This commit is contained in:
Aparna 2017-07-26 11:22:04 +00:00
parent 8e8d261d9b
commit 8f48ebaf76
9 changed files with 363 additions and 2 deletions

View File

@ -667,7 +667,11 @@ class RedfishOperations(operations.IloOperations):
or tpm_state == sys_cons.TPM_PRESENT_DISABLED)),
('secure_boot',
GET_SECUREBOOT_CURRENT_BOOT_MAP.get(
sushy_system.secure_boot.current_boot)),) if value})
sushy_system.secure_boot.current_boot)),
('iscsi_boot',
(sushy_system.bios_settings.iscsi_settings.
is_iscsi_boot_supported())),
) if value})
except sushy.exceptions.SushyError as e:
msg = (self._("The Redfish controller is unable to get "

View File

@ -18,6 +18,7 @@ from sushy.resources import base
from proliantutils import exception
from proliantutils import log
from proliantutils.redfish.resources.system import constants as sys_cons
from proliantutils.redfish.resources.system import iscsi
from proliantutils.redfish.resources.system import mappings
from proliantutils.redfish import utils
@ -44,6 +45,8 @@ class BIOSSettings(base.ResourceBase):
cpu_vt = base.MappedField(["Attributes", "ProcVirtualization"],
mappings.CPUVT_MAP)
_iscsi_settings = None
_pending_settings = None
_boot_settings = None
_base_configs = None
@ -80,6 +83,22 @@ class BIOSSettings(base.ResourceBase):
return self._boot_settings
@property
def iscsi_settings(self):
"""Property to provide reference to bios iscsi instance
It is calculated once when the first time it is queried. On refresh,
this property gets reset.
"""
if self._iscsi_settings is None:
self._iscsi_settings = iscsi.ISCSISettings(
self._conn,
utils.get_subresource_path_by(
self, ["Oem", "Hpe", "Links", "iScsi"]),
redfish_version=self.redfish_version)
return self._iscsi_settings
def _get_base_configs(self):
"""Method that returns object of bios base configs."""
if self._base_configs is None:
@ -100,6 +119,7 @@ class BIOSSettings(base.ResourceBase):
self._pending_settings = None
self._boot_settings = None
self._base_configs = None
self._iscsi_settings = None
class BIOSBaseConfigs(base.ResourceBase):

View File

@ -0,0 +1,36 @@
# Copyright 2017 Hewlett Packard Enterprise Development LP
#
# 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.
from sushy.resources import base
from proliantutils.redfish import utils
class ISCSISettings(base.ResourceBase):
"""Class that represents the iSCSI settings resource.
This class extends the functionality of base resource class
from sushy.
"""
def is_iscsi_boot_supported(self):
"""Checks whether iscsi boot is supported or not.
To find the iscsi boot support, check whether the PATCH
operation is allowed on the iscsi 'settings' uri.
:returns: True if it is supported else False
"""
return utils.is_operation_allowed(
'PATCH', self,
['@Redfish.Settings', 'SettingsObject'])

View File

@ -81,3 +81,30 @@ def get_supported_boot_mode(supported_boot_mode):
return SupportedBootModes(boot_mode_bios=boot_mode_bios,
boot_mode_uefi=boot_mode_uefi)
def get_allowed_operations(resource, subresouce_path):
"""Helper function to get the HTTP allowed methods.
:param resource: ResourceBase instance from which the path is loaded.
:param subresource_path: JSON field to fetch the value from.
Either a string, or a list of strings in case of a nested field.
:returns: A list of allowed HTTP methods.
"""
uri = get_subresource_path_by(resource, subresouce_path)
response = resource._conn.get(path=uri)
return response.headers['Allow']
def is_operation_allowed(method, resource, subresouce_path):
"""Checks whether the operation is allowed for the resource.
This method checks whether a HTTP method is allowed to be
performed on the given sub resource path.
:param method: A HTTP method. example: GET, PATCH, POST
:param resource: ResourceBase instance from which the path is loaded.
:param subresource_path: JSON field to fetch the value from.
Either a string, or a list of strings in case of a nested field.
:returns: True if the operation is allowed else False
"""
return method in get_allowed_operations(resource, subresouce_path)

View File

@ -0,0 +1,152 @@
{
"@Redfish.Settings":
{
"@odata.type": "#Settings.v1_0_0.Settings",
"ETag": "",
"Messages":
[
{
"MessageId": "Base.1.0.Success"
}
],
"SettingsObject":
{
"@odata.id": "/redfish/v1/systems/1/bios/iscsi/settings/"
},
"Time": ""
},
"@odata.context": "/redfish/v1/$metadata#HpeiSCSISoftwareInitiator.HpeiSCSISoftwareInitiator",
"@odata.etag": "W/\"A772A3CF45F53A3A3A285057B9B505F1\"",
"@odata.id": "/redfish/v1/systems/1/bios/iscsi/",
"@odata.type": "#HpeiSCSISoftwareInitiator.v2_0_0.HpeiSCSISoftwareInitiator",
"Id": "iscsi",
"Name": "iSCSI Software Initiator Current Settings",
"Oem":
{
"Hpe":
{
"@odata.type": "#HpeBiosExt.v2_0_0.HpeBiosExt",
"Links":
{
"BaseConfigs":
{
"@odata.id": "/redfish/v1/systems/1/bios/iscsi/baseconfigs/"
}
},
"SettingsObject":
{
"UnmodifiedETag": "W/\"F92FCCF7BBE885858530538BD89804A4\""
}
}
},
"iSCSIInitiatorName": "iqn.2015-02.com.hpe:uefi-U31",
"iSCSISources":
[
{
"StructuredBootString": null,
"UEFIDevicePath": null,
"iSCSIAttemptInstance": 0,
"iSCSIAttemptName": "",
"iSCSIAuthenticationMethod": "None",
"iSCSIChapSecret": null,
"iSCSIChapType": "OneWay",
"iSCSIChapUsername": null,
"iSCSIConnectRetry": 3,
"iSCSIConnectTimeoutMS": 20000,
"iSCSIConnection": "Disabled",
"iSCSIInitiatorGateway": "0.0.0.0",
"iSCSIInitiatorInfoViaDHCP": true,
"iSCSIInitiatorIpAddress": "0.0.0.0",
"iSCSIInitiatorNetmask": "0.0.0.0",
"iSCSIIpAddressType": "IPv4",
"iSCSILUN": "0",
"iSCSINicSource": null,
"iSCSIReverseChapSecret": null,
"iSCSIReverseChapUsername": null,
"iSCSITargetInfoViaDHCP": true,
"iSCSITargetIpAddress": "0.0.0.0",
"iSCSITargetName": "",
"iSCSITargetTcpPort": 3260
},
{
"StructuredBootString": null,
"UEFIDevicePath": null,
"iSCSIAttemptInstance": 0,
"iSCSIAttemptName": "",
"iSCSIAuthenticationMethod": "None",
"iSCSIChapSecret": null,
"iSCSIChapType": "OneWay",
"iSCSIChapUsername": null,
"iSCSIConnectRetry": 3,
"iSCSIConnectTimeoutMS": 20000,
"iSCSIConnection": "Disabled",
"iSCSIInitiatorGateway": "0.0.0.0",
"iSCSIInitiatorInfoViaDHCP": true,
"iSCSIInitiatorIpAddress": "0.0.0.0",
"iSCSIInitiatorNetmask": "0.0.0.0",
"iSCSIIpAddressType": "IPv4",
"iSCSILUN": "0",
"iSCSINicSource": null,
"iSCSIReverseChapSecret": null,
"iSCSIReverseChapUsername": null,
"iSCSITargetInfoViaDHCP": true,
"iSCSITargetIpAddress": "0.0.0.0",
"iSCSITargetName": "",
"iSCSITargetTcpPort": 3260
},
{
"StructuredBootString": null,
"UEFIDevicePath": null,
"iSCSIAttemptInstance": 0,
"iSCSIAttemptName": "",
"iSCSIAuthenticationMethod": "None",
"iSCSIChapSecret": null,
"iSCSIChapType": "OneWay",
"iSCSIChapUsername": null,
"iSCSIConnectRetry": 3,
"iSCSIConnectTimeoutMS": 20000,
"iSCSIConnection": "Disabled",
"iSCSIInitiatorGateway": "0.0.0.0",
"iSCSIInitiatorInfoViaDHCP": true,
"iSCSIInitiatorIpAddress": "0.0.0.0",
"iSCSIInitiatorNetmask": "0.0.0.0",
"iSCSIIpAddressType": "IPv4",
"iSCSILUN": "0",
"iSCSINicSource": null,
"iSCSIReverseChapSecret": null,
"iSCSIReverseChapUsername": null,
"iSCSITargetInfoViaDHCP": true,
"iSCSITargetIpAddress": "0.0.0.0",
"iSCSITargetName": "",
"iSCSITargetTcpPort": 3260
},
{
"StructuredBootString": null,
"UEFIDevicePath": null,
"iSCSIAttemptInstance": 0,
"iSCSIAttemptName": "",
"iSCSIAuthenticationMethod": "None",
"iSCSIChapSecret": null,
"iSCSIChapType": "OneWay",
"iSCSIChapUsername": null,
"iSCSIConnectRetry": 3,
"iSCSIConnectTimeoutMS": 20000,
"iSCSIConnection": "Disabled",
"iSCSIInitiatorGateway": "0.0.0.0",
"iSCSIInitiatorInfoViaDHCP": true,
"iSCSIInitiatorIpAddress": "0.0.0.0",
"iSCSIInitiatorNetmask": "0.0.0.0",
"iSCSIIpAddressType": "IPv4",
"iSCSILUN": "0",
"iSCSINicSource": null,
"iSCSIReverseChapSecret": null,
"iSCSIReverseChapUsername": null,
"iSCSITargetInfoViaDHCP": true,
"iSCSITargetIpAddress": "0.0.0.0",
"iSCSITargetName": "",
"iSCSITargetTcpPort": 3260
}
]
}

View File

@ -22,6 +22,7 @@ import testtools
from proliantutils import exception
from proliantutils.redfish.resources.system import bios
from proliantutils.redfish.resources.system import constants as sys_cons
from proliantutils.redfish.resources.system import iscsi
class BIOSSettingsTestCase(testtools.TestCase):
@ -82,6 +83,24 @@ class BIOSSettingsTestCase(testtools.TestCase):
self.bios_inst.boot_settings)
self.conn.get.return_value.json.assert_not_called()
def test_iscsi_settings(self):
self.assertIsNone(self.bios_inst._iscsi_settings)
self.conn.get.return_value.json.reset_mock()
with open('proliantutils/tests/redfish/'
'json_samples/bios_boot.json', 'r') as f:
self.conn.get.return_value.json.return_value = (
json.loads(f.read())['Default'])
actual_settings = self.bios_inst.iscsi_settings
self.assertIsInstance(actual_settings,
iscsi.ISCSISettings)
self.conn.get.return_value.json.assert_called_once_with()
# reset mock
self.conn.get.return_value.json.reset_mock()
self.assertIs(actual_settings,
self.bios_inst.iscsi_settings)
self.conn.get.return_value.json.assert_not_called()
def test__get_base_configs(self):
self.assertIsNone(self.bios_inst._base_configs)
with open('proliantutils/tests/redfish/'
@ -140,6 +159,29 @@ class BIOSSettingsTestCase(testtools.TestCase):
self.assertIsInstance(actual_settings,
bios.BIOSBootSettings)
def test_iscsi_settings_on_refresh(self):
with open('proliantutils/tests/redfish/'
'json_samples/bios_boot.json', 'r') as f:
self.conn.get.return_value.json.return_value = (
json.loads(f.read())['Default'])
actual_settings = self.bios_inst.iscsi_settings
self.assertIsInstance(actual_settings,
iscsi.ISCSISettings)
with open('proliantutils/tests/redfish/'
'json_samples/bios.json', 'r') as f:
self.conn.get.return_value.json.return_value = (
json.loads(f.read())['Default'])
self.bios_inst.refresh()
self.assertIsNone(self.bios_inst._iscsi_settings)
with open('proliantutils/tests/redfish/'
'json_samples/bios_boot.json', 'r') as f:
self.conn.get.return_value.json.return_value = (
json.loads(f.read())['Default'])
self.assertIsInstance(actual_settings,
iscsi.ISCSISettings)
def test__get_base_configs_on_refresh(self):
with open('proliantutils/tests/redfish/'
'json_samples/bios_base_configs.json', 'r') as f:

View File

@ -0,0 +1,49 @@
# Copyright 2017 Hewlett Packard Enterprise Development LP
# All Rights Reserved.
#
# 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 json
import ddt
import mock
import testtools
from proliantutils.redfish.resources.system import iscsi
from proliantutils.redfish import utils
@ddt.ddt
class ISCSISettingsTestCase(testtools.TestCase):
def setUp(self):
super(ISCSISettingsTestCase, self).setUp()
self.conn = mock.MagicMock()
with open('proliantutils/tests/redfish/'
'json_samples/iscsi.json', 'r') as f:
self.conn.get.return_value.json.return_value = (
json.loads(f.read()))
self.iscsi_inst = iscsi.ISCSISettings(
self.conn, '/redfish/v1/Systems/1/bios/iscsi',
redfish_version='1.0.2')
@ddt.data((['GET', 'PATCH', 'POST', 'HEAD'], True),
(['GET', 'HEAD'], False))
@ddt.unpack
@mock.patch.object(utils, 'get_allowed_operations')
def test_is_iscsi_boot_supported(self, allowed_method,
expected, get_method_mock):
get_method_mock.return_value = allowed_method
ret_val = self.iscsi_inst.is_iscsi_boot_supported()
self.assertEqual(ret_val, expected)

View File

@ -30,6 +30,7 @@ from proliantutils.redfish.resources.manager import manager
from proliantutils.redfish.resources.manager import virtual_media
from proliantutils.redfish.resources.system import bios
from proliantutils.redfish.resources.system import constants as sys_cons
from proliantutils.redfish.resources.system import iscsi
from proliantutils.redfish.resources.system import pci_device
from proliantutils.redfish.resources.system import system as pro_sys
from sushy.resources.system import system
@ -690,6 +691,10 @@ class RedfishOperationsTestCase(testtools.TestCase):
tpm_mock)
type(get_system_mock.return_value).supported_boot_mode = (
sys_cons.SUPPORTED_LEGACY_BIOS_AND_UEFI)
iscsi_mock = mock.MagicMock(spec=iscsi.ISCSISettings)
iscsi_mock.is_iscsi_boot_supported = mock.MagicMock(return_value=True)
type(get_system_mock.return_value.bios_settings).iscsi_settings = (
iscsi_mock)
actual = self.rf_client.get_server_capabilities()
expected = {'pci_gpu_devices': 1, 'sriov_enabled': 'true',
'secure_boot': 'true', 'cpu_vt': 'true',
@ -699,7 +704,7 @@ class RedfishOperationsTestCase(testtools.TestCase):
'trusted_boot': 'true',
'server_model': 'ProLiant DL180 Gen10',
'boot_mode_bios': 'true',
'boot_mode_uefi': 'true'}
'boot_mode_uefi': 'true', 'iscsi_boot': 'true'}
self.assertEqual(expected, actual)
@mock.patch.object(redfish.RedfishOperations, '_get_sushy_system')
@ -733,6 +738,10 @@ class RedfishOperationsTestCase(testtools.TestCase):
tpm_mock)
type(get_system_mock.return_value).supported_boot_mode = (
sys_cons.SUPPORTED_UEFI_ONLY)
iscsi_mock = mock.MagicMock(spec=iscsi.ISCSISettings)
iscsi_mock.is_iscsi_boot_supported = mock.MagicMock(return_value=False)
type(get_system_mock.return_value.bios_settings).iscsi_settings = (
iscsi_mock)
actual = self.rf_client.get_server_capabilities()
expected = {'pci_gpu_devices': 1,
'rom_firmware_version': 'U31 v1.00 (03/11/2017)',

View File

@ -82,3 +82,25 @@ class UtilsTestCase(testtools.TestCase):
expected_boot_modes):
actual_boot_modes = utils.get_supported_boot_mode(boot_mode)
self.assertEqual(expected_boot_modes, actual_boot_modes)
@ddt.data(('SecureBoot', ['HEAD', 'GET', 'PATCH', 'POST']),
('Bios', ['GET', 'HEAD']))
@ddt.unpack
def test_get_allowed_operations(self, subresource_path, expected):
response_mock = mock.MagicMock()
response_mock.headers = {'Allow': expected}
self.conn.get.return_value = response_mock
ret_val = utils.get_allowed_operations(self.sys_inst, subresource_path)
self.assertEqual(ret_val, expected)
@ddt.data(('PATCH', 'SecureBoot', ['HEAD', 'GET', 'PATCH', 'POST'], True),
('POST', 'Bios', ['GET', 'HEAD'], False))
@ddt.unpack
@mock.patch.object(utils, 'get_allowed_operations')
def test_is_operation_allowed(self, method, subresource_path,
allowed_operations, expected,
get_method_mock):
get_method_mock.return_value = allowed_operations
ret_val = utils.is_operation_allowed(method, self.sys_inst,
subresource_path)
self.assertEqual(ret_val, expected)