pyghmi 1.5.72__py3-none-any.whl → 1.5.75__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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]}
|