pyghmi 1.5.72__py3-none-any.whl → 1.5.76__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 +225 -146
- pyghmi/redfish/oem/generic.py +274 -85
- pyghmi/redfish/oem/lenovo/main.py +10 -3
- pyghmi/redfish/oem/lenovo/smm3.py +85 -0
- pyghmi/redfish/oem/lenovo/xcc.py +1 -0
- pyghmi/redfish/oem/lenovo/xcc3.py +324 -13
- pyghmi/util/webclient.py +32 -8
- {pyghmi-1.5.72.dist-info → pyghmi-1.5.76.dist-info}/METADATA +6 -7
- {pyghmi-1.5.72.dist-info → pyghmi-1.5.76.dist-info}/RECORD +19 -18
- {pyghmi-1.5.72.dist-info → pyghmi-1.5.76.dist-info}/WHEEL +1 -1
- {pyghmi-1.5.72.dist-info → pyghmi-1.5.76.dist-info}/entry_points.txt +0 -1
- pyghmi-1.5.76.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.76.dist-info}/AUTHORS +0 -0
- {pyghmi-1.5.72.dist-info → pyghmi-1.5.76.dist-info}/LICENSE +0 -0
- {pyghmi-1.5.72.dist-info → pyghmi-1.5.76.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.
|
@@ -68,6 +68,7 @@ _healthmap = {
|
|
68
68
|
'Unknown': const.Health.Warning,
|
69
69
|
'Warning': const.Health.Warning,
|
70
70
|
'OK': const.Health.Ok,
|
71
|
+
None: const.Health.Ok,
|
71
72
|
}
|
72
73
|
|
73
74
|
|
@@ -113,6 +114,7 @@ def natural_sort(iterable):
|
|
113
114
|
class SensorReading(object):
|
114
115
|
def __init__(self, healthinfo, sensor=None, value=None, units=None,
|
115
116
|
unavailable=False):
|
117
|
+
self.states = []
|
116
118
|
if sensor:
|
117
119
|
self.name = sensor['name']
|
118
120
|
else:
|
@@ -122,7 +124,8 @@ class SensorReading(object):
|
|
122
124
|
self.states = [healthinfo.get('Status', {}).get('Health',
|
123
125
|
'Unknown')]
|
124
126
|
self.health = _healthmap[healthinfo['Status']['Health']]
|
125
|
-
self.
|
127
|
+
if self.health == const.Health.Ok:
|
128
|
+
self.states = []
|
126
129
|
self.value = value
|
127
130
|
self.state_ids = None
|
128
131
|
self.imprecision = None
|
@@ -161,6 +164,7 @@ class Command(object):
|
|
161
164
|
self._gpool = pool
|
162
165
|
self._bmcv4ip = None
|
163
166
|
self._bmcv6ip = None
|
167
|
+
self.xauthtoken = None
|
164
168
|
for addrinf in socket.getaddrinfo(bmc, 0, 0, socket.SOCK_STREAM):
|
165
169
|
if addrinf[0] == socket.AF_INET:
|
166
170
|
self._bmcv4ip = socket.inet_pton(addrinf[0], addrinf[-1][0])
|
@@ -173,38 +177,76 @@ class Command(object):
|
|
173
177
|
self.wc.set_header('Accept-Encoding', 'gzip')
|
174
178
|
self.wc.set_header('OData-Version', '4.0')
|
175
179
|
overview = self.wc.grab_json_response('/redfish/v1/')
|
176
|
-
self.wc.set_basic_credentials(userid, password)
|
177
180
|
self.username = userid
|
178
181
|
self.password = password
|
182
|
+
self.wc.set_basic_credentials(self.username, self.password)
|
179
183
|
self.wc.set_header('Content-Type', 'application/json')
|
180
|
-
if 'Systems' not in overview:
|
184
|
+
if 'Systems' not in overview and 'Managers' not in overview:
|
181
185
|
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]
|
186
|
+
if 'SessionService' in overview:
|
187
|
+
self._get_session_token(self.wc)
|
189
188
|
self._varsensormap = {}
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
189
|
+
self.powerurl = None
|
190
|
+
self.sysurl = None
|
191
|
+
if 'Managers' in overview:
|
192
|
+
bmcoll = systems = overview['Managers']['@odata.id']
|
193
|
+
res = self.wc.grab_json_response_with_status(bmcoll)
|
194
|
+
if res[1] == 401:
|
195
|
+
raise exc.PyghmiException('Access Denied')
|
196
|
+
elif res[1] < 200 or res[1] >= 300:
|
197
|
+
raise exc.PyghmiException(repr(res[0]))
|
198
|
+
bmcs = res[0]['Members']
|
199
|
+
if len(bmcs) == 1:
|
200
|
+
self._varbmcurl = bmcs[0]['@odata.id']
|
201
|
+
if 'Systems' in overview:
|
202
|
+
systems = overview['Systems']['@odata.id']
|
203
|
+
res = self.wc.grab_json_response_with_status(systems)
|
204
|
+
if res[1] == 401:
|
205
|
+
raise exc.PyghmiException('Access Denied')
|
206
|
+
elif res[1] < 200 or res[1] >= 300:
|
207
|
+
raise exc.PyghmiException(repr(res[0]))
|
208
|
+
members = res[0]
|
209
|
+
systems = members['Members']
|
210
|
+
if sysurl:
|
211
|
+
for system in systems:
|
212
|
+
if system['@odata.id'] == sysurl or system['@odata.id'].split('/')[-1] == sysurl:
|
213
|
+
self.sysurl = system['@odata.id']
|
214
|
+
break
|
215
|
+
else:
|
216
|
+
raise exc.PyghmiException(
|
217
|
+
'Specified sysurl not found: {0}'.format(sysurl))
|
196
218
|
else:
|
197
|
-
|
198
|
-
'
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
219
|
+
if len(systems) > 1:
|
220
|
+
systems = [x for x in systems if 'DPU' not in x['@odata.id']]
|
221
|
+
if len(systems) > 1:
|
222
|
+
raise exc.PyghmiException(
|
223
|
+
'Multi system manager, sysurl is required parameter')
|
224
|
+
if len(systems):
|
225
|
+
self.sysurl = systems[0]['@odata.id']
|
226
|
+
else:
|
227
|
+
self.sysurl = None
|
228
|
+
self.powerurl = self.sysinfo.get('Actions', {}).get(
|
229
|
+
'#ComputerSystem.Reset', {}).get('target', None)
|
230
|
+
|
231
|
+
def _get_session_token(self, wc):
|
232
|
+
# specification actually indicates we can skip straight to this url
|
233
|
+
username = self.username
|
234
|
+
password = self.password
|
235
|
+
if not isinstance(username, str):
|
236
|
+
username = username.decode()
|
237
|
+
if not isinstance(password, str):
|
238
|
+
password = password.decode()
|
239
|
+
rsp = wc.grab_rsp('/redfish/v1/SessionService/Sessions',
|
240
|
+
{'UserName': username, 'Password': password})
|
241
|
+
rsp.read()
|
242
|
+
self.xauthtoken = rsp.getheader('X-Auth-Token')
|
243
|
+
if self.xauthtoken:
|
244
|
+
if 'Authorization' in wc.stdheaders:
|
245
|
+
del wc.stdheaders['Authorization']
|
246
|
+
if 'Authorization' in self.wc.stdheaders:
|
247
|
+
del self.wc.stdheaders['Authorization']
|
248
|
+
wc.stdheaders['X-Auth-Token'] = self.xauthtoken
|
249
|
+
self.wc.stdheaders['X-Auth-Token'] = self.xauthtoken
|
208
250
|
|
209
251
|
@property
|
210
252
|
def _accountserviceurl(self):
|
@@ -431,7 +473,13 @@ class Command(object):
|
|
431
473
|
|
432
474
|
@property
|
433
475
|
def sysinfo(self):
|
434
|
-
|
476
|
+
if not self.sysurl:
|
477
|
+
return {}
|
478
|
+
try:
|
479
|
+
return self._do_web_request(self.sysurl)
|
480
|
+
except exc.RedfishError:
|
481
|
+
self.sysurl = None
|
482
|
+
return {}
|
435
483
|
|
436
484
|
@property
|
437
485
|
def bmcinfo(self):
|
@@ -520,6 +568,17 @@ class Command(object):
|
|
520
568
|
finally:
|
521
569
|
if 'If-Match' in wc.stdheaders:
|
522
570
|
del wc.stdheaders['If-Match']
|
571
|
+
if res[1] == 401 and self.xauthtoken:
|
572
|
+
wc.set_basic_credentials(self.username, self.password)
|
573
|
+
self._get_session_token(wc)
|
574
|
+
if etag:
|
575
|
+
wc.stdheaders['If-Match'] = etag
|
576
|
+
try:
|
577
|
+
res = wc.grab_json_response_with_status(url, payload,
|
578
|
+
method=method)
|
579
|
+
finally:
|
580
|
+
if 'If-Match' in wc.stdheaders:
|
581
|
+
del wc.stdheaders['If-Match']
|
523
582
|
if res[1] < 200 or res[1] >= 300:
|
524
583
|
try:
|
525
584
|
info = json.loads(res[0])
|
@@ -619,34 +678,55 @@ class Command(object):
|
|
619
678
|
@property
|
620
679
|
def _sensormap(self):
|
621
680
|
if not self._varsensormap:
|
622
|
-
|
623
|
-
self.
|
681
|
+
if self.sysinfo:
|
682
|
+
for chassis in self.sysinfo.get('Links', {}).get('Chassis', []):
|
683
|
+
self._mapchassissensors(chassis)
|
684
|
+
else: # no system, but check if this is a singular chassis
|
685
|
+
rootinfo = self._do_web_request('/redfish/v1/')
|
686
|
+
chassiscol = rootinfo.get('Chassis', {}).get('@odata.id', '')
|
687
|
+
if chassiscol:
|
688
|
+
chassislist = self._do_web_request(chassiscol)
|
689
|
+
if len(chassislist.get('Members', [])) == 1:
|
690
|
+
self._mapchassissensors(chassislist['Members'][0])
|
624
691
|
return self._varsensormap
|
625
692
|
|
626
693
|
def _mapchassissensors(self, chassis):
|
627
694
|
chassisurl = chassis['@odata.id']
|
628
695
|
chassisinfo = self._do_web_request(chassisurl)
|
629
|
-
|
630
|
-
if
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
696
|
+
sensors = None
|
697
|
+
if self.oem.usegenericsensors:
|
698
|
+
sensors = chassisinfo.get('Sensors', {}).get('@odata.id', '')
|
699
|
+
if sensors:
|
700
|
+
sensorinf = self._do_web_request(sensors)
|
701
|
+
for sensor in sensorinf.get('Members', []):
|
702
|
+
sensedata = self._do_web_request(sensor['@odata.id'])
|
703
|
+
if 'Name' in sensedata:
|
704
|
+
sensetype = sensedata.get('ReadingType', 'Unknown')
|
705
|
+
self._varsensormap[sensedata['Name']] = {
|
706
|
+
'name': sensedata['Name'], 'type': sensetype,
|
707
|
+
'url': sensor['@odata.id'], 'generic': True}
|
708
|
+
else:
|
709
|
+
powurl = chassisinfo.get('Power', {}).get('@odata.id', '')
|
710
|
+
if powurl:
|
711
|
+
powinf = self._do_web_request(powurl)
|
712
|
+
for voltage in powinf.get('Voltages', []):
|
713
|
+
if 'Name' in voltage:
|
714
|
+
self._varsensormap[voltage['Name']] = {
|
715
|
+
'name': voltage['Name'], 'url': powurl,
|
716
|
+
'type': 'Voltage'}
|
717
|
+
thermurl = chassisinfo.get('Thermal', {}).get('@odata.id', '')
|
718
|
+
if thermurl:
|
719
|
+
therminf = self._do_web_request(thermurl)
|
720
|
+
for fan in therminf.get('Fans', []):
|
721
|
+
if 'Name' in fan:
|
722
|
+
self._varsensormap[fan['Name']] = {
|
723
|
+
'name': fan['Name'], 'type': 'Fan',
|
724
|
+
'url': thermurl}
|
725
|
+
for temp in therminf.get('Temperatures', []):
|
726
|
+
if 'Name' in temp:
|
727
|
+
self._varsensormap[temp['Name']] = {
|
728
|
+
'name': temp['Name'], 'type': 'Temperature',
|
729
|
+
'url': thermurl}
|
650
730
|
for subchassis in chassisinfo.get('Links', {}).get('Contains', []):
|
651
731
|
self._mapchassissensors(subchassis)
|
652
732
|
|
@@ -757,8 +837,18 @@ class Command(object):
|
|
757
837
|
self._do_web_request(url, {'ResetType': action})
|
758
838
|
|
759
839
|
def set_identify(self, on=True, blink=None):
|
840
|
+
targurl = self.sysurl
|
841
|
+
if not targurl:
|
842
|
+
root = self._do_web_request('/redfish/v1')
|
843
|
+
systemsurl = root.get('Systems', {}).get('@odata.id', None)
|
844
|
+
if systemsurl:
|
845
|
+
targurl = self._do_web_request(systemsurl)
|
846
|
+
if len(targurl.get('Members', [])) == 1:
|
847
|
+
targurl = targurl['Members'][0]['@odata.id']
|
848
|
+
if not targurl:
|
849
|
+
raise Exception("Unable to identify system url")
|
760
850
|
self._do_web_request(
|
761
|
-
|
851
|
+
targurl,
|
762
852
|
{'IndicatorLED': 'Blinking' if blink else 'Lit' if on else 'Off'},
|
763
853
|
method='PATCH', etag='*')
|
764
854
|
|
@@ -805,6 +895,54 @@ class Command(object):
|
|
805
895
|
def set_system_configuration(self, changeset):
|
806
896
|
return self.oem.set_system_configuration(changeset, self)
|
807
897
|
|
898
|
+
def get_ntp_enabled(self):
|
899
|
+
bmcinfo = self._do_web_request(self._bmcurl)
|
900
|
+
netprotocols = bmcinfo.get('NetworkProtocol', {}).get('@odata.id', None)
|
901
|
+
if netprotocols:
|
902
|
+
netprotoinfo = self._do_web_request(netprotocols)
|
903
|
+
enabled = netprotoinfo.get('NTP', {}).get('ProtocolEnabled', False)
|
904
|
+
return enabled
|
905
|
+
return False
|
906
|
+
|
907
|
+
def set_ntp_enabled(self, enable):
|
908
|
+
bmcinfo = self._do_web_request(self._bmcurl)
|
909
|
+
netprotocols = bmcinfo.get('NetworkProtocol', {}).get('@odata.id', None)
|
910
|
+
if netprotocols:
|
911
|
+
request = {'NTP':{'ProtocolEnabled': enable}}
|
912
|
+
self._do_web_request(netprotocols, request, method='PATCH')
|
913
|
+
self._do_web_request(netprotocols, cache=0)
|
914
|
+
|
915
|
+
def get_ntp_servers(self):
|
916
|
+
bmcinfo = self._do_web_request(self._bmcurl)
|
917
|
+
netprotocols = bmcinfo.get('NetworkProtocol', {}).get('@odata.id', None)
|
918
|
+
if not netprotocols:
|
919
|
+
return []
|
920
|
+
netprotoinfo = self._do_web_request(netprotocols)
|
921
|
+
return netprotoinfo.get('NTP', {}).get('NTPServers', [])
|
922
|
+
|
923
|
+
def set_ntp_server(self, server, index=None):
|
924
|
+
bmcinfo = self._do_web_request(self._bmcurl)
|
925
|
+
netprotocols = bmcinfo.get('NetworkProtocol', {}).get('@odata.id', None)
|
926
|
+
currntpservers = self.get_ntp_servers()
|
927
|
+
if index is None:
|
928
|
+
if server in currntpservers:
|
929
|
+
return
|
930
|
+
currntpservers = [server] + currntpservers
|
931
|
+
else:
|
932
|
+
if (index + 1) > len(currntpservers):
|
933
|
+
if not server:
|
934
|
+
return
|
935
|
+
currntpservers.append(server)
|
936
|
+
else:
|
937
|
+
if not server:
|
938
|
+
del currntpservers[index]
|
939
|
+
else:
|
940
|
+
currntpservers[index] = server
|
941
|
+
request = {'NTP':{'NTPServers': currntpservers}}
|
942
|
+
self._do_web_request(netprotocols, request, method='PATCH')
|
943
|
+
self._do_web_request(netprotocols, cache=0)
|
944
|
+
|
945
|
+
|
808
946
|
def clear_bmc_configuration(self):
|
809
947
|
"""Reset BMC to factory default
|
810
948
|
|
@@ -816,7 +954,7 @@ class Command(object):
|
|
816
954
|
rc = bmcinfo.get('Actions', {}).get('#Manager.ResetToDefaults', {})
|
817
955
|
actinf = rc.get('ResetType@Redfish.AllowableValues', [])
|
818
956
|
if 'ResetAll' in actinf:
|
819
|
-
acturl =
|
957
|
+
acturl = rc.get('target', None)
|
820
958
|
if acturl:
|
821
959
|
self._do_web_request(acturl, {'ResetType': 'ResetAll'})
|
822
960
|
return
|
@@ -962,6 +1100,7 @@ class Command(object):
|
|
962
1100
|
{'HostName': hostname}, 'PATCH')
|
963
1101
|
|
964
1102
|
def get_firmware(self, components=()):
|
1103
|
+
self._fwnamemap = {}
|
965
1104
|
try:
|
966
1105
|
for firminfo in self.oem.get_firmware_inventory(components, self):
|
967
1106
|
yield firminfo
|
@@ -969,7 +1108,6 @@ class Command(object):
|
|
969
1108
|
return
|
970
1109
|
fwlist = self._do_web_request(self._fwinventory)
|
971
1110
|
fwurls = [x['@odata.id'] for x in fwlist.get('Members', [])]
|
972
|
-
self._fwnamemap = {}
|
973
1111
|
for res in self._do_bulk_requests(fwurls):
|
974
1112
|
res = self._extract_fwinfo(res)
|
975
1113
|
if res[0] is None:
|
@@ -1072,6 +1210,10 @@ class Command(object):
|
|
1072
1210
|
@property
|
1073
1211
|
def oem(self):
|
1074
1212
|
if not self._oem:
|
1213
|
+
if self.sysurl:
|
1214
|
+
self._do_web_request(self.sysurl, cache=False) # This is to trigger token validation and renewel
|
1215
|
+
elif self._varbmcurl:
|
1216
|
+
self._do_web_request(self._varbmcurl, cache=False) # This is to trigger token validation and renewel
|
1075
1217
|
self._oem = oem.get_oem_handler(
|
1076
1218
|
self.sysinfo, self.sysurl, self.wc, self._urlcache, self)
|
1077
1219
|
self._oem.set_credentials(self.username, self.password)
|
@@ -1081,70 +1223,7 @@ class Command(object):
|
|
1081
1223
|
return self.oem.get_description(self)
|
1082
1224
|
|
1083
1225
|
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
|
1226
|
+
return self.oem.get_event_log(clear, self)
|
1148
1227
|
|
1149
1228
|
def _get_chassis_env(self, chassis):
|
1150
1229
|
chassisurl = chassis['@odata.id']
|
@@ -1160,35 +1239,11 @@ class Command(object):
|
|
1160
1239
|
return retval
|
1161
1240
|
|
1162
1241
|
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')
|
1242
|
+
return self.oem.get_average_processor_temperature(self)
|
1243
|
+
|
1179
1244
|
|
1180
1245
|
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
|
1246
|
+
return self.oem.get_system_power_watts(self)
|
1192
1247
|
|
1193
1248
|
def get_inlet_temperature(self):
|
1194
1249
|
inlets = []
|
@@ -1222,6 +1277,16 @@ class Command(object):
|
|
1222
1277
|
yield self.get_sensor_reading(sensor)
|
1223
1278
|
|
1224
1279
|
def _extract_reading(self, sensor, reading):
|
1280
|
+
if sensor.get('generic', False): # generic sensor
|
1281
|
+
val = reading.get('Reading', None)
|
1282
|
+
unavail = val is None
|
1283
|
+
units = reading.get('ReadingUnits', None)
|
1284
|
+
if units == 'Cel':
|
1285
|
+
units = '°C'
|
1286
|
+
if units == 'cft_i/min':
|
1287
|
+
units = 'CFM'
|
1288
|
+
return SensorReading(reading, None, value=val, units=units,
|
1289
|
+
unavailable=unavail)
|
1225
1290
|
if sensor['type'] == 'Fan':
|
1226
1291
|
for fan in reading['Fans']:
|
1227
1292
|
if fan['Name'] == sensor['name']:
|
@@ -1317,9 +1382,18 @@ class Command(object):
|
|
1317
1382
|
if vmcoll:
|
1318
1383
|
vmlist = self._do_web_request(vmcoll)
|
1319
1384
|
vmurls = [x['@odata.id'] for x in vmlist.get('Members', [])]
|
1385
|
+
suspendedxauth = False
|
1386
|
+
if 'X-Auth-Token' in self.wc.stdheaders:
|
1387
|
+
suspendedxauth = True
|
1388
|
+
del self.wc.stdheaders['X-Auth-Token']
|
1389
|
+
self.wc.set_basic_credentials(self.username, self.password)
|
1320
1390
|
try:
|
1321
1391
|
self.oem.attach_remote_media(url, username, password, vmurls)
|
1322
1392
|
except exc.BypassGenericBehavior:
|
1393
|
+
if suspendedxauth:
|
1394
|
+
self.wc.stdheaders['X-Auth-Token'] = self.xauthtoken
|
1395
|
+
if 'Authorization' in self.wc.stdheaders:
|
1396
|
+
del self.wc.stdheaders['Authorization']
|
1323
1397
|
return
|
1324
1398
|
for vmurl in vmurls:
|
1325
1399
|
vminfo = self._do_web_request(vmurl, cache=False)
|
@@ -1341,6 +1415,10 @@ class Command(object):
|
|
1341
1415
|
else:
|
1342
1416
|
raise
|
1343
1417
|
break
|
1418
|
+
if suspendedxauth:
|
1419
|
+
self.wc.stdheaders['X-Auth-Token'] = self.xauthtoken
|
1420
|
+
if 'Authorization' in self.wc.stdheaders:
|
1421
|
+
del self.wc.stdheaders['Authorization']
|
1344
1422
|
|
1345
1423
|
def detach_remote_media(self):
|
1346
1424
|
try:
|
@@ -1430,3 +1508,4 @@ if __name__ == '__main__':
|
|
1430
1508
|
print(repr(
|
1431
1509
|
Command(sys.argv[1], os.environ['BMCUSER'], os.environ['BMCPASS'],
|
1432
1510
|
verifycallback=lambda x: True).get_power()))
|
1511
|
+
|