pyghmi 1.5.72__py3-none-any.whl → 1.5.76__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):
@@ -179,6 +184,7 @@ class AttrDependencyHandler(object):
179
184
 
180
185
  class OEMHandler(object):
181
186
  hostnic = None
187
+ usegenericsensors = True
182
188
 
183
189
  def __init__(self, sysinfo, sysurl, webclient, cache, gpool=None):
184
190
  self._gpool = gpool
@@ -187,6 +193,122 @@ class OEMHandler(object):
187
193
  self._urlcache = cache
188
194
  self.webclient = webclient
189
195
 
196
+ def supports_expand(self, url):
197
+ # Unfortunately, the state of expand in redfish is pretty dicey,
198
+ # so an OEM handler must opt into this behavior
199
+ # There is a way an implementation advertises support, however
200
+ # this isn't to be trusted.
201
+ # Even among some generally reputable implementations, they will fail in some scenarios
202
+ # and you'll see in their documentation "some urls will fail if you try to expand them"
203
+ # perhaps being specific, but other times being vague, but in either case,
204
+ # nothing programattic to consume to know when to do or not do an expand..
205
+ return False
206
+
207
+ def get_system_power_watts(self, fishclient):
208
+ totalwatts = 0
209
+ gotpower = False
210
+ for chassis in fishclient.sysinfo.get('Links', {}).get('Chassis', []):
211
+ envinfo = fishclient._get_chassis_env(chassis)
212
+ currwatts = envinfo.get('watts', None)
213
+ if currwatts is not None:
214
+ gotpower = True
215
+ totalwatts += envinfo['watts']
216
+ if not gotpower:
217
+ raise exc.UnsupportedFunctionality("System does not provide Power under redfish EnvironmentMetrics")
218
+ return totalwatts
219
+
220
+ def _get_cpu_temps(self, fishclient):
221
+ cputemps = []
222
+ for chassis in fishclient.sysinfo.get('Links', {}).get('Chassis', []):
223
+ thermals = fishclient._get_thermals(chassis)
224
+ for temp in thermals:
225
+ if temp.get('PhysicalContext', '') != 'CPU':
226
+ continue
227
+ if temp.get('ReadingCelsius', None) is None:
228
+ continue
229
+ cputemps.append(temp)
230
+ return cputemps
231
+
232
+ def get_event_log(self, clear=False, fishclient=None, extraurls=[]):
233
+ bmcinfo = self._do_web_request(fishclient._bmcurl)
234
+ lsurl = bmcinfo.get('LogServices', {}).get('@odata.id', None)
235
+ if not lsurl:
236
+ return
237
+ currtime = bmcinfo.get('DateTime', None)
238
+ correction = timedelta(0)
239
+ utz = tz.tzoffset('', 0)
240
+ ltz = tz.gettz()
241
+ if currtime:
242
+ currtime = parse_time(currtime)
243
+ if currtime:
244
+ now = datetime.now(utz)
245
+ try:
246
+ correction = now - currtime
247
+ except TypeError:
248
+ correction = now - currtime.replace(tzinfo=utz)
249
+ lurls = self._do_web_request(lsurl).get('Members', [])
250
+ lurls.extend(extraurls)
251
+ for lurl in lurls:
252
+ lurl = lurl['@odata.id']
253
+ loginfo = self._do_web_request(lurl, cache=(not clear))
254
+ entriesurl = loginfo.get('Entries', {}).get('@odata.id', None)
255
+ if not entriesurl:
256
+ continue
257
+ logid = loginfo.get('Id', '')
258
+ entries = self._do_web_request(entriesurl, cache=False)
259
+ if clear:
260
+ # The clear is against the log service etag, not entries
261
+ # so we have to fetch service etag after we fetch entries
262
+ # until we can verify that the etag is consistent to prove
263
+ # that the clear is atomic
264
+ newloginfo = self._do_web_request(lurl, cache=False)
265
+ clearurl = newloginfo.get('Actions', {}).get(
266
+ '#LogService.ClearLog', {}).get('target', '')
267
+ while clearurl:
268
+ try:
269
+ self._do_web_request(clearurl, method='POST',
270
+ payload={})
271
+ clearurl = False
272
+ except exc.PyghmiException as e:
273
+ if 'EtagPreconditionalFailed' not in str(e):
274
+ raise
275
+ # This doesn't guarantee atomicity, but it mitigates
276
+ # greatly. Unfortunately some implementations
277
+ # mutate the tag endlessly and we have no hope
278
+ entries = self._do_web_request(entriesurl, cache=False)
279
+ newloginfo = self._do_web_request(lurl, cache=False)
280
+ for log in entries.get('Members', []):
281
+ if ('Created' not in log and 'Message' not in log
282
+ and 'Severity' not in log):
283
+ # without any data, this log entry isn't actionable
284
+ continue
285
+ record = {}
286
+ record['log_id'] = logid
287
+ parsedtime = parse_time(log.get('Created', ''))
288
+ if not parsedtime:
289
+ parsedtime = parse_time(log.get('EventTimestamp', ''))
290
+ if parsedtime:
291
+ entime = parsedtime + correction
292
+ entime = entime.astimezone(ltz)
293
+ record['timestamp'] = entime.strftime('%Y-%m-%dT%H:%M:%S')
294
+ else:
295
+ record['timestamp'] = log.get('Created', '')
296
+ record['message'] = log.get('Message', None)
297
+ record['severity'] = _healthmap.get(
298
+ log.get('Severity', 'Warning'), const.Health.Ok)
299
+ yield record
300
+
301
+ def get_average_processor_temperature(self, fishclient):
302
+ cputemps = self._get_cpu_temps(fishclient)
303
+ if not cputemps:
304
+ return SensorReading(
305
+ None, {'name': 'Average Processor Temperature'}, value=None, units='°C',
306
+ unavailable=True)
307
+ cputemps = [x['ReadingCelsius'] for x in cputemps]
308
+ avgtemp = sum(cputemps) / len(cputemps)
309
+ return SensorReading(
310
+ None, {'name': 'Average Processor Temperature'}, value=avgtemp, units='°C')
311
+
190
312
  def get_health(self, fishclient, verbose=True):
