pyghmi 1.5.72__py3-none-any.whl → 1.5.76__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- pyghmi/ipmi/command.py +6 -2
- pyghmi/ipmi/oem/generic.py +3 -1
- pyghmi/ipmi/oem/lenovo/handler.py +3 -1
- pyghmi/ipmi/oem/lenovo/imm.py +9 -0
- pyghmi/redfish/command.py +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
|
+
|