pyghmi 1.5.72__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):
@@ -187,6 +192,122 @@ class OEMHandler(object):
187
192
  self._urlcache = cache
188
193
  self.webclient = webclient
189
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
+
190
311
  def get_health(self, fishclient, verbose=True):
191
312
  health = fishclient.sysinfo.get('Status', {})
192
313
  health = health.get('HealthRollup', health.get('Health', 'Unknown'))
@@ -226,9 +347,8 @@ class OEMHandler(object):
226
347
  memsumstatus.get('Health', None))
227
348
  if memsumstatus != 'OK':
228
349
  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'])
350
+ dimmdata = self._get_mem_data()
351
+ for dimminfo in dimmdata['Members']:
232
352
  if dimminfo.get('Status', {}).get(
233
353
  'State', None) == 'Absent':
234
354
  continue
@@ -520,8 +640,15 @@ class OEMHandler(object):
520
640
  return None
521
641
 
522
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}
523
649
  return {}
524
650
 
651
+
525
652
  def get_firmware_inventory(self, components, fishclient):
526
653
  return []
527
654
 
@@ -561,15 +688,30 @@ class OEMHandler(object):
561
688
  for adp in self._get_adp_inventory(True, withids):
562
689
  yield adp
563
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
+
564
705
  def get_inventory_of_component(self, component):
565
706
  if component.lower() == 'system':
707
+ nodeinfo = self._get_node_info()
566
708
  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', '')),
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', '')),
573
715
  }
574
716
  if sysinfo['UUID'] and '-' not in sysinfo['UUID']:
575
717
  sysinfo['UUID'] = '-'.join((
@@ -584,13 +726,14 @@ class OEMHandler(object):
584
726
  return invpair[1]
585
727
 
586
728
  def get_inventory(self, withids=False):
729
+ nodeinfo = self._get_node_info()
587
730
  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', '')),
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', '')),
594
737
  }
595
738
  if sysinfo['UUID'] and '-' not in sysinfo['UUID']:
596
739
  sysinfo['UUID'] = '-'.join((
@@ -600,32 +743,14 @@ class OEMHandler(object):
600
743
  sysinfo['UUID'] = sysinfo['UUID'].lower()
601
744
  yield ('System', sysinfo)
602
745
  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
746
  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
747
  diskurls = self._get_disk_urls()
621
- allurls = adpurls + cpurls + memurls + diskurls
748
+ allurls = adpurls + diskurls
622
749
  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
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
629
754
  for adp in self._get_adp_inventory(withids=withids, urls=adpurls):
630
755
  yield adp
631
756
  for disk in self._get_disk_inventory(withids=withids, urls=diskurls):
@@ -652,13 +777,11 @@ class OEMHandler(object):
652
777
  foundmacs = False
653
778
  macinfobyadpname = {}
654
779
  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")
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")
662
785
  if nadurl:
663
786
  nadinfo = self._do_web_request(nadurl)
664
787
  if 'Name' not in nadinfo:
@@ -666,20 +789,31 @@ class OEMHandler(object):
666
789
  nicname = nadinfo['Name']
667
790
  yieldinf = {}
668
791
  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', [])]
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', [])]
677
796
  for mac in macs:
678
797
  label = 'MAC Address {}'.format(macidx)
679
798
  yieldinf[label] = _normalize_mac(mac)
680
799
  macidx += 1
681
800
  foundmacs = True
682
- 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
683
817
  if not urls:
684
818
  urls = self._get_adp_urls()
685
819
  for inf in self._do_bulk_requests(urls):
@@ -730,6 +864,8 @@ class OEMHandler(object):
730
864
  if aname in macinfobyadpname:
731
865
  del macinfobyadpname[aname]
732
866
  yield aname, yieldinf
867
+ if onlyname:
868
+ return
733
869
  if macinfobyadpname:
734
870
  for adp in macinfobyadpname:
735
871
  yield adp, macinfobyadpname[adp]
@@ -774,12 +910,9 @@ class OEMHandler(object):
774
910
  return urls
775
911
 
776
912
  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
913
+ for currcpuinfo in self._get_cpu_data().get(
914
+ 'Members', []):
915
+ url = currcpuinfo['@odata.id']
783
916
  name = currcpuinfo.get('Name', 'CPU')
784
917
  if name in self._hwnamemap:
785
918
  self._hwnamemap[name] = None
@@ -804,21 +937,20 @@ class OEMHandler(object):
804
937
  return urls
805
938
 
806
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='.'):
807
944
  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
945
+ if not cpurl:
946
+ return {}
947
+ return self._get_expanded_data(cpurl, expand)
948
+
814
949
 
815
950
  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
951
+ memdata = self._get_mem_data()
952
+ for currmeminfo in memdata.get('Members', []): # self._do_bulk_requests(urls):
953
+ url = currmeminfo['@odata.id']
822
954
  name = currmeminfo.get('Name', 'Memory')
823
955
  if name in self._hwnamemap:
824
956
  self._hwnamemap[name] = None
@@ -847,13 +979,31 @@ class OEMHandler(object):
847
979
  yield (name, meminfo)
848
980
 
849
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='.'):
850
986
  memurl = self._varsysinfo.get('Memory', {}).get('@odata.id', None)
851
987
  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
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
857
1007
 
858
1008
  def get_storage_configuration(self):
859
1009
  raise exc.UnsupportedFunctionality(
@@ -875,8 +1025,9 @@ class OEMHandler(object):
875
1025
  raise exc.UnsupportedFunctionality(
876
1026
  'Remote media upload not supported on this platform')
877
1027
 
878
- def update_firmware(self, filename, data=None, progress=None, bank=None):
879
- 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)
880
1031
  upurl = usd.get('MultipartHttpPushUri', None)
881
1032
  ismultipart = True
882
1033
  if not upurl:
@@ -895,7 +1046,7 @@ class OEMHandler(object):
895
1046
  try:
896
1047
  uploadthread = webclient.FileUploader(
897
1048
  self.webclient, upurl, filename, data, formwrap=ismultipart,
898
- excepterror=False)
1049
+ excepterror=False, otherfields=otherfields)
899
1050
  uploadthread.start()
900
1051
  wc = self.webclient
901
1052
  while uploadthread.isAlive():
@@ -976,6 +1127,26 @@ class OEMHandler(object):
976
1127
  cache=True):
977
1128
  return self._do_web_request(url, payload, method, cache), url
978
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
+
979
1150
  def _do_web_request(self, url, payload=None, method=None, cache=True):
980
1151
  res = None
981
1152
  if cache and payload is None and method is None:
@@ -984,6 +1155,11 @@ class OEMHandler(object):
984
1155
  return res
985
1156
  wc = self.webclient.dupe()
986
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)
987
1163
  if res[1] < 200 or res[1] >= 300:
988
1164
  try:
989
1165
  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]}