pyghmi 1.5.71__py3-none-any.whl → 1.5.75__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyghmi/ipmi/command.py +6 -2
- pyghmi/ipmi/oem/generic.py +3 -1
- pyghmi/ipmi/oem/lenovo/handler.py +3 -1
- pyghmi/ipmi/oem/lenovo/imm.py +9 -0
- pyghmi/redfish/command.py +212 -163
- pyghmi/redfish/oem/generic.py +325 -117
- pyghmi/redfish/oem/lenovo/main.py +24 -4
- pyghmi/redfish/oem/lenovo/smm3.py +85 -0
- pyghmi/redfish/oem/lenovo/xcc.py +2 -2
- pyghmi/redfish/oem/lenovo/xcc3.py +395 -0
- pyghmi/util/webclient.py +32 -8
- {pyghmi-1.5.71.dist-info → pyghmi-1.5.75.dist-info}/METADATA +6 -7
- {pyghmi-1.5.71.dist-info → pyghmi-1.5.75.dist-info}/RECORD +19 -17
- {pyghmi-1.5.71.dist-info → pyghmi-1.5.75.dist-info}/WHEEL +1 -1
- {pyghmi-1.5.71.dist-info → pyghmi-1.5.75.dist-info}/entry_points.txt +0 -1
- pyghmi-1.5.75.dist-info/pbr.json +1 -0
- pyghmi-1.5.71.dist-info/pbr.json +0 -1
- {pyghmi-1.5.71.dist-info → pyghmi-1.5.75.dist-info}/AUTHORS +0 -0
- {pyghmi-1.5.71.dist-info → pyghmi-1.5.75.dist-info}/LICENSE +0 -0
- {pyghmi-1.5.71.dist-info → pyghmi-1.5.75.dist-info}/top_level.txt +0 -0
pyghmi/redfish/oem/generic.py
CHANGED
@@ -17,12 +17,17 @@ from fnmatch import fnmatch
|
|
17
17
|
import json
|
18
18
|
import os
|
19
19
|
import re
|
20
|
+
from datetime import datetime
|
21
|
+
from datetime import timedelta
|
22
|
+
from dateutil import tz
|
20
23
|
import time
|
21
24
|
|
22
25
|
import pyghmi.constants as const
|
23
26
|
import pyghmi.exceptions as exc
|
24
27
|
import pyghmi.media as media
|
25
28
|
import pyghmi.util.webclient as webclient
|
29
|
+
from pyghmi.util.parse import parse_time
|
30
|
+
|
26
31
|
|
27
32
|
|
28
33
|
class SensorReading(object):
|
@@ -44,11 +49,29 @@ class SensorReading(object):
|
|
44
49
|
self.units = units
|
45
50
|
self.unavailable = unavailable
|
46
51
|
|
52
|
+
|
53
|
+
def _to_boolean(attrval):
|
54
|
+
attrval = attrval.lower()
|
55
|
+
if not attrval:
|
56
|
+
return False
|
57
|
+
if ('true'.startswith(attrval) or 'yes'.startswith(attrval)
|
58
|
+
or 'enabled'.startswith(attrval) or attrval == '1'):
|
59
|
+
return True
|
60
|
+
if ('false'.startswith(attrval) or 'no'.startswith(attrval)
|
61
|
+
or 'disabled'.startswith(attrval) or attrval == '0'):
|
62
|
+
return False
|
63
|
+
raise Exception(
|
64
|
+
'Unrecognized candidate for boolean: {0}'.format(attrval))
|
65
|
+
|
66
|
+
|
47
67
|
def _normalize_mac(mac):
|
48
68
|
if ':' not in mac:
|
49
|
-
mac = ':'.join((
|
69
|
+
mac = ':'.join((
|
70
|
+
mac[:2], mac[2:4], mac[4:6],
|
71
|
+
mac[6:8], mac[8:10], mac[10:12]))
|
50
72
|
return mac.lower()
|
51
73
|
|
74
|
+
|
52
75
|
_healthmap = {
|
53
76
|
'Critical': const.Health.Critical,
|
54
77
|
'Unknown': const.Health.Warning,
|
@@ -169,6 +192,122 @@ class OEMHandler(object):
|
|
169
192
|
self._urlcache = cache
|
170
193
|
self.webclient = webclient
|
171
194
|
|
195
|
+
def supports_expand(self, url):
|
196
|
+
# Unfortunately, the state of expand in redfish is pretty dicey,
|
197
|
+
# so an OEM handler must opt into this behavior
|
198
|
+
# There is a way an implementation advertises support, however
|
199
|
+
# this isn't to be trusted.
|
200
|
+
# Even among some generally reputable implementations, they will fail in some scenarios
|
201
|
+
# and you'll see in their documentation "some urls will fail if you try to expand them"
|
202
|
+
# perhaps being specific, but other times being vague, but in either case,
|
203
|
+
# nothing programattic to consume to know when to do or not do an expand..
|
204
|
+
return False
|
205
|
+
|
206
|
+
def get_system_power_watts(self, fishclient):
|
207
|
+
totalwatts = 0
|
208
|
+
gotpower = False
|
209
|
+
for chassis in fishclient.sysinfo.get('Links', {}).get('Chassis', []):
|
210
|
+
envinfo = fishclient._get_chassis_env(chassis)
|
211
|
+
currwatts = envinfo.get('watts', None)
|
212
|
+
if currwatts is not None:
|
213
|
+
gotpower = True
|
214
|
+
totalwatts += envinfo['watts']
|
215
|
+
if not gotpower:
|
216
|
+
raise exc.UnsupportedFunctionality("System does not provide Power under redfish EnvironmentMetrics")
|
217
|
+
return totalwatts
|
218
|
+
|
219
|
+
def _get_cpu_temps(self, fishclient):
|
220
|
+
cputemps = []
|
221
|
+
for chassis in fishclient.sysinfo.get('Links', {}).get('Chassis', []):
|
222
|
+
thermals = fishclient._get_thermals(chassis)
|
223
|
+
for temp in thermals:
|
224
|
+
if temp.get('PhysicalContext', '') != 'CPU':
|
225
|
+
continue
|
226
|
+
if temp.get('ReadingCelsius', None) is None:
|
227
|
+
continue
|
228
|
+
cputemps.append(temp)
|
229
|
+
return cputemps
|
230
|
+
|
231
|
+
def get_event_log(self, clear=False, fishclient=None, extraurls=[]):
|
232
|
+
bmcinfo = self._do_web_request(fishclient._bmcurl)
|
233
|
+
lsurl = bmcinfo.get('LogServices', {}).get('@odata.id', None)
|
234
|
+
if not lsurl:
|
235
|
+
return
|
236
|
+
currtime = bmcinfo.get('DateTime', None)
|
237
|
+
correction = timedelta(0)
|
238
|
+
utz = tz.tzoffset('', 0)
|
239
|
+
ltz = tz.gettz()
|
240
|
+
if currtime:
|
241
|
+
currtime = parse_time(currtime)
|
242
|
+
if currtime:
|
243
|
+
now = datetime.now(utz)
|
244
|
+
try:
|
245
|
+
correction = now - currtime
|
246
|
+
except TypeError:
|
247
|
+
correction = now - currtime.replace(tzinfo=utz)
|
248
|
+
lurls = self._do_web_request(lsurl).get('Members', [])
|
249
|
+
lurls.extend(extraurls)
|
250
|
+
for lurl in lurls:
|
251
|
+
lurl = lurl['@odata.id']
|
252
|
+
loginfo = self._do_web_request(lurl, cache=(not clear))
|
253
|
+
entriesurl = loginfo.get('Entries', {}).get('@odata.id', None)
|
254
|
+
if not entriesurl:
|
255
|
+
continue
|
256
|
+
logid = loginfo.get('Id', '')
|
257
|
+
entries = self._do_web_request(entriesurl, cache=False)
|
258
|
+
if clear:
|
259
|
+
# The clear is against the log service etag, not entries
|
260
|
+
# so we have to fetch service etag after we fetch entries
|
261
|
+
# until we can verify that the etag is consistent to prove
|
262
|
+
# that the clear is atomic
|
263
|
+
newloginfo = self._do_web_request(lurl, cache=False)
|
264
|
+
clearurl = newloginfo.get('Actions', {}).get(
|
265
|
+
'#LogService.ClearLog', {}).get('target', '')
|
266
|
+
while clearurl:
|
267
|
+
try:
|
268
|
+
self._do_web_request(clearurl, method='POST',
|
269
|
+
payload={})
|
270
|
+
clearurl = False
|
271
|
+
except exc.PyghmiException as e:
|
272
|
+
if 'EtagPreconditionalFailed' not in str(e):
|
273
|
+
raise
|
274
|
+
# This doesn't guarantee atomicity, but it mitigates
|
275
|
+
# greatly. Unfortunately some implementations
|
276
|
+
# mutate the tag endlessly and we have no hope
|
277
|
+
entries = self._do_web_request(entriesurl, cache=False)
|
278
|
+
newloginfo = self._do_web_request(lurl, cache=False)
|
279
|
+
for log in entries.get('Members', []):
|
280
|
+
if ('Created' not in log and 'Message' not in log
|
281
|
+
and 'Severity' not in log):
|
282
|
+
# without any data, this log entry isn't actionable
|
283
|
+
continue
|
284
|
+
record = {}
|
285
|
+
record['log_id'] = logid
|
286
|
+
parsedtime = parse_time(log.get('Created', ''))
|
287
|
+
if not parsedtime:
|
288
|
+
parsedtime = parse_time(log.get('EventTimestamp', ''))
|
289
|
+
if parsedtime:
|
290
|
+
entime = parsedtime + correction
|
291
|
+
entime = entime.astimezone(ltz)
|
292
|
+
record['timestamp'] = entime.strftime('%Y-%m-%dT%H:%M:%S')
|
293
|
+
else:
|
294
|
+
record['timestamp'] = log.get('Created', '')
|
295
|
+
record['message'] = log.get('Message', None)
|
296
|
+
record['severity'] = _healthmap.get(
|
297
|
+
log.get('Severity', 'Warning'), const.Health.Ok)
|
298
|
+
yield record
|
299
|
+
|
300
|
+
def get_average_processor_temperature(self, fishclient):
|
301
|
+
cputemps = self._get_cpu_temps(fishclient)
|
302
|
+
if not cputemps:
|
303
|
+
return SensorReading(
|
304
|
+
None, {'name': 'Average Processor Temperature'}, value=None, units='°C',
|
305
|
+
unavailable=True)
|
306
|
+
cputemps = [x['ReadingCelsius'] for x in cputemps]
|
307
|
+
avgtemp = sum(cputemps) / len(cputemps)
|
308
|
+
return SensorReading(
|
309
|
+
None, {'name': 'Average Processor Temperature'}, value=avgtemp, units='°C')
|
310
|
+
|
172
311
|
def get_health(self, fishclient, verbose=True):
|
173
312
|
health = fishclient.sysinfo.get('Status', {})
|
174
313
|
health = health.get('HealthRollup', health.get('Health', 'Unknown'))
|
@@ -208,9 +347,8 @@ class OEMHandler(object):
|
|
208
347
|
memsumstatus.get('Health', None))
|
209
348
|
if memsumstatus != 'OK':
|
210
349
|
dimmfound = False
|
211
|
-
|
212
|
-
|
213
|
-
dimminfo = fishclient._do_web_request(mem['@odata.id'])
|
350
|
+
dimmdata = self._get_mem_data()
|
351
|
+
for dimminfo in dimmdata['Members']:
|
214
352
|
if dimminfo.get('Status', {}).get(
|
215
353
|
'State', None) == 'Absent':
|
216
354
|
continue
|
@@ -345,6 +483,38 @@ class OEMHandler(object):
|
|
345
483
|
def get_system_configuration(self, hideadvanced=True, fishclient=None):
|
346
484
|
return self._getsyscfg(fishclient)[0]
|
347
485
|
|
486
|
+
def _get_attrib_registry(self, fishclient, attribreg):
|
487
|
+
overview = fishclient._do_web_request('/redfish/v1/')
|
488
|
+
reglist = overview['Registries']['@odata.id']
|
489
|
+
reglist = fishclient._do_web_request(reglist)
|
490
|
+
regurl = None
|
491
|
+
for cand in reglist.get('Members', []):
|
492
|
+
cand = cand.get('@odata.id', '')
|
493
|
+
candname = cand.split('/')[-1]
|
494
|
+
if candname == '': # implementation uses trailing slash
|
495
|
+
candname = cand.split('/')[-2]
|
496
|
+
if candname == attribreg:
|
497
|
+
regurl = cand
|
498
|
+
break
|
499
|
+
if not regurl:
|
500
|
+
# Workaround a vendor bug where they link to a
|
501
|
+
# non-existant name
|
502
|
+
for cand in reglist.get('Members', []):
|
503
|
+
cand = cand.get('@odata.id', '')
|
504
|
+
candname = cand.split('/')[-1]
|
505
|
+
candname = candname.split('.')[0]
|
506
|
+
if candname == attribreg.split('.')[0]:
|
507
|
+
regurl = cand
|
508
|
+
break
|
509
|
+
if regurl:
|
510
|
+
reginfo = fishclient._do_web_request(regurl)
|
511
|
+
for reg in reginfo.get('Location', []):
|
512
|
+
if reg.get('Language', 'en').startswith('en'):
|
513
|
+
reguri = reg['Uri']
|
514
|
+
reginfo = self._get_biosreg(reguri, fishclient)
|
515
|
+
return reginfo
|
516
|
+
extrainfo, valtodisplay, _, self.attrdeps = reginfo
|
517
|
+
|
348
518
|
def _getsyscfg(self, fishclient):
|
349
519
|
biosinfo = self._do_web_request(fishclient._biosurl, cache=False)
|
350
520
|
reginfo = ({}, {}, {}, {})
|
@@ -352,36 +522,9 @@ class OEMHandler(object):
|
|
352
522
|
valtodisplay = {}
|
353
523
|
self.attrdeps = {'Dependencies': [], 'Attributes': []}
|
354
524
|
if 'AttributeRegistry' in biosinfo:
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
regurl = None
|
359
|
-
for cand in reglist.get('Members', []):
|
360
|
-
cand = cand.get('@odata.id', '')
|
361
|
-
candname = cand.split('/')[-1]
|
362
|
-
if candname == '': # implementation uses trailing slash
|
363
|
-
candname = cand.split('/')[-2]
|
364
|
-
if candname == biosinfo['AttributeRegistry']:
|
365
|
-
regurl = cand
|
366
|
-
break
|
367
|
-
if not regurl:
|
368
|
-
# Workaround a vendor bug where they link to a
|
369
|
-
# non-existant name
|
370
|
-
for cand in reglist.get('Members', []):
|
371
|
-
cand = cand.get('@odata.id', '')
|
372
|
-
candname = cand.split('/')[-1]
|
373
|
-
candname = candname.split('.')[0]
|
374
|
-
if candname == biosinfo[
|
375
|
-
'AttributeRegistry'].split('.')[0]:
|
376
|
-
regurl = cand
|
377
|
-
break
|
378
|
-
if regurl:
|
379
|
-
reginfo = fishclient._do_web_request(regurl)
|
380
|
-
for reg in reginfo.get('Location', []):
|
381
|
-
if reg.get('Language', 'en').startswith('en'):
|
382
|
-
reguri = reg['Uri']
|
383
|
-
reginfo = self._get_biosreg(reguri, fishclient)
|
384
|
-
extrainfo, valtodisplay, _, self.attrdeps = reginfo
|
525
|
+
reginfo = self._get_attrib_registry(fishclient, biosinfo['AttributeRegistry'])
|
526
|
+
if reginfo:
|
527
|
+
extrainfo, valtodisplay, _, self.attrdeps = reginfo
|
385
528
|
currsettings = {}
|
386
529
|
try:
|
387
530
|
pendingsettings = fishclient._do_web_request(
|
@@ -418,10 +561,19 @@ class OEMHandler(object):
|
|
418
561
|
rawsettings = fishclient._do_web_request(fishclient._biosurl,
|
419
562
|
cache=False)
|
420
563
|
rawsettings = rawsettings.get('Attributes', {})
|
421
|
-
pendingsettings = fishclient._do_web_request(
|
564
|
+
pendingsettings = fishclient._do_web_request(
|
565
|
+
fishclient._setbiosurl)
|
566
|
+
return self._set_redfish_settings(
|
567
|
+
changeset, fishclient, currsettings, rawsettings,
|
568
|
+
pendingsettings, self.attrdeps, reginfo,
|
569
|
+
fishclient._setbiosurl)
|
570
|
+
|
571
|
+
def _set_redfish_settings(self, changeset, fishclient, currsettings,
|
572
|
+
rawsettings, pendingsettings, attrdeps, reginfo,
|
573
|
+
seturl):
|
422
574
|
etag = pendingsettings.get('@odata.etag', None)
|
423
575
|
pendingsettings = pendingsettings.get('Attributes', {})
|
424
|
-
dephandler = AttrDependencyHandler(
|
576
|
+
dephandler = AttrDependencyHandler(attrdeps, rawsettings,
|
425
577
|
pendingsettings)
|
426
578
|
for change in list(changeset):
|
427
579
|
if change not in currsettings:
|
@@ -440,7 +592,7 @@ class OEMHandler(object):
|
|
440
592
|
changeval = changeset[change]
|
441
593
|
overrides, blameattrs = dephandler.get_overrides(change)
|
442
594
|
meta = {}
|
443
|
-
for attr in
|
595
|
+
for attr in attrdeps['Attributes']:
|
444
596
|
if attr['AttributeName'] == change:
|
445
597
|
meta = dict(attr)
|
446
598
|
break
|
@@ -479,7 +631,7 @@ class OEMHandler(object):
|
|
479
631
|
changeset[change] = _to_boolean(changeset[change])
|
480
632
|
redfishsettings = {'Attributes': changeset}
|
481
633
|
fishclient._do_web_request(
|
482
|
-
|
634
|
+
seturl, redfishsettings, 'PATCH', etag=etag)
|
483
635
|
|
484
636
|
def attach_remote_media(self, url, username, password, vmurls):
|
485
637
|
return None
|
@@ -487,10 +639,17 @@ class OEMHandler(object):
|
|
487
639
|
def detach_remote_media(self):
|
488
640
|
return None
|
489
641
|
|
490
|
-
def get_description(self):
|
642
|
+
def get_description(self, fishclient):
|
643
|
+
for chassis in fishclient.sysinfo.get('Links', {}).get('Chassis', []):
|
644
|
+
chassisurl = chassis['@odata.id']
|
645
|
+
chassisinfo = self._do_web_request(chassisurl)
|
646
|
+
hmm = chassisinfo.get('HeightMm', None)
|
647
|
+
if hmm:
|
648
|
+
return {'height': hmm/44.45}
|
491
649
|
return {}
|
492
650
|
|
493
|
-
|
651
|
+
|
652
|
+
def get_firmware_inventory(self, components, fishclient):
|
494
653
|
return []
|
495
654
|
|
496
655
|
def set_credentials(self, username, password):
|
@@ -529,15 +688,30 @@ class OEMHandler(object):
|
|
529
688
|
for adp in self._get_adp_inventory(True, withids):
|
530
689
|
yield adp
|
531
690
|
|
691
|
+
def _get_node_info(self):
|
692
|
+
nodeinfo = self._varsysinfo
|
693
|
+
if not nodeinfo:
|
694
|
+
overview = self._do_web_request('/redfish/v1/')
|
695
|
+
chassismembs = overview.get('Chassis', {}).get('@odata.id', None)
|
696
|
+
if not chassismembs:
|
697
|
+
return nodeinfo
|
698
|
+
chassislist = self._do_web_request(chassismembs)
|
699
|
+
chassismembs = chassislist.get('Members', [])
|
700
|
+
if len(chassismembs) == 1:
|
701
|
+
chassisurl = chassismembs[0]['@odata.id']
|
702
|
+
nodeinfo = self._do_web_request(chassisurl)
|
703
|
+
return nodeinfo
|
704
|
+
|
532
705
|
def get_inventory_of_component(self, component):
|
533
706
|
if component.lower() == 'system':
|
707
|
+
nodeinfo = self._get_node_info()
|
534
708
|
sysinfo = {
|
535
|
-
'UUID':
|
536
|
-
'Serial Number':
|
537
|
-
'Manufacturer':
|
538
|
-
'Product name':
|
539
|
-
'Model':
|
540
|
-
'SKU',
|
709
|
+
'UUID': nodeinfo.get('UUID', ''),
|
710
|
+
'Serial Number': nodeinfo.get('SerialNumber', ''),
|
711
|
+
'Manufacturer': nodeinfo.get('Manufacturer', ''),
|
712
|
+
'Product name': nodeinfo.get('Model', ''),
|
713
|
+
'Model': nodeinfo.get(
|
714
|
+
'SKU', nodeinfo.get('PartNumber', '')),
|
541
715
|
}
|
542
716
|
if sysinfo['UUID'] and '-' not in sysinfo['UUID']:
|
543
717
|
sysinfo['UUID'] = '-'.join((
|
@@ -552,13 +726,14 @@ class OEMHandler(object):
|
|
552
726
|
return invpair[1]
|
553
727
|
|
554
728
|
def get_inventory(self, withids=False):
|
729
|
+
nodeinfo = self._get_node_info()
|
555
730
|
sysinfo = {
|
556
|
-
'UUID':
|
557
|
-
'Serial Number':
|
558
|
-
'Manufacturer':
|
559
|
-
'Product name':
|
560
|
-
'Model':
|
561
|
-
'SKU',
|
731
|
+
'UUID': nodeinfo.get('UUID', ''),
|
732
|
+
'Serial Number': nodeinfo.get('SerialNumber', ''),
|
733
|
+
'Manufacturer': nodeinfo.get('Manufacturer', ''),
|
734
|
+
'Product name': nodeinfo.get('Model', ''),
|
735
|
+
'Model': nodeinfo.get(
|
736
|
+
'SKU', nodeinfo.get('PartNumber', '')),
|
562
737
|
}
|
563
738
|
if sysinfo['UUID'] and '-' not in sysinfo['UUID']:
|
564
739
|
sysinfo['UUID'] = '-'.join((
|
@@ -568,32 +743,14 @@ class OEMHandler(object):
|
|
568
743
|
sysinfo['UUID'] = sysinfo['UUID'].lower()
|
569
744
|
yield ('System', sysinfo)
|
570
745
|
self._hwnamemap = {}
|
571
|
-
cpumemurls = []
|
572
|
-
memurl = self._varsysinfo.get('Memory', {}).get('@odata.id', None)
|
573
|
-
if memurl:
|
574
|
-
cpumemurls.append(memurl)
|
575
|
-
cpurl = self._varsysinfo.get('Processors', {}).get('@odata.id', None)
|
576
|
-
if cpurl:
|
577
|
-
cpumemurls.append(cpurl)
|
578
|
-
list(self._do_bulk_requests(cpumemurls))
|
579
746
|
adpurls = self._get_adp_urls()
|
580
|
-
if cpurl:
|
581
|
-
cpurls = self._get_cpu_urls()
|
582
|
-
else:
|
583
|
-
cpurls = []
|
584
|
-
if memurl:
|
585
|
-
memurls = self._get_mem_urls()
|
586
|
-
else:
|
587
|
-
memurls = []
|
588
747
|
diskurls = self._get_disk_urls()
|
589
|
-
allurls = adpurls +
|
748
|
+
allurls = adpurls + diskurls
|
590
749
|
list(self._do_bulk_requests(allurls))
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
for mem in self._get_mem_inventory(withids=withids, urls=memurls):
|
596
|
-
yield mem
|
750
|
+
for cpu in self._get_cpu_inventory(withids=withids):
|
751
|
+
yield cpu
|
752
|
+
for mem in self._get_mem_inventory(withids=withids):
|
753
|
+
yield mem
|
597
754
|
for adp in self._get_adp_inventory(withids=withids, urls=adpurls):
|
598
755
|
yield adp
|
599
756
|
for disk in self._get_disk_inventory(withids=withids, urls=diskurls):
|
@@ -620,13 +777,11 @@ class OEMHandler(object):
|
|
620
777
|
foundmacs = False
|
621
778
|
macinfobyadpname = {}
|
622
779
|
if 'NetworkInterfaces' in self._varsysinfo:
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
nadurl = nifinfo.get('Links', {}).get('NetworkAdapter', {}).get("@odata.id")
|
780
|
+
nifdata = self._get_expanded_data(
|
781
|
+
self._varsysinfo['NetworkInterfaces']['@odata.id'])
|
782
|
+
for nifinfo in nifdata.get('Members', []):
|
783
|
+
nadurl = nifinfo.get(
|
784
|
+
'Links', {}).get('NetworkAdapter', {}).get("@odata.id")
|
630
785
|
if nadurl:
|
631
786
|
nadinfo = self._do_web_request(nadurl)
|
632
787
|
if 'Name' not in nadinfo:
|
@@ -634,20 +789,31 @@ class OEMHandler(object):
|
|
634
789
|
nicname = nadinfo['Name']
|
635
790
|
yieldinf = {}
|
636
791
|
macidx = 1
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
portinfo = self._do_web_request(porturl)
|
642
|
-
macs = [x for x in portinfo.get(
|
643
|
-
'Ethernet', {}).get(
|
644
|
-
'AssociatedMACAddresses', [])]
|
792
|
+
if 'Ports' in nadinfo:
|
793
|
+
for portinfo in self._get_expanded_data(
|
794
|
+
nadinfo['Ports']['@odata.id']).get('Members', []):
|
795
|
+
macs = [x for x in portinfo.get('Ethernet', {}).get('AssociatedMACAddresses', [])]
|
645
796
|
for mac in macs:
|
646
797
|
label = 'MAC Address {}'.format(macidx)
|
647
798
|
yieldinf[label] = _normalize_mac(mac)
|
648
799
|
macidx += 1
|
649
800
|
foundmacs = True
|
650
|
-
|
801
|
+
macinfobyadpname[nicname] = yieldinf
|
802
|
+
else:
|
803
|
+
for ctrlr in nadinfo.get('Controllers', []):
|
804
|
+
porturls = [x['@odata.id'] for x in ctrlr.get(
|
805
|
+
'Links', {}).get('Ports', [])]
|
806
|
+
for porturl in porturls:
|
807
|
+
portinfo = self._do_web_request(porturl)
|
808
|
+
macs = [x for x in portinfo.get(
|
809
|
+
'Ethernet', {}).get(
|
810
|
+
'AssociatedMACAddresses', [])]
|
811
|
+
for mac in macs:
|
812
|
+
label = 'MAC Address {}'.format(macidx)
|
813
|
+
yieldinf[label] = _normalize_mac(mac)
|
814
|
+
macidx += 1
|
815
|
+
foundmacs = True
|
816
|
+
macinfobyadpname[nicname] = yieldinf
|
651
817
|
if not urls:
|
652
818
|
urls = self._get_adp_urls()
|
653
819
|
for inf in self._do_bulk_requests(urls):
|
@@ -698,6 +864,8 @@ class OEMHandler(object):
|
|
698
864
|
if aname in macinfobyadpname:
|
699
865
|
del macinfobyadpname[aname]
|
700
866
|
yield aname, yieldinf
|
867
|
+
if onlyname:
|
868
|
+
return
|
701
869
|
if macinfobyadpname:
|
702
870
|
for adp in macinfobyadpname:
|
703
871
|
yield adp, macinfobyadpname[adp]
|
@@ -742,12 +910,9 @@ class OEMHandler(object):
|
|
742
910
|
return urls
|
743
911
|
|
744
912
|
def _get_cpu_inventory(self, onlynames=False, withids=False, urls=None):
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
return
|
749
|
-
for res in self._do_bulk_requests(urls):
|
750
|
-
currcpuinfo, url = res
|
913
|
+
for currcpuinfo in self._get_cpu_data().get(
|
914
|
+
'Members', []):
|
915
|
+
url = currcpuinfo['@odata.id']
|
751
916
|
name = currcpuinfo.get('Name', 'CPU')
|
752
917
|
if name in self._hwnamemap:
|
753
918
|
self._hwnamemap[name] = None
|
@@ -772,21 +937,20 @@ class OEMHandler(object):
|
|
772
937
|
return urls
|
773
938
|
|
774
939
|
def _get_cpu_urls(self):
|
940
|
+
md = self._get_cpu_data(False)
|
941
|
+
return [x['@odata.id'] for x in md.get('Members', [])]
|
942
|
+
|
943
|
+
def _get_cpu_data(self, expand='.'):
|
775
944
|
cpurl = self._varsysinfo.get('Processors', {}).get('@odata.id', None)
|
776
|
-
if cpurl
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
urls = [x['@odata.id'] for x in cpurl.get('Members', [])]
|
781
|
-
return urls
|
945
|
+
if not cpurl:
|
946
|
+
return {}
|
947
|
+
return self._get_expanded_data(cpurl, expand)
|
948
|
+
|
782
949
|
|
783
950
|
def _get_mem_inventory(self, onlyname=False, withids=False, urls=None):
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
return
|
788
|
-
for mem in self._do_bulk_requests(urls):
|
789
|
-
currmeminfo, url = mem
|
951
|
+
memdata = self._get_mem_data()
|
952
|
+
for currmeminfo in memdata.get('Members', []): # self._do_bulk_requests(urls):
|
953
|
+
url = currmeminfo['@odata.id']
|
790
954
|
name = currmeminfo.get('Name', 'Memory')
|
791
955
|
if name in self._hwnamemap:
|
792
956
|
self._hwnamemap[name] = None
|
@@ -815,13 +979,31 @@ class OEMHandler(object):
|
|
815
979
|
yield (name, meminfo)
|
816
980
|
|
817
981
|
def _get_mem_urls(self):
|
982
|
+
md = self._get_mem_data(False)
|
983
|
+
return [x['@odata.id'] for x in md.get('Members', [])]
|
984
|
+
|
985
|
+
def _get_mem_data(self, expand='.'):
|
818
986
|
memurl = self._varsysinfo.get('Memory', {}).get('@odata.id', None)
|
819
987
|
if not memurl:
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
988
|
+
return {}
|
989
|
+
return self._get_expanded_data(memurl, expand)
|
990
|
+
|
991
|
+
def _get_expanded_data(self, url, expand='.'):
|
992
|
+
topdata = []
|
993
|
+
if not url:
|
994
|
+
return topdata
|
995
|
+
if not expand:
|
996
|
+
return self._do_web_request(url)
|
997
|
+
elif self.supports_expand(url):
|
998
|
+
return self._do_web_request(url + '?$expand=' + expand)
|
999
|
+
else: # emulate expand behavior
|
1000
|
+
topdata = self._do_web_request(url)
|
1001
|
+
newmembers = []
|
1002
|
+
for x in topdata.get('Members', []):
|
1003
|
+
newmembers.append(self._do_web_request(x['@odata.id']))
|
1004
|
+
topdata['Members'] = newmembers
|
1005
|
+
return topdata
|
1006
|
+
return topdata
|
825
1007
|
|
826
1008
|
def get_storage_configuration(self):
|
827
1009
|
raise exc.UnsupportedFunctionality(
|
@@ -843,8 +1025,9 @@ class OEMHandler(object):
|
|
843
1025
|
raise exc.UnsupportedFunctionality(
|
844
1026
|
'Remote media upload not supported on this platform')
|
845
1027
|
|
846
|
-
def update_firmware(self, filename, data=None, progress=None, bank=None):
|
847
|
-
|
1028
|
+
def update_firmware(self, filename, data=None, progress=None, bank=None, otherfields=()):
|
1029
|
+
# disable cache to make sure we trigger the token renewal logic if needed
|
1030
|
+
usd = self._do_web_request('/redfish/v1/UpdateService', cache=False)
|
848
1031
|
upurl = usd.get('MultipartHttpPushUri', None)
|
849
1032
|
ismultipart = True
|
850
1033
|
if not upurl:
|
@@ -863,7 +1046,7 @@ class OEMHandler(object):
|
|
863
1046
|
try:
|
864
1047
|
uploadthread = webclient.FileUploader(
|
865
1048
|
self.webclient, upurl, filename, data, formwrap=ismultipart,
|
866
|
-
excepterror=False)
|
1049
|
+
excepterror=False, otherfields=otherfields)
|
867
1050
|
uploadthread.start()
|
868
1051
|
wc = self.webclient
|
869
1052
|
while uploadthread.isAlive():
|
@@ -944,6 +1127,26 @@ class OEMHandler(object):
|
|
944
1127
|
cache=True):
|
945
1128
|
return self._do_web_request(url, payload, method, cache), url
|
946
1129
|
|
1130
|
+
def _get_session_token(self, wc):
|
1131
|
+
username = self.username
|
1132
|
+
password = self.password
|
1133
|
+
if not isinstance(username, str):
|
1134
|
+
username = username.decode()
|
1135
|
+
if not isinstance(password, str):
|
1136
|
+
password = password.decode()
|
1137
|
+
# specification actually indicates we can skip straight to this url
|
1138
|
+
rsp = wc.grab_rsp('/redfish/v1/SessionService/Sessions',
|
1139
|
+
{'UserName': username, 'Password': password})
|
1140
|
+
rsp.read()
|
1141
|
+
self.xauthtoken = rsp.getheader('X-Auth-Token')
|
1142
|
+
if self.xauthtoken:
|
1143
|
+
if 'Authorization' in wc.stdheaders:
|
1144
|
+
del wc.stdheaders['Authorization']
|
1145
|
+
if 'Authorization' in self.webclient.stdheaders:
|
1146
|
+
del self.webclient.stdheaders['Authorization']
|
1147
|
+
wc.stdheaders['X-Auth-Token'] = self.xauthtoken
|
1148
|
+
self.webclient.stdheaders['X-Auth-Token'] = self.xauthtoken
|
1149
|
+
|
947
1150
|
def _do_web_request(self, url, payload=None, method=None, cache=True):
|
948
1151
|
res = None
|
949
1152
|
if cache and payload is None and method is None:
|
@@ -952,6 +1155,11 @@ class OEMHandler(object):
|
|
952
1155
|
return res
|
953
1156
|
wc = self.webclient.dupe()
|
954
1157
|
res = wc.grab_json_response_with_status(url, payload, method=method)
|
1158
|
+
if res[1] == 401 and 'X-Auth-Token' in self.webclient.stdheaders:
|
1159
|
+
wc.set_basic_credentials(self.username, self.password)
|
1160
|
+
self._get_session_token(wc)
|
1161
|
+
res = wc.grab_json_response_with_status(url, payload,
|
1162
|
+
method=method)
|
955
1163
|
if res[1] < 200 or res[1] >= 300:
|
956
1164
|
try:
|
957
1165
|
info = json.loads(res[0])
|
@@ -15,15 +15,28 @@
|
|
15
15
|
import pyghmi.redfish.oem.generic as generic
|
16
16
|
from pyghmi.redfish.oem.lenovo import tsma
|
17
17
|
from pyghmi.redfish.oem.lenovo import xcc
|
18
|
-
|
18
|
+
from pyghmi.redfish.oem.lenovo import xcc3
|
19
|
+
from pyghmi.redfish.oem.lenovo import smm3
|
19
20
|
|
20
21
|
def get_handler(sysinfo, sysurl, webclient, cache, cmd):
|
21
22
|
leninf = sysinfo.get('Oem', {}).get('Lenovo', {})
|
23
|
+
mgrinfo = {}
|
24
|
+
if leninf:
|
25
|
+
mgrinf, status = webclient.grab_json_response_with_status('/redfish/v1/Managers/1')
|
26
|
+
if status != 200:
|
27
|
+
mgrinfo = {}
|
22
28
|
if not leninf:
|
23
29
|
bmcinfo = cmd.bmcinfo
|
24
30
|
if 'Ami' in bmcinfo.get('Oem', {}):
|
25
31
|
return tsma.TsmHandler(sysinfo, sysurl, webclient, cache)
|
26
|
-
|
32
|
+
elif 'xclarity controller' in mgrinf.get('Model', '').lower():
|
33
|
+
if mgrinf['Model'].endswith('3'):
|
34
|
+
return xcc3.OEMHandler(sysinfo, sysurl, webclient, cache,
|
35
|
+
gpool=cmd._gpool)
|
36
|
+
else:
|
37
|
+
return xcc.OEMHandler(sysinfo, sysurl, webclient, cache,
|
38
|
+
gpool=cmd._gpool)
|
39
|
+
elif 'FrontPanelUSB' in leninf or 'USBManagementPortAssignment' in leninf or sysinfo.get('SKU', '').startswith('7X58'):
|
27
40
|
return xcc.OEMHandler(sysinfo, sysurl, webclient, cache,
|
28
41
|
gpool=cmd._gpool)
|
29
42
|
else:
|
@@ -32,5 +45,12 @@ def get_handler(sysinfo, sysurl, webclient, cache, cmd):
|
|
32
45
|
if 'hdd' in leninv and 'hostMAC' in leninv and 'backPlane' in leninv:
|
33
46
|
return tsma.TsmHandler(sysinfo, sysurl, webclient, cache,
|
34
47
|
gpool=cmd._gpool)
|
35
|
-
|
36
|
-
|
48
|
+
try:
|
49
|
+
devdesc = webclient.grab_json_response_with_status('/DeviceDescription.json')
|
50
|
+
if devdesc[1] == 200:
|
51
|
+
if devdesc[0]['type'].lower() == 'lenovo-smm3':
|
52
|
+
return smm3.OEMHandler(sysinfo, sysurl, webclient, cache, gpool=cmd._gpool)
|
53
|
+
except Exception:
|
54
|
+
pass
|
55
|
+
return generic.OEMHandler(sysinfo, sysurl, webclient, cache,
|
56
|
+
gpool=cmd._gpool)
|