191
313
  health = fishclient.sysinfo.get('Status', {})
192
314
  health = health.get('HealthRollup', health.get('Health', 'Unknown'))
@@ -226,9 +348,8 @@ class OEMHandler(object):
226
348
  memsumstatus.get('Health', None))
227
349
  if memsumstatus != 'OK':
228
350
  dimmfound = False
229
- for mem in fishclient._do_web_request(
230
- fishclient.sysinfo['Memory']['@odata.id'])['Members']:
231
- dimminfo = fishclient._do_web_request(mem['@odata.id'])
351
+ dimmdata = self._get_mem_data()
352
+ for dimminfo in dimmdata['Members']:
232
353
  if dimminfo.get('Status', {}).get(
233
354
  'State', None) == 'Absent':
234
355
  continue
@@ -520,8 +641,15 @@ class OEMHandler(object):
520
641
  return None
521
642
 
522
643
  def get_description(self, fishclient):
644
+ for chassis in fishclient.sysinfo.get('Links', {}).get('Chassis', []):
645
+ chassisurl = chassis['@odata.id']
646
+ chassisinfo = self._do_web_request(chassisurl)
647
+ hmm = chassisinfo.get('HeightMm', None)
648
+ if hmm:
649
+ return {'height': hmm/44.45}
523
650
  return {}
524
651
 
652
+
525
653
  def get_firmware_inventory(self, components, fishclient):
526
654
  return []
527
655
 
@@ -561,15 +689,30 @@ class OEMHandler(object):
561
689
  for adp in self._get_adp_inventory(True, withids):
562
690
  yield adp
563
691
 
