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 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:
@@ -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
 
@@ -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 2021 Lenovo
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
- self.states = [healthinfo['Status']['Health']]
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
- systems = overview['Systems']['@odata.id']
183
- res = self.wc.grab_json_response_with_status(systems)
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
- systems = members['Members']
191
- if sysurl:
192
- for system in systems:
193
- if system['@odata.id'] == sysurl or system['@odata.id'].split('/')[-1] == sysurl:
194
- self.sysurl = system['@odata.id']
195
- break
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
- raise exc.PyghmiException(
198
- 'Specified sysurl not found: {0}'.format(sysurl))
199
- else:
200
- if len(systems) != 1:
201
- systems = [x for x in systems if 'DPU' not in x['@odata.id']]
202
- if len(systems) != 1:
203
- raise exc.PyghmiException(
204
- 'Multi system manager, sysurl is required parameter')
205
- self.sysurl = systems[0]['@odata.id']
206
- self.powerurl = self.sysinfo.get('Actions', {}).get(
207
- '#ComputerSystem.Reset', {}).get('target', None)
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
- return self._do_web_request(self.sysurl)
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
- for chassis in self.sysinfo.get('Links', {}).get('Chassis', []):
623
- self._mapchassissensors(chassis)
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
- powurl = chassisinfo.get('Power', {}).get('@odata.id', '')
630
- if powurl:
631
- powinf = self._do_web_request(powurl)
632
- for voltage in powinf.get('Voltages', []):
633
- if 'Name' in voltage:
634
- self._varsensormap[voltage['Name']] = {
635
- 'name': voltage['Name'], 'url': powurl,
636
- 'type': 'Voltage'}
637
- thermurl = chassisinfo.get('Thermal', {}).get('@odata.id', '')
638
- if thermurl:
639
- therminf = self._do_web_request(thermurl)
640
- for fan in therminf.get('Fans', []):
641
- if 'Name' in fan:
642
- self._varsensormap[fan['Name']] = {
643
- 'name': fan['Name'], 'type': 'Fan',
644
- 'url': thermurl}
645
- for temp in therminf.get('Temperatures', []):
646
- if 'Name' in temp:
647
- self._varsensormap[temp['Name']] = {
648
- 'name': temp['Name'], 'type': 'Temperature',
649
- 'url': thermurl}
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
- self.sysurl,
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 = actinf.get('target', None)
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
- bmcinfo = self._do_web_request(self._bmcurl)
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
- cputemps = []
1164
- for chassis in self.sysinfo.get('Links', {}).get('Chassis', []):
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
- totalwatts = 0
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
+