|
| 1 | +Copyright 2019 DMTF. All rights reserved. |
| 2 | + |
| 3 | +# Redfish Ansible OEM Extensions |
| 4 | + |
| 5 | +## About |
| 6 | + |
| 7 | +This document describes an approach for extending the standard Redfish Ansible modules in order to support OEM extensions used by various vendors. |
| 8 | + |
| 9 | +## The Approach |
| 10 | + |
| 11 | +With the current Redfish Ansible support, there are three main modules (`redfish_command.py`, `redfish_config.py` and `redfish_info.py`). These three modules are relatively thin modules that establish the commands and their parameters and then call out to a utility module, `redfish_utils.py`, that has the bulk of the logic to interact with the Redfish services. |
| 12 | + |
| 13 | +The `redfish_utils.py` module implements a `RedfishUtils` class that has many methods to perform the various Redfish operations. With this architecture, a new vendor module that needs to add a specific Oem feature can create a new class (e.g. `ContosoRedfishUtils`) that extends the existing `RedfishUtils`. New method(s) can be added to that class (or existing methods overridden) while still being able to leverage all the existing standard methods in the parent class. Then a new vendor module (e.g. `contoso_redfish_command.py`) can be written that is basically a sparse version of `redfish_command.py` that only needs to handle the new or changed vendor commands. |
| 14 | + |
| 15 | +Here is an example module that does what is described above. This example provides a non-standard `CreateBiosConfigJob` command. Here is the code: |
| 16 | + |
| 17 | +``` |
| 18 | +DOCUMENTATION = ''' |
| 19 | +--- |
| 20 | +module: contoso_redfish_command |
| 21 | +version_added: "2.7" |
| 22 | +short_description: Manages Out-Of-Band controllers using Contoso Oem Redfish APIs |
| 23 | +description: |
| 24 | + - Builds Redfish URIs locally and sends them to remote OOB controllers to |
| 25 | + perform an action. |
| 26 | + - For use with operations that require Contoso Oem extensions |
| 27 | +options: |
| 28 | + category: |
| 29 | + required: true |
| 30 | + description: |
| 31 | + - Category to execute on OOB controller |
| 32 | + command: |
| 33 | + required: true |
| 34 | + description: |
| 35 | + - List of commands to execute on OOB controller |
| 36 | + baseuri: |
| 37 | + required: true |
| 38 | + description: |
| 39 | + - Base URI of OOB controller |
| 40 | + user: |
| 41 | + required: true |
| 42 | + description: |
| 43 | + - User for authentication with OOB controller |
| 44 | + password: |
| 45 | + required: true |
| 46 | + description: |
| 47 | + - Password for authentication with OOB controller |
| 48 | +
|
| 49 | +author: "" |
| 50 | +''' |
| 51 | +
|
| 52 | +EXAMPLES = ''' |
| 53 | + - name: Create BIOS config job |
| 54 | + contoso_redfish_command: |
| 55 | + category: Systems |
| 56 | + command: CreateBiosConfigJob |
| 57 | + baseuri: "{{ baseuri }}" |
| 58 | + user: "{{ user }}" |
| 59 | + password: "{{ password }}" |
| 60 | +''' |
| 61 | +
|
| 62 | +RETURN = ''' |
| 63 | +msg: |
| 64 | + description: Message with action result or error description |
| 65 | + returned: always |
| 66 | + type: string |
| 67 | + sample: "Action was successful" |
| 68 | +''' |
| 69 | +
|
| 70 | +import re |
| 71 | +from ansible.module_utils.basic import AnsibleModule |
| 72 | +from ansible.module_utils.redfish_utils import RedfishUtils, HEADERS |
| 73 | +from ansible.module_utils._text import to_native |
| 74 | +
|
| 75 | +class ContosoRedfishUtils(RedfishUtils): |
| 76 | +
|
| 77 | + def create_bios_config_job(self): |
| 78 | + result = {} |
| 79 | + key = "Bios" |
| 80 | + jobs = "Jobs" |
| 81 | +
|
| 82 | + # Search for 'key' entry and extract URI from it |
| 83 | + response = self.get_request(self.root_uri + self.systems_uri) |
| 84 | + if response['ret'] is False: |
| 85 | + return response |
| 86 | + result['ret'] = True |
| 87 | + data = response['data'] |
| 88 | +
|
| 89 | + if key not in data: |
| 90 | + return {'ret': False, 'msg': "Key %s not found" % key} |
| 91 | +
|
| 92 | + bios_uri = data[key]["@odata.id"] |
| 93 | +
|
| 94 | + # Extract proper URI |
| 95 | + response = self.get_request(self.root_uri + bios_uri) |
| 96 | + if response['ret'] is False: |
| 97 | + return response |
| 98 | + result['ret'] = True |
| 99 | + data = response['data'] |
| 100 | + set_bios_attr_uri = data["@Redfish.Settings"]["SettingsObject"][ |
| 101 | + "@odata.id"] |
| 102 | +
|
| 103 | + payload = {"TargetSettingsURI": set_bios_attr_uri} |
| 104 | + response = self.post_request( |
| 105 | + self.root_uri + self.manager_uri + "/" + jobs, |
| 106 | + payload, HEADERS) |
| 107 | + if response['ret'] is False: |
| 108 | + return response |
| 109 | +
|
| 110 | + response_output = response['resp'].__dict__ |
| 111 | + job_id = response_output["headers"]["Location"] |
| 112 | + job_id = re.search("JID_.+", job_id).group() |
| 113 | + # Currently not passing job_id back to user but patch is coming |
| 114 | + return {'ret': True, 'msg': "Config job %s created" % job_id} |
| 115 | +
|
| 116 | +
|
| 117 | +CATEGORY_COMMANDS_ALL = { |
| 118 | + "Systems": ["CreateBiosConfigJob"], |
| 119 | + "Accounts": [], |
| 120 | + "Manager": [] |
| 121 | +} |
| 122 | +
|
| 123 | +
|
| 124 | +def main(): |
| 125 | + result = {} |
| 126 | + module = AnsibleModule( |
| 127 | + argument_spec=dict( |
| 128 | + category=dict(required=True), |
| 129 | + command=dict(required=True, type='list'), |
| 130 | + baseuri=dict(required=True), |
| 131 | + user=dict(required=True), |
| 132 | + password=dict(required=True, no_log=True) |
| 133 | + ), |
| 134 | + supports_check_mode=False |
| 135 | + ) |
| 136 | +
|
| 137 | + category = module.params['category'] |
| 138 | + command_list = module.params['command'] |
| 139 | +
|
| 140 | + # admin credentials used for authentication |
| 141 | + creds = {'user': module.params['user'], |
| 142 | + 'pswd': module.params['password']} |
| 143 | +
|
| 144 | + # Build root URI |
| 145 | + root_uri = "https://" + module.params['baseuri'] |
| 146 | + rf_uri = "/redfish/v1/" |
| 147 | + rf_utils = ContosoRedfishUtils(creds, root_uri) |
| 148 | +
|
| 149 | + # Check that Category is valid |
| 150 | + if category not in CATEGORY_COMMANDS_ALL: |
| 151 | + module.fail_json(msg=to_native("Invalid Category '%s'. Valid Categories = %s" % (category, CATEGORY_COMMANDS_ALL.keys()))) |
| 152 | +
|
| 153 | + # Check that all commands are valid |
| 154 | + for cmd in command_list: |
| 155 | + # Fail if even one command given is invalid |
| 156 | + if cmd not in CATEGORY_COMMANDS_ALL[category]: |
| 157 | + module.fail_json(msg=to_native("Invalid Command '%s'. Valid Commands = %s" % (cmd, CATEGORY_COMMANDS_ALL[category]))) |
| 158 | +
|
| 159 | + # Organize by Categories / Commands |
| 160 | +
|
| 161 | + if category == "Systems": |
| 162 | + # execute only if we find a System resource |
| 163 | + result = rf_utils._find_systems_resource(rf_uri) |
| 164 | + if result['ret'] is False: |
| 165 | + module.fail_json(msg=to_native(result['msg'])) |
| 166 | +
|
| 167 | + for command in command_list: |
| 168 | + if command == "CreateBiosConfigJob": |
| 169 | + # execute only if we find a Managers resource |
| 170 | + result = rf_utils._find_managers_resource(rf_uri) |
| 171 | + if result['ret'] is False: |
| 172 | + module.fail_json(msg=to_native(result['msg'])) |
| 173 | + result = rf_utils.create_bios_config_job() |
| 174 | +
|
| 175 | + # Return data back or fail with proper message |
| 176 | + if result['ret'] is True: |
| 177 | + del result['ret'] |
| 178 | + module.exit_json(changed=True, msg='Action was successful') |
| 179 | + else: |
| 180 | + module.fail_json(msg=to_native(result['msg'])) |
| 181 | +
|
| 182 | +
|
| 183 | +if __name__ == '__main__': |
| 184 | + main() |
| 185 | +``` |
| 186 | + |
| 187 | +Given this new vendor module, playbooks like this can be written that call a mix of tasks using standard and vendor modules. For example, here is a playbook that sets some BIOS attributes using a standard module and then applies the attributes using a vendor module: |
| 188 | + |
| 189 | +``` |
| 190 | +--- |
| 191 | +- hosts: myhosts |
| 192 | + connection: local |
| 193 | + name: Set BIOS attribute - choose below |
| 194 | + gather_facts: False |
| 195 | +
|
| 196 | + # Updating BIOS settings requires creating a configuration job to implement, |
| 197 | + # which then reboots the system. |
| 198 | +
|
| 199 | + # BIOS attributes that have been tested |
| 200 | + # |
| 201 | + # Name Value |
| 202 | + # -------------------------------------------------- |
| 203 | + # OsWatchdogTimer Disabled / Enabled |
| 204 | + # ProcVirtualization Disabled / Enabled |
| 205 | + # MemTest Disabled / Enabled |
| 206 | + # SriovGlobalEnable Disabled / Enabled |
| 207 | +
|
| 208 | + vars: |
| 209 | + - attribute_name1: SriovGlobalEnable |
| 210 | + - attribute_value1: Disabled |
| 211 | + - attribute_name2: ProcVirtualization |
| 212 | + - attribute_value2: Enabled |
| 213 | +
|
| 214 | + tasks: |
| 215 | +
|
| 216 | + - name: Set BIOS attribute {{ attribute_name1 }} to {{ attribute_value1 }} |
| 217 | + redfish_config: |
| 218 | + category: Systems |
| 219 | + command: SetBiosAttributes |
| 220 | + bios_attr_name: "{{ attribute_name1 }}" |
| 221 | + bios_attr_value: "{{ attribute_value1 }}" |
| 222 | + baseuri: "{{ baseuri }}" |
| 223 | + user: "{{ user }}" |
| 224 | + password: "{{ password }}" |
| 225 | +
|
| 226 | + - name: Set BIOS attribute {{ attribute_name2 }} to {{ attribute_value2 }} |
| 227 | + redfish_config: |
| 228 | + category: Systems |
| 229 | + command: SetBiosAttributes |
| 230 | + bios_attr_name: "{{ attribute_name2 }}" |
| 231 | + bios_attr_value: "{{ attribute_value2 }}" |
| 232 | + baseuri: "{{ baseuri }}" |
| 233 | + user: "{{ user }}" |
| 234 | + password: "{{ password }}" |
| 235 | +
|
| 236 | + - name: Schedule Config Job - Reboot |
| 237 | + contoso_redfish_command: |
| 238 | + category: Systems |
| 239 | + command: CreateBiosConfigJob |
| 240 | + baseuri: "{{ baseuri }}" |
| 241 | + user: "{{ user }}" |
| 242 | + password: "{{ password }}" |
| 243 | +``` |
| 244 | + |
| 245 | +Note that the first 2 tasks use a standard module (`redfish_config`) and the last task uses a vendor module (`contoso_redfish_command`). |
| 246 | + |
| 247 | +And here is an example playbook to set the BIOS attributes using all standard modules: |
| 248 | + |
| 249 | +``` |
| 250 | +--- |
| 251 | +- hosts: myhosts |
| 252 | + connection: local |
| 253 | + name: Set BIOS attribute - choose below |
| 254 | + gather_facts: False |
| 255 | +
|
| 256 | + # Updating BIOS settings requires creating a configuration job to implement, |
| 257 | + # which then reboots the system. |
| 258 | +
|
| 259 | + # BIOS attributes that have been tested |
| 260 | + # |
| 261 | + # Name Value |
| 262 | + # -------------------------------------------------- |
| 263 | + # OsWatchdogTimer Disabled / Enabled |
| 264 | + # ProcVirtualization Disabled / Enabled |
| 265 | + # MemTest Disabled / Enabled |
| 266 | + # SriovGlobalEnable Disabled / Enabled |
| 267 | +
|
| 268 | + vars: |
| 269 | + - attribute_name1: SriovGlobalEnable |
| 270 | + - attribute_value1: Disabled |
| 271 | + - attribute_name2: ProcVirtualization |
| 272 | + - attribute_value2: Enabled |
| 273 | +
|
| 274 | + tasks: |
| 275 | +
|
| 276 | + - name: Set BIOS attribute {{ attribute_name1 }} to {{ attribute_value1 }} |
| 277 | + redfish_config: |
| 278 | + category: Systems |
| 279 | + command: SetBiosAttributes |
| 280 | + bios_attr_name: "{{ attribute_name1 }}" |
| 281 | + bios_attr_value: "{{ attribute_value1 }}" |
| 282 | + baseuri: "{{ baseuri }}" |
| 283 | + user: "{{ user }}" |
| 284 | + password: "{{ password }}" |
| 285 | +
|
| 286 | + - name: Set BIOS attribute {{ attribute_name2 }} to {{ attribute_value2 }} |
| 287 | + redfish_config: |
| 288 | + category: Systems |
| 289 | + command: SetBiosAttributes |
| 290 | + bios_attr_name: "{{ attribute_name2 }}" |
| 291 | + bios_attr_value: "{{ attribute_value2 }}" |
| 292 | + baseuri: "{{ baseuri }}" |
| 293 | + user: "{{ user }}" |
| 294 | + password: "{{ password }}" |
| 295 | +
|
| 296 | + - name: Reboot |
| 297 | + redfish_command: |
| 298 | + category: Systems |
| 299 | + command: PowerReboot |
| 300 | + baseuri: "{{ baseuri }}" |
| 301 | + user: "{{ user }}" |
| 302 | + password: "{{ password }}" |
| 303 | +``` |
0 commit comments