692
+ def _get_node_info(self):
693
+ nodeinfo = self._varsysinfo
694
+ if not nodeinfo:
695
+ overview = self._do_web_request('/redfish/v1/')
696
+ chassismembs = overview.get('Chassis', {}).get('@odata.id', None)
697
+ if not chassismembs:
698
+ return nodeinfo
699
+ chassislist = self._do_web_request(chassismembs)
700
+ chassismembs = chassislist.get('Members', [])
701
+ if len(chassismembs) == 1:
702
+ chassisurl = chassismembs[0]['@odata.id']
703
+ nodeinfo = self._do_web_request(chassisurl)
704
+ return nodeinfo
705
+
564
706
  def get_inventory_of_component(self, component):
565
707
  if component.lower() == 'system':
708
+ nodeinfo = self._get_node_info()
566
709
  sysinfo = {
567
- 'UUID': self._varsysinfo.get('UUID', ''),
568
- 'Serial Number': self._varsysinfo.get('SerialNumber', ''),
569
- 'Manufacturer': self._varsysinfo.get('Manufacturer', ''),
570
- 'Product name': self._varsysinfo.get('Model', ''),
571
- 'Model': self._varsysinfo.get(
572
- 'SKU', self._varsysinfo.get('PartNumber', '')),
710
+ 'UUID': nodeinfo.get('UUID', ''),
711
+ 'Serial Number': nodeinfo.get('SerialNumber', ''),
712
+ 'Manufacturer': nodeinfo.get('Manufacturer', ''),
713
+ 'Product name': nodeinfo.get('Model', ''),
714
+ 'Model': nodeinfo.get(
715
+ 'SKU', nodeinfo.get('PartNumber', '')),
573
716
  }
574
717
  if sysinfo['UUID'] and '-' not in sysinfo['UUID']:
