pyghmi 1.5.71__py3-none-any.whl → 1.5.75__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|