pyghmi 1.5.72__tar.gz → 1.5.75__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. {pyghmi-1.5.72 → pyghmi-1.5.75}/ChangeLog +29 -0
  2. {pyghmi-1.5.72 → pyghmi-1.5.75}/PKG-INFO +12 -9
  3. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/command.py +6 -2
  4. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/oem/generic.py +3 -1
  5. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/oem/lenovo/handler.py +3 -1
  6. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/oem/lenovo/imm.py +9 -0
  7. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/redfish/command.py +209 -146
  8. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/redfish/oem/generic.py +256 -80
  9. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/redfish/oem/lenovo/main.py +10 -3
  10. pyghmi-1.5.75/pyghmi/redfish/oem/lenovo/smm3.py +85 -0
  11. pyghmi-1.5.75/pyghmi/redfish/oem/lenovo/xcc3.py +395 -0
  12. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/util/webclient.py +32 -8
  13. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi.egg-info/PKG-INFO +12 -9
  14. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi.egg-info/SOURCES.txt +1 -0
  15. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi.egg-info/entry_points.txt +0 -1
  16. pyghmi-1.5.75/pyghmi.egg-info/pbr.json +1 -0
  17. pyghmi-1.5.72/pyghmi/redfish/oem/lenovo/xcc3.py +0 -137
  18. pyghmi-1.5.72/pyghmi.egg-info/pbr.json +0 -1
  19. {pyghmi-1.5.72 → pyghmi-1.5.75}/.coveragerc +0 -0
  20. {pyghmi-1.5.72 → pyghmi-1.5.75}/.stestr.conf +0 -0
  21. {pyghmi-1.5.72 → pyghmi-1.5.75}/AUTHORS +0 -0
  22. {pyghmi-1.5.72 → pyghmi-1.5.75}/CONTRIBUTING.rst +0 -0
  23. {pyghmi-1.5.72 → pyghmi-1.5.75}/LICENSE +0 -0
  24. {pyghmi-1.5.72 → pyghmi-1.5.75}/MANIFEST.in +0 -0
  25. {pyghmi-1.5.72 → pyghmi-1.5.75}/README +0 -0
  26. {pyghmi-1.5.72 → pyghmi-1.5.75}/README.md +0 -0
  27. {pyghmi-1.5.72 → pyghmi-1.5.75}/builddeb +0 -0
  28. {pyghmi-1.5.72 → pyghmi-1.5.75}/buildrpm +0 -0
  29. {pyghmi-1.5.72 → pyghmi-1.5.75}/doc/requirements.txt +0 -0
  30. {pyghmi-1.5.72 → pyghmi-1.5.75}/doc/source/conf.py +0 -0
  31. {pyghmi-1.5.72 → pyghmi-1.5.75}/doc/source/contributor/index.rst +0 -0
  32. {pyghmi-1.5.72 → pyghmi-1.5.75}/doc/source/index.rst +0 -0
  33. {pyghmi-1.5.72 → pyghmi-1.5.75}/doc/source/install/index.rst +0 -0
  34. {pyghmi-1.5.72 → pyghmi-1.5.75}/doc/source/reference/index.rst +0 -0
  35. {pyghmi-1.5.72 → pyghmi-1.5.75}/makesetup +0 -0
  36. {pyghmi-1.5.72 → pyghmi-1.5.75}/playbooks/legacy/tempest-devstack-ironic-pxe_ipmitool-pyghmi-src/post.yaml +0 -0
  37. {pyghmi-1.5.72 → pyghmi-1.5.75}/playbooks/legacy/tempest-devstack-ironic-pxe_ipmitool-pyghmi-src/run.yaml +0 -0
  38. {pyghmi-1.5.72 → pyghmi-1.5.75}/py27-constraints.txt +0 -0
  39. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/__init__.py +0 -0
  40. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/cmd/__init__.py +0 -0
  41. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/cmd/fakebmc.py +0 -0
  42. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/cmd/pyghmicons.py +0 -0
  43. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/cmd/pyghmiutil.py +0 -0
  44. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/cmd/virshbmc.py +0 -0
  45. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/constants.py +0 -0
  46. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/exceptions.py +0 -0
  47. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/__init__.py +0 -0
  48. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/bmc.py +0 -0
  49. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/console.py +0 -0
  50. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/events.py +0 -0
  51. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/fru.py +0 -0
  52. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/oem/__init__.py +0 -0
  53. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/oem/lenovo/__init__.py +0 -0
  54. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/oem/lenovo/config.py +0 -0
  55. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/oem/lenovo/cpu.py +0 -0
  56. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/oem/lenovo/dimm.py +0 -0
  57. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/oem/lenovo/drive.py +0 -0
  58. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/oem/lenovo/energy.py +0 -0
  59. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/oem/lenovo/firmware.py +0 -0
  60. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/oem/lenovo/inventory.py +0 -0
  61. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/oem/lenovo/nextscale.py +0 -0
  62. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/oem/lenovo/pci.py +0 -0
  63. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/oem/lenovo/psu.py +0 -0
  64. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/oem/lenovo/raid_controller.py +0 -0
  65. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/oem/lenovo/raid_drive.py +0 -0
  66. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/oem/lookup.py +0 -0
  67. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/private/__init__.py +0 -0
  68. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/private/constants.py +0 -0
  69. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/private/localsession.py +0 -0
  70. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/private/serversession.py +0 -0
  71. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/private/session.py +0 -0
  72. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/private/simplesession.py +0 -0
  73. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/private/spd.py +0 -0
  74. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/private/util.py +0 -0
  75. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/sdr.py +0 -0
  76. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/media.py +0 -0
  77. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/redfish/__init__.py +0 -0
  78. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/redfish/oem/__init__.py +0 -0
  79. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/redfish/oem/dell/__init__.py +0 -0
  80. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/redfish/oem/dell/idrac.py +0 -0
  81. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/redfish/oem/dell/main.py +0 -0
  82. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/redfish/oem/lenovo/__init__.py +0 -0
  83. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/redfish/oem/lenovo/tsma.py +0 -0
  84. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/redfish/oem/lenovo/xcc.py +0 -0
  85. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/redfish/oem/lookup.py +0 -0
  86. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/storage.py +0 -0
  87. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/tests/__init__.py +0 -0
  88. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/tests/unit/__init__.py +0 -0
  89. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/tests/unit/base.py +0 -0
  90. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/tests/unit/ipmi/__init__.py +0 -0
  91. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/tests/unit/ipmi/test_sdr.py +0 -0
  92. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/util/__init__.py +0 -0
  93. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/util/parse.py +0 -0
  94. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/version.py +0 -0
  95. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi.egg-info/dependency_links.txt +0 -0
  96. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi.egg-info/not-zip-safe +0 -0
  97. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi.egg-info/requires.txt +0 -0
  98. {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi.egg-info/top_level.txt +0 -0
  99. {pyghmi-1.5.72 → pyghmi-1.5.75}/python-pyghmi.spec +0 -0
  100. {pyghmi-1.5.72 → pyghmi-1.5.75}/python-pyghmi.spec.tmpl +0 -0
  101. {pyghmi-1.5.72 → pyghmi-1.5.75}/requirements.txt +0 -0
  102. {pyghmi-1.5.72 → pyghmi-1.5.75}/setup.cfg +0 -0
  103. {pyghmi-1.5.72 → pyghmi-1.5.75}/setup.py +0 -0
  104. {pyghmi-1.5.72 → pyghmi-1.5.75}/setup.py.tmpl +0 -0
  105. {pyghmi-1.5.72 → pyghmi-1.5.75}/test-requirements.txt +0 -0
  106. {pyghmi-1.5.72 → pyghmi-1.5.75}/tox.ini +0 -0
  107. {pyghmi-1.5.72 → pyghmi-1.5.75}/wheezy.patch +0 -0
  108. {pyghmi-1.5.72 → pyghmi-1.5.75}/zuul.d/project.yaml +0 -0
@@ -1,6 +1,35 @@
1
1
  CHANGES
2
2
  =======
3
3
 
4
+ 1.5.75
5
+ ------
6
+
7
+ * Add standard NTP handling to redfish
8
+ * Attempt to get half-working sysurl if broken
9
+ * Handle inventory for systemless redfish endpoint
10
+ * Prefer generic sensors and handle system-less
11
+ * Relocate event log to oem, override for SMMv3
12
+ * Add reseat for SMMv3
13
+ * Fix coordinate calculation for XCC3 in new DWC chassis
14
+ * Implement next gen SMM3 support
15
+ * Fix redfish reset BMC to defaults
16
+ * Handle new variants of USB ethernet configuration in XCC3
17
+ * Add FFDC support for XCC3
18
+ * Provide update for backup XCC3 bank
19
+ * Implement normalized bmc settings for XCC3
20
+ * Ignore exhaust when looking for inlet
21
+ * Fix omitted comma in replace call
22
+ * Move normalized CPU and power to OEM
23
+ * Fix returning too much data when asked only for names
24
+ * Repair session token implicitly on expiration in OEM
25
+ * Fix IPMI identify on/off for IMM/XCC systems
26
+ * Add RDOC support for XCC3
27
+ * Fix issues with OEM retrieval
28
+ * Attempt generic height information extraction
29
+ * Add expanded/emulated expanded redfish support
30
+ * Negotiate a redfish session, if possible
31
+ * Add blink support to Lenovo "IPMI" support
32
+
4
33
  1.5.72
5
34
  ------
6
35
 
@@ -1,18 +1,11 @@
1
- Metadata-Version: 1.1
1
+ Metadata-Version: 2.1
2
2
  Name: pyghmi
3
- Version: 1.5.72
3
+ Version: 1.5.75
4
4
  Summary: Python General Hardware Management Initiative (IPMI and others)
5
5
  Home-page: http://github.com/openstack/pyghmi/
6
6
  Author: Jarrod Johnson
7
7
  Author-email: jjohnson2@lenovo.com
8
8
  License: Apache License, Version 2.0
9
- Description: This is a pure python implementation of IPMI protocol.
10
-
11
- pyghmicons and pyghmiutil are example scripts to show how one may incorporate
12
- this library into python code
13
-
14
-
15
- Platform: UNKNOWN
16
9
  Classifier: Intended Audience :: Information Technology
17
10
  Classifier: Intended Audience :: System Administrators
18
11
  Classifier: License :: OSI Approved :: Apache Software License
@@ -24,3 +17,13 @@ Classifier: Programming Language :: Python :: 3
24
17
  Classifier: Programming Language :: Python :: 3.6
25
18
  Classifier: Programming Language :: Python :: 3.7
26
19
  Classifier: Programming Language :: Python :: 3.8
20
+ License-File: LICENSE
21
+ Requires-Dist: cryptography>=2.1
22
+ Requires-Dist: python-dateutil>=2.8.1
23
+ Requires-Dist: six>=1.10.0
24
+
25
+ This is a pure python implementation of IPMI protocol.
26
+
27
+ pyghmicons and pyghmiutil are example scripts to show how one may incorporate
28
+ this library into python code
29
+
@@ -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]
@@ -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
+