1# Copyright (c) 2017-2018 Dell EMC Inc.
2# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
3
4from __future__ import absolute_import, division, print_function
5__metaclass__ = type
6
7import json
8from ansible.module_utils.urls import open_url
9from ansible.module_utils._text import to_native
10from ansible.module_utils._text import to_text
11from ansible.module_utils.six.moves import http_client
12from ansible.module_utils.six.moves.urllib.error import URLError, HTTPError
13
14GET_HEADERS = {'accept': 'application/json', 'OData-Version': '4.0'}
15POST_HEADERS = {'content-type': 'application/json', 'accept': 'application/json',
16                'OData-Version': '4.0'}
17PATCH_HEADERS = {'content-type': 'application/json', 'accept': 'application/json',
18                 'OData-Version': '4.0'}
19DELETE_HEADERS = {'accept': 'application/json', 'OData-Version': '4.0'}
20
21
22class RedfishUtils(object):
23
24    def __init__(self, creds, root_uri, timeout, module):
25        self.root_uri = root_uri
26        self.creds = creds
27        self.timeout = timeout
28        self.module = module
29        self.service_root = '/redfish/v1/'
30        self._init_session()
31
32    # The following functions are to send GET/POST/PATCH/DELETE requests
33    def get_request(self, uri):
34        try:
35            resp = open_url(uri, method="GET", headers=GET_HEADERS,
36                            url_username=self.creds['user'],
37                            url_password=self.creds['pswd'],
38                            force_basic_auth=True, validate_certs=False,
39                            follow_redirects='all',
40                            use_proxy=False, timeout=self.timeout)
41            data = json.loads(to_native(resp.read()))
42            headers = dict((k.lower(), v) for (k, v) in resp.info().items())
43        except HTTPError as e:
44            msg = self._get_extended_message(e)
45            return {'ret': False,
46                    'msg': "HTTP Error %s on GET request to '%s', extended message: '%s'"
47                           % (e.code, uri, msg)}
48        except URLError as e:
49            return {'ret': False, 'msg': "URL Error on GET request to '%s': '%s'"
50                                         % (uri, e.reason)}
51        # Almost all errors should be caught above, but just in case
52        except Exception as e:
53            return {'ret': False,
54                    'msg': "Failed GET request to '%s': '%s'" % (uri, to_text(e))}
55        return {'ret': True, 'data': data, 'headers': headers}
56
57    def post_request(self, uri, pyld):
58        try:
59            resp = open_url(uri, data=json.dumps(pyld),
60                            headers=POST_HEADERS, method="POST",
61                            url_username=self.creds['user'],
62                            url_password=self.creds['pswd'],
63                            force_basic_auth=True, validate_certs=False,
64                            follow_redirects='all',
65                            use_proxy=False, timeout=self.timeout)
66        except HTTPError as e:
67            msg = self._get_extended_message(e)
68            return {'ret': False,
69                    'msg': "HTTP Error %s on POST request to '%s', extended message: '%s'"
70                           % (e.code, uri, msg)}
71        except URLError as e:
72            return {'ret': False, 'msg': "URL Error on POST request to '%s': '%s'"
73                                         % (uri, e.reason)}
74        # Almost all errors should be caught above, but just in case
75        except Exception as e:
76            return {'ret': False,
77                    'msg': "Failed POST request to '%s': '%s'" % (uri, to_text(e))}
78        return {'ret': True, 'resp': resp}
79
80    def patch_request(self, uri, pyld):
81        headers = PATCH_HEADERS
82        r = self.get_request(uri)
83        if r['ret']:
84            # Get etag from etag header or @odata.etag property
85            etag = r['headers'].get('etag')
86            if not etag:
87                etag = r['data'].get('@odata.etag')
88            if etag:
89                # Make copy of headers and add If-Match header
90                headers = dict(headers)
91                headers['If-Match'] = etag
92        try:
93            resp = open_url(uri, data=json.dumps(pyld),
94                            headers=headers, method="PATCH",
95                            url_username=self.creds['user'],
96                            url_password=self.creds['pswd'],
97                            force_basic_auth=True, validate_certs=False,
98                            follow_redirects='all',
99                            use_proxy=False, timeout=self.timeout)
100        except HTTPError as e:
101            msg = self._get_extended_message(e)
102            return {'ret': False,
103                    'msg': "HTTP Error %s on PATCH request to '%s', extended message: '%s'"
104                           % (e.code, uri, msg)}
105        except URLError as e:
106            return {'ret': False, 'msg': "URL Error on PATCH request to '%s': '%s'"
107                                         % (uri, e.reason)}
108        # Almost all errors should be caught above, but just in case
109        except Exception as e:
110            return {'ret': False,
111                    'msg': "Failed PATCH request to '%s': '%s'" % (uri, to_text(e))}
112        return {'ret': True, 'resp': resp}
113
114    def delete_request(self, uri, pyld):
115        try:
116            resp = open_url(uri, data=json.dumps(pyld),
117                            headers=DELETE_HEADERS, method="DELETE",
118                            url_username=self.creds['user'],
119                            url_password=self.creds['pswd'],
120                            force_basic_auth=True, validate_certs=False,
121                            follow_redirects='all',
122                            use_proxy=False, timeout=self.timeout)
123        except HTTPError as e:
124            msg = self._get_extended_message(e)
125            return {'ret': False,
126                    'msg': "HTTP Error %s on DELETE request to '%s', extended message: '%s'"
127                           % (e.code, uri, msg)}
128        except URLError as e:
129            return {'ret': False, 'msg': "URL Error on DELETE request to '%s': '%s'"
130                                         % (uri, e.reason)}
131        # Almost all errors should be caught above, but just in case
132        except Exception as e:
133            return {'ret': False,
134                    'msg': "Failed DELETE request to '%s': '%s'" % (uri, to_text(e))}
135        return {'ret': True, 'resp': resp}
136
137    @staticmethod
138    def _get_extended_message(error):
139        """
140        Get Redfish ExtendedInfo message from response payload if present
141        :param error: an HTTPError exception
142        :type error: HTTPError
143        :return: the ExtendedInfo message if present, else standard HTTP error
144        """
145        msg = http_client.responses.get(error.code, '')
146        if error.code >= 400:
147            try:
148                body = error.read().decode('utf-8')
149                data = json.loads(body)
150                ext_info = data['error']['@Message.ExtendedInfo']
151                msg = ext_info[0]['Message']
152            except Exception:
153                pass
154        return msg
155
156    def _init_session(self):
157        pass
158
159    def _find_accountservice_resource(self):
160        response = self.get_request(self.root_uri + self.service_root)
161        if response['ret'] is False:
162            return response
163        data = response['data']
164        if 'AccountService' not in data:
165            return {'ret': False, 'msg': "AccountService resource not found"}
166        else:
167            account_service = data["AccountService"]["@odata.id"]
168            response = self.get_request(self.root_uri + account_service)
169            if response['ret'] is False:
170                return response
171            data = response['data']
172            accounts = data['Accounts']['@odata.id']
173            if accounts[-1:] == '/':
174                accounts = accounts[:-1]
175            self.accounts_uri = accounts
176        return {'ret': True}
177
178    def _find_sessionservice_resource(self):
179        response = self.get_request(self.root_uri + self.service_root)
180        if response['ret'] is False:
181            return response
182        data = response['data']
183        if 'SessionService' not in data:
184            return {'ret': False, 'msg': "SessionService resource not found"}
185        else:
186            session_service = data["SessionService"]["@odata.id"]
187            response = self.get_request(self.root_uri + session_service)
188            if response['ret'] is False:
189                return response
190            data = response['data']
191            sessions = data['Sessions']['@odata.id']
192            if sessions[-1:] == '/':
193                sessions = sessions[:-1]
194            self.sessions_uri = sessions
195        return {'ret': True}
196
197    def _find_systems_resource(self):
198        response = self.get_request(self.root_uri + self.service_root)
199        if response['ret'] is False:
200            return response
201        data = response['data']
202        if 'Systems' not in data:
203            return {'ret': False, 'msg': "Systems resource not found"}
204        response = self.get_request(self.root_uri + data['Systems']['@odata.id'])
205        if response['ret'] is False:
206            return response
207        self.systems_uris = [
208            i['@odata.id'] for i in response['data'].get('Members', [])]
209        if not self.systems_uris:
210            return {
211                'ret': False,
212                'msg': "ComputerSystem's Members array is either empty or missing"}
213        return {'ret': True}
214
215    def _find_updateservice_resource(self):
216        response = self.get_request(self.root_uri + self.service_root)
217        if response['ret'] is False:
218            return response
219        data = response['data']
220        if 'UpdateService' not in data:
221            return {'ret': False, 'msg': "UpdateService resource not found"}
222        else:
223            update = data["UpdateService"]["@odata.id"]
224            self.update_uri = update
225            response = self.get_request(self.root_uri + update)
226            if response['ret'] is False:
227                return response
228            data = response['data']
229            firmware_inventory = data['FirmwareInventory'][u'@odata.id']
230            self.firmware_uri = firmware_inventory
231            return {'ret': True}
232
233    def _find_chassis_resource(self):
234        chassis_service = []
235        response = self.get_request(self.root_uri + self.service_root)
236        if response['ret'] is False:
237            return response
238        data = response['data']
239        if 'Chassis' not in data:
240            return {'ret': False, 'msg': "Chassis resource not found"}
241        else:
242            chassis = data["Chassis"]["@odata.id"]
243            response = self.get_request(self.root_uri + chassis)
244            if response['ret'] is False:
245                return response
246            data = response['data']
247            for member in data[u'Members']:
248                chassis_service.append(member[u'@odata.id'])
249            self.chassis_uri_list = chassis_service
250            return {'ret': True}
251
252    def _find_managers_resource(self):
253        response = self.get_request(self.root_uri + self.service_root)
254        if response['ret'] is False:
255            return response
256        data = response['data']
257        if 'Managers' not in data:
258            return {'ret': False, 'msg': "Manager resource not found"}
259        else:
260            manager = data["Managers"]["@odata.id"]
261            response = self.get_request(self.root_uri + manager)
262            if response['ret'] is False:
263                return response
264            data = response['data']
265            for member in data[u'Members']:
266                manager_service = member[u'@odata.id']
267            self.manager_uri = manager_service
268            return {'ret': True}
269
270    def get_logs(self):
271        log_svcs_uri_list = []
272        list_of_logs = []
273
274        # Find LogService
275        response = self.get_request(self.root_uri + self.manager_uri)
276        if response['ret'] is False:
277            return response
278        data = response['data']
279        if 'LogServices' not in data:
280            return {'ret': False, 'msg': "LogServices resource not found"}
281
282        # Find all entries in LogServices
283        logs_uri = data["LogServices"]["@odata.id"]
284        response = self.get_request(self.root_uri + logs_uri)
285        if response['ret'] is False:
286            return response
287        data = response['data']
288        for log_svcs_entry in data[u'Members']:
289            response = self.get_request(self.root_uri + log_svcs_entry[u'@odata.id'])
290            if response['ret'] is False:
291                return response
292            _data = response['data']
293            log_svcs_uri_list.append(_data['Entries'][u'@odata.id'])
294
295        # For each entry in LogServices, get log name and all log entries
296        for log_svcs_uri in log_svcs_uri_list:
297            logs = {}
298            list_of_log_entries = []
299            response = self.get_request(self.root_uri + log_svcs_uri)
300            if response['ret'] is False:
301                return response
302            data = response['data']
303            logs['Description'] = data['Description']
304            # Get all log entries for each type of log found
305            for logEntry in data[u'Members']:
306                # I only extract some fields - Are these entry names standard?
307                list_of_log_entries.append(dict(
308                    Name=logEntry[u'Name'],
309                    Created=logEntry[u'Created'],
310                    Message=logEntry[u'Message'],
311                    Severity=logEntry[u'Severity']))
312            log_name = log_svcs_uri.split('/')[-1]
313            logs[log_name] = list_of_log_entries
314            list_of_logs.append(logs)
315
316        # list_of_logs[logs{list_of_log_entries[entry{}]}]
317        return {'ret': True, 'entries': list_of_logs}
318
319    def clear_logs(self):
320        # Find LogService
321        response = self.get_request(self.root_uri + self.manager_uri)
322        if response['ret'] is False:
323            return response
324        data = response['data']
325        if 'LogServices' not in data:
326            return {'ret': False, 'msg': "LogServices resource not found"}
327
328        # Find all entries in LogServices
329        logs_uri = data["LogServices"]["@odata.id"]
330        response = self.get_request(self.root_uri + logs_uri)
331        if response['ret'] is False:
332            return response
333        data = response['data']
334
335        for log_svcs_entry in data[u'Members']:
336            response = self.get_request(self.root_uri + log_svcs_entry["@odata.id"])
337            if response['ret'] is False:
338                return response
339            _data = response['data']
340            # Check to make sure option is available, otherwise error is ugly
341            if "Actions" in _data:
342                if "#LogService.ClearLog" in _data[u"Actions"]:
343                    self.post_request(self.root_uri + _data[u"Actions"]["#LogService.ClearLog"]["target"], {})
344                    if response['ret'] is False:
345                        return response
346        return {'ret': True}
347
348    def aggregate(self, func):
349        ret = True
350        entries = []
351        for systems_uri in self.systems_uris:
352            inventory = func(systems_uri)
353            ret = inventory.pop('ret') and ret
354            if 'entries' in inventory:
355                entries.append(({'systems_uri': systems_uri},
356                                inventory['entries']))
357        return dict(ret=ret, entries=entries)
358
359    def get_storage_controller_inventory(self, systems_uri):
360        result = {}
361        controller_list = []
362        controller_results = []
363        # Get these entries, but does not fail if not found
364        properties = ['CacheSummary', 'FirmwareVersion', 'Identifiers',
365                      'Location', 'Manufacturer', 'Model', 'Name',
366                      'PartNumber', 'SerialNumber', 'SpeedGbps', 'Status']
367        key = "StorageControllers"
368
369        # Find Storage service
370        response = self.get_request(self.root_uri + systems_uri)
371        if response['ret'] is False:
372            return response
373        data = response['data']
374
375        if 'Storage' not in data:
376            return {'ret': False, 'msg': "Storage resource not found"}
377
378        # Get a list of all storage controllers and build respective URIs
379        storage_uri = data['Storage']["@odata.id"]
380        response = self.get_request(self.root_uri + storage_uri)
381        if response['ret'] is False:
382            return response
383        result['ret'] = True
384        data = response['data']
385
386        # Loop through Members and their StorageControllers
387        # and gather properties from each StorageController
388        if data[u'Members']:
389            for storage_member in data[u'Members']:
390                storage_member_uri = storage_member[u'@odata.id']
391                response = self.get_request(self.root_uri + storage_member_uri)
392                data = response['data']
393
394                if key in data:
395                    controller_list = data[key]
396                    for controller in controller_list:
397                        controller_result = {}
398                        for property in properties:
399                            if property in controller:
400                                controller_result[property] = controller[property]
401                        controller_results.append(controller_result)
402                result['entries'] = controller_results
403            return result
404        else:
405            return {'ret': False, 'msg': "Storage resource not found"}
406
407    def get_multi_storage_controller_inventory(self):
408        return self.aggregate(self.get_storage_controller_inventory)
409
410    def get_disk_inventory(self, systems_uri):
411        result = {'entries': []}
412        controller_list = []
413        disk_results = []
414        # Get these entries, but does not fail if not found
415        properties = ['BlockSizeBytes', 'CapableSpeedGbs', 'CapacityBytes',
416                      'EncryptionAbility', 'EncryptionStatus',
417                      'FailurePredicted', 'HotspareType', 'Id', 'Identifiers',
418                      'Manufacturer', 'MediaType', 'Model', 'Name',
419                      'PartNumber', 'PhysicalLocation', 'Protocol', 'Revision',
420                      'RotationSpeedRPM', 'SerialNumber', 'Status']
421
422        # Find Storage service
423        response = self.get_request(self.root_uri + systems_uri)
424        if response['ret'] is False:
425            return response
426        data = response['data']
427
428        if 'SimpleStorage' not in data and 'Storage' not in data:
429            return {'ret': False, 'msg': "SimpleStorage and Storage resource \
430                     not found"}
431
432        if 'Storage' in data:
433            # Get a list of all storage controllers and build respective URIs
434            storage_uri = data[u'Storage'][u'@odata.id']
435            response = self.get_request(self.root_uri + storage_uri)
436            if response['ret'] is False:
437                return response
438            result['ret'] = True
439            data = response['data']
440
441            if data[u'Members']:
442                for controller in data[u'Members']:
443                    controller_list.append(controller[u'@odata.id'])
444                for c in controller_list:
445                    uri = self.root_uri + c
446                    response = self.get_request(uri)
447                    if response['ret'] is False:
448                        return response
449                    data = response['data']
450                    if 'Drives' in data:
451                        for device in data[u'Drives']:
452                            disk_uri = self.root_uri + device[u'@odata.id']
453                            response = self.get_request(disk_uri)
454                            data = response['data']
455
456                            disk_result = {}
457                            for property in properties:
458                                if property in data:
459                                    if data[property] is not None:
460                                        disk_result[property] = data[property]
461                            disk_results.append(disk_result)
462                result["entries"].append(disk_results)
463
464        if 'SimpleStorage' in data:
465            # Get a list of all storage controllers and build respective URIs
466            storage_uri = data["SimpleStorage"]["@odata.id"]
467            response = self.get_request(self.root_uri + storage_uri)
468            if response['ret'] is False:
469                return response
470            result['ret'] = True
471            data = response['data']
472
473            for controller in data[u'Members']:
474                controller_list.append(controller[u'@odata.id'])
475
476            for c in controller_list:
477                uri = self.root_uri + c
478                response = self.get_request(uri)
479                if response['ret'] is False:
480                    return response
481                data = response['data']
482
483                for device in data[u'Devices']:
484                    disk_result = {}
485                    for property in properties:
486                        if property in device:
487                            disk_result[property] = device[property]
488                    disk_results.append(disk_result)
489            result["entries"].append(disk_results)
490
491        return result
492
493    def get_multi_disk_inventory(self):
494        return self.aggregate(self.get_disk_inventory)
495
496    def get_volume_inventory(self, systems_uri):
497        result = {'entries': []}
498        controller_list = []
499        volume_list = []
500        volume_results = []
501        # Get these entries, but does not fail if not found
502        properties = ['Id', 'Name', 'RAIDType', 'VolumeType', 'BlockSizeBytes',
503                      'Capacity', 'CapacityBytes', 'CapacitySources',
504                      'Encrypted', 'EncryptionTypes', 'Identifiers',
505                      'Operations', 'OptimumIOSizeBytes', 'AccessCapabilities',
506                      'AllocatedPools', 'Status']
507
508        # Find Storage service
509        response = self.get_request(self.root_uri + systems_uri)
510        if response['ret'] is False:
511            return response
512        data = response['data']
513
514        if 'SimpleStorage' not in data and 'Storage' not in data:
515            return {'ret': False, 'msg': "SimpleStorage and Storage resource \
516                     not found"}
517
518        if 'Storage' in data:
519            # Get a list of all storage controllers and build respective URIs
520            storage_uri = data[u'Storage'][u'@odata.id']
521            response = self.get_request(self.root_uri + storage_uri)
522            if response['ret'] is False:
523                return response
524            result['ret'] = True
525            data = response['data']
526
527            if data.get('Members'):
528                for controller in data[u'Members']:
529                    controller_list.append(controller[u'@odata.id'])
530                for c in controller_list:
531                    uri = self.root_uri + c
532                    response = self.get_request(uri)
533                    if response['ret'] is False:
534                        return response
535                    data = response['data']
536
537                    if 'Volumes' in data:
538                        # Get a list of all volumes and build respective URIs
539                        volumes_uri = data[u'Volumes'][u'@odata.id']
540                        response = self.get_request(self.root_uri + volumes_uri)
541                        data = response['data']
542
543                        if data.get('Members'):
544                            for volume in data[u'Members']:
545                                volume_list.append(volume[u'@odata.id'])
546                            for v in volume_list:
547                                uri = self.root_uri + v
548                                response = self.get_request(uri)
549                                if response['ret'] is False:
550                                    return response
551                                data = response['data']
552
553                                volume_result = {}
554                                for property in properties:
555                                    if property in data:
556                                        if data[property] is not None:
557                                            volume_result[property] = data[property]
558
559                                # Get related Drives Id
560                                drive_id_list = []
561                                if 'Links' in data:
562                                    if 'Drives' in data[u'Links']:
563                                        for link in data[u'Links'][u'Drives']:
564                                            drive_id_link = link[u'@odata.id']
565                                            drive_id = drive_id_link.split("/")[-1]
566                                            drive_id_list.append({'Id': drive_id})
567                                        volume_result['Linked_drives'] = drive_id_list
568
569                                volume_results.append(volume_result)
570                result["entries"].append(volume_results)
571        else:
572            return {'ret': False, 'msg': "Storage resource not found"}
573
574        return result
575
576    def get_multi_volume_inventory(self):
577        return self.aggregate(self.get_volume_inventory)
578
579    def restart_manager_gracefully(self):
580        result = {}
581        key = "Actions"
582
583        # Search for 'key' entry and extract URI from it
584        response = self.get_request(self.root_uri + self.manager_uri)
585        if response['ret'] is False:
586            return response
587        result['ret'] = True
588        data = response['data']
589        action_uri = data[key]["#Manager.Reset"]["target"]
590
591        payload = {'ResetType': 'GracefulRestart'}
592        response = self.post_request(self.root_uri + action_uri, payload)
593        if response['ret'] is False:
594            return response
595        return {'ret': True}
596
597    def manage_indicator_led(self, command):
598        result = {}
599        key = 'IndicatorLED'
600
601        payloads = {'IndicatorLedOn': 'Lit', 'IndicatorLedOff': 'Off', "IndicatorLedBlink": 'Blinking'}
602
603        result = {}
604        for chassis_uri in self.chassis_uri_list:
605            response = self.get_request(self.root_uri + chassis_uri)
606            if response['ret'] is False:
607                return response
608            result['ret'] = True
609            data = response['data']
610            if key not in data:
611                return {'ret': False, 'msg': "Key %s not found" % key}
612
613            if command in payloads.keys():
614                payload = {'IndicatorLED': payloads[command]}
615                response = self.patch_request(self.root_uri + chassis_uri, payload)
616                if response['ret'] is False:
617                    return response
618            else:
619                return {'ret': False, 'msg': 'Invalid command'}
620
621        return result
622
623    def manage_system_power(self, command):
624        key = "Actions"
625
626        # Search for 'key' entry and extract URI from it
627        response = self.get_request(self.root_uri + self.systems_uris[0])
628        if response['ret'] is False:
629            return response
630        data = response['data']
631        power_state = data["PowerState"]
632
633        if power_state == "On" and command == 'PowerOn':
634            return {'ret': True, 'changed': False}
635
636        if power_state == "Off" and command in ['PowerGracefulShutdown', 'PowerForceOff']:
637            return {'ret': True, 'changed': False}
638
639        reset_action = data[key]["#ComputerSystem.Reset"]
640        action_uri = reset_action["target"]
641        allowable_vals = reset_action.get("ResetType@Redfish.AllowableValues", [])
642        restart_cmd = "GracefulRestart"
643        if "ForceRestart" in allowable_vals and "GracefulRestart" not in allowable_vals:
644            restart_cmd = "ForceRestart"
645
646        # Define payload accordingly
647        if command == "PowerOn":
648            payload = {'ResetType': 'On'}
649        elif command == "PowerForceOff":
650            payload = {'ResetType': 'ForceOff'}
651        elif command == "PowerForceRestart":
652            payload = {'ResetType': "ForceRestart"}
653        elif command == "PowerGracefulRestart":
654            payload = {'ResetType': 'GracefulRestart'}
655        elif command == "PowerGracefulShutdown":
656            payload = {'ResetType': 'GracefulShutdown'}
657        elif command == "PowerReboot":
658            if power_state == "On":
659                payload = {'ResetType': restart_cmd}
660            else:
661                payload = {'ResetType': "On"}
662        else:
663            return {'ret': False, 'msg': 'Invalid Command'}
664
665        response = self.post_request(self.root_uri + action_uri, payload)
666        if response['ret'] is False:
667            return response
668        return {'ret': True, 'changed': True}
669
670    def list_users(self):
671        result = {}
672        # listing all users has always been slower than other operations, why?
673        user_list = []
674        users_results = []
675        # Get these entries, but does not fail if not found
676        properties = ['Id', 'Name', 'UserName', 'RoleId', 'Locked', 'Enabled']
677
678        response = self.get_request(self.root_uri + self.accounts_uri)
679        if response['ret'] is False:
680            return response
681        result['ret'] = True
682        data = response['data']
683
684        for users in data[u'Members']:
685            user_list.append(users[u'@odata.id'])   # user_list[] are URIs
686
687        # for each user, get details
688        for uri in user_list:
689            user = {}
690            response = self.get_request(self.root_uri + uri)
691            if response['ret'] is False:
692                return response
693            data = response['data']
694
695            for property in properties:
696                if property in data:
697                    user[property] = data[property]
698
699            users_results.append(user)
700        result["entries"] = users_results
701        return result
702
703    def add_user(self, user):
704        uri = self.root_uri + self.accounts_uri + "/" + user['userid']
705        username = {'UserName': user['username']}
706        pswd = {'Password': user['userpswd']}
707        roleid = {'RoleId': user['userrole']}
708        enabled = {'Enabled': True}
709        for payload in username, pswd, roleid, enabled:
710            response = self.patch_request(uri, payload)
711            if response['ret'] is False:
712                return response
713        return {'ret': True}
714
715    def enable_user(self, user):
716        uri = self.root_uri + self.accounts_uri + "/" + user['userid']
717        payload = {'Enabled': True}
718        response = self.patch_request(uri, payload)
719        if response['ret'] is False:
720            return response
721        return {'ret': True}
722
723    def delete_user(self, user):
724        uri = self.root_uri + self.accounts_uri + "/" + user['userid']
725        payload = {'UserName': ""}
726        response = self.patch_request(uri, payload)
727        if response['ret'] is False:
728            return response
729        return {'ret': True}
730
731    def disable_user(self, user):
732        uri = self.root_uri + self.accounts_uri + "/" + user['userid']
733        payload = {'Enabled': False}
734        response = self.patch_request(uri, payload)
735        if response['ret'] is False:
736            return response
737        return {'ret': True}
738
739    def update_user_role(self, user):
740        uri = self.root_uri + self.accounts_uri + "/" + user['userid']
741        payload = {'RoleId': user['userrole']}
742        response = self.patch_request(uri, payload)
743        if response['ret'] is False:
744            return response
745        return {'ret': True}
746
747    def update_user_password(self, user):
748        uri = self.root_uri + self.accounts_uri + "/" + user['userid']
749        payload = {'Password': user['userpswd']}
750        response = self.patch_request(uri, payload)
751        if response['ret'] is False:
752            return response
753        return {'ret': True}
754
755    def get_sessions(self):
756        result = {}
757        # listing all users has always been slower than other operations, why?
758        session_list = []
759        sessions_results = []
760        # Get these entries, but does not fail if not found
761        properties = ['Description', 'Id', 'Name', 'UserName']
762
763        response = self.get_request(self.root_uri + self.sessions_uri)
764        if response['ret'] is False:
765            return response
766        result['ret'] = True
767        data = response['data']
768
769        for sessions in data[u'Members']:
770            session_list.append(sessions[u'@odata.id'])   # session_list[] are URIs
771
772        # for each session, get details
773        for uri in session_list:
774            session = {}
775            response = self.get_request(self.root_uri + uri)
776            if response['ret'] is False:
777                return response
778            data = response['data']
779
780            for property in properties:
781                if property in data:
782                    session[property] = data[property]
783
784            sessions_results.append(session)
785        result["entries"] = sessions_results
786        return result
787
788    def get_firmware_update_capabilities(self):
789        result = {}
790        response = self.get_request(self.root_uri + self.update_uri)
791        if response['ret'] is False:
792            return response
793
794        result['ret'] = True
795
796        result['entries'] = {}
797
798        data = response['data']
799
800        if "Actions" in data:
801            actions = data['Actions']
802            if len(actions) > 0:
803                for key in actions.keys():
804                    action = actions.get(key)
805                    if 'title' in action:
806                        title = action['title']
807                    else:
808                        title = key
809                    result['entries'][title] = action.get('TransferProtocol@Redfish.AllowableValues',
810                                                          ["Key TransferProtocol@Redfish.AllowableValues not found"])
811            else:
812                return {'ret': "False", 'msg': "Actions list is empty."}
813        else:
814            return {'ret': "False", 'msg': "Key Actions not found."}
815        return result
816
817    def get_firmware_inventory(self):
818        result = {}
819        response = self.get_request(self.root_uri + self.firmware_uri)
820        if response['ret'] is False:
821            return response
822        result['ret'] = True
823        data = response['data']
824
825        result['entries'] = []
826        for device in data[u'Members']:
827            uri = self.root_uri + device[u'@odata.id']
828            # Get details for each device
829            response = self.get_request(uri)
830            if response['ret'] is False:
831                return response
832            result['ret'] = True
833            data = response['data']
834            firmware = {}
835            # Get these standard properties if present
836            for key in ['Name', 'Id', 'Status', 'Version', 'Updateable',
837                        'SoftwareId', 'LowestSupportedVersion', 'Manufacturer',
838                        'ReleaseDate']:
839                if key in data:
840                    firmware[key] = data.get(key)
841            result['entries'].append(firmware)
842        return result
843
844    def get_bios_attributes(self, systems_uri):
845        result = {}
846        bios_attributes = {}
847        key = "Bios"
848
849        # Search for 'key' entry and extract URI from it
850        response = self.get_request(self.root_uri + systems_uri)
851        if response['ret'] is False:
852            return response
853        result['ret'] = True
854        data = response['data']
855
856        if key not in data:
857            return {'ret': False, 'msg': "Key %s not found" % key}
858
859        bios_uri = data[key]["@odata.id"]
860
861        response = self.get_request(self.root_uri + bios_uri)
862        if response['ret'] is False:
863            return response
864        result['ret'] = True
865        data = response['data']
866        for attribute in data[u'Attributes'].items():
867            bios_attributes[attribute[0]] = attribute[1]
868        result["entries"] = bios_attributes
869        return result
870
871    def get_multi_bios_attributes(self):
872        return self.aggregate(self.get_bios_attributes)
873
874    def get_boot_order(self, systems_uri):
875        result = {}
876        # Get these entries from BootOption, if present
877        properties = ['DisplayName', 'BootOptionReference']
878
879        # Retrieve System resource
880        response = self.get_request(self.root_uri + systems_uri)
881        if response['ret'] is False:
882            return response
883        result['ret'] = True
884        data = response['data']
885
886        # Confirm needed Boot properties are present
887        if 'Boot' not in data or 'BootOrder' not in data['Boot']:
888            return {'ret': False, 'msg': "Key BootOrder not found"}
889
890        boot = data['Boot']
891        boot_order = boot['BootOrder']
892
893        # Retrieve BootOptions if present
894        if 'BootOptions' in boot and '@odata.id' in boot['BootOptions']:
895            boot_options_uri = boot['BootOptions']["@odata.id"]
896            # Get BootOptions resource
897            response = self.get_request(self.root_uri + boot_options_uri)
898            if response['ret'] is False:
899                return response
900            data = response['data']
901
902            # Retrieve Members array
903            if 'Members' not in data:
904                return {'ret': False,
905                        'msg': "Members not found in BootOptionsCollection"}
906            members = data['Members']
907        else:
908            members = []
909
910        # Build dict of BootOptions keyed by BootOptionReference
911        boot_options_dict = {}
912        for member in members:
913            if '@odata.id' not in member:
914                return {'ret': False, 'msg': "@odata.id not found in BootOptions"}
915            boot_option_uri = member['@odata.id']
916            response = self.get_request(self.root_uri + boot_option_uri)
917            if response['ret'] is False:
918                return response
919            data = response['data']
920            if 'BootOptionReference' not in data:
921                return {'ret': False, 'msg': "BootOptionReference not found in BootOption"}
922            boot_option_ref = data['BootOptionReference']
923
924            # fetch the props to display for this boot device
925            boot_props = {}
926            for prop in properties:
927                if prop in data:
928                    boot_props[prop] = data[prop]
929
930            boot_options_dict[boot_option_ref] = boot_props
931
932        # Build boot device list
933        boot_device_list = []
934        for ref in boot_order:
935            boot_device_list.append(
936                boot_options_dict.get(ref, {'BootOptionReference': ref}))
937
938        result["entries"] = boot_device_list
939        return result
940
941    def get_multi_boot_order(self):
942        return self.aggregate(self.get_boot_order)
943
944    def get_boot_override(self, systems_uri):
945        result = {}
946
947        properties = ["BootSourceOverrideEnabled", "BootSourceOverrideTarget",
948                      "BootSourceOverrideMode", "UefiTargetBootSourceOverride", "BootSourceOverrideTarget@Redfish.AllowableValues"]
949
950        response = self.get_request(self.root_uri + systems_uri)
951        if response['ret'] is False:
952            return response
953        result['ret'] = True
954        data = response['data']
955
956        if 'Boot' not in data:
957            return {'ret': False, 'msg': "Key Boot not found"}
958
959        boot = data['Boot']
960
961        boot_overrides = {}
962        if "BootSourceOverrideEnabled" in boot:
963            if boot["BootSourceOverrideEnabled"] is not False:
964                for property in properties:
965                    if property in boot:
966                        if boot[property] is not None:
967                            boot_overrides[property] = boot[property]
968        else:
969            return {'ret': False, 'msg': "No boot override is enabled."}
970
971        result['entries'] = boot_overrides
972        return result
973
974    def get_multi_boot_override(self):
975        return self.aggregate(self.get_boot_override)
976
977    def set_bios_default_settings(self):
978        result = {}
979        key = "Bios"
980
981        # Search for 'key' entry and extract URI from it
982        response = self.get_request(self.root_uri + self.systems_uris[0])
983        if response['ret'] is False:
984            return response
985        result['ret'] = True
986        data = response['data']
987
988        if key not in data:
989            return {'ret': False, 'msg': "Key %s not found" % key}
990
991        bios_uri = data[key]["@odata.id"]
992
993        # Extract proper URI
994        response = self.get_request(self.root_uri + bios_uri)
995        if response['ret'] is False:
996            return response
997        result['ret'] = True
998        data = response['data']
999        reset_bios_settings_uri = data["Actions"]["#Bios.ResetBios"]["target"]
1000
1001        response = self.post_request(self.root_uri + reset_bios_settings_uri, {})
1002        if response['ret'] is False:
1003            return response
1004        return {'ret': True, 'changed': True, 'msg': "Set BIOS to default settings"}
1005
1006    def set_one_time_boot_device(self, bootdevice, uefi_target, boot_next):
1007        result = {}
1008        key = "Boot"
1009
1010        if not bootdevice:
1011            return {'ret': False,
1012                    'msg': "bootdevice option required for SetOneTimeBoot"}
1013
1014        # Search for 'key' entry and extract URI from it
1015        response = self.get_request(self.root_uri + self.systems_uris[0])
1016        if response['ret'] is False:
1017            return response
1018        result['ret'] = True
1019        data = response['data']
1020
1021        if key not in data:
1022            return {'ret': False, 'msg': "Key %s not found" % key}
1023
1024        boot = data[key]
1025
1026        annotation = 'BootSourceOverrideTarget@Redfish.AllowableValues'
1027        if annotation in boot:
1028            allowable_values = boot[annotation]
1029            if isinstance(allowable_values, list) and bootdevice not in allowable_values:
1030                return {'ret': False,
1031                        'msg': "Boot device %s not in list of allowable values (%s)" %
1032                               (bootdevice, allowable_values)}
1033
1034        # read existing values
1035        enabled = boot.get('BootSourceOverrideEnabled')
1036        target = boot.get('BootSourceOverrideTarget')
1037        cur_uefi_target = boot.get('UefiTargetBootSourceOverride')
1038        cur_boot_next = boot.get('BootNext')
1039
1040        if bootdevice == 'UefiTarget':
1041            if not uefi_target:
1042                return {'ret': False,
1043                        'msg': "uefi_target option required to SetOneTimeBoot for UefiTarget"}
1044            if enabled == 'Once' and target == bootdevice and uefi_target == cur_uefi_target:
1045                # If properties are already set, no changes needed
1046                return {'ret': True, 'changed': False}
1047            payload = {
1048                'Boot': {
1049                    'BootSourceOverrideEnabled': 'Once',
1050                    'BootSourceOverrideTarget': bootdevice,
1051                    'UefiTargetBootSourceOverride': uefi_target
1052                }
1053            }
1054        elif bootdevice == 'UefiBootNext':
1055            if not boot_next:
1056                return {'ret': False,
1057                        'msg': "boot_next option required to SetOneTimeBoot for UefiBootNext"}
1058            if enabled == 'Once' and target == bootdevice and boot_next == cur_boot_next:
1059                # If properties are already set, no changes needed
1060                return {'ret': True, 'changed': False}
1061            payload = {
1062                'Boot': {
1063                    'BootSourceOverrideEnabled': 'Once',
1064                    'BootSourceOverrideTarget': bootdevice,
1065                    'BootNext': boot_next
1066                }
1067            }
1068        else:
1069            if enabled == 'Once' and target == bootdevice:
1070                # If properties are already set, no changes needed
1071                return {'ret': True, 'changed': False}
1072            payload = {
1073                'Boot': {
1074                    'BootSourceOverrideEnabled': 'Once',
1075                    'BootSourceOverrideTarget': bootdevice
1076                }
1077            }
1078
1079        response = self.patch_request(self.root_uri + self.systems_uris[0], payload)
1080        if response['ret'] is False:
1081            return response
1082        return {'ret': True, 'changed': True}
1083
1084    def set_bios_attributes(self, attr):
1085        result = {}
1086        key = "Bios"
1087
1088        # Search for 'key' entry and extract URI from it
1089        response = self.get_request(self.root_uri + self.systems_uris[0])
1090        if response['ret'] is False:
1091            return response
1092        result['ret'] = True
1093        data = response['data']
1094
1095        if key not in data:
1096            return {'ret': False, 'msg': "Key %s not found" % key}
1097
1098        bios_uri = data[key]["@odata.id"]
1099
1100        # Extract proper URI
1101        response = self.get_request(self.root_uri + bios_uri)
1102        if response['ret'] is False:
1103            return response
1104        result['ret'] = True
1105        data = response['data']
1106
1107        # First, check if BIOS attribute exists
1108        if attr['bios_attr_name'] not in data[u'Attributes']:
1109            return {'ret': False, 'msg': "BIOS attribute not found"}
1110
1111        # Find out if value is already set to what we want. If yes, return
1112        if data[u'Attributes'][attr['bios_attr_name']] == attr['bios_attr_value']:
1113            return {'ret': True, 'changed': False, 'msg': "BIOS attribute already set"}
1114
1115        set_bios_attr_uri = data["@Redfish.Settings"]["SettingsObject"]["@odata.id"]
1116
1117        # Example: bios_attr = {\"name\":\"value\"}
1118        bios_attr = "{\"" + attr['bios_attr_name'] + "\":\"" + attr['bios_attr_value'] + "\"}"
1119        payload = {"Attributes": json.loads(bios_attr)}
1120        response = self.patch_request(self.root_uri + set_bios_attr_uri, payload)
1121        if response['ret'] is False:
1122            return response
1123        return {'ret': True, 'changed': True, 'msg': "Modified BIOS attribute"}
1124
1125    def get_chassis_inventory(self):
1126        result = {}
1127        chassis_results = []
1128
1129        # Get these entries, but does not fail if not found
1130        properties = ['ChassisType', 'PartNumber', 'AssetTag',
1131                      'Manufacturer', 'IndicatorLED', 'SerialNumber', 'Model']
1132
1133        # Go through list
1134        for chassis_uri in self.chassis_uri_list:
1135            response = self.get_request(self.root_uri + chassis_uri)
1136            if response['ret'] is False:
1137                return response
1138            result['ret'] = True
1139            data = response['data']
1140            chassis_result = {}
1141            for property in properties:
1142                if property in data:
1143                    chassis_result[property] = data[property]
1144            chassis_results.append(chassis_result)
1145
1146        result["entries"] = chassis_results
1147        return result
1148
1149    def get_fan_inventory(self):
1150        result = {}
1151        fan_results = []
1152        key = "Thermal"
1153        # Get these entries, but does not fail if not found
1154        properties = ['FanName', 'Reading', 'ReadingUnits', 'Status']
1155
1156        # Go through list
1157        for chassis_uri in self.chassis_uri_list:
1158            response = self.get_request(self.root_uri + chassis_uri)
1159            if response['ret'] is False:
1160                return response
1161            result['ret'] = True
1162            data = response['data']
1163            if key in data:
1164                # match: found an entry for "Thermal" information = fans
1165                thermal_uri = data[key]["@odata.id"]
1166                response = self.get_request(self.root_uri + thermal_uri)
1167                if response['ret'] is False:
1168                    return response
1169                result['ret'] = True
1170                data = response['data']
1171
1172                for device in data[u'Fans']:
1173                    fan = {}
1174                    for property in properties:
1175                        if property in device:
1176                            fan[property] = device[property]
1177                    fan_results.append(fan)
1178        result["entries"] = fan_results
1179        return result
1180
1181    def get_chassis_power(self):
1182        result = {}
1183        key = "Power"
1184
1185        # Get these entries, but does not fail if not found
1186        properties = ['Name', 'PowerAllocatedWatts',
1187                      'PowerAvailableWatts', 'PowerCapacityWatts',
1188                      'PowerConsumedWatts', 'PowerMetrics',
1189                      'PowerRequestedWatts', 'RelatedItem', 'Status']
1190
1191        chassis_power_results = []
1192        # Go through list
1193        for chassis_uri in self.chassis_uri_list:
1194            chassis_power_result = {}
1195            response = self.get_request(self.root_uri + chassis_uri)
1196            if response['ret'] is False:
1197                return response
1198            result['ret'] = True
1199            data = response['data']
1200            if key in data:
1201                response = self.get_request(self.root_uri + data[key]['@odata.id'])
1202                data = response['data']
1203                if 'PowerControl' in data:
1204                    if len(data['PowerControl']) > 0:
1205                        data = data['PowerControl'][0]
1206                        for property in properties:
1207                            if property in data:
1208                                chassis_power_result[property] = data[property]
1209                else:
1210                    return {'ret': False, 'msg': 'Key PowerControl not found.'}
1211                chassis_power_results.append(chassis_power_result)
1212            else:
1213                return {'ret': False, 'msg': 'Key Power not found.'}
1214
1215        result['entries'] = chassis_power_results
1216        return result
1217
1218    def get_chassis_thermals(self):
1219        result = {}
1220        sensors = []
1221        key = "Thermal"
1222
1223        # Get these entries, but does not fail if not found
1224        properties = ['Name', 'PhysicalContext', 'UpperThresholdCritical',
1225                      'UpperThresholdFatal', 'UpperThresholdNonCritical',
1226                      'LowerThresholdCritical', 'LowerThresholdFatal',
1227                      'LowerThresholdNonCritical', 'MaxReadingRangeTemp',
1228                      'MinReadingRangeTemp', 'ReadingCelsius', 'RelatedItem',
1229                      'SensorNumber']
1230
1231        # Go through list
1232        for chassis_uri in self.chassis_uri_list:
1233            response = self.get_request(self.root_uri + chassis_uri)
1234            if response['ret'] is False:
1235                return response
1236            result['ret'] = True
1237            data = response['data']
1238            if key in data:
1239                thermal_uri = data[key]["@odata.id"]
1240                response = self.get_request(self.root_uri + thermal_uri)
1241                if response['ret'] is False:
1242                    return response
1243                result['ret'] = True
1244                data = response['data']
1245                if "Temperatures" in data:
1246                    for sensor in data[u'Temperatures']:
1247                        sensor_result = {}
1248                        for property in properties:
1249                            if property in sensor:
1250                                if sensor[property] is not None:
1251                                    sensor_result[property] = sensor[property]
1252                        sensors.append(sensor_result)
1253
1254        if sensors is None:
1255            return {'ret': False, 'msg': 'Key Temperatures was not found.'}
1256
1257        result['entries'] = sensors
1258        return result
1259
1260    def get_cpu_inventory(self, systems_uri):
1261        result = {}
1262        cpu_list = []
1263        cpu_results = []
1264        key = "Processors"
1265        # Get these entries, but does not fail if not found
1266        properties = ['Id', 'Manufacturer', 'Model', 'MaxSpeedMHz', 'TotalCores',
1267                      'TotalThreads', 'Status']
1268
1269        # Search for 'key' entry and extract URI from it
1270        response = self.get_request(self.root_uri + systems_uri)
1271        if response['ret'] is False:
1272            return response
1273        result['ret'] = True
1274        data = response['data']
1275
1276        if key not in data:
1277            return {'ret': False, 'msg': "Key %s not found" % key}
1278
1279        processors_uri = data[key]["@odata.id"]
1280
1281        # Get a list of all CPUs and build respective URIs
1282        response = self.get_request(self.root_uri + processors_uri)
1283        if response['ret'] is False:
1284            return response
1285        result['ret'] = True
1286        data = response['data']
1287
1288        for cpu in data[u'Members']:
1289            cpu_list.append(cpu[u'@odata.id'])
1290
1291        for c in cpu_list:
1292            cpu = {}
1293            uri = self.root_uri + c
1294            response = self.get_request(uri)
1295            if response['ret'] is False:
1296                return response
1297            data = response['data']
1298
1299            for property in properties:
1300                if property in data:
1301                    cpu[property] = data[property]
1302
1303            cpu_results.append(cpu)
1304        result["entries"] = cpu_results
1305        return result
1306
1307    def get_multi_cpu_inventory(self):
1308        return self.aggregate(self.get_cpu_inventory)
1309
1310    def get_memory_inventory(self, systems_uri):
1311        result = {}
1312        memory_list = []
1313        memory_results = []
1314        key = "Memory"
1315        # Get these entries, but does not fail if not found
1316        properties = ['SerialNumber', 'MemoryDeviceType', 'PartNuber',
1317                      'MemoryLocation', 'RankCount', 'CapacityMiB', 'OperatingMemoryModes', 'Status', 'Manufacturer', 'Name']
1318
1319        # Search for 'key' entry and extract URI from it
1320        response = self.get_request(self.root_uri + systems_uri)
1321        if response['ret'] is False:
1322            return response
1323        result['ret'] = True
1324        data = response['data']
1325
1326        if key not in data:
1327            return {'ret': False, 'msg': "Key %s not found" % key}
1328
1329        memory_uri = data[key]["@odata.id"]
1330
1331        # Get a list of all DIMMs and build respective URIs
1332        response = self.get_request(self.root_uri + memory_uri)
1333        if response['ret'] is False:
1334            return response
1335        result['ret'] = True
1336        data = response['data']
1337
1338        for dimm in data[u'Members']:
1339            memory_list.append(dimm[u'@odata.id'])
1340
1341        for m in memory_list:
1342            dimm = {}
1343            uri = self.root_uri + m
1344            response = self.get_request(uri)
1345            if response['ret'] is False:
1346                return response
1347            data = response['data']
1348
1349            if "Status" in data:
1350                if "State" in data["Status"]:
1351                    if data["Status"]["State"] == "Absent":
1352                        continue
1353            else:
1354                continue
1355
1356            for property in properties:
1357                if property in data:
1358                    dimm[property] = data[property]
1359
1360            memory_results.append(dimm)
1361        result["entries"] = memory_results
1362        return result
1363
1364    def get_multi_memory_inventory(self):
1365        return self.aggregate(self.get_memory_inventory)
1366
1367    def get_nic_inventory(self, resource_uri):
1368        result = {}
1369        nic_list = []
1370        nic_results = []
1371        key = "EthernetInterfaces"
1372        # Get these entries, but does not fail if not found
1373        properties = ['Description', 'FQDN', 'IPv4Addresses', 'IPv6Addresses',
1374                      'NameServers', 'MACAddress', 'PermanentMACAddress',
1375                      'SpeedMbps', 'MTUSize', 'AutoNeg', 'Status']
1376
1377        response = self.get_request(self.root_uri + resource_uri)
1378        if response['ret'] is False:
1379            return response
1380        result['ret'] = True
1381        data = response['data']
1382
1383        if key not in data:
1384            return {'ret': False, 'msg': "Key %s not found" % key}
1385
1386        ethernetinterfaces_uri = data[key]["@odata.id"]
1387
1388        # Get a list of all network controllers and build respective URIs
1389        response = self.get_request(self.root_uri + ethernetinterfaces_uri)
1390        if response['ret'] is False:
1391            return response
1392        result['ret'] = True
1393        data = response['data']
1394
1395        for nic in data[u'Members']:
1396            nic_list.append(nic[u'@odata.id'])
1397
1398        for n in nic_list:
1399            nic = {}
1400            uri = self.root_uri + n
1401            response = self.get_request(uri)
1402            if response['ret'] is False:
1403                return response
1404            data = response['data']
1405
1406            for property in properties:
1407                if property in data:
1408                    nic[property] = data[property]
1409
1410            nic_results.append(nic)
1411        result["entries"] = nic_results
1412        return result
1413
1414    def get_multi_nic_inventory(self, resource_type):
1415        ret = True
1416        entries = []
1417
1418        #  Given resource_type, use the proper URI
1419        if resource_type == 'Systems':
1420            resource_uris = self.systems_uris
1421        elif resource_type == 'Manager':
1422            # put in a list to match what we're doing with systems_uris
1423            resource_uris = [self.manager_uri]
1424
1425        for resource_uri in resource_uris:
1426            inventory = self.get_nic_inventory(resource_uri)
1427            ret = inventory.pop('ret') and ret
1428            if 'entries' in inventory:
1429                entries.append(({'resource_uri': resource_uri},
1430                                inventory['entries']))
1431        return dict(ret=ret, entries=entries)
1432
1433    def get_virtualmedia(self, resource_uri):
1434        result = {}
1435        virtualmedia_list = []
1436        virtualmedia_results = []
1437        key = "VirtualMedia"
1438        # Get these entries, but does not fail if not found
1439        properties = ['Description', 'ConnectedVia', 'Id', 'MediaTypes',
1440                      'Image', 'ImageName', 'Name', 'WriteProtected',
1441                      'TransferMethod', 'TransferProtocolType']
1442
1443        response = self.get_request(self.root_uri + resource_uri)
1444        if response['ret'] is False:
1445            return response
1446        result['ret'] = True
1447        data = response['data']
1448
1449        if key not in data:
1450            return {'ret': False, 'msg': "Key %s not found" % key}
1451
1452        virtualmedia_uri = data[key]["@odata.id"]
1453
1454        # Get a list of all virtual media and build respective URIs
1455        response = self.get_request(self.root_uri + virtualmedia_uri)
1456        if response['ret'] is False:
1457            return response
1458        result['ret'] = True
1459        data = response['data']
1460
1461        for virtualmedia in data[u'Members']:
1462            virtualmedia_list.append(virtualmedia[u'@odata.id'])
1463
1464        for n in virtualmedia_list:
1465            virtualmedia = {}
1466            uri = self.root_uri + n
1467            response = self.get_request(uri)
1468            if response['ret'] is False:
1469                return response
1470            data = response['data']
1471
1472            for property in properties:
1473                if property in data:
1474                    virtualmedia[property] = data[property]
1475
1476            virtualmedia_results.append(virtualmedia)
1477        result["entries"] = virtualmedia_results
1478        return result
1479
1480    def get_multi_virtualmedia(self):
1481        ret = True
1482        entries = []
1483
1484        # Because _find_managers_resource() only find last Manager uri in self.manager_uri, not one list. This should be 1 issue.
1485        # I have to put manager_uri into list to reduce future changes when the issue is fixed.
1486        resource_uris = [self.manager_uri]
1487
1488        for resource_uri in resource_uris:
1489            virtualmedia = self.get_virtualmedia(resource_uri)
1490            ret = virtualmedia.pop('ret') and ret
1491            if 'entries' in virtualmedia:
1492                entries.append(({'resource_uri': resource_uri},
1493                               virtualmedia['entries']))
1494        return dict(ret=ret, entries=entries)
1495
1496    def get_psu_inventory(self):
1497        result = {}
1498        psu_list = []
1499        psu_results = []
1500        key = "PowerSupplies"
1501        # Get these entries, but does not fail if not found
1502        properties = ['Name', 'Model', 'SerialNumber', 'PartNumber', 'Manufacturer',
1503                      'FirmwareVersion', 'PowerCapacityWatts', 'PowerSupplyType',
1504                      'Status']
1505
1506        # Get a list of all Chassis and build URIs, then get all PowerSupplies
1507        # from each Power entry in the Chassis
1508        chassis_uri_list = self.chassis_uri_list
1509        for chassis_uri in chassis_uri_list:
1510            response = self.get_request(self.root_uri + chassis_uri)
1511            if response['ret'] is False:
1512                return response
1513
1514            result['ret'] = True
1515            data = response['data']
1516
1517            if 'Power' in data:
1518                power_uri = data[u'Power'][u'@odata.id']
1519            else:
1520                continue
1521
1522            response = self.get_request(self.root_uri + power_uri)
1523            data = response['data']
1524
1525            if key not in data:
1526                return {'ret': False, 'msg': "Key %s not found" % key}
1527
1528            psu_list = data[key]
1529            for psu in psu_list:
1530                psu_not_present = False
1531                psu_data = {}
1532                for property in properties:
1533                    if property in psu:
1534                        if psu[property] is not None:
1535                            if property == 'Status':
1536                                if 'State' in psu[property]:
1537                                    if psu[property]['State'] == 'Absent':
1538                                        psu_not_present = True
1539                            psu_data[property] = psu[property]
1540                if psu_not_present:
1541                    continue
1542                psu_results.append(psu_data)
1543
1544        result["entries"] = psu_results
1545        if not result["entries"]:
1546            return {'ret': False, 'msg': "No PowerSupply objects found"}
1547        return result
1548
1549    def get_multi_psu_inventory(self):
1550        return self.aggregate(self.get_psu_inventory)
1551
1552    def get_system_inventory(self, systems_uri):
1553        result = {}
1554        inventory = {}
1555        # Get these entries, but does not fail if not found
1556        properties = ['Status', 'HostName', 'PowerState', 'Model', 'Manufacturer',
1557                      'PartNumber', 'SystemType', 'AssetTag', 'ServiceTag',
1558                      'SerialNumber', 'SKU', 'BiosVersion', 'MemorySummary',
1559                      'ProcessorSummary', 'TrustedModules']
1560
1561        response = self.get_request(self.root_uri + systems_uri)
1562        if response['ret'] is False:
1563            return response
1564        result['ret'] = True
1565        data = response['data']
1566
1567        for property in properties:
1568            if property in data:
1569                inventory[property] = data[property]
1570
1571        result["entries"] = inventory
1572        return result
1573
1574    def get_multi_system_inventory(self):
1575        return self.aggregate(self.get_system_inventory)
1576