Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add capabilities to the Redfish SimpleUpdate redfish_command #4276

Closed
1 task done
sylgeist opened this issue Feb 23, 2022 · 8 comments · Fixed by #5580
Closed
1 task done

Add capabilities to the Redfish SimpleUpdate redfish_command #4276

sylgeist opened this issue Feb 23, 2022 · 8 comments · Fixed by #5580
Labels
feature This issue/PR relates to a feature request

Comments

@sylgeist
Copy link
Contributor

Summary

The Redfish Firmware Updater process described here (See Chapter 6) allows for asynchronous polling of the update job by querying the returned "TaskMonitor". Currently, the Redfish Command handled by:

def simple_update(self, update_opts):
image_uri = update_opts.get('update_image_uri')
protocol = update_opts.get('update_protocol')
targets = update_opts.get('update_targets')
creds = update_opts.get('update_creds')
if not image_uri:
return {'ret': False, 'msg':
'Must specify update_image_uri for the SimpleUpdate command'}
response = self.get_request(self.root_uri + self.update_uri)
if response['ret'] is False:
return response
data = response['data']
if 'Actions' not in data:
return {'ret': False, 'msg': 'Service does not support SimpleUpdate'}
if '#UpdateService.SimpleUpdate' not in data['Actions']:
return {'ret': False, 'msg': 'Service does not support SimpleUpdate'}
action = data['Actions']['#UpdateService.SimpleUpdate']
if 'target' not in action:
return {'ret': False, 'msg': 'Service does not support SimpleUpdate'}
update_uri = action['target']
if protocol:
default_values = ['CIFS', 'FTP', 'SFTP', 'HTTP', 'HTTPS', 'NSF',
'SCP', 'TFTP', 'OEM', 'NFS']
allowable_values = self._get_allowable_values(action,
'TransferProtocol',
default_values)
if protocol not in allowable_values:
return {'ret': False,
'msg': 'Specified update_protocol (%s) not supported '
'by service. Supported protocols: %s' %
(protocol, allowable_values)}
if targets:
allowable_values = self._get_allowable_values(action, 'Targets')
if allowable_values:
for target in targets:
if target not in allowable_values:
return {'ret': False,
'msg': 'Specified target (%s) not supported '
'by service. Supported targets: %s' %
(target, allowable_values)}
payload = {
'ImageURI': image_uri
}
if protocol:
payload["TransferProtocol"] = protocol
if targets:
payload["Targets"] = targets
if creds:
if creds.get('username'):
payload["Username"] = creds.get('username')
if creds.get('password'):
payload["Password"] = creds.get('password')
response = self.post_request(self.root_uri + update_uri, payload)
if response['ret'] is False:
return response
return {'ret': True, 'changed': True,
'msg': "SimpleUpdate requested"}

only returns True/False on the job submission, but no other details about the job. This adds extra complexity in searching for the Task after the fact and manually polling.

It would be great if the RF utils module could return Task Monitors where appropriate for long running jobs and It would be even more awesome if the module could abstract the async job process away from the user:

  • Accept a "Wait for completion" parameter that would instruct the module to poll the Task and return status.
  • Accept the @Redfish.OperationApplyTime parameter for more control over the update process timing.

See also Redfish Task Presentation

Issue Type

Feature Idea

Component Name

Redfish Utils

Additional Information

When using the URI module a SimpleUpdate returns:

{
...
  "json": {
    "@odata.context": "/redfish/v1/$metadata#Task.Task",
    "@odata.id": "/redfish/v1/TaskService/Tasks/2",
    "@odata.type": "#Task.v1_3_0.Task",
    "Description": "Update Task",
    "Id": "2",
    "Messages": [
      {}
    ],
    "Name": "Task 2",
    "Payload": {
      "HttpOperation": "POST",
      "JsonBody": "{\"ImageURI\":\"http://updatefile\"}",
      "TargetUri": "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate"
    },
    "StartTime": "2022-02-07T17:12:41Z",
    "TaskMonitor": "/redfish/v1/TaskService/TaskMonitors/2",
    "TaskState": "New"
  },
  "location": "https://1.2.3.4/redfish/v1/TaskService/TaskMonitors/2",
  "msg": "OK (unknown bytes)",
...
}

