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.
- 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 +209 -146
- pyghmi/redfish/oem/generic.py +256 -80
- pyghmi/redfish/oem/lenovo/main.py +10 -3
- pyghmi/redfish/oem/lenovo/smm3.py +85 -0
- pyghmi/redfish/oem/lenovo/xcc3.py +272 -14
- pyghmi/util/webclient.py +32 -8
- {pyghmi-1.5.72.dist-info → pyghmi-1.5.75.dist-info}/METADATA +6 -7
- {pyghmi-1.5.72.dist-info → pyghmi-1.5.75.dist-info}/RECORD +18 -17
- {pyghmi-1.5.72.dist-info → pyghmi-1.5.75.dist-info}/WHEEL +1 -1
- {pyghmi-1.5.72.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.72.dist-info/pbr.json +0 -1
- {pyghmi-1.5.72.dist-info → pyghmi-1.5.75.dist-info}/AUTHORS +0 -0
- {pyghmi-1.5.72.dist-info → pyghmi-1.5.75.dist-info}/LICENSE +0 -0
- {pyghmi-1.5.72.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):
|
@@ -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
|
-
|
230
|
-
|
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':
|
568
|
-
'Serial Number':
|
569
|
-
'Manufacturer':
|
570
|
-
'Product name':
|
571
|
-
'Model':
|
572
|
-
'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', '')),
|
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':
|
589
|
-
'Serial Number':
|
590
|
-
'Manufacturer':
|
591
|
-
'Product name':
|
592
|
-
'Model':
|
593
|
-
'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', '')),
|
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 +
|
748
|
+
allurls = adpurls + diskurls
|
622
749
|
list(self._do_bulk_requests(allurls))
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
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
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
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
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
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
|
-
|
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
|
-
|
778
|
-
|
779
|
-
|
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
|
809
|
-
|
810
|
-
|
811
|
-
|
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
|
-
|
817
|
-
|
818
|
-
|
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
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
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
|
-
|
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
|
-
|
49
|
-
|
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]}
|