575
718
  sysinfo['UUID'] = '-'.join((
@@ -584,13 +727,14 @@ class OEMHandler(object):
584
727
  return invpair[1]
585
728
 
586
729
  def get_inventory(self, withids=False):
730
+ nodeinfo = self._get_node_info()
587
731
  sysinfo = {
588
- 'UUID': self._varsysinfo.get('UUID', ''),
589
- 'Serial Number': self._varsysinfo.get('SerialNumber', ''),
590
- 'Manufacturer': self._varsysinfo.get('Manufacturer', ''),
591
- 'Product name': self._varsysinfo.get('Model', ''),
592
- 'Model': self._varsysinfo.get(
593
- 'SKU', self._varsysinfo.get('PartNumber', '')),
732
+ 'UUID': nodeinfo.get('UUID', ''),
733
+ 'Serial Number': nodeinfo.get('SerialNumber', ''),
734
+ 'Manufacturer': nodeinfo.get('Manufacturer', ''),
735
+ 'Product name': nodeinfo.get('Model', ''),
736
+ 'Model': nodeinfo.get(
737
+ 'SKU', nodeinfo.get('PartNumber', '')),
594
738
  }
595
739
  if sysinfo['UUID'] and '-' not in sysinfo['UUID']:
596
740
  sysinfo['UUID'] = '-'.join((
@@ -600,32 +744,14 @@ class OEMHandler(object):
600
744
  sysinfo['UUID'] = sysinfo['UUID'].lower()
601
745
  yield ('System', sysinfo)
602
746
  self._hwnamemap = {}
603
- cpumemurls = []
604
- memurl = self._varsysinfo.get('Memory', {}).get('@odata.id', None)
605
- if memurl:
606
- cpumemurls.append(memurl)
607
- cpurl = self._varsysinfo.get('Processors', {}).get('@odata.id', None)
608
- if cpurl:
609
- cpumemurls.append(cpurl)
610
- list(self._do_bulk_requests(cpumemurls))
611
747
  adpurls = self._get_adp_urls()
612
- if cpurl:
613
- cpurls = self._get_cpu_urls()
614
- else:
615
- cpurls = []
616
- if memurl:
617
- memurls = self._get_mem_urls()
618
- else:
619
- memurls = []
620
748
  diskurls = self._get_disk_urls()
621
- allurls = adpurls + cpurls + memurls + diskurls
749
+ allurls = adpurls + diskurls
622
750
  list(self._do_bulk_requests(allurls))
623
- if cpurl:
624
- for cpu in self._get_cpu_inventory(withids=withids, urls=cpurls):
625
- yield cpu
626
- if memurl:
627
- for mem in self._get_mem_inventory(withids=withids, urls=memurls):
628
- yield mem
751
+ for cpu in self._get_cpu_inventory(withids=withids):
752
+ yield cpu
753
+ for mem in self._get_mem_inventory(withids=withids):
754
+ yield mem
629
755
  for adp in self._get_adp_inventory(withids=withids, urls=adpurls):
630
756
  yield adp
631
757
  for disk in self._get_disk_inventory(withids=withids, urls=diskurls):
@@ -652,34 +778,55 @@ class OEMHandler(object):
652
778
  foundmacs = False
653
779
  macinfobyadpname = {}
654
780
  if 'NetworkInterfaces' in self._varsysinfo:
655
- nifurls = self._do_web_request(self._varsysinfo['NetworkInterfaces']['@odata.id'])
656
- nifurls = nifurls.get('Members', [])
657
- nifurls = [x['@odata.id'] for x in nifurls]
658
- for nifurl in nifurls:
659
- nifinfo = self._do_web_request(nifurl)
660
-
661
- nadurl = nifinfo.get('Links', {}).get('NetworkAdapter', {}).get("@odata.id")
781
+ nifdata = self._get_expanded_data(
782
+ self._varsysinfo['NetworkInterfaces']['@odata.id'])
783
+ for nifinfo in nifdata.get('Members', []):
784
+ nadurl = nifinfo.get(
785
+ 'Links', {}).get('NetworkAdapter', {}).get("@odata.id")
662
786
  if nadurl:
663
787
  nadinfo = self._do_web_request(nadurl)
664
788
  if 'Name' not in nadinfo:
665
789
  continue
666
790
  nicname = nadinfo['Name']
791
+ if nicname == 'NetworkAdapter':
792
+ nicname = nadinfo.get('Model', nicname)
667
793
  yieldinf = {}
668
794
  macidx = 1
669
- for ctrlr in nadinfo.get('Controllers', []):
670
- porturls = [x['@odata.id'] for x in ctrlr.get(
671
- 'Links', {}).get('Ports', [])]
672
- for porturl in porturls:
673
- portinfo = self._do_web_request(porturl)
674
- macs = [x for x in portinfo.get(
675
- 'Ethernet', {}).get(
676
- 'AssociatedMACAddresses', [])]
677
- for mac in macs:
678
- label = 'MAC Address {}'.format(macidx)
679
- yieldinf[label] = _normalize_mac(mac)
680
- macidx += 1
681
- foundmacs = True
682
- macinfobyadpname[nicname] = yieldinf
795
+ if 'Ports' in nadinfo:
796
+ for portinfo in self._get_expanded_data(
797
+ nadinfo['Ports']['@odata.id']).get('Members', []):
798
+ ethinfo = portinfo.get('Ethernet', {})
799
+ if ethinfo:
800
+ macs = [x for x in ethinfo.get('AssociatedMACAddresses', [])]
801
+ for mac in macs:
802
+ label = 'MAC Address {}'.format(macidx)
803
+ yieldinf[label] = _normalize_mac(mac)
804
+ macidx += 1
805
+ foundmacs = True
806
+ ibinfo = portinfo.get('InfiniBand', {})
807
+ if ibinfo:
808
+ macs = [x for x in ibinfo.get('AssociatedPortGUIDs', [])]
809
+ for mac in macs:
810
+ label = 'Port GUID {}'.format(macidx)
811
+ yieldinf[label] = mac
812
+ macidx += 1
813
+ foundmacs = True
814
+ macinfobyadpname[nicname] = yieldinf
815
+ else:
816
+ for ctrlr in nadinfo.get('Controllers', []):
817
+ porturls = [x['@odata.id'] for x in ctrlr.get(
818
+ 'Links', {}).get('Ports', [])]
819
+ for porturl in porturls:
820
+ portinfo = self._do_web_request(porturl)
821
+ macs = [x for x in portinfo.get(
822
+ 'Ethernet', {}).get(
823
+ 'AssociatedMACAddresses', [])]
824
+ for mac in macs:
825
+ label = 'MAC Address {}'.format(macidx)
826
+ yieldinf[label] = _normalize_mac(mac)
827
+ macidx += 1
828
+ foundmacs = True
829
+ macinfobyadpname[nicname] = yieldinf
683
830
  if not urls:
684
831
  urls = self._get_adp_urls()
685
832
  for inf in self._do_bulk_requests(urls):
@@ -730,6 +877,8 @@ class OEMHandler(object):
730
877
  if aname in macinfobyadpname:
731
878
  del macinfobyadpname[aname]
732
879
  yield aname, yieldinf
880
+ if onlyname:
881
+ return
733
882
  if macinfobyadpname:
734
883
  for adp in macinfobyadpname:
735
884
  yield adp, macinfobyadpname[adp]
@@ -774,12 +923,9 @@ class OEMHandler(object):
774
923
  return urls
775
924
 
776
925
  def _get_cpu_inventory(self, onlynames=False, withids=False, urls=None):
777
- if not urls:
778
- urls = self._get_cpu_urls()
779
- if not urls:
780
- return
781
- for res in self._do_bulk_requests(urls):
782
- currcpuinfo, url = res
926
+ for currcpuinfo in self._get_cpu_data().get(
927
+ 'Members', []):
928
+ url = currcpuinfo['@odata.id']
783
929
  name = currcpuinfo.get('Name', 'CPU')
784
930
  if name in self._hwnamemap:
785
931
  self._hwnamemap[name] = None
@@ -804,21 +950,20 @@ class OEMHandler(object):
804
950
  return urls
805
951
 
806
952
  def _get_cpu_urls(self):
953
+ md = self._get_cpu_data(False)
954
+ return [x['@odata.id'] for x in md.get('Members', [])]
955
+
956
+ def _get_cpu_data(self, expand='.'):
807
957
  cpurl = self._varsysinfo.get('Processors', {}).get('@odata.id', None)
808
- if cpurl is None:
809
- urls = []
810
- else:
811
- cpurl = self._do_web_request(cpurl)
812
- urls = [x['@odata.id'] for x in cpurl.get('Members', [])]
813
- return urls
958
+ if not cpurl:
959
+ return {}
960
+ return self._get_expanded_data(cpurl, expand)
961
+
814
962
 
815
963
  def _get_mem_inventory(self, onlyname=False, withids=False, urls=None):
816
- if not urls:
817
- urls = self._get_mem_urls()
818
- if not urls:
819
- return
820
- for mem in self._do_bulk_requests(urls):
821
- currmeminfo, url = mem
964
+ memdata = self._get_mem_data()
965
+ for currmeminfo in memdata.get('Members', []): # self._do_bulk_requests(urls):
966
+ url = currmeminfo['@odata.id']
822
967
  name = currmeminfo.get('Name', 'Memory')
823
968
  if name in self._hwnamemap:
824
969
  self._hwnamemap[name] = None
@@ -847,13 +992,31 @@ class OEMHandler(object):
847
992
  yield (name, meminfo)
848
993
 
849
994
  def _get_mem_urls(self):
995
+ md = self._get_mem_data(False)
996
+ return [x['@odata.id'] for x in md.get('Members', [])]
997
+
998
+ def _get_mem_data(self, expand='.'):
850
999
  memurl = self._varsysinfo.get('Memory', {}).get('@odata.id', None)
851
1000
  if not memurl:
852
- urls = []
853
- else:
854
- memurl = self._do_web_request(memurl)
855
- urls = [x['@odata.id'] for x in memurl.get('Members', [])]
856
- return urls
1001
+ return {}
1002
+ return self._get_expanded_data(memurl, expand)
1003
+
1004
+ def _get_expanded_data(self, url, expand='.'):
1005
+ topdata = []
1006
+ if not url:
1007
+ return topdata
1008
+ if not expand:
1009
+ return self._do_web_request(url)
1010
+ elif self.supports_expand(url):
1011
+ return self._do_web_request(url + '?$expand=' + expand)
1012
+ else: # emulate expand behavior
1013
+ topdata = self._do_web_request(url)
1014
+ newmembers = []
1015
+ for x in topdata.get('Members', []):
1016
+ newmembers.append(self._do_web_request(x['@odata.id']))
1017
+ topdata['Members'] = newmembers
1018
+ return topdata
1019
+ return topdata
857
1020
 
858
1021
  def get_storage_configuration(self):
859
1022
  raise exc.UnsupportedFunctionality(
@@ -875,8 +1038,9 @@ class OEMHandler(object):
875
1038
  raise exc.UnsupportedFunctionality(
876
1039
  'Remote media upload not supported on this platform')
877
1040
 
878
- def update_firmware(self, filename, data=None, progress=None, bank=None):
879
- usd = self._do_web_request('/redfish/v1/UpdateService')
1041
+ def update_firmware(self, filename, data=None, progress=None, bank=None, otherfields=()):
1042
+ # disable cache to make sure we trigger the token renewal logic if needed
1043
+ usd = self._do_web_request('/redfish/v1/UpdateService', cache=False)
880
1044
  upurl = usd.get('MultipartHttpPushUri', None)
881
1045
  ismultipart = True
882
1046
  if not upurl:
@@ -895,7 +1059,7 @@ class OEMHandler(object):
895
1059
  try:
896
1060
  uploadthread = webclient.FileUploader(
897
1061
  self.webclient, upurl, filename, data, formwrap=ismultipart,
898
- excepterror=False)
1062
+ excepterror=False, otherfields=otherfields)
899
1063
  uploadthread.start()
900
1064
  wc = self.webclient
901
1065
  while uploadthread.isAlive():
@@ -976,6 +1140,26 @@ class OEMHandler(object):
976
1140
  cache=True):
977
1141
  return self._do_web_request(url, payload, method, cache), url
978
1142
 
1143
+ def _get_session_token(self, wc):
1144
+ username = self.username
1145
+ password = self.password
1146
+ if not isinstance(username, str):
1147
+ username = username.decode()
1148
+ if not isinstance(password, str):
1149
+ password = password.decode()
1150
+ # specification actually indicates we can skip straight to this url
1151
+ rsp = wc.grab_rsp('/redfish/v1/SessionService/Sessions',
1152
+ {'UserName': username, 'Password': password})
1153
+ rsp.read()
1154
+ self.xauthtoken = rsp.getheader('X-Auth-Token')
1155
+ if self.xauthtoken:
1156
+ if 'Authorization' in wc.stdheaders:
1157
+ del wc.stdheaders['Authorization']
1158
+ if 'Authorization' in self.webclient.stdheaders:
1159
+ del self.webclient.stdheaders['Authorization']
1160
+ wc.stdheaders['X-Auth-Token'] = self.xauthtoken
1161
+ self.webclient.stdheaders['X-Auth-Token'] = self.xauthtoken
1162
+
979
1163
  def _do_web_request(self, url, payload=None, method=None, cache=True):
980
1164
  res = None
981
1165
  if cache and payload is None and method is None:
@@ -984,6 +1168,11 @@ class OEMHandler(object):
984
1168
  return res
985
1169
  wc = self.webclient.dupe()
986
1170
  res = wc.grab_json_response_with_status(url, payload, method=method)
1171
+ if res[1] == 401 and 'X-Auth-Token' in self.webclient.stdheaders:
1172
+ wc.set_basic_credentials(self.username, self.password)
1173
+ self._get_session_token(wc)
1174
+ res = wc.grab_json_response_with_status(url, payload,
1175
+ method=method)
987
1176
  if res[1] < 200 or res[1] >= 300:
988
1177
  try:
989
1178
  info = json.loads(res[0])
@@ -16,7 +16,7 @@ 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
-
19
+ from pyghmi.redfish.oem.lenovo import smm3
20
20
 
21
21
  def get_handler(sysinfo, sysurl, webclient, cache, cmd):
22
22
  leninf = sysinfo.get('Oem', {}).get('Lenovo', {})
@@ -45,5 +45,12 @@ def get_handler(sysinfo, sysurl, webclient, cache, cmd):
45
45
  if 'hdd' in leninv and 'hostMAC' in leninv and 'backPlane' in leninv:
46
46
  return tsma.TsmHandler(sysinfo, sysurl, webclient, cache,
47
47
  gpool=cmd._gpool)
48
- return generic.OEMHandler(sysinfo, sysurl, webclient, cache,
49
- 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)
@@ -0,0 +1,85 @@
1
+ # Copyright 2025 Lenovo Corporation
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import pyghmi.redfish.oem.generic as generic
16
+ import pyghmi.constants as pygconst
17
+
18
+ healthlookup = {
19
+ 'ok': pygconst.Health.Ok,
20
+ 'critical': pygconst.Health.Critical
21
+ }
22
+
23
+ def _baytonumber(bay):
24
+ try:
25
+ return int(bay)
26
+ except ValueError:
27
+ if len(bay) == 2:
28
+ # Treat a hexadecimal system as a leading decimal digit and letter compile
29
+ # 1a == slot 1, 1b == slot 2, 2a == slot 1, etc..
30
+ try:
31
+ tmp = int(bay, 16)
32
+ return (2 * (tmp >> 4) - 1) + ((tmp & 15) % 10)
33
+ except ValueError:
34
+ return None
35
+ return None
36
+
37
+
38
+ def _baytolabel(bay):
39
+ try:
40
+ baynum = int(bay)
41
+ # need to convert to 1a, 1b, etc...
42
+ vertidx = ((baynum - 1) // 2 + 1) << 4
43
+ horizidx = (baynum - 1) % 2 + 10
44
+ bayid = vertidx | horizidx
45
+ return '{:02x}'.format(bayid)
46
+ except ValueError:
47
+ return bay
48
+ return None
49
+
50
+ class OEMHandler(generic.OEMHandler):
51
+ def get_health(self, fishclient, verbose=True):
52
+ rsp = self._do_web_request('/redfish/v1/Chassis/chassis1')
53
+ health = rsp.get('Status', {}).get('Health', 'Unknown').lower()
54
+ health = healthlookup.get(health, pygconst.Health.Critical)
55
+ return {'health': health}
56
+
57
+ def get_system_configuration(self, hideadvanced=True, fishclient=None):
58
+ return {}
59
+
60
+ def _get_node_info(self):
61
+ nodeinfo = self._varsysinfo
62
+ if not nodeinfo:
63
+ overview = self._do_web_request('/redfish/v1/')
64
+ chassismembs = overview.get('Chassis', {}).get('@odata.id', None)
65
+ if not chassismembs:
66
+ return nodeinfo
67
+ chassislist = self._do_web_request(chassismembs)
68
+ chassismembs = chassislist.get('Members', [])
69
+ if len(chassismembs) == 1:
70
+ chassisurl = chassismembs[0]['@odata.id']
71
+ nodeinfo = self._do_web_request(chassisurl)
72
+ nodeinfo['SKU'] = nodeinfo['Model']
73
+ nodeinfo['Model'] = 'N1380 Enclosure'
74
+ return nodeinfo
75
+
76
+ def reseat_bay(self, bay):
77
+ bayid = _baytolabel(bay)
78
+ url = '/redfish/v1/Chassis/chassis1/Oem/Lenovo/Nodes/{}/Actions/Node.Reseat'.format(bayid)
79
+ rsp = self._do_web_request(url, method='POST')
80
+
81
+ def get_event_log(self, clear=False, fishclient=None):
82
+ return super().get_event_log(clear, fishclient, extraurls=[{'@odata.id':'/redfish/v1/Chassis/chassis1/LogServices/EventLog'}])
83
+
84
+ def get_description(self, fishclient):
85
+ return {'height': 13, 'slot': 0, 'slots': [8, 2]}
@@ -119,6 +119,7 @@ def str_to_size(sizestr):
119
119
 
120
120
 
121
121
  class OEMHandler(generic.OEMHandler):
122
+ usegenericsensors = False
122
123
  logouturl = '/api/providers/logout'
123
124
  bmcname = 'XCC'
124
125
  ADP_URL = '/api/dataset/imm_adapters?params=pci_GetAdapters'