Where the TaskMonitor and TaskState are returned for further use.

The Redfish Command SimpleUpdate only returns:
TASK [Simple update] ************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************
changed: [localhost] => {"attempts": 1, "changed": true, "msg": "Action was successful", "session": {}}

TASK [debug] ********************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************
ok: [localhost] => {
    "bios_upgrade": {
        "attempts": 1,
        "changed": true,
        "failed": false,
        "msg": "Action was successful",
        "session": {}
    }
}

Code of Conduct

  • I agree to follow the Ansible Code of Conduct
@ansibullbot
Copy link
Collaborator

Files identified in the description:
None

If these files are incorrect, please update the component name section of the description or use the !component bot command.

click here for bot help

@ansibullbot ansibullbot added the feature This issue/PR relates to a feature request label Feb 23, 2022
@mraineri
Copy link
Contributor

mraineri commented Apr 6, 2022

Agreed this should be added to the module. One complexity to consider though is the flow might require user intervention in order for the update process to make forward progress (such as performing system resets). Would it be reasonable to have the module issue the reset request automatically as it appears in the job? Having the playbook pause indefinitely for the user to do an operation via other methods doesn't seem like a good approach. Maybe we can make the assumption that if a user issues this type of request that they're prepared for the system to be reset?

@mraineri
Copy link
Contributor

mraineri commented Apr 8, 2022

@felixfontein do you have any guidance from an "Ansible best practices" perspective? Is it safe to assume that if a user runs a playbook, they want the desired outcome regardless of the side effects? If not, is it okay for a playbook to sit and wait for a user to perform some other operation (like invoke a system reset)?

@felixfontein
Copy link
Collaborator

@mraineri hmm, that's tricky. It depends a lot on what the side-effects are, resp. how obvious they are :) If the command is 'reset system' it's not surprising that the system is restarted. If the command is 'upgrade firmware' some might only expect that the upgrade will happen on the next restart, and not that the system is restarted right now. But also if the operation requires user interaction and the user isn't aware that it does, that wouldn't be very helpful.

I would definitely make the wait optional, and potentially outsource it to another module, like have the module return some task monitor ID, and have another module that accepts one or more task monitor IDs and waits for all of them to complete. That would keep the original module simpler and allow to add more complexity to the wait module without impacting the original module. (You might want to have wait timeouts, or if multiple task monitor IDs are specified just wait until N of them finished and not all, or ...)

@mraineri
Copy link
Contributor

mraineri commented May 5, 2022

@felixfontein I've been thinking about how to approach this a bit more based on your feedback. Here is what I'm thinking:

  1. Change SimpleUpdate to add new parameters that are allowed by the Redfish standard (at least from this issue, we might only be missing OperationApplyTime).
  2. Add to the output of SimpleUpdate the task handle returned by the service.
  3. Add a new command (proposing "CheckUpdateProgress") that takes a task handle for querying the service; the output will be the progress of the update, if any resets are needed, other information, and the task handle (in case the update progress is redirected elsewhere).
  4. Add a new command (proposing "PerformInterventions", but I think the name needs some work) that takes a task handle, and it will walk the status reported by the service to perform any requested operations (such as reset operations to devices or systems).

Essentially using these commands from an end-to-end perspective would look something like this:

update_status, handle = SimpleUpdate()
while( handle ) {
    update_status, handle, intervention_required = CheckUpdateProgress( handle )
    if intervention_required {
        PerformInterventions( handle )
    }
}
print_update_results( update_status )

I'm considering this approach simply because all of the above operations will not block the caller waiting for some event, so it gives the users the flexibility to decide how frequently to poll, how to handle pauses in the update progress, and other types of situations where you might need to wait for something to happen.

@felixfontein
Copy link
Collaborator

@mraineri I think that sounds great! Having these 'building blocks' enables users to do everything they want, even though it might require some extra work on the playbook / role level (multiple tasks for one operation).

@mraineri
Copy link
Contributor

Thanks for the feedback; I'll work on this when I'm back to work.

@sseekamp
Copy link
Contributor

Thank you @mraineri! This is great!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature This issue/PR relates to a feature request
Projects
None yet
5 participants