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.
@@ -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((mac[:2], mac[2:4], mac[4:6], mac[6:8], mac[8:10], mac[10:12]))
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
- for mem in fishclient._do_web_request(
212
- fishclient.sysinfo['Memory']['@odata.id'])['Members']:
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
- overview = fishclient._do_web_request('/redfish/v1/')
356
- reglist = overview['Registries']['@odata.id']
357
- reglist = fishclient._do_web_request(reglist)
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(fishclient._setbiosurl)
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(self.attrdeps, rawsettings,
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 self.attrdeps['Attributes']:
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
- fishclient._setbiosurl, redfishsettings, 'PATCH', etag=etag)
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
- def get_firmware_inventory(self, components):
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': self._varsysinfo.get('UUID', ''),
536
- 'Serial Number': self._varsysinfo.get('SerialNumber', ''),
537
- 'Manufacturer': self._varsysinfo.get('Manufacturer', ''),
538
- 'Product name': self._varsysinfo.get('Model', ''),
539
- 'Model': self._varsysinfo.get(
540
- 'SKU', self._varsysinfo.get('PartNumber', '')),
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': self._varsysinfo.get('UUID', ''),
557
- 'Serial Number': self._varsysinfo.get('SerialNumber', ''),
558
- 'Manufacturer': self._varsysinfo.get('Manufacturer', ''),
559
- 'Product name': self._varsysinfo.get('Model', ''),
560
- 'Model': self._varsysinfo.get(
561
- 'SKU', self._varsysinfo.get('PartNumber', '')),
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 + cpurls + memurls + diskurls
748
+ allurls = adpurls + diskurls
590
749
  list(self._do_bulk_requests(allurls))
591
- if cpurl:
592
- for cpu in self._get_cpu_inventory(withids=withids, urls=cpurls):
593
- yield cpu
594
- if memurl:
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
- nifurls = self._do_web_request(self._varsysinfo['NetworkInterfaces']['@odata.id'])
624
- nifurls = nifurls.get('Members', [])
625
- nifurls = [x['@odata.id'] for x in nifurls]
626
- for nifurl in nifurls:
627
- nifinfo = self._do_web_request(nifurl)
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
- for ctrlr in nadinfo.get('Controllers', []):
638
- porturls = [x['@odata.id'] for x in ctrlr.get(
639
- 'Links', {}).get('Ports', [])]
640
- for porturl in porturls:
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
- macinfobyadpname[nicname] = yieldinf
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
- if not urls:
746
- urls = self._get_cpu_urls()
747
- if not urls:
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 is None:
777
- urls = []
778
- else:
779
- cpurl = self._do_web_request(cpurl)
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
- if not urls:
785
- urls = self._get_mem_urls()
786
- if not urls:
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
- urls = []
821
- else:
822
- memurl = self._do_web_request(memurl)
823
- urls = [x['@odata.id'] for x in memurl.get('Members', [])]
824
- return urls
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
- usd = self._do_web_request('/redfish/v1/UpdateService')
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
- if 'FrontPanelUSB' in leninf or 'USBManagementPortAssignment' in leninf or sysinfo.get('SKU', '').startswith('7X58'):
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
- return generic.OEMHandler(sysinfo, sysurl, webclient, cache,
36
- gpool=cmd._gpool)
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)