
This change adds support for exporting system configuration. The configuration of the entire system or individual subsystems, such as iDRAC, BIOS, NIC, and RAID, can be exported. Change-Id: Ia7db0223d72566cf4a4d013a35ef2782f273695f Co-Authored-By: Sonali Borkar <sonaliborkar85@gmail.com>
249 lines
8.1 KiB
Python
249 lines
8.1 KiB
Python
# Copyright (c) 2020-2021 Dell Inc. or its subsidiaries.
|
|
#
|
|
# 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 logging
|
|
import time
|
|
|
|
import sushy
|
|
from sushy.resources import base
|
|
from sushy.resources import common
|
|
from sushy.resources.oem import base as oem_base
|
|
|
|
from sushy_oem_idrac import asynchronous
|
|
from sushy_oem_idrac import constants
|
|
from sushy_oem_idrac.resources import common as res_common
|
|
from sushy_oem_idrac.resources.manager import mappings as mgr_maps
|
|
from sushy_oem_idrac import utils
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class DellManagerActionsField(base.CompositeField):
|
|
import_system_configuration = common.ActionField(
|
|
lambda key, **kwargs: key.endswith(
|
|
'#OemManager.ImportSystemConfiguration'))
|
|
|
|
export_system_configuration = res_common.ExportActionField(
|
|
lambda key, **kwargs: key.endswith(
|
|
'#OemManager.ExportSystemConfiguration'))
|
|
|
|
|
|
class DellManagerExtension(oem_base.OEMResourceBase):
|
|
|
|
_actions = DellManagerActionsField('Actions')
|
|
|
|
ACTION_DATA = {
|
|
'ShareParameters': {
|
|
'Target': 'ALL'
|
|
},
|
|
'ImportBuffer': None
|
|
}
|
|
|
|
# NOTE(etingof): iDRAC job would fail if this XML has
|
|
# insignificant whitespaces
|
|
|
|
IDRAC_CONFIG_CD = """\
|
|
<SystemConfiguration>\
|
|
<Component FQDD="%s">\
|
|
<Attribute Name="ServerBoot.1#BootOnce">\
|
|
%s\
|
|
</Attribute>\
|
|
<Attribute Name="ServerBoot.1#FirstBootDevice">\
|
|
VCD-DVD\
|
|
</Attribute>\
|
|
</Component>\
|
|
</SystemConfiguration>\
|
|
"""
|
|
|
|
IDRAC_CONFIG_FLOPPY = """\
|
|
<SystemConfiguration>\
|
|
<Component FQDD="%s">\
|
|
<Attribute Name="ServerBoot.1#BootOnce">\
|
|
%s\
|
|
</Attribute>\
|
|
<Attribute Name="ServerBoot.1#FirstBootDevice">\
|
|
VFDD\
|
|
</Attribute>\
|
|
</Component>\
|
|
</SystemConfiguration>\
|
|
"""
|
|
|
|
IDRAC_MEDIA_TYPES = {
|
|
sushy.VIRTUAL_MEDIA_FLOPPY: IDRAC_CONFIG_FLOPPY,
|
|
sushy.VIRTUAL_MEDIA_CD: IDRAC_CONFIG_CD
|
|
}
|
|
|
|
RETRY_COUNT = 10
|
|
RETRY_DELAY = 15
|
|
|
|
@property
|
|
def import_system_configuration_uri(self):
|
|
return self._actions.import_system_configuration.target_uri
|
|
|
|
@property
|
|
def export_system_configuration_uri(self):
|
|
return self._actions.export_system_configuration.target_uri
|
|
|
|
def set_virtual_boot_device(self, device, persistent=False,
|
|
manager=None, system=None):
|
|
"""Set boot device for a node.
|
|
|
|
Dell iDRAC Redfish implementation does not support setting
|
|
boot device to virtual media via standard Redfish means.
|
|
However, this still can be done via an OEM extension.
|
|
|
|
:param device: Boot device. Values are vendor-specific.
|
|
:param persistent: Whether to set next-boot, or make the change
|
|
permanent. Default: False.
|
|
:raises: InvalidParameterValue if Dell OEM extension can't
|
|
be used.
|
|
:raises: ExtensionError on failure to perform requested
|
|
operation.
|
|
"""
|
|
try:
|
|
idrac_media = self.IDRAC_MEDIA_TYPES[device]
|
|
|
|
except KeyError:
|
|
raise sushy.exceptions.InvalidParameterValue(
|
|
error='Unknown or unsupported device %s' % device)
|
|
|
|
idrac_media = idrac_media % (
|
|
manager.identity, 'Disabled' if persistent else 'Enabled')
|
|
|
|
action_data = dict(self.ACTION_DATA, ImportBuffer=idrac_media)
|
|
|
|
# TODO(etingof): figure out if on-time or persistent boot can at
|
|
# all be implemented via this OEM call
|
|
|
|
attempts = self.RETRY_COUNT
|
|
rebooted = False
|
|
|
|
while True:
|
|
try:
|
|
response = asynchronous.http_call(
|
|
self._conn, 'post',
|
|
self.import_system_configuration_uri,
|
|
data=action_data,
|
|
sushy_task_poll_period=1)
|
|
|
|
LOG.info("Set boot device to %(device)s via "
|
|
"Dell OEM magic spell (%(retries)d "
|
|
"retries)", {'device': device,
|
|
'retries': self.RETRY_COUNT - attempts})
|
|
|
|
return response
|
|
|
|
except (sushy.exceptions.ServerSideError,
|
|
sushy.exceptions.BadRequestError) as exc:
|
|
LOG.warning(
|
|
'Dell OEM set boot device failed (attempts left '
|
|
'%d): %s', attempts, exc)
|
|
|
|
errors = exc.body and exc.body.get(
|
|
'@Message.ExtendedInfo') or []
|
|
|
|
for error in errors:
|
|
message_id = error.get('MessageId')
|
|
|
|
LOG.warning('iDRAC error: %s',
|
|
error.get('Message', 'Unknown error'))
|
|
|
|
if message_id == constants.IDRAC_CONFIG_PENDING:
|
|
if not rebooted:
|
|
LOG.warning(
|
|
'Let\'s try to turn it off and on again... '
|
|
'This may consume one-time boot settings if '
|
|
'set previously!')
|
|
utils.reboot_system(system)
|
|
rebooted = True
|
|
break
|
|
|
|
elif message_id == constants.IDRAC_JOB_RUNNING:
|
|
pass
|
|
|
|
else:
|
|
time.sleep(self.RETRY_DELAY)
|
|
|
|
if not attempts:
|
|
LOG.error('Too many (%d) retries, bailing '
|
|
'out.', self.RETRY_COUNT)
|
|
raise
|
|
|
|
attempts -= 1
|
|
|
|
def get_allowed_export_system_config_values(self):
|
|
"""Get the allowed values of export system configuration.
|
|
|
|
:returns: A set of allowed values.
|
|
"""
|
|
export_action = self._actions.export_system_configuration
|
|
allowed_values = export_action.allowed_values[
|
|
'Target@Redfish.AllowableValues']
|
|
|
|
return set([mgr_maps.EXPORT_CONFIG_VALUE_MAP[value] for value in
|
|
set(mgr_maps.EXPORT_CONFIG_VALUE_MAP).
|
|
intersection(allowed_values)])
|
|
|
|
def _export_system_configuration(self, target):
|
|
"""Export system configuration.
|
|
|
|
It exports system configuration for specified target
|
|
like NIC, BIOS, RAID.
|
|
|
|
:param target: Component of the system to export the
|
|
configuration from. Can be the entire system.
|
|
Valid values can be gotten from
|
|
`get_allowed_export_system_config_values`.
|
|
:returns: a response object containing configuration details for
|
|
the specified target.
|
|
:raises: InvalidParameterValueError on invalid target.
|
|
:raises: ExtensionError on failure to perform requested
|
|
operation
|
|
"""
|
|
valid_allowed_targets = self.get_allowed_export_system_config_values()
|
|
if target not in valid_allowed_targets:
|
|
raise sushy.exceptions.InvalidParameterValueError(
|
|
parameter='target', value=target,
|
|
valid_values=valid_allowed_targets)
|
|
|
|
target = mgr_maps.EXPORT_CONFIG_VALUE_MAP_REV[target]
|
|
|
|
action_data = {
|
|
'ShareParameters': {
|
|
'Target': target
|
|
},
|
|
'ExportFormat': "JSON"
|
|
}
|
|
|
|
try:
|
|
response = asynchronous.http_call(
|
|
self._conn,
|
|
'post',
|
|
self.export_system_configuration_uri,
|
|
data=action_data)
|
|
|
|
LOG.info("Successfully exported system configuration "
|
|
"for %(target)s", {'target': target})
|
|
|
|
return response
|
|
|
|
except (sushy.exceptions.ExtensionError,
|
|
sushy.exceptions.InvalidParameterValueError) as exc:
|
|
LOG.error('Dell OEM export system configuration failed : %s', exc)
|
|
raise
|
|
|
|
|
|
def get_extension(*args, **kwargs):
|
|
return DellManagerExtension
|