pyghmi 1.5.72__py3-none-any.whl → 1.5.75__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 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
+