Redfish: Adds 'update_firmware' API support through redfish

This commit provides functionality to support firmware update
on redish systems.

Change-Id: I564f4cc69a8bb8b567e930082c1b68bb2046d747
This commit is contained in:
Anshul Jain 2017-06-09 05:49:35 +00:00
parent ead74eadad
commit e17153856b
8 changed files with 527 additions and 0 deletions

View File

@ -71,6 +71,7 @@ SUPPORTED_REDFISH_METHODS = [
'eject_virtual_media',
'insert_virtual_media',
'set_vm_status'
'update_firmware',
]
LOG = log.get_logger(__name__)

View File

@ -18,6 +18,7 @@ import sushy
from proliantutils.redfish.resources.manager import manager
from proliantutils.redfish.resources.system import system
from proliantutils.redfish.resources import update_service
from proliantutils.redfish import utils
@ -51,3 +52,14 @@ class HPESushy(sushy.Sushy):
"""
return manager.HPEManager(self._conn, identity,
redfish_version=self.redfish_version)
def get_update_service(self):
"""Return a HPEUpdateService object
:returns: The UpdateService object
"""
update_service_url = utils.get_subresource_path_by(self,
'UpdateService')
return (update_service.
HPEUpdateService(self._conn, update_service_url,
redfish_version=self.redfish_version))

View File

@ -19,6 +19,7 @@ import sushy
from sushy import utils
from proliantutils import exception
from proliantutils.ilo import firmware_controller
from proliantutils.ilo import operations
from proliantutils import log
from proliantutils.redfish import main
@ -424,3 +425,23 @@ class RedfishOperations(operations.IloOperations):
{'device': device, 'error': str(e)})
LOG.debug(msg)
raise exception.IloError(msg)
@firmware_controller.check_firmware_update_component
def update_firmware(self, file_url, component_type):
"""Updates the given firmware on the server for the given component.
:param file_url: location of the raw firmware file. Extraction of the
firmware file (if in compact format) is expected to
happen prior to this invocation.
:param component_type: Type of component to be applied to.
:raises: IloError, on an error from iLO.
"""
try:
update_service_inst = self._sushy.get_update_service()
update_service_inst.flash_firmware(self, file_url)
except sushy.exceptions.SushyError as e:
msg = (self._('The Redfish controller failed to update firmware '
'with firmware %(file)s Error %(error)s') %
{'file': file_url, 'error': str(e)})
LOG.debug(msg)
raise exception.IloError(msg)

View File

@ -0,0 +1,161 @@
# 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.
__author__ = 'HPE'
import sushy
from sushy.resources import base
from sushy.resources import common as sushy_common
from proliantutils import exception
from proliantutils.ilo import common
from proliantutils import log
LOG = log.get_logger(__name__)
class ActionsField(base.CompositeField):
update_firmware = (sushy_common.
ResetActionField('#UpdateService.SimpleUpdate'))
class HPEUpdateService(base.ResourceBase):
"""Class that extends the functionality of Base resource class
This class extends the functionality of Base resource class
from sushy
"""
firmware_state = base.Field(['Oem', 'Hpe', 'State'])
firmware_percentage = base.Field(['Oem', 'Hpe', 'FlashProgressPercent'])
_actions = ActionsField(['Actions'], required=True)
def _get_firmware_update_element(self):
"""Get the url for firmware update
:returns: firmware update url
:raises: Missing resource error on missing url
"""
fw_update_action = self._actions.update_firmware
if not fw_update_action:
raise (sushy.exceptions.
MissingActionError(action='#UpdateService.SimpleUpdate',
resource=self._path))
return fw_update_action
def flash_firmware(self, redfish_inst, file_url):
"""Perform firmware flashing on a redfish system
:param file_url: url to firmware bits.
:param redfish_inst: redfish instance
:raises: IloError, on an error from iLO.
"""
action_data = {
'ImageURI': file_url,
}
target_uri = self._get_firmware_update_element().target_uri
try:
self._conn.post(target_uri, data=action_data)
except sushy.exceptions.SushyError as e:
msg = (('The Redfish controller failed to update firmware '
'with file %(file)s Error %(error)s') %
{'file': file_url, 'error': str(e)})
LOG.debug(msg) # noqa
raise exception.IloError(msg)
self.wait_for_redfish_firmware_update_to_complete(redfish_inst)
try:
state, percent = self.get_firmware_update_progress()
except sushy.exceptions.SushyError as e:
msg = ('Failed to get firmware progress update '
'Error %(error)s' % {'error': str(e)})
LOG.debug(msg)
raise exception.IloError(msg)
if state == "Error":
msg = 'Unable to update firmware'
LOG.debug(msg) # noqa
raise exception.IloError(msg)
elif state == "Unknown":
msg = 'Status of firmware update not known'
LOG.debug(msg) # noqa
else: # "Complete" | "Idle"
LOG.info('Flashing firmware file: %s ... done', file_url)
def wait_for_redfish_firmware_update_to_complete(self, redfish_object):
"""Continuously polls for iLO firmware update to complete.
:param redfish_object: redfish instance
"""
p_state = ['Idle']
c_state = ['Idle']
def has_firmware_flash_completed():
"""Checks for completion status of firmware update operation
The below table shows the conditions for which the firmware update
will be considered as DONE (be it success or error)::
+-----------------------------------+-----------------------------+
| Previous state | Current state |
+===================================+=============================+
| Idle | Error, Complete |
+-----------------------------------+-----------------------------+
| Updating, Verifying, | Complete, Error, |
| Uploading, Writing | Unknown, Idle |
+-----------------------------------+-----------------------------+
:returns: True upon firmware update completion otherwise False
"""
curr_state, curr_percent = self.get_firmware_update_progress()
p_state[0] = c_state[0]
c_state[0] = curr_state
if (((p_state[0] in ['Updating', 'Verifying',
'Uploading', 'Writing'])
and (c_state[0] in ['Complete', 'Error',
'Unknown', 'Idle']))
or (p_state[0] == 'Idle' and (c_state[0] in
['Complete', 'Error']))):
return True
return False
common.wait_for_operation_to_complete(
has_firmware_flash_completed,
delay_bw_retries=30,
failover_msg='iLO firmware update has failed.'
)
common.wait_for_ilo_after_reset(redfish_object)
def get_firmware_update_progress(self):
"""Get the progress of the firmware update.
:returns: firmware update state, one of the following values:
"Idle","Uploading","Verifying","Writing",
"Updating","Complete","Error".
If the update resource is not found, then "Unknown".
:returns: firmware update progress percent
"""
# perform refresh
try:
self.refresh()
except sushy.exceptions.SushyError as e:
msg = (('Progress of firmware update not known. '
'Error %(error)s') %
{'error': str(e)})
LOG.debug(msg)
return "Unknown", "Unknown"
# NOTE: Percentage is returned None after firmware flash is completed.
return (self.firmware_state, self.firmware_percentage)

View File

@ -0,0 +1,47 @@
{
"@odata.context": "/redfish/v1/$metadata#UpdateService",
"@odata.etag": "W/\"505EF3C5\"",
"@odata.id": "/redfish/v1/UpdateService/",
"@odata.type": "#UpdateService.v1_1_0.UpdateService",
"Actions": {
"#UpdateService.SimpleUpdate": {
"target": "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate/"
}
},
"Description": "iLO Update Service",
"FirmwareInventory": {
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/"
},
"HttpPushUri": "/cgi-bin/uploadFile",
"Id": "UpdateService",
"Name": "Update Service",
"Oem": {
"Hpe": {
"@odata.type": "#HpeiLOUpdateServiceExt.v2_0_0.HpeiLOUpdateServiceExt",
"Actions": {
"#HpeiLOUpdateServiceExt.AddFromUri": {
"target": "/redfish/v1/UpdateService/Actions/Oem/Hpe/HpeiLOUpdateServiceExt.AddFromUri/"
},
"#HpeiLOUpdateServiceExt.StartFirmwareIntegrityCheck": {
"target": "/redfish/v1/UpdateService/Actions/Oem/Hpe/HpeiLOUpdateServiceExt.StartFirmwareIntegrityCheck/"
}
},
"ComponentRepository": {
"@odata.id": "/redfish/v1/UpdateService/ComponentRepository/"
},
"CurrentTime": "2017-06-17T09:15:05Z",
"FlashProgressPercent": 24,
"InstallSets": {
"@odata.id": "/redfish/v1/UpdateService/InstallSets/"
},
"State": "Updating",
"UpdateTaskQueue": {
"@odata.id": "/redfish/v1/UpdateService/UpdateTaskQueue/"
}
}
},
"ServiceEnabled": true,
"SoftwareInventory": {
"@odata.id": "/redfish/v1/UpdateService/SoftwareInventory/"
}
}

View File

@ -0,0 +1,255 @@
# 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 mock
import sushy
import testtools
import time
from proliantutils import exception
from proliantutils.ilo import common
from proliantutils.redfish import main
from proliantutils.redfish import redfish
from proliantutils.redfish.resources import update_service
class HPEUpdateServiceTestCase(testtools.TestCase):
@mock.patch.object(main, 'HPESushy', autospec=True)
def setUp(self, sushy_mock):
super(HPEUpdateServiceTestCase, self).setUp()
self.conn = mock.MagicMock()
self.sushy = mock.MagicMock()
sushy_mock.return_value = self.sushy
with open('proliantutils/tests/redfish/'
'json_samples/update_service.json', 'r') as f:
self.conn.get.return_value.json.return_value = json.loads(f.read())
self.rf_client = redfish.RedfishOperations(
'1.2.3.4', username='foo', password='bar')
sushy_mock.assert_called_once_with(
'https://1.2.3.4', 'foo', 'bar', '/redfish/v1/', False)
self.us_inst = update_service.HPEUpdateService(
self.conn, '/redfish/v1/UpdateService/1',
redfish_version='1.0.2')
def test__get_firmware_update_element(self):
value = self.us_inst._get_firmware_update_element()
expected_uri = ('/redfish/v1/UpdateService/Actions/'
'UpdateService.SimpleUpdate/')
self.assertEqual(expected_uri, value.target_uri)
def test__get_firmware_update_element_missing_url_action(self):
self.us_inst._actions.update_firmware = None
self.assertRaisesRegex(
sushy.exceptions.MissingActionError,
'action #UpdateService.SimpleUpdate',
self.us_inst._get_firmware_update_element)
@mock.patch.object(time, 'sleep')
@mock.patch.object(update_service.HPEUpdateService,
'get_firmware_update_progress', autospec=True)
@mock.patch.object(update_service.HPEUpdateService,
'wait_for_redfish_firmware_update_to_complete',
autospec=True)
@mock.patch.object(common, 'wait_for_ilo_after_reset', lambda x: None)
def test_flash_firmware(self,
wait_for_redfish_firmware_update_to_complete_mock,
get_firmware_update_progress_mock,
sleep_mock):
# | GIVEN |
target_uri = ('/redfish/v1/UpdateService/Actions/'
'UpdateService.SimpleUpdate/')
get_firmware_update_progress_mock.return_value = 'Complete', None
# | WHEN |
self.us_inst.flash_firmware(self.rf_client, 'web_url')
# | THEN |
self.us_inst._conn.post.assert_called_once_with(
target_uri, data={'ImageURI': 'web_url'})
self.assertTrue(wait_for_redfish_firmware_update_to_complete_mock.
called)
self.assertTrue(get_firmware_update_progress_mock.called)
@mock.patch.object(time, 'sleep')
@mock.patch.object(update_service.HPEUpdateService,
'get_firmware_update_progress', autospec=True)
@mock.patch.object(common, 'wait_for_ilo_after_reset', lambda x: None)
def test_flash_firmware_post_fails(self, get_firmware_update_progress_mock,
sleep_mock):
get_firmware_update_progress_mock.return_value = 'Complete', None
self.us_inst._conn.post.side_effect = (
sushy.exceptions.SushyError)
self.assertRaisesRegex(
exception.IloError,
'The Redfish controller failed to update firmware',
self.us_inst.flash_firmware, self.rf_client, 'web_url')
@mock.patch.object(time, 'sleep')
@mock.patch.object(update_service.HPEUpdateService,
'get_firmware_update_progress', autospec=True)
@mock.patch.object(update_service.HPEUpdateService,
'wait_for_redfish_firmware_update_to_complete',
autospec=True)
@mock.patch.object(common, 'wait_for_ilo_after_reset', lambda x: None)
def test_flash_firmware_get_firmware_update_progress_throws_exception(
self, wait_for_redfish_firmware_update_to_complete_mock,
get_firmware_update_progress_mock, sleep_mock):
# | GIVEN |
self.us_inst._conn.post.return_value.status_code = 200
get_firmware_update_progress_mock.side_effect = (
sushy.exceptions.SushyError)
# | WHEN & THEN|
self.assertRaisesRegex(
exception.IloError,
'Failed to get firmware progress update',
self.us_inst.flash_firmware, self.rf_client, 'web_url')
@mock.patch.object(time, 'sleep')
@mock.patch.object(update_service.HPEUpdateService,
'get_firmware_update_progress', autospec=True)
@mock.patch.object(update_service.HPEUpdateService,
'wait_for_redfish_firmware_update_to_complete',
autospec=True)
@mock.patch.object(common, 'wait_for_ilo_after_reset', lambda x: None)
def test_flash_firmware_get_firmware_update_progress_in_error_state(
self, wait_for_redfish_firmware_update_to_complete_mock,
get_firmware_update_progress_mock, sleep_mock):
# | GIVEN |
self.us_inst._conn.post.return_value.status_code = 200
get_firmware_update_progress_mock.side_effect = [('Error', None)]
# | WHEN & THEN|
self.assertRaisesRegex(
exception.IloError,
'Unable to update firmware',
self.us_inst.flash_firmware, self.rf_client, 'web_url')
@mock.patch.object(time, 'sleep')
@mock.patch.object(update_service.HPEUpdateService,
'get_firmware_update_progress', autospec=True)
@mock.patch.object(update_service.HPEUpdateService,
'wait_for_redfish_firmware_update_to_complete',
autospec=True)
@mock.patch.object(common, 'wait_for_ilo_after_reset', lambda x: None)
def test_flash_firmware_get_firmware_update_progress_in_unknown_state(
self, wait_for_redfish_firmware_update_to_complete_mock,
get_firmware_update_progress_mock, sleep_mock):
# | GIVEN |
target_uri = ('/redfish/v1/UpdateService/Actions/'
'UpdateService.SimpleUpdate/')
get_firmware_update_progress_mock.return_value = 'Unknown', None
# | WHEN |
self.us_inst.flash_firmware(self.rf_client, 'web_url')
# | THEN |
self.us_inst._conn.post.assert_called_once_with(
target_uri, data={'ImageURI': 'web_url'})
self.assertTrue(wait_for_redfish_firmware_update_to_complete_mock.
called)
self.assertTrue(get_firmware_update_progress_mock.called)
@mock.patch.object(time, 'sleep')
@mock.patch.object(update_service.HPEUpdateService,
'get_firmware_update_progress', autospec=True)
@mock.patch.object(common, 'wait_for_ilo_after_reset', lambda x: None)
def test_wait_for_redfish_firmware_update_to_complete_ok(
self, get_firmware_update_progress_mock, sleep_mock):
# | GIVEN |
get_firmware_update_progress_mock.side_effect = [('Updating', 25),
('Complete', None)]
# | WHEN |
(self.us_inst.
wait_for_redfish_firmware_update_to_complete(self.rf_client))
# | THEN |
self.assertEqual(2, get_firmware_update_progress_mock.call_count)
@mock.patch.object(time, 'sleep')
@mock.patch.object(update_service.HPEUpdateService,
'get_firmware_update_progress', autospec=True)
@mock.patch.object(common, 'wait_for_ilo_after_reset', lambda x: None)
def test_wait_for_redfish_firmware_update_to_complete_multiple_retries(
self, get_firmware_update_progress_mock, sleep_mock):
# | GIVEN |
get_firmware_update_progress_mock.side_effect = [('Idle', 0),
('Updating', 25),
('Updating', 50),
('Updating', 75),
('Error', 0)]
# | WHEN |
(self.us_inst.
wait_for_redfish_firmware_update_to_complete(self.rf_client))
# | THEN |
self.assertEqual(5, get_firmware_update_progress_mock.call_count)
@mock.patch.object(time, 'sleep')
@mock.patch.object(update_service.HPEUpdateService,
'get_firmware_update_progress', autospec=True)
@mock.patch.object(common, 'wait_for_ilo_after_reset', lambda x: None)
def test_wait_for_redfish_firmware_update_to_complete_retry_on_exception(
self, get_firmware_update_progress_mock, sleep_mock):
# | GIVEN |
exc = exception.IloError('error')
get_firmware_update_progress_mock.side_effect = [('Updating', 25),
exc,
('Complete', None)]
# | WHEN |
(self.us_inst.
wait_for_redfish_firmware_update_to_complete(self.rf_client))
# | THEN |
self.assertEqual(3, get_firmware_update_progress_mock.call_count)
@mock.patch.object(time, 'sleep')
@mock.patch.object(update_service.HPEUpdateService,
'get_firmware_update_progress', autospec=True)
@mock.patch.object(common, 'wait_for_ilo_after_reset', lambda x: None)
def test_wait_for_redfish_firmware_update_to_complete_very_quick_update(
self, get_firmware_update_progress_mock, sleep_mock):
# | GIVEN |
get_firmware_update_progress_mock.side_effect = [('Complete', None)]
# | WHEN |
(self.us_inst.
wait_for_redfish_firmware_update_to_complete(self.rf_client))
# | THEN |
self.assertEqual(1, get_firmware_update_progress_mock.call_count)
@mock.patch.object(time, 'sleep')
@mock.patch.object(update_service.HPEUpdateService,
'get_firmware_update_progress', autospec=True)
@mock.patch.object(common, 'wait_for_ilo_after_reset', lambda x: None)
def test_wait_for_redfish_firmware_update_to_complete_fail(
self, get_firmware_update_progress_mock, sleep_mock):
# | GIVEN |
exc = exception.IloError('error')
get_firmware_update_progress_mock.side_effect = exc
# | WHEN & THEN|
self.assertRaises(exception.IloError,
(self.us_inst.
wait_for_redfish_firmware_update_to_complete),
self.rf_client)
self.assertEqual(10, get_firmware_update_progress_mock.call_count)
@mock.patch.object(update_service.HPEUpdateService, 'refresh',
autospec=True)
def test_get_firmware_update_progress(self, refresh_mock):
refresh_mock.return_value.status_code = 200
state, percent = self.us_inst.get_firmware_update_progress()
self.assertEqual(('Updating', 24), (state, percent))
@mock.patch.object(update_service.HPEUpdateService,
'refresh', autospec=True)
def test_get_firmware_update_progress_refresh_exception(self,
refresh_mock):
refresh_mock.side_effect = (sushy.exceptions.SushyError)
state, percent = self.us_inst.get_firmware_update_progress()
self.assertEqual(('Unknown', 'Unknown'), (state, percent))

View File

@ -23,6 +23,7 @@ from proliantutils import exception
from proliantutils.redfish import main
from proliantutils.redfish.resources.manager import manager
from proliantutils.redfish.resources.system import system
from proliantutils.redfish.resources import update_service
class HPESushyTestCase(testtools.TestCase):
@ -78,3 +79,12 @@ class HPESushyTestCase(testtools.TestCase):
mock_manager.assert_called_once_with(self.hpe_sushy._conn,
'1234',
self.hpe_sushy.redfish_version)
@mock.patch.object(update_service, 'HPEUpdateService', autospec=True)
def test_get_update_service(self, mock_update_service):
us_inst = self.hpe_sushy.get_update_service()
self.assertIsInstance(us_inst,
update_service.HPEUpdateService.__class__)
mock_update_service.assert_called_once_with(
self.hpe_sushy._conn, "/redfish/v1/UpdateService/",
self.hpe_sushy.redfish_version)

View File

@ -418,3 +418,23 @@ class RedfishOperationsTestCase(testtools.TestCase):
self.rf_client.set_vm_status(device='CDROM', boot_option='CONNECT')
self.assertFalse(manager_mock.called)
self.assertFalse(set_mock.called)
def test_update_firmware(self):
self.rf_client.update_firmware('fw_file_url', 'ilo')
(self.sushy.get_update_service.return_value.flash_firmware.
assert_called_once_with(self.rf_client, 'fw_file_url'))
def test_update_firmware_flash_firmware_fail(self):
(self.sushy.get_update_service.return_value.
flash_firmware.side_effect) = sushy.exceptions.SushyError
self.assertRaisesRegex(
exception.IloError,
'The Redfish controller failed to update firmware',
self.rf_client.update_firmware, 'fw_file_url', 'cpld')
def test_update_firmware_get_update_service_fail(self):
self.sushy.get_update_service.side_effect = sushy.exceptions.SushyError
self.assertRaisesRegex(
exception.IloError,
'The Redfish controller failed to update firmware',
self.rf_client.update_firmware, 'fw_file_url', 'cpld')