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.
- {pyghmi-1.5.72 → pyghmi-1.5.75}/ChangeLog +29 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/PKG-INFO +12 -9
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/command.py +6 -2
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/oem/generic.py +3 -1
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/oem/lenovo/handler.py +3 -1
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/oem/lenovo/imm.py +9 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/redfish/command.py +209 -146
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/redfish/oem/generic.py +256 -80
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/redfish/oem/lenovo/main.py +10 -3
- pyghmi-1.5.75/pyghmi/redfish/oem/lenovo/smm3.py +85 -0
- pyghmi-1.5.75/pyghmi/redfish/oem/lenovo/xcc3.py +395 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/util/webclient.py +32 -8
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi.egg-info/PKG-INFO +12 -9
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi.egg-info/SOURCES.txt +1 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi.egg-info/entry_points.txt +0 -1
- pyghmi-1.5.75/pyghmi.egg-info/pbr.json +1 -0
- pyghmi-1.5.72/pyghmi/redfish/oem/lenovo/xcc3.py +0 -137
- pyghmi-1.5.72/pyghmi.egg-info/pbr.json +0 -1
- {pyghmi-1.5.72 → pyghmi-1.5.75}/.coveragerc +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/.stestr.conf +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/AUTHORS +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/CONTRIBUTING.rst +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/LICENSE +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/MANIFEST.in +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/README +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/README.md +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/builddeb +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/buildrpm +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/doc/requirements.txt +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/doc/source/conf.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/doc/source/contributor/index.rst +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/doc/source/index.rst +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/doc/source/install/index.rst +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/doc/source/reference/index.rst +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/makesetup +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/playbooks/legacy/tempest-devstack-ironic-pxe_ipmitool-pyghmi-src/post.yaml +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/playbooks/legacy/tempest-devstack-ironic-pxe_ipmitool-pyghmi-src/run.yaml +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/py27-constraints.txt +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/__init__.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/cmd/__init__.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/cmd/fakebmc.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/cmd/pyghmicons.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/cmd/pyghmiutil.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/cmd/virshbmc.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/constants.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/exceptions.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/__init__.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/bmc.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/console.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/events.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/fru.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/oem/__init__.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/oem/lenovo/__init__.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/oem/lenovo/config.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/oem/lenovo/cpu.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/oem/lenovo/dimm.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/oem/lenovo/drive.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/oem/lenovo/energy.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/oem/lenovo/firmware.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/oem/lenovo/inventory.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/oem/lenovo/nextscale.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/oem/lenovo/pci.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/oem/lenovo/psu.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/oem/lenovo/raid_controller.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/oem/lenovo/raid_drive.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/oem/lookup.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/private/__init__.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/private/constants.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/private/localsession.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/private/serversession.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/private/session.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/private/simplesession.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/private/spd.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/private/util.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/ipmi/sdr.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/media.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/redfish/__init__.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/redfish/oem/__init__.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/redfish/oem/dell/__init__.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/redfish/oem/dell/idrac.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/redfish/oem/dell/main.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/redfish/oem/lenovo/__init__.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/redfish/oem/lenovo/tsma.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/redfish/oem/lenovo/xcc.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/redfish/oem/lookup.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/storage.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/tests/__init__.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/tests/unit/__init__.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/tests/unit/base.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/tests/unit/ipmi/__init__.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/tests/unit/ipmi/test_sdr.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/util/__init__.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/util/parse.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi/version.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi.egg-info/dependency_links.txt +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi.egg-info/not-zip-safe +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi.egg-info/requires.txt +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/pyghmi.egg-info/top_level.txt +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/python-pyghmi.spec +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/python-pyghmi.spec.tmpl +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/requirements.txt +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/setup.cfg +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/setup.py +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/setup.py.tmpl +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/test-requirements.txt +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/tox.ini +0 -0
- {pyghmi-1.5.72 → pyghmi-1.5.75}/wheezy.patch +0 -0
- {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
|
+
Metadata-Version: 2.1
|
2
2
|
Name: pyghmi
|
3
|
-
Version: 1.5.
|
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
|
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
|
-
|
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
|
-
|
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]
|
185
|
+
if 'SessionService' in overview:
|
186
|
+
self._get_session_token(self.wc)
|
189
187
|
self._varsensormap = {}
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
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
|
-
|
198
|
-
'
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
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
|
-
|
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
|
-
|
623
|
-
self.
|
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
|
-
|
630
|
-
if
|
631
|
-
|
632
|
-
for
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
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
|
-
|
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 =
|
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
|
-
|
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
|
-
|
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')
|
1239
|
+
return self.oem.get_average_processor_temperature(self)
|
1240
|
+
|
1179
1241
|
|
1180
1242
|
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
|
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
|
+
|