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/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.
|
@@ -113,6 +113,7 @@ def natural_sort(iterable):
|
|
113
113
|
class SensorReading(object):
|
114
114
|
def __init__(self, healthinfo, sensor=None, value=None, units=None,
|
115
115
|
unavailable=False):
|
116
|
+
self.states = []
|
116
117
|
if sensor:
|
117
118
|
self.name = sensor['name']
|
118
119
|
else:
|
@@ -122,7 +123,8 @@ class SensorReading(object):
|
|
122
123
|
self.states = [healthinfo.get('Status', {}).get('Health',
|
123
124
|
'Unknown')]
|
124
125
|
self.health = _healthmap[healthinfo['Status']['Health']]
|
125
|
-
|
126
|
+
if healthinfo['Status']['Health'].lower() == 'ok':
|
127
|
+
self.states = []
|
126
128
|
self.value = value
|
127
129
|
self.state_ids = None
|
128
130
|
self.imprecision = None
|
@@ -161,6 +163,7 @@ class Command(object):
|
|
161
163
|
self._gpool = pool
|
162
164
|
self._bmcv4ip = None
|
163
165
|
self._bmcv6ip = None
|
166
|
+
self.xauthtoken = None
|
164
167
|
for addrinf in socket.getaddrinfo(bmc, 0, 0, socket.SOCK_STREAM):
|
165
168
|
if addrinf[0] == socket.AF_INET:
|
166
169
|
self._bmcv4ip = socket.inet_pton(addrinf[0], addrinf[-1][0])
|
@@ -173,38 +176,76 @@ class Command(object):
|
|
173
176
|
self.wc.set_header('Accept-Encoding', 'gzip')
|
174
177
|
self.wc.set_header('OData-Version', '4.0')
|
175
178
|
overview = self.wc.grab_json_response('/redfish/v1/')
|
176
|
-
self.wc.set_basic_credentials(userid, password)
|
177
179
|
self.username = userid
|
178
180
|
self.password = password
|
181
|
+
self.wc.set_basic_credentials(self.username, self.password)
|
179
182
|
self.wc.set_header('Content-Type', 'application/json')
|
180
|
-
if 'Systems' not in overview:
|
183
|
+
if 'Systems' not in overview and 'Managers' not in overview:
|
181
184
|
raise exc.PyghmiException('Redfish not ready')
|
182
|
-
|
183
|
-
|
184
|
-
if res[1] == 401:
|
185
|
-
raise exc.PyghmiException('Access Denied')
|
186
|
-
elif res[1] < 200 or res[1] >= 300:
|
187
|
-
raise exc.PyghmiException(repr(res[0]))
|
188
|
-
members = res[0]
|
185
|
+
if 'SessionService' in overview:
|
186
|
+
self._get_session_token(self.wc)
|
189
187
|
self._varsensormap = {}
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
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))
|
196
217
|
else:
|
197
|
-
|
198
|
-
'
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
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
|
208
249
|
|
209
250
|
@property
|
210
251
|
def _accountserviceurl(self):
|
@@ -431,7 +472,13 @@ class Command(object):
|
|
431
472
|
|
432
473
|
@property
|
433
474
|
def sysinfo(self):
|
434
|
-
|
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 {}
|
435
482
|
|
436
483
|
@property
|
437
484
|
def bmcinfo(self):
|
@@ -520,6 +567,17 @@ class Command(object):
|
|
520
567
|
finally:
|
521
568
|
if 'If-Match' in wc.stdheaders:
|
522
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']
|
523
581
|
if res[1] < 200 or res[1] >= 300:
|
524
582
|
try:
|
525
583
|
info = json.loads(res[0])
|
@@ -619,34 +677,53 @@ class Command(object):
|
|
619
677
|
@property
|
620
678
|
def _sensormap(self):
|
621
679
|
if not self._varsensormap:
|
622
|
-
|
623
|
-
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])
|
624
690
|
return self._varsensormap
|
625
691
|
|
626
692
|
def _mapchassissensors(self, chassis):
|
627
693
|
chassisurl = chassis['@odata.id']
|
628
694
|
chassisinfo = self._do_web_request(chassisurl)
|
629
|
-
|
630
|
-
if
|
631
|
-
|
632
|
-
for
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
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}
|
650
727
|
for subchassis in chassisinfo.get('Links', {}).get('Contains', []):
|
651
728
|
self._mapchassissensors(subchassis)
|
652
729
|
|
@@ -757,8 +834,18 @@ class Command(object):
|
|
757
834
|
self._do_web_request(url, {'ResetType': action})
|
758
835
|
|
759
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")
|
760
847
|
self._do_web_request(
|
761
|
-
|
848
|
+
targurl,
|
762
849
|
{'IndicatorLED': 'Blinking' if blink else 'Lit' if on else 'Off'},
|
763
850
|
method='PATCH', etag='*')
|
764
851
|
|
@@ -805,6 +892,54 @@ class Command(object):
|
|
805
892
|
def set_system_configuration(self, changeset):
|
806
893
|
return self.oem.set_system_configuration(changeset, self)
|
807
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
|
+
|
808
943
|
def clear_bmc_configuration(self):
|
809
944
|
"""Reset BMC to factory default
|
810
945
|
|
@@ -816,7 +951,7 @@ class Command(object):
|
|
816
951
|
rc = bmcinfo.get('Actions', {}).get('#Manager.ResetToDefaults', {})
|
817
952
|
actinf = rc.get('ResetType@Redfish.AllowableValues', [])
|
818
953
|
if 'ResetAll' in actinf:
|
819
|
-
acturl =
|
954
|
+
acturl = rc.get('target', None)
|
820
955
|
if acturl:
|
821
956
|
self._do_web_request(acturl, {'ResetType': 'ResetAll'})
|
822
957
|
return
|
@@ -962,6 +1097,7 @@ class Command(object):
|
|
962
1097
|
{'HostName': hostname}, 'PATCH')
|
963
1098
|
|
964
1099
|
def get_firmware(self, components=()):
|
1100
|
+
self._fwnamemap = {}
|
965
1101
|
try:
|
966
1102
|
for firminfo in self.oem.get_firmware_inventory(components, self):
|
967
1103
|
yield firminfo
|
@@ -969,7 +1105,6 @@ class Command(object):
|
|
969
1105
|
return
|
970
1106
|
fwlist = self._do_web_request(self._fwinventory)
|
971
1107
|
fwurls = [x['@odata.id'] for x in fwlist.get('Members', [])]
|
972
|
-
self._fwnamemap = {}
|
973
1108
|
for res in self._do_bulk_requests(fwurls):
|
974
1109
|
res = self._extract_fwinfo(res)
|
975
1110
|
if res[0] is None:
|
@@ -1072,6 +1207,10 @@ class Command(object):
|
|
1072
1207
|
@property
|
1073
1208
|
def oem(self):
|
1074
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
|
1075
1214
|
self._oem = oem.get_oem_handler(
|
1076
1215
|
self.sysinfo, self.sysurl, self.wc, self._urlcache, self)
|
1077
1216
|
self._oem.set_credentials(self.username, self.password)
|
@@ -1081,70 +1220,7 @@ class Command(object):
|
|
1081
1220
|
return self.oem.get_description(self)
|
1082
1221
|
|
1083
1222
|
def get_event_log(self, clear=False):
|
1084
|
-
|
1085
|
-
lsurl = bmcinfo.get('LogServices', {}).get('@odata.id', None)
|
1086
|
-
if not lsurl:
|
1087
|
-
return
|
1088
|
-
currtime = bmcinfo.get('DateTime', None)
|
1089
|
-
correction = timedelta(0)
|
1090
|
-
utz = tz.tzoffset('', 0)
|
1091
|
-
ltz = tz.gettz()
|
1092
|
-
if currtime:
|
1093
|
-
currtime = parse_time(currtime)
|
1094
|
-
if currtime:
|
1095
|
-
now = datetime.now(utz)
|
1096
|
-
try:
|
1097
|
-
correction = now - currtime
|
1098
|
-
except TypeError:
|
1099
|
-
correction = now - currtime.replace(tzinfo=utz)
|
1100
|
-
lurls = self._do_web_request(lsurl).get('Members', [])
|
1101
|
-
for lurl in lurls:
|
1102
|
-
lurl = lurl['@odata.id']
|
1103
|
-
loginfo = self._do_web_request(lurl, cache=(not clear))
|
1104
|
-
entriesurl = loginfo.get('Entries', {}).get('@odata.id', None)
|
1105
|
-
if not entriesurl:
|
1106
|
-
continue
|
1107
|
-
logid = loginfo.get('Id', '')
|
1108
|
-
entries = self._do_web_request(entriesurl, cache=False)
|
1109
|
-
if clear:
|
1110
|
-
# The clear is against the log service etag, not entries
|
1111
|
-
# so we have to fetch service etag after we fetch entries
|
1112
|
-
# until we can verify that the etag is consistent to prove
|
1113
|
-
# that the clear is atomic
|
1114
|
-
newloginfo = self._do_web_request(lurl, cache=False)
|
1115
|
-
clearurl = newloginfo.get('Actions', {}).get(
|
1116
|
-
'#LogService.ClearLog', {}).get('target', '')
|
1117
|
-
while clearurl:
|
1118
|
-
try:
|
1119
|
-
self._do_web_request(clearurl, method='POST',
|
1120
|
-
payload={})
|
1121
|
-
clearurl = False
|
1122
|
-
except exc.PyghmiException as e:
|
1123
|
-
if 'EtagPreconditionalFailed' not in str(e):
|
1124
|
-
raise
|
1125
|
-
# This doesn't guarantee atomicity, but it mitigates
|
1126
|
-
# greatly. Unfortunately some implementations
|
1127
|
-
# mutate the tag endlessly and we have no hope
|
1128
|
-
entries = self._do_web_request(entriesurl, cache=False)
|
1129
|
-
newloginfo = self._do_web_request(lurl, cache=False)
|
1130
|
-
for log in entries.get('Members', []):
|
1131
|
-
if ('Created' not in log and 'Message' not in log
|
1132
|
-
and 'Severity' not in log):
|
1133
|
-
# without any data, this log entry isn't actionable
|
1134
|
-
continue
|
1135
|
-
record = {}
|
1136
|
-
record['log_id'] = logid
|
1137
|
-
parsedtime = parse_time(log.get('Created', ''))
|
1138
|
-
if parsedtime:
|
1139
|
-
entime = parsedtime + correction
|
1140
|
-
entime = entime.astimezone(ltz)
|
1141
|
-
record['timestamp'] = entime.strftime('%Y-%m-%dT%H:%M:%S')
|
1142
|
-
else:
|
1143
|
-
record['timestamp'] = log.get('Created', '')
|
1144
|
-
record['message'] = log.get('Message', None)
|
1145
|
-
record['severity'] = _healthmap.get(
|
1146
|
-
log.get('Severity', 'Warning'), const.Health.Ok)
|
1147
|
-
yield record
|
1223
|
+
return self.oem.get_event_log(clear, self)
|
1148
1224
|
|
1149
1225
|
def _get_chassis_env(self, chassis):
|
1150
1226
|
chassisurl = chassis['@odata.id']
|
@@ -1160,35 +1236,11 @@ class Command(object):
|
|
1160
1236
|
return retval
|
1161
1237
|
|
1162
1238
|
def get_average_processor_temperature(self):
|
1163
|
-
|
1164
|
-
|
1165
|
-
thermals = self._get_thermals(chassis)
|
1166
|
-
for temp in thermals:
|
1167
|
-
if temp.get('PhysicalContext', '') != 'CPU':
|
1168
|
-
continue
|
1169
|
-
if temp.get('ReadingCelsius', None) is None:
|
1170
|
-
continue
|
1171
|
-
cputemps.append(temp['ReadingCelsius'])
|
1172
|
-
if not cputemps:
|
1173
|
-
return SensorReading(
|
1174
|
-
None, {'name': 'Average Processor Temperature'}, value=None, units='°C',
|
1175
|
-
unavailable=True)
|
1176
|
-
avgtemp = sum(cputemps) / len(cputemps)
|
1177
|
-
return SensorReading(
|
1178
|
-
None, {'name': 'Average Processor Temperature'}, value=avgtemp, units='°C')
|
1239
|
+
return self.oem.get_average_processor_temperature(self)
|
1240
|
+
|
1179
1241
|
|
1180
1242
|
def get_system_power_watts(self):
|
1181
|
-
|
1182
|
-
gotpower = False
|
1183
|
-
for chassis in self.sysinfo.get('Links', {}).get('Chassis', []):
|
1184
|
-
envinfo = self._get_chassis_env(chassis)
|
1185
|
-
currwatts = envinfo.get('watts', None)
|
1186
|
-
if currwatts is not None:
|
1187
|
-
gotpower = True
|
1188
|
-
totalwatts += envinfo['watts']
|
1189
|
-
if not gotpower:
|
1190
|
-
raise exc.UnsupportedFunctionality("System does not provide Power under redfish EnvironmentMetrics")
|
1191
|
-
return totalwatts
|
1243
|
+
return self.oem.get_system_power_watts(self)
|
1192
1244
|
|
1193
1245
|
def get_inlet_temperature(self):
|
1194
1246
|
inlets = []
|
@@ -1222,6 +1274,16 @@ class Command(object):
|
|
1222
1274
|
yield self.get_sensor_reading(sensor)
|
1223
1275
|
|
1224
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)
|
1225
1287
|
if sensor['type'] == 'Fan':
|
1226
1288
|
for fan in reading['Fans']:
|
1227
1289
|
if fan['Name'] == sensor['name']:
|
@@ -1430,3 +1492,4 @@ if __name__ == '__main__':
|
|
1430
1492
|
print(repr(
|
1431
1493
|
Command(sys.argv[1], os.environ['BMCUSER'], os.environ['BMCPASS'],
|
1432
1494
|
verifycallback=lambda x: True).get_power()))
|
1495
|
+
|