pyghmi 1.5.71__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 +212 -163
- pyghmi/redfish/oem/generic.py +325 -117
- pyghmi/redfish/oem/lenovo/main.py +24 -4
- pyghmi/redfish/oem/lenovo/smm3.py +85 -0
- pyghmi/redfish/oem/lenovo/xcc.py +2 -2
- pyghmi/redfish/oem/lenovo/xcc3.py +395 -0
- pyghmi/util/webclient.py +32 -8
- {pyghmi-1.5.71.dist-info → pyghmi-1.5.75.dist-info}/METADATA +6 -7
- {pyghmi-1.5.71.dist-info → pyghmi-1.5.75.dist-info}/RECORD +19 -17
- {pyghmi-1.5.71.dist-info → pyghmi-1.5.75.dist-info}/WHEEL +1 -1
- {pyghmi-1.5.71.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.71.dist-info/pbr.json +0 -1
- {pyghmi-1.5.71.dist-info → pyghmi-1.5.75.dist-info}/AUTHORS +0 -0
- {pyghmi-1.5.71.dist-info → pyghmi-1.5.75.dist-info}/LICENSE +0 -0
- {pyghmi-1.5.71.dist-info → pyghmi-1.5.75.dist-info}/top_level.txt +0 -0
pyghmi/ipmi/command.py
CHANGED
@@ -551,7 +551,7 @@ class Command(object):
|
|
551
551
|
return {'powerstate': self._get_power_state(
|
552
552
|
bridge_request=bridge_request)}
|
553
553
|
|
554
|
-
def set_identify(self, on=True, duration=None):
|
554
|
+
def set_identify(self, on=True, duration=None, blink=False):
|
555
555
|
"""Request identify light
|
556
556
|
|
557
557
|
Request the identify light to turn off, on for a duration,
|
@@ -563,10 +563,14 @@ class Command(object):
|
|
563
563
|
"""
|
564
564
|
self.oem_init()
|
565
565
|
try:
|
566
|
-
self._oem.set_identify(on, duration)
|
566
|
+
self._oem.set_identify(on, duration, blink)
|
567
|
+
return
|
568
|
+
except exc.BypassGenericBehavior:
|
567
569
|
return
|
568
570
|
except exc.UnsupportedFunctionality:
|
569
571
|
pass
|
572
|
+
if blink:
|
573
|
+
raise exc.IpmiException('Blink not supported with generic IPMI')
|
570
574
|
if duration is not None:
|
571
575
|
duration = int(duration)
|
572
576
|
if duration > 255:
|
pyghmi/ipmi/oem/generic.py
CHANGED
@@ -104,6 +104,8 @@ class OEMHandler(object):
|
|
104
104
|
if sensor.sensor_type != 'Temperature':
|
105
105
|
continue
|
106
106
|
if sensor.entity == 'External environment':
|
107
|
+
if 'exhaust' in sensor.sensor_name.lower():
|
108
|
+
continue
|
107
109
|
extenv.append(sensor.sensor_name)
|
108
110
|
if sensor.entity == 'Air inlet':
|
109
111
|
airinlets.append(sensor.sensor_name)
|
@@ -377,7 +379,7 @@ class OEMHandler(object):
|
|
377
379
|
def list_media(self):
|
378
380
|
raise exc.UnsupportedFunctionality()
|
379
381
|
|
380
|
-
def set_identify(self, on, duration):
|
382
|
+
def set_identify(self, on, duration, blink):
|
381
383
|
"""Provide an OEM override for set_identify
|
382
384
|
|
383
385
|
Some systems may require an override for set identify.
|
@@ -671,9 +671,11 @@ class OEMHandler(generic.OEMHandler):
|
|
671
671
|
led_status_default)
|
672
672
|
yield (name, {'status': status})
|
673
673
|
|
674
|
-
def set_identify(self, on, duration):
|
674
|
+
def set_identify(self, on, duration, blink):
|
675
675
|
if on and not duration and self.is_sd350:
|
676
676
|
self.ipmicmd.xraw_command(netfn=0x3a, command=6, data=(1, 1))
|
677
|
+
elif self.has_xcc:
|
678
|
+
self.immhandler.set_identify(on, duration, blink)
|
677
679
|
else:
|
678
680
|
raise pygexc.UnsupportedFunctionality()
|
679
681
|
|
pyghmi/ipmi/oem/lenovo/imm.py
CHANGED
@@ -968,6 +968,15 @@ class XCCClient(IMMClient):
|
|
968
968
|
fru['manufacturer'] = memi['memory_manufacturer']
|
969
969
|
break
|
970
970
|
|
971
|
+
def set_identify(self, on, duration, blink):
|
972
|
+
if blink:
|
973
|
+
self.grab_redfish_response_with_status(
|
974
|
+
'/redfish/v1/Systems/1',
|
975
|
+
{'IndicatorLED': 'Blinking'},
|
976
|
+
method='PATCH')
|
977
|
+
raise pygexc.BypassGenericBehavior()
|
978
|
+
raise pygexc.UnsupportedFunctionality()
|
979
|
+
|
971
980
|
def get_description(self):
|
972
981
|
dsc = self.wc.grab_json_response('/DeviceDescription.json')
|
973
982
|
dsc = dsc[0]
|
pyghmi/redfish/command.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# coding: utf8
|
2
|
-
# Copyright
|
2
|
+
# Copyright 2025 Lenovo
|
3
3
|
#
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
5
|
# you may not use this file except in compliance with the License.
|
@@ -81,20 +81,6 @@ def _mask_to_cidr(mask):
|
|
81
81
|
return cidr
|
82
82
|
|
83
83
|
|
84
|
-
def _to_boolean(attrval):
|
85
|
-
attrval = attrval.lower()
|
86
|
-
if not attrval:
|
87
|
-
return False
|
88
|
-
if ('true'.startswith(attrval) or 'yes'.startswith(attrval)
|
89
|
-
or 'enabled'.startswith(attrval) or attrval == '1'):
|
90
|
-
return True
|
91
|
-
if ('false'.startswith(attrval) or 'no'.startswith(attrval)
|
92
|
-
or 'disabled'.startswith(attrval) or attrval == '0'):
|
93
|
-
return False
|
94
|
-
raise Exception(
|
95
|
-
'Unrecognized candidate for boolean: {0}'.format(attrval))
|
96
|
-
|
97
|
-
|
98
84
|
def _cidr_to_mask(cidr):
|
99
85
|
return socket.inet_ntop(
|
100
86
|
socket.AF_INET, struct.pack(
|
@@ -127,6 +113,7 @@ def natural_sort(iterable):
|
|
127
113
|
class SensorReading(object):
|
128
114
|
def __init__(self, healthinfo, sensor=None, value=None, units=None,
|
129
115
|
unavailable=False):
|
116
|
+
self.states = []
|
130
117
|
if sensor:
|
131
118
|
self.name = sensor['name']
|
132
119
|
else:
|
@@ -136,7 +123,8 @@ class SensorReading(object):
|
|
136
123
|
self.states = [healthinfo.get('Status', {}).get('Health',
|
137
124
|
'Unknown')]
|
138
125
|
self.health = _healthmap[healthinfo['Status']['Health']]
|
139
|
-
|
126
|
+
if healthinfo['Status']['Health'].lower() == 'ok':
|
127
|
+
self.states = []
|
140
128
|
self.value = value
|
141
129
|
self.state_ids = None
|
142
130
|
self.imprecision = None
|
@@ -175,6 +163,7 @@ class Command(object):
|
|
175
163
|
self._gpool = pool
|
176
164
|
self._bmcv4ip = None
|
177
165
|
self._bmcv6ip = None
|
166
|
+
self.xauthtoken = None
|
178
167
|
for addrinf in socket.getaddrinfo(bmc, 0, 0, socket.SOCK_STREAM):
|
179
168
|
if addrinf[0] == socket.AF_INET:
|
180
169
|
self._bmcv4ip = socket.inet_pton(addrinf[0], addrinf[-1][0])
|
@@ -187,38 +176,76 @@ class Command(object):
|
|
187
176
|
self.wc.set_header('Accept-Encoding', 'gzip')
|
188
177
|
self.wc.set_header('OData-Version', '4.0')
|
189
178
|
overview = self.wc.grab_json_response('/redfish/v1/')
|
190
|
-
self.wc.set_basic_credentials(userid, password)
|
191
179
|
self.username = userid
|
192
180
|
self.password = password
|
181
|
+
self.wc.set_basic_credentials(self.username, self.password)
|
193
182
|
self.wc.set_header('Content-Type', 'application/json')
|
194
|
-
if 'Systems' not in overview:
|
183
|
+
if 'Systems' not in overview and 'Managers' not in overview:
|
195
184
|
raise exc.PyghmiException('Redfish not ready')
|
196
|
-
|
197
|
-
|
198
|
-
if res[1] == 401:
|
199
|
-
raise exc.PyghmiException('Access Denied')
|
200
|
-
elif res[1] < 200 or res[1] >= 300:
|
201
|
-
raise exc.PyghmiException(repr(res[0]))
|
202
|
-
members = res[0]
|
185
|
+
if 'SessionService' in overview:
|
186
|
+
self._get_session_token(self.wc)
|
203
187
|
self._varsensormap = {}
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
188
|
+
self.powerurl = None
|
189
|
+
self.sysurl = None
|
190
|
+
if 'Managers' in overview:
|
191
|
+
bmcoll = systems = overview['Managers']['@odata.id']
|
192
|
+
res = self.wc.grab_json_response_with_status(bmcoll)
|
193
|
+
if res[1] == 401:
|
194
|
+
raise exc.PyghmiException('Access Denied')
|
195
|
+
elif res[1] < 200 or res[1] >= 300:
|
196
|
+
raise exc.PyghmiException(repr(res[0]))
|
197
|
+
bmcs = res[0]['Members']
|
198
|
+
if len(bmcs) == 1:
|
199
|
+
self._varbmcurl = bmcs[0]['@odata.id']
|
200
|
+
if 'Systems' in overview:
|
201
|
+
systems = overview['Systems']['@odata.id']
|
202
|
+
res = self.wc.grab_json_response_with_status(systems)
|
203
|
+
if res[1] == 401:
|
204
|
+
raise exc.PyghmiException('Access Denied')
|
205
|
+
elif res[1] < 200 or res[1] >= 300:
|
206
|
+
raise exc.PyghmiException(repr(res[0]))
|
207
|
+
members = res[0]
|
208
|
+
systems = members['Members']
|
209
|
+
if sysurl:
|
210
|
+
for system in systems:
|
211
|
+
if system['@odata.id'] == sysurl or system['@odata.id'].split('/')[-1] == sysurl:
|
212
|
+
self.sysurl = system['@odata.id']
|
213
|
+
break
|
214
|
+
else:
|
215
|
+
raise exc.PyghmiException(
|
216
|
+
'Specified sysurl not found: {0}'.format(sysurl))
|
210
217
|
else:
|
211
|
-
|
212
|
-
'
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
218
|
+
if len(systems) > 1:
|
219
|
+
systems = [x for x in systems if 'DPU' not in x['@odata.id']]
|
220
|
+
if len(systems) > 1:
|
221
|
+
raise exc.PyghmiException(
|
222
|
+
'Multi system manager, sysurl is required parameter')
|
223
|
+
if len(systems):
|
224
|
+
self.sysurl = systems[0]['@odata.id']
|
225
|
+
else:
|
226
|
+
self.sysurl = None
|
227
|
+
self.powerurl = self.sysinfo.get('Actions', {}).get(
|
228
|
+
'#ComputerSystem.Reset', {}).get('target', None)
|
229
|
+
|
230
|
+
def _get_session_token(self, wc):
|
231
|
+
# specification actually indicates we can skip straight to this url
|
232
|
+
username = self.username
|
233
|
+
password = self.password
|
234
|
+
if not isinstance(username, str):
|
235
|
+
username = username.decode()
|
236
|
+
if not isinstance(password, str):
|
237
|
+
password = password.decode()
|
238
|
+
rsp = wc.grab_rsp('/redfish/v1/SessionService/Sessions',
|
239
|
+
{'UserName': username, 'Password': password})
|
240
|
+
rsp.read()
|
241
|
+
self.xauthtoken = rsp.getheader('X-Auth-Token')
|
242
|
+
if self.xauthtoken:
|
243
|
+
if 'Authorization' in wc.stdheaders:
|
244
|
+
del wc.stdheaders['Authorization']
|
245
|
+
if 'Authorization' in self.wc.stdheaders:
|
246
|
+
del self.wc.stdheaders['Authorization']
|
247
|
+
wc.stdheaders['X-Auth-Token'] = self.xauthtoken
|
248
|
+
self.wc.stdheaders['X-Auth-Token'] = self.xauthtoken
|
222
249
|
|
223
250
|
@property
|
224
251
|
def _accountserviceurl(self):
|
@@ -445,7 +472,13 @@ class Command(object):
|
|
445
472
|
|
446
473
|
@property
|
447
474
|
def sysinfo(self):
|
448
|
-
|
475
|
+
if not self.sysurl:
|
476
|
+
return {}
|
477
|
+
try:
|
478
|
+
return self._do_web_request(self.sysurl)
|
479
|
+
except exc.RedfishError:
|
480
|
+
self.sysurl = None
|
481
|
+
return {}
|
449
482
|
|
450
483
|
@property
|
451
484
|
def bmcinfo(self):
|
@@ -534,6 +567,17 @@ class Command(object):
|
|
534
567
|
finally:
|
535
568
|
if 'If-Match' in wc.stdheaders:
|
536
569
|
del wc.stdheaders['If-Match']
|
570
|
+
if res[1] == 401 and self.xauthtoken:
|
571
|
+
wc.set_basic_credentials(self.username, self.password)
|
572
|
+
self._get_session_token(wc)
|
573
|
+
if etag:
|
574
|
+
wc.stdheaders['If-Match'] = etag
|
575
|
+
try:
|
576
|
+
res = wc.grab_json_response_with_status(url, payload,
|
577
|
+
method=method)
|
578
|
+
finally:
|
579
|
+
if 'If-Match' in wc.stdheaders:
|
580
|
+
del wc.stdheaders['If-Match']
|
537
581
|
if res[1] < 200 or res[1] >= 300:
|
538
582
|
try:
|
539
583
|
info = json.loads(res[0])
|
@@ -547,7 +591,7 @@ class Command(object):
|
|
547
591
|
msgid = ','.join(msgid)
|
548
592
|
raise exc.RedfishError(errmsg, msgid=msgid)
|
549
593
|
except (ValueError, KeyError):
|
550
|
-
raise exc.PyghmiException(str(url) + ":" + res[0])
|
594
|
+
raise exc.PyghmiException(str(url) + ":" + str(res[0]))
|
551
595
|
if payload is None and method is None:
|
552
596
|
self._urlcache[url] = {'contents': res[0],
|
553
597
|
'vintage': os.times()[4]}
|
@@ -633,34 +677,53 @@ class Command(object):
|
|
633
677
|
@property
|
634
678
|
def _sensormap(self):
|
635
679
|
if not self._varsensormap:
|
636
|
-
|
637
|
-
self.
|
680
|
+
if self.sysinfo:
|
681
|
+
for chassis in self.sysinfo.get('Links', {}).get('Chassis', []):
|
682
|
+
self._mapchassissensors(chassis)
|
683
|
+
else: # no system, but check if this is a singular chassis
|
684
|
+
rootinfo = self._do_web_request('/redfish/v1/')
|
685
|
+
chassiscol = rootinfo.get('Chassis', {}).get('@odata.id', '')
|
686
|
+
if chassiscol:
|
687
|
+
chassislist = self._do_web_request(chassiscol)
|
688
|
+
if len(chassislist.get('Members', [])) == 1:
|
689
|
+
self._mapchassissensors(chassislist['Members'][0])
|
638
690
|
return self._varsensormap
|
639
691
|
|
640
692
|
def _mapchassissensors(self, chassis):
|
641
693
|
chassisurl = chassis['@odata.id']
|
642
694
|
chassisinfo = self._do_web_request(chassisurl)
|
643
|
-
|
644
|
-
if
|
645
|
-
|
646
|
-
for
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
695
|
+
sensors = chassisinfo.get('Sensors', {}).get('@odata.id', '')
|
696
|
+
if sensors:
|
697
|
+
sensorinf = self._do_web_request(sensors)
|
698
|
+
for sensor in sensorinf.get('Members', []):
|
699
|
+
sensedata = self._do_web_request(sensor['@odata.id'])
|
700
|
+
if 'Name' in sensedata:
|
701
|
+
sensetype = sensedata.get('ReadingType', 'Unknown')
|
702
|
+
self._varsensormap[sensedata['Name']] = {
|
703
|
+
'name': sensedata['Name'], 'type': sensetype,
|
704
|
+
'url': sensor['@odata.id'], 'generic': True}
|
705
|
+
else:
|
706
|
+
powurl = chassisinfo.get('Power', {}).get('@odata.id', '')
|
707
|
+
if powurl:
|
708
|
+
powinf = self._do_web_request(powurl)
|
709
|
+
for voltage in powinf.get('Voltages', []):
|
710
|
+
if 'Name' in voltage:
|
711
|
+
self._varsensormap[voltage['Name']] = {
|
712
|
+
'name': voltage['Name'], 'url': powurl,
|
713
|
+
'type': 'Voltage'}
|
714
|
+
thermurl = chassisinfo.get('Thermal', {}).get('@odata.id', '')
|
715
|
+
if thermurl:
|
716
|
+
therminf = self._do_web_request(thermurl)
|
717
|
+
for fan in therminf.get('Fans', []):
|
718
|
+
if 'Name' in fan:
|
719
|
+
self._varsensormap[fan['Name']] = {
|
720
|
+
'name': fan['Name'], 'type': 'Fan',
|
721
|
+
'url': thermurl}
|
722
|
+
for temp in therminf.get('Temperatures', []):
|
723
|
+
if 'Name' in temp:
|
724
|
+
self._varsensormap[temp['Name']] = {
|
725
|
+
'name': temp['Name'], 'type': 'Temperature',
|
726
|
+
'url': thermurl}
|
664
727
|
for subchassis in chassisinfo.get('Links', {}).get('Contains', []):
|
665
728
|
self._mapchassissensors(subchassis)
|
666
729
|
|
@@ -771,8 +834,18 @@ class Command(object):
|
|
771
834
|
self._do_web_request(url, {'ResetType': action})
|
772
835
|
|
773
836
|
def set_identify(self, on=True, blink=None):
|
837
|
+
targurl = self.sysurl
|
838
|
+
if not targurl:
|
839
|
+
root = self._do_web_request('/redfish/v1')
|
840
|
+
systemsurl = root.get('Systems', {}).get('@odata.id', None)
|
841
|
+
if systemsurl:
|
842
|
+
targurl = self._do_web_request(systemsurl)
|
843
|
+
if len(targurl.get('Members', [])) == 1:
|
844
|
+
targurl = targurl['Members'][0]['@odata.id']
|
845
|
+
if not targurl:
|
846
|
+
raise Exception("Unable to identify system url")
|
774
847
|
self._do_web_request(
|
775
|
-
|
848
|
+
targurl,
|
776
849
|
{'IndicatorLED': 'Blinking' if blink else 'Lit' if on else 'Off'},
|
777
850
|
method='PATCH', etag='*')
|
778
851
|
|
@@ -819,6 +892,54 @@ class Command(object):
|
|
819
892
|
def set_system_configuration(self, changeset):
|
820
893
|
return self.oem.set_system_configuration(changeset, self)
|
821
894
|
|
895
|
+
def get_ntp_enabled(self):
|
896
|
+
bmcinfo = self._do_web_request(self._bmcurl)
|
897
|
+
netprotocols = bmcinfo.get('NetworkProtocol', {}).get('@odata.id', None)
|
898
|
+
if netprotocols:
|
899
|
+
netprotoinfo = self._do_web_request(netprotocols)
|
900
|
+
enabled = netprotoinfo.get('NTP', {}).get('ProtocolEnabled', False)
|
901
|
+
return enabled
|
902
|
+
return False
|
903
|
+
|
904
|
+
def set_ntp_enabled(self, enable):
|
905
|
+
bmcinfo = self._do_web_request(self._bmcurl)
|
906
|
+
netprotocols = bmcinfo.get('NetworkProtocol', {}).get('@odata.id', None)
|
907
|
+
if netprotocols:
|
908
|
+
request = {'NTP':{'ProtocolEnabled': enable}}
|
909
|
+
self._do_web_request(netprotocols, request, method='PATCH')
|
910
|
+
self._do_web_request(netprotocols, cache=0)
|
911
|
+
|
912
|
+
def get_ntp_servers(self):
|
913
|
+
bmcinfo = self._do_web_request(self._bmcurl)
|
914
|
+
netprotocols = bmcinfo.get('NetworkProtocol', {}).get('@odata.id', None)
|
915
|
+
if not netprotocols:
|
916
|
+
return []
|
917
|
+
netprotoinfo = self._do_web_request(netprotocols)
|
918
|
+
return netprotoinfo.get('NTP', {}).get('NTPServers', [])
|
919
|
+
|
920
|
+
def set_ntp_server(self, server, index=None):
|
921
|
+
bmcinfo = self._do_web_request(self._bmcurl)
|
922
|
+
netprotocols = bmcinfo.get('NetworkProtocol', {}).get('@odata.id', None)
|
923
|
+
currntpservers = self.get_ntp_servers()
|
924
|
+
if index is None:
|
925
|
+
if server in currntpservers:
|
926
|
+
return
|
927
|
+
currntpservers = [server] + currntpservers
|
928
|
+
else:
|
929
|
+
if (index + 1) > len(currntpservers):
|
930
|
+
if not server:
|
931
|
+
return
|
932
|
+
currntpservers.append(server)
|
933
|
+
else:
|
934
|
+
if not server:
|
935
|
+
del currntpservers[index]
|
936
|
+
else:
|
937
|
+
currntpservers[index] = server
|
938
|
+
request = {'NTP':{'NTPServers': currntpservers}}
|
939
|
+
self._do_web_request(netprotocols, request, method='PATCH')
|
940
|
+
self._do_web_request(netprotocols, cache=0)
|
941
|
+
|
942
|
+
|
822
943
|
def clear_bmc_configuration(self):
|
823
944
|
"""Reset BMC to factory default
|
824
945
|
|
@@ -830,7 +951,7 @@ class Command(object):
|
|
830
951
|
rc = bmcinfo.get('Actions', {}).get('#Manager.ResetToDefaults', {})
|
831
952
|
actinf = rc.get('ResetType@Redfish.AllowableValues', [])
|
832
953
|
if 'ResetAll' in actinf:
|
833
|
-
acturl =
|
954
|
+
acturl = rc.get('target', None)
|
834
955
|
if acturl:
|
835
956
|
self._do_web_request(acturl, {'ResetType': 'ResetAll'})
|
836
957
|
return
|
@@ -976,14 +1097,14 @@ class Command(object):
|
|
976
1097
|
{'HostName': hostname}, 'PATCH')
|
977
1098
|
|
978
1099
|
def get_firmware(self, components=()):
|
1100
|
+
self._fwnamemap = {}
|
979
1101
|
try:
|
980
|
-
for firminfo in self.oem.get_firmware_inventory(components):
|
1102
|
+
for firminfo in self.oem.get_firmware_inventory(components, self):
|
981
1103
|
yield firminfo
|
982
1104
|
except exc.BypassGenericBehavior:
|
983
1105
|
return
|
984
1106
|
fwlist = self._do_web_request(self._fwinventory)
|
985
1107
|
fwurls = [x['@odata.id'] for x in fwlist.get('Members', [])]
|
986
|
-
self._fwnamemap = {}
|
987
1108
|
for res in self._do_bulk_requests(fwurls):
|
988
1109
|
res = self._extract_fwinfo(res)
|
989
1110
|
if res[0] is None:
|
@@ -1086,79 +1207,20 @@ class Command(object):
|
|
1086
1207
|
@property
|
1087
1208
|
def oem(self):
|
1088
1209
|
if not self._oem:
|
1210
|
+
if self.sysurl:
|
1211
|
+
self._do_web_request(self.sysurl, cache=False) # This is to trigger token validation and renewel
|
1212
|
+
elif self._varbmcurl:
|
1213
|
+
self._do_web_request(self._varbmcurl, cache=False) # This is to trigger token validation and renewel
|
1089
1214
|
self._oem = oem.get_oem_handler(
|
1090
1215
|
self.sysinfo, self.sysurl, self.wc, self._urlcache, self)
|
1091
1216
|
self._oem.set_credentials(self.username, self.password)
|
1092
1217
|
return self._oem
|
1093
1218
|
|
1094
1219
|
def get_description(self):
|
1095
|
-
return self.oem.get_description()
|
1220
|
+
return self.oem.get_description(self)
|
1096
1221
|
|
1097
1222
|
def get_event_log(self, clear=False):
|
1098
|
-
|
1099
|
-
lsurl = bmcinfo.get('LogServices', {}).get('@odata.id', None)
|
1100
|
-
if not lsurl:
|
1101
|
-
return
|
1102
|
-
currtime = bmcinfo.get('DateTime', None)
|
1103
|
-
correction = timedelta(0)
|
1104
|
-
utz = tz.tzoffset('', 0)
|
1105
|
-
ltz = tz.gettz()
|
1106
|
-
if currtime:
|
1107
|
-
currtime = parse_time(currtime)
|
1108
|
-
if currtime:
|
1109
|
-
now = datetime.now(utz)
|
1110
|
-
try:
|
1111
|
-
correction = now - currtime
|
1112
|
-
except TypeError:
|
1113
|
-
correction = now - currtime.replace(tzinfo=utz)
|
1114
|
-
lurls = self._do_web_request(lsurl).get('Members', [])
|
1115
|
-
for lurl in lurls:
|
1116
|
-
lurl = lurl['@odata.id']
|
1117
|
-
loginfo = self._do_web_request(lurl, cache=(not clear))
|
1118
|
-
entriesurl = loginfo.get('Entries', {}).get('@odata.id', None)
|
1119
|
-
if not entriesurl:
|
1120
|
-
continue
|
1121
|
-
logid = loginfo.get('Id', '')
|
1122
|
-
entries = self._do_web_request(entriesurl, cache=False)
|
1123
|
-
if clear:
|
1124
|
-
# The clear is against the log service etag, not entries
|
1125
|
-
# so we have to fetch service etag after we fetch entries
|
1126
|
-
# until we can verify that the etag is consistent to prove
|
1127
|
-
# that the clear is atomic
|
1128
|
-
newloginfo = self._do_web_request(lurl, cache=False)
|
1129
|
-
clearurl = newloginfo.get('Actions', {}).get(
|
1130
|
-
'#LogService.ClearLog', {}).get('target', '')
|
1131
|
-
while clearurl:
|
1132
|
-
try:
|
1133
|
-
self._do_web_request(clearurl, method='POST',
|
1134
|
-
payload={})
|
1135
|
-
clearurl = False
|
1136
|
-
except exc.PyghmiException as e:
|
1137
|
-
if 'EtagPreconditionalFailed' not in str(e):
|
1138
|
-
raise
|
1139
|
-
# This doesn't guarantee atomicity, but it mitigates
|
1140
|
-
# greatly. Unfortunately some implementations
|
1141
|
-
# mutate the tag endlessly and we have no hope
|
1142
|
-
entries = self._do_web_request(entriesurl, cache=False)
|
1143
|
-
newloginfo = self._do_web_request(lurl, cache=False)
|
1144
|
-
for log in entries.get('Members', []):
|
1145
|
-
if ('Created' not in log and 'Message' not in log
|
1146
|
-
and 'Severity' not in log):
|
1147
|
-
# without any data, this log entry isn't actionable
|
1148
|
-
continue
|
1149
|
-
record = {}
|
1150
|
-
record['log_id'] = logid
|
1151
|
-
parsedtime = parse_time(log.get('Created', ''))
|
1152
|
-
if parsedtime:
|
1153
|
-
entime = parsedtime + correction
|
1154
|
-
entime = entime.astimezone(ltz)
|
1155
|
-
record['timestamp'] = entime.strftime('%Y-%m-%dT%H:%M:%S')
|
1156
|
-
else:
|
1157
|
-
record['timestamp'] = log.get('Created', '')
|
1158
|
-
record['message'] = log.get('Message', None)
|
1159
|
-
record['severity'] = _healthmap.get(
|
1160
|
-
log.get('Severity', 'Warning'), const.Health.Ok)
|
1161
|
-
yield record
|
1223
|
+
return self.oem.get_event_log(clear, self)
|
1162
1224
|
|
1163
1225
|
def _get_chassis_env(self, chassis):
|
1164
1226
|
chassisurl = chassis['@odata.id']
|
@@ -1174,35 +1236,11 @@ class Command(object):
|
|
1174
1236
|
return retval
|
1175
1237
|
|
1176
1238
|
def get_average_processor_temperature(self):
|
1177
|
-
|
1178
|
-
|
1179
|
-
thermals = self._get_thermals(chassis)
|
1180
|
-
for temp in thermals:
|
1181
|
-
if temp.get('PhysicalContext', '') != 'CPU':
|
1182
|
-
continue
|
1183
|
-
if temp.get('ReadingCelsius', None) is None:
|
1184
|
-
continue
|
1185
|
-
cputemps.append(temp['ReadingCelsius'])
|
1186
|
-
if not cputemps:
|
1187
|
-
return SensorReading(
|
1188
|
-
None, {'name': 'Average Processor Temperature'}, value=None, units='°C',
|
1189
|
-
unavailable=True)
|
1190
|
-
avgtemp = sum(cputemps) / len(cputemps)
|
1191
|
-
return SensorReading(
|
1192
|
-
None, {'name': 'Average Processor Temperature'}, value=avgtemp, units='°C')
|
1239
|
+
return self.oem.get_average_processor_temperature(self)
|
1240
|
+
|
1193
1241
|
|
1194
1242
|
def get_system_power_watts(self):
|
1195
|
-
|
1196
|
-
gotpower = False
|
1197
|
-
for chassis in self.sysinfo.get('Links', {}).get('Chassis', []):
|
1198
|
-
envinfo = self._get_chassis_env(chassis)
|
1199
|
-
currwatts = envinfo.get('watts', None)
|
1200
|
-
if currwatts is not None:
|
1201
|
-
gotpower = True
|
1202
|
-
totalwatts += envinfo['watts']
|
1203
|
-
if not gotpower:
|
1204
|
-
raise exc.UnsupportedFunctionality("System does not provide Power under redfish EnvironmentMetrics")
|
1205
|
-
return totalwatts
|
1243
|
+
return self.oem.get_system_power_watts(self)
|
1206
1244
|
|
1207
1245
|
def get_inlet_temperature(self):
|
1208
1246
|
inlets = []
|
@@ -1236,6 +1274,16 @@ class Command(object):
|
|
1236
1274
|
yield self.get_sensor_reading(sensor)
|
1237
1275
|
|
1238
1276
|
def _extract_reading(self, sensor, reading):
|
1277
|
+
if sensor.get('generic', False): # generic sensor
|
1278
|
+
val = reading.get('Reading', None)
|
1279
|
+
unavail = val is None
|
1280
|
+
units = reading.get('ReadingUnits', None)
|
1281
|
+
if units == 'Cel':
|
1282
|
+
units = '°C'
|
1283
|
+
if units == 'cft_i/min':
|
1284
|
+
units = 'CFM'
|
1285
|
+
return SensorReading(reading, None, value=val, units=units,
|
1286
|
+
unavailable=unavail)
|
1239
1287
|
if sensor['type'] == 'Fan':
|
1240
1288
|
for fan in reading['Fans']:
|
1241
1289
|
if fan['Name'] == sensor['name']:
|
@@ -1444,3 +1492,4 @@ if __name__ == '__main__':
|
|
1444
1492
|
print(repr(
|
1445
1493
|
Command(sys.argv[1], os.environ['BMCUSER'], os.environ['BMCPASS'],
|
1446
1494
|
verifycallback=lambda x: True).get_power()))
|
1495
|
+
|