pyghmi 1.5.71__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 +6 -2
- pyghmi/ipmi/oem/generic.py +3 -1
- pyghmi/ipmi/oem/lenovo/handler.py +3 -1
- pyghmi/ipmi/oem/lenovo/imm.py +9 -0
- pyghmi/redfish/command.py +212 -163
- pyghmi/redfish/oem/generic.py +325 -117
- pyghmi/redfish/oem/lenovo/main.py +24 -4
- pyghmi/redfish/oem/lenovo/smm3.py +85 -0
- pyghmi/redfish/oem/lenovo/xcc.py +2 -2
- pyghmi/redfish/oem/lenovo/xcc3.py +395 -0
- pyghmi/util/webclient.py +32 -8
- {pyghmi-1.5.71.dist-info → pyghmi-1.5.75.dist-info}/METADATA +6 -7
- {pyghmi-1.5.71.dist-info → pyghmi-1.5.75.dist-info}/RECORD +19 -17
- {pyghmi-1.5.71.dist-info → pyghmi-1.5.75.dist-info}/WHEEL +1 -1
- {pyghmi-1.5.71.dist-info → pyghmi-1.5.75.dist-info}/entry_points.txt +0 -1
- pyghmi-1.5.75.dist-info/pbr.json +1 -0
- pyghmi-1.5.71.dist-info/pbr.json +0 -1
- {pyghmi-1.5.71.dist-info → pyghmi-1.5.75.dist-info}/AUTHORS +0 -0
- {pyghmi-1.5.71.dist-info → pyghmi-1.5.75.dist-info}/LICENSE +0 -0
- {pyghmi-1.5.71.dist-info → pyghmi-1.5.75.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,85 @@
|
|
1
|
+
# Copyright 2025 Lenovo Corporation
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
import pyghmi.redfish.oem.generic as generic
|
16
|
+
import pyghmi.constants as pygconst
|
17
|
+
|
18
|
+
healthlookup = {
|
19
|
+
'ok': pygconst.Health.Ok,
|
20
|
+
'critical': pygconst.Health.Critical
|
21
|
+
}
|
22
|
+
|
23
|
+
def _baytonumber(bay):
|
24
|
+
try:
|
25
|
+
return int(bay)
|
26
|
+
except ValueError:
|
27
|
+
if len(bay) == 2:
|
28
|
+
# Treat a hexadecimal system as a leading decimal digit and letter compile
|
29
|
+
# 1a == slot 1, 1b == slot 2, 2a == slot 1, etc..
|
30
|
+
try:
|
31
|
+
tmp = int(bay, 16)
|
32
|
+
return (2 * (tmp >> 4) - 1) + ((tmp & 15) % 10)
|
33
|
+
except ValueError:
|
34
|
+
return None
|
35
|
+
return None
|
36
|
+
|
37
|
+
|
38
|
+
def _baytolabel(bay):
|
39
|
+
try:
|
40
|
+
baynum = int(bay)
|
41
|
+
# need to convert to 1a, 1b, etc...
|
42
|
+
vertidx = ((baynum - 1) // 2 + 1) << 4
|
43
|
+
horizidx = (baynum - 1) % 2 + 10
|
44
|
+
bayid = vertidx | horizidx
|
45
|
+
return '{:02x}'.format(bayid)
|
46
|
+
except ValueError:
|
47
|
+
return bay
|
48
|
+
return None
|
49
|
+
|
50
|
+
class OEMHandler(generic.OEMHandler):
|
51
|
+
def get_health(self, fishclient, verbose=True):
|
52
|
+
rsp = self._do_web_request('/redfish/v1/Chassis/chassis1')
|
53
|
+
health = rsp.get('Status', {}).get('Health', 'Unknown').lower()
|
54
|
+
health = healthlookup.get(health, pygconst.Health.Critical)
|
55
|
+
return {'health': health}
|
56
|
+
|
57
|
+
def get_system_configuration(self, hideadvanced=True, fishclient=None):
|
58
|
+
return {}
|
59
|
+
|
60
|
+
def _get_node_info(self):
|
61
|
+
nodeinfo = self._varsysinfo
|
62
|
+
if not nodeinfo:
|
63
|
+
overview = self._do_web_request('/redfish/v1/')
|
64
|
+
chassismembs = overview.get('Chassis', {}).get('@odata.id', None)
|
65
|
+
if not chassismembs:
|
66
|
+
return nodeinfo
|
67
|
+
chassislist = self._do_web_request(chassismembs)
|
68
|
+
chassismembs = chassislist.get('Members', [])
|
69
|
+
if len(chassismembs) == 1:
|
70
|
+
chassisurl = chassismembs[0]['@odata.id']
|
71
|
+
nodeinfo = self._do_web_request(chassisurl)
|
72
|
+
nodeinfo['SKU'] = nodeinfo['Model']
|
73
|
+
nodeinfo['Model'] = 'N1380 Enclosure'
|
74
|
+
return nodeinfo
|
75
|
+
|
76
|
+
def reseat_bay(self, bay):
|
77
|
+
bayid = _baytolabel(bay)
|
78
|
+
url = '/redfish/v1/Chassis/chassis1/Oem/Lenovo/Nodes/{}/Actions/Node.Reseat'.format(bayid)
|
79
|
+
rsp = self._do_web_request(url, method='POST')
|
80
|
+
|
81
|
+
def get_event_log(self, clear=False, fishclient=None):
|
82
|
+
return super().get_event_log(clear, fishclient, extraurls=[{'@odata.id':'/redfish/v1/Chassis/chassis1/LogServices/EventLog'}])
|
83
|
+
|
84
|
+
def get_description(self, fishclient):
|
85
|
+
return {'height': 13, 'slot': 0, 'slots': [8, 2]}
|
pyghmi/redfish/oem/lenovo/xcc.py
CHANGED
@@ -579,7 +579,7 @@ class OEMHandler(generic.OEMHandler):
|
|
579
579
|
summary['badreadings'] = fallbackdata
|
580
580
|
return summary
|
581
581
|
|
582
|
-
def get_description(self):
|
582
|
+
def get_description(self, fishclient):
|
583
583
|
description = self._do_web_request('/DeviceDescription.json')
|
584
584
|
if description:
|
585
585
|
description = description[0]
|
@@ -674,7 +674,7 @@ class OEMHandler(generic.OEMHandler):
|
|
674
674
|
for diskent in adp.get('aimDisks', ()):
|
675
675
|
yield self._get_disk_firmware_single(diskent)
|
676
676
|
|
677
|
-
def get_firmware_inventory(self, components):
|
677
|
+
def get_firmware_inventory(self, components, fishclient):
|
678
678
|
sysinf = self.wc.grab_json_response('/api/dataset/sys_info')
|
679
679
|
for item in sysinf.get('items', {}):
|
680
680
|
for firm in item.get('firmware', []):
|
@@ -0,0 +1,395 @@
|
|
1
|
+
# Copyright 2025 Lenovo Corporation
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
import copy
|
15
|
+
import json
|
16
|
+
import pyghmi.redfish.oem.generic as generic
|
17
|
+
import pyghmi.exceptions as pygexc
|
18
|
+
import pyghmi.util.webclient as webclient
|
19
|
+
import zipfile
|
20
|
+
import time
|
21
|
+
import os.path
|
22
|
+
|
23
|
+
class OEMHandler(generic.OEMHandler):
|
24
|
+
|
25
|
+
def supports_expand(self, url):
|
26
|
+
return True
|
27
|
+
|
28
|
+
def get_diagnostic_data(self, savefile, progress=None, autosuffix=False):
|
29
|
+
tsk = self._do_web_request(
|
30
|
+
'/redfish/v1/Systems/1/LogServices/DiagnosticLog/Actions/LogService.CollectDiagnosticData',
|
31
|
+
{"DiagnosticDataType": "Manager", "SelectDataTypes": []})
|
32
|
+
taskrunning = True
|
33
|
+
taskurl = tsk.get('TaskMonitor', None)
|
34
|
+
pct = 0 if taskurl else 100
|
35
|
+
durl = None
|
36
|
+
while pct < 100 and taskrunning:
|
37
|
+
status = self._do_web_request(taskurl)
|
38
|
+
durl = status.get('AdditionalDataURI', '')
|
39
|
+
pct = status.get('PercentComplete', 0)
|
40
|
+
taskrunning = status.get('TaskState', 'Complete') == 'Running'
|
41
|
+
if progress:
|
42
|
+
progress({'phase': 'initializing', 'progress': float(pct)})
|
43
|
+
if taskrunning:
|
44
|
+
time.sleep(3)
|
45
|
+
if not durl:
|
46
|
+
raise Exception("Failed getting service data url")
|
47
|
+
fname = os.path.basename(durl)
|
48
|
+
if autosuffix and not savefile.endswith('.tar.zst'):
|
49
|
+
savefile += '-{0}'.format(fname)
|
50
|
+
fd = webclient.FileDownloader(self.webclient, durl, savefile)
|
51
|
+
fd.start()
|
52
|
+
while fd.isAlive():
|
53
|
+
fd.join(1)
|
54
|
+
if progress and self.webclient.get_download_progress():
|
55
|
+
progress({'phase': 'download',
|
56
|
+
'progress': 100 * self.webclient.get_download_progress()})
|
57
|
+
if fd.exc:
|
58
|
+
raise fd.exc
|
59
|
+
if progress:
|
60
|
+
progress({'phase': 'complete'})
|
61
|
+
return savefile
|
62
|
+
|
63
|
+
def get_system_power_watts(self, fishclient):
|
64
|
+
powerinfo = fishclient._do_web_request('/redfish/v1/Chassis/1/Sensors/power_Sys_Power')
|
65
|
+
return powerinfo['Reading']
|
66
|
+
|
67
|
+
def _get_cpu_temps(self, fishclient):
|
68
|
+
cputemps = []
|
69
|
+
for reading in super()._get_cpu_temps(fishclient):
|
70
|
+
if 'Margin' in reading['Name']:
|
71
|
+
continue
|
72
|
+
cputemps.append(reading)
|
73
|
+
return cputemps
|
74
|
+
|
75
|
+
def get_system_configuration(self, hideadvanced=True, fishclient=None):
|
76
|
+
stgs = self._getsyscfg(fishclient)[0]
|
77
|
+
outstgs = {}
|
78
|
+
for stg in stgs:
|
79
|
+
outstgs[f'UEFI.{stg}'] = stgs[stg]
|
80
|
+
return outstgs
|
81
|
+
|
82
|
+
def set_system_configuration(self, changeset, fishclient):
|
83
|
+
bmchangeset = {}
|
84
|
+
vpdchangeset = {}
|
85
|
+
for stg in list(changeset):
|
86
|
+
if stg.startswith('BMC.'):
|
87
|
+
bmchangeset[stg.replace('BMC.', '')] = changeset[stg]
|
88
|
+
del changeset[stg]
|
89
|
+
if stg.startswith('UEFI.'):
|
90
|
+
changeset[stg.replace('UEFI.', '')] = changeset[stg]
|
91
|
+
del changeset[stg]
|
92
|
+
if stg.startswith('VPD.'):
|
93
|
+
vpdchangeset[stg.replace('VPD.', '')] = changeset[stg]
|
94
|
+
del changeset[stg]
|
95
|
+
if changeset:
|
96
|
+
super().set_system_configuration(changeset, fishclient)
|
97
|
+
if bmchangeset:
|
98
|
+
self._set_xcc3_settings(bmchangeset, fishclient)
|
99
|
+
if vpdchangeset:
|
100
|
+
self._set_xcc3_vpd(vpdchangeset, fishclient)
|
101
|
+
|
102
|
+
def _set_xcc3_vpd(self, changeset, fishclient):
|
103
|
+
newvpd = {'Attributes': changeset}
|
104
|
+
fishclient._do_web_request(
|
105
|
+
'/redfish/v1/Chassis/1/Oem/Lenovo/SysvpdSettings/Actions/LenovoSysVpdSettings.SetVpdSettings',
|
106
|
+
newvpd)
|
107
|
+
|
108
|
+
|
109
|
+
def _set_xcc3_settings(self, changeset, fishclient):
|
110
|
+
currsettings, reginfo = self._get_lnv_bmcstgs(fishclient)
|
111
|
+
rawsettings = fishclient._do_web_request('/redfish/v1/Managers/1/Oem/Lenovo/BMCSettings',
|
112
|
+
cache=False)
|
113
|
+
rawsettings = rawsettings.get('Attributes', {})
|
114
|
+
pendingsettings = {}
|
115
|
+
ret = self._set_redfish_settings(
|
116
|
+
changeset, fishclient, currsettings, rawsettings,
|
117
|
+
pendingsettings, self.lenovobmcattrdeps, reginfo,
|
118
|
+
'/redfish/v1/Managers/1/Oem/Lenovo/BMCSettings')
|
119
|
+
fishclient._do_web_request('/redfish/v1/Managers/1/Oem/Lenovo/BMCSettings', cache=False)
|
120
|
+
return ret
|
121
|
+
|
122
|
+
oemacctmap = {
|
123
|
+
'password_reuse_count': 'MinimumPasswordReuseCycle',
|
124
|
+
'password_change_interval': 'MinimumPasswordChangeIntervalHours',
|
125
|
+
'password_expiration': 'PasswordExpirationPeriodDays',
|
126
|
+
'password_complexity': 'ComplexPassword',
|
127
|
+
}
|
128
|
+
|
129
|
+
acctmap = {
|
130
|
+
'password_login_failures': 'AccountLockoutThreshold',
|
131
|
+
'password_min_length': 'MinPasswordLength',
|
132
|
+
'password_lockout_period': 'AccountLockoutDuration',
|
133
|
+
}
|
134
|
+
|
135
|
+
def update_firmware(self, filename, data=None, progress=None, bank=None, otherfields=()):
|
136
|
+
if not otherfields and bank == 'backup':
|
137
|
+
uxzcount = 0
|
138
|
+
otherfields = {'UpdateParameters': {"Targets": ["/redfish/v1/UpdateService/FirmwareInventory/BMC-Backup"]}}
|
139
|
+
needseek = False
|
140
|
+
if data and hasattr(data, 'read'):
|
141
|
+
if zipfile.is_zipfile(data):
|
142
|
+
needseek = True
|
143
|
+
z = zipfile.ZipFile(data)
|
144
|
+
else:
|
145
|
+
data.seek(0)
|
146
|
+
elif data is None and zipfile.is_zipfile(filename):
|
147
|
+
z = zipfile.ZipFile(filename)
|
148
|
+
if z:
|
149
|
+
for tmpname in z.namelist():
|
150
|
+
if tmpname.startswith('payloads/'):
|
151
|
+
uxzcount += 1
|
152
|
+
if tmpname.endswith('.uxz'):
|
153
|
+
wrappedfilename = tmpname
|
154
|
+
if uxzcount == 1 and wrappedfilename:
|
155
|
+
filename = os.path.basename(wrappedfilename)
|
156
|
+
data = z.open(wrappedfilename)
|
157
|
+
elif needseek:
|
158
|
+
data.seek(0)
|
159
|
+
super().update_firmware(filename, data=data, progress=progress, bank=bank, otherfields=otherfields)
|
160
|
+
|
161
|
+
def get_bmc_configuration(self):
|
162
|
+
settings = {}
|
163
|
+
acctsrv = self._do_web_request('/redfish/v1/AccountService')
|
164
|
+
for oemstg in self.oemacctmap:
|
165
|
+
settings[oemstg] = {
|
166
|
+
'value': acctsrv['Oem']['Lenovo'][self.oemacctmap[oemstg]]}
|
167
|
+
for stg in self.acctmap:
|
168
|
+
settings[stg] = {
|
169
|
+
'value': acctsrv[self.acctmap[stg]]}
|
170
|
+
bmcstgs = self._do_web_request('/redfish/v1/Managers/1/Oem/Lenovo/BMCSettings')
|
171
|
+
bmcattrs = bmcstgs['Attributes']
|
172
|
+
self.ethoverusb = True if 'EthOverUSBEnabled' in bmcattrs else False
|
173
|
+
usbcfg = bmcattrs.get('NetMgrUsb0Enabled', bmcattrs.get('EthOverUSBEnabled', 'False'))
|
174
|
+
usbeth = 'Enable' if usbcfg == 'True' else 'Disable'
|
175
|
+
settings['usb_ethernet'] = {
|
176
|
+
'value': usbeth
|
177
|
+
}
|
178
|
+
usbcfg = bmcattrs.get('NetMgrUsb0PortForwardingEnabled', bmcattrs.get('EthOverUSBPortForwardingEnabled', 'False'))
|
179
|
+
fwd = 'Enable' if usbcfg == 'True' else 'Disable'
|
180
|
+
settings['usb_ethernet_port_forwarding'] = fwd
|
181
|
+
mappings = []
|
182
|
+
for idx in range(1, 11):
|
183
|
+
keyname = 'NetMgrUsb0PortForwardingPortMapping.{}'.format(idx)
|
184
|
+
keyaltname = 'EthOverUSBPortForwardingPortMapping_{}'.format(idx)
|
185
|
+
currval = bmcattrs.get(keyname, bmcattrs.get(keyaltname, '0,0'))
|
186
|
+
if currval == '0,0':
|
187
|
+
continue
|
188
|
+
src, dst = currval.split(',')
|
189
|
+
mappings.append('{}:{}'.format(src,dst))
|
190
|
+
settings['usb_forwarded_ports'] = {'value': ','.join(mappings)}
|
191
|
+
return settings
|
192
|
+
|
193
|
+
def set_bmc_configuration(self, changeset):
|
194
|
+
acctattribs = {}
|
195
|
+
usbsettings = {}
|
196
|
+
for key in changeset:
|
197
|
+
if isinstance(changeset[key], str):
|
198
|
+
changeset[key] = {'value': changeset[key]}
|
199
|
+
currval = changeset[key].get('value', None)
|
200
|
+
if key == 'password_complexity':
|
201
|
+
if currval.lower() in ("false", 0):
|
202
|
+
currval = False
|
203
|
+
elif currval.lower() in ('true', 1):
|
204
|
+
currval = True
|
205
|
+
elif key.lower().startswith('usb_'):
|
206
|
+
if 'forwarded_ports' not in key.lower():
|
207
|
+
currval = currval.lower()
|
208
|
+
if currval and 'disabled'.startswith(currval):
|
209
|
+
currval = 'False'
|
210
|
+
elif currval and 'enabled'.startswith(currval):
|
211
|
+
currval = 'True'
|
212
|
+
else:
|
213
|
+
currval = int(currval)
|
214
|
+
if key.lower() in self.oemacctmap:
|
215
|
+
if 'Oem' not in acctattribs:
|
216
|
+
acctattribs['Oem'] = {'Lenovo': {}}
|
217
|
+
acctattribs['Oem']['Lenovo'][
|
218
|
+
self.oemacctmap[key.lower()]] = currval
|
219
|
+
if key.lower() == 'password_expiration':
|
220
|
+
warntime = str(int(int(currval) * 0.08))
|
221
|
+
acctattribs['Oem']['Lenovo'][
|
222
|
+
'PasswordExpirationWarningPeriod'] = warntime
|
223
|
+
elif key.lower() in self.acctmap:
|
224
|
+
acctattribs[self.acctmap[key.lower()]] = currval
|
225
|
+
elif key.lower() in (
|
226
|
+
'usb_ethernet', 'usb_ethernet_port_forwarding',
|
227
|
+
'usb_forwarded_ports'):
|
228
|
+
usbsettings[key] = currval
|
229
|
+
else:
|
230
|
+
raise pygexc.InvalidParameterValue(
|
231
|
+
'{0} not a known setting'.format(key))
|
232
|
+
if acctattribs:
|
233
|
+
self._do_web_request(
|
234
|
+
'/redfish/v1/AccountService', acctattribs, method='PATCH')
|
235
|
+
self._do_web_request('/redfish/v1/AccountService', cache=False)
|
236
|
+
if usbsettings:
|
237
|
+
self.apply_usb_configuration(usbsettings)
|
238
|
+
|
239
|
+
def apply_usb_configuration(self, usbsettings):
|
240
|
+
bmcattribs = {}
|
241
|
+
if not hasattr(self, 'ethoverusb'):
|
242
|
+
self.get_bmc_configuration()
|
243
|
+
|
244
|
+
if 'usb_forwarded_ports' in usbsettings:
|
245
|
+
pairs = usbsettings['usb_forwarded_ports'].split(',')
|
246
|
+
idx = 1
|
247
|
+
for pair in pairs:
|
248
|
+
if self.ethoverusb:
|
249
|
+
keyname = 'EthOverUSBPortForwardingPortMapping_{}'.format(idx)
|
250
|
+
else:
|
251
|
+
keyname = 'NetMgrUsb0PortForwardingPortMapping.{}'.format(idx)
|
252
|
+
pair = pair.replace(':', ',')
|
253
|
+
bmcattribs[keyname] = pair
|
254
|
+
idx += 1
|
255
|
+
while idx < 11:
|
256
|
+
if self.ethoverusb:
|
257
|
+
keyname = 'EthOverUSBPortForwardingPortMapping_{}'.format(idx)
|
258
|
+
else:
|
259
|
+
keyname = 'NetMgrUsb0PortForwardingPortMapping.{}'.format(idx)
|
260
|
+
bmcattribs[keyname] = '0,0'
|
261
|
+
idx += 1
|
262
|
+
if 'usb_ethernet' in usbsettings:
|
263
|
+
keyname = 'EthOverUSBEnabled' if self.ethoverusb else 'NetMgrUsb0Enabled'
|
264
|
+
bmcattribs[keyname] = usbsettings['usb_ethernet']
|
265
|
+
if 'usb_ethernet_port_forwarding' in usbsettings:
|
266
|
+
keyname = 'EthOverUSBPortForwardingEnabled' if self.ethoverusb else 'NetMgrUsb0PortForwardingEnabled'
|
267
|
+
bmcattribs[keyname] = usbsettings[
|
268
|
+
'usb_ethernet_port_forwarding']
|
269
|
+
self._do_web_request(
|
270
|
+
'/redfish/v1/Managers/1/Oem/Lenovo/BMCSettings',
|
271
|
+
{'Attributes': bmcattribs}, method='PATCH')
|
272
|
+
self._do_web_request(
|
273
|
+
'/redfish/v1/Managers/1/Oem/Lenovo/BMCSettings', cache=False)
|
274
|
+
|
275
|
+
def get_extended_bmc_configuration(self, fishclient, hideadvanced=True):
|
276
|
+
cfgin = self._get_lnv_bmcstgs(fishclient)[0]
|
277
|
+
cfgout = {}
|
278
|
+
for stgname in cfgin:
|
279
|
+
cfgout[f'BMC.{stgname}'] = cfgin[stgname]
|
280
|
+
vpdin = self._get_lnv_vpd(fishclient)[0]
|
281
|
+
for stgname in vpdin:
|
282
|
+
cfgout[f'VPD.{stgname}'] = vpdin[stgname]
|
283
|
+
return cfgout
|
284
|
+
|
285
|
+
def _get_lnv_vpd(self, fishclient):
|
286
|
+
currsettings, reginfo = self._get_lnv_stgs(
|
287
|
+
fishclient, '/redfish/v1/Chassis/1/Oem/Lenovo/SysvpdSettings')
|
288
|
+
self.lenovobmcattrdeps = reginfo[3]
|
289
|
+
return currsettings, reginfo
|
290
|
+
|
291
|
+
def _get_lnv_bmcstgs(self, fishclient):
|
292
|
+
currsettings, reginfo = self._get_lnv_stgs(
|
293
|
+
fishclient, '/redfish/v1/Managers/1/Oem/Lenovo/BMCSettings')
|
294
|
+
self.lenovobmcattrdeps = reginfo[3]
|
295
|
+
return currsettings, reginfo
|
296
|
+
|
297
|
+
def _get_lnv_stgs(self, fishclient, url):
|
298
|
+
bmcstgs = fishclient._do_web_request(url)
|
299
|
+
bmcreg = bmcstgs.get('AttributeRegistry', None)
|
300
|
+
extrainfo = {}
|
301
|
+
valtodisplay = {}
|
302
|
+
currsettings = {}
|
303
|
+
reginfo = {}, {}, {}, {}
|
304
|
+
if bmcreg:
|
305
|
+
reginfo = self._get_attrib_registry(fishclient, bmcreg)
|
306
|
+
if reginfo:
|
307
|
+
extrainfo, valtodisplay, _, _ = reginfo
|
308
|
+
for setting in bmcstgs.get('Attributes', {}):
|
309
|
+
val = bmcstgs['Attributes'][setting]
|
310
|
+
currval = val
|
311
|
+
val = valtodisplay.get(setting, {}).get(val, val)
|
312
|
+
val = {'value': val}
|
313
|
+
val.update(**extrainfo.get(setting, {}))
|
314
|
+
currsettings[setting] = val
|
315
|
+
return currsettings, reginfo
|
316
|
+
|
317
|
+
def get_description(self, fishclient):
|
318
|
+
rsp = self._get_expanded_data('/redfish/v1/Chassis')
|
319
|
+
for chassis in rsp['Members']:
|
320
|
+
if (chassis['@odata.id'] == '/redfish/v1/Chassis/1'
|
321
|
+
and chassis['ChassisType'] != 'Blade'):
|
322
|
+
hmm = chassis.get('HeightMm', None)
|
323
|
+
if hmm:
|
324
|
+
return {'height': hmm/44.45}
|
325
|
+
if (chassis['@odata.id'] == '/redfish/v1/Chassis/Enclosure'
|
326
|
+
and chassis.get('ChassisType', None) == 'Enclosure'):
|
327
|
+
try:
|
328
|
+
slot = chassis['Location']['PartLocation']['LocationOrdinalValue']
|
329
|
+
slotnum = (2 * (slot >> 4) - 1) + ((slot & 15) % 10)
|
330
|
+
slotcoord = [slot >> 4, (slot & 15) - 9]
|
331
|
+
return {'slot': slotnum, 'slotlabel': '{:02x}'.format(slot), 'slotcoord': slotcoord}
|
332
|
+
except KeyError:
|
333
|
+
continue
|
334
|
+
return {}
|
335
|
+
|
336
|
+
def upload_media(self, filename, progress=None, data=None):
|
337
|
+
wc = self.webclient
|
338
|
+
uploadthread = webclient.FileUploader(
|
339
|
+
wc, '/rdoc_upload', filename, data,
|
340
|
+
formname='file',
|
341
|
+
formwrap=True)
|
342
|
+
uploadthread.start()
|
343
|
+
while uploadthread.isAlive():
|
344
|
+
uploadthread.join(3)
|
345
|
+
if progress:
|
346
|
+
progress({'phase': 'upload',
|
347
|
+
'progress': 100 * wc.get_upload_progress()})
|
348
|
+
rsp = json.loads(uploadthread.rsp)
|
349
|
+
if rsp['return'] != 0:
|
350
|
+
raise Exception('Issue uploading file')
|
351
|
+
remfilename = rsp['upload_filename']
|
352
|
+
if progress:
|
353
|
+
progress({'phase': 'upload',
|
354
|
+
'progress': 100.0})
|
355
|
+
self._do_web_request(
|
356
|
+
'/redfish/v1/Systems/1/VirtualMedia/RDOC1',
|
357
|
+
{'Image':'file:///gpx/rdocupload/' + remfilename,
|
358
|
+
'WriteProtected': False}, method='PATCH')
|
359
|
+
if progress:
|
360
|
+
progress({'phase': 'complete'})
|
361
|
+
|
362
|
+
def get_firmware_inventory(self, components, fishclient):
|
363
|
+
fwlist = fishclient._do_web_request(fishclient._fwinventory + '?$expand=.')
|
364
|
+
fwlist = copy.deepcopy(fwlist.get('Members', []))
|
365
|
+
self._fwnamemap = {}
|
366
|
+
for redres in fwlist:
|
367
|
+
fwurl = redres['@odata.id']
|
368
|
+
res = (redres, fwurl)
|
369
|
+
if fwurl.startswith('/redfish/v1/UpdateService/FirmwareInventory/Bundle.'):
|
370
|
+
continue # skip Bundle information for now
|
371
|
+
if redres.get('Name', '').startswith('Firmware:'):
|
372
|
+
redres['Name'] = redres['Name'].replace('Firmware:', '')
|
373
|
+
if redres['Name'].startswith('Firmware-PSoC') and 'Drive_Backplane' in redres["@odata.id"]:
|
374
|
+
redres['Name'] = 'Drive Backplane'
|
375
|
+
if redres['Name'].startswith('DEVICE-'):
|
376
|
+
redres['Name'] = redres['Name'].replace('DEVICE-', '')
|
377
|
+
if redres['Name'].startswith('POWER-PSU'):
|
378
|
+
redres['Name'] = redres['Name'].replace('POWER-', '')
|
379
|
+
swid = redres.get('SoftwareId', '')
|
380
|
+
buildid = ''
|
381
|
+
version = redres.get('Version', None)
|
382
|
+
if swid.startswith('FPGA-') or swid.startswith('UEFI-') or swid.startswith('BMC-'):
|
383
|
+
buildid = swid.split('-')[1] + version.split('-')[0]
|
384
|
+
version = '-'.join(version.split('-')[1:])
|
385
|
+
if version:
|
386
|
+
redres['Version'] = version
|
387
|
+
cres = fishclient._extract_fwinfo(res)
|
388
|
+
if cres[0] is None:
|
389
|
+
continue
|
390
|
+
if buildid:
|
391
|
+
cres[1]['build'] = buildid
|
392
|
+
yield cres
|
393
|
+
raise pygexc.BypassGenericBehavior()
|
394
|
+
|
395
|
+
|
pyghmi/util/webclient.py
CHANGED
@@ -97,8 +97,9 @@ class FileDownloader(threading.Thread):
|
|
97
97
|
|
98
98
|
|
99
99
|
def get_upload_form(filename, data, formname, otherfields):
|
100
|
+
ffilename = filename.split('/')[-1]
|
100
101
|
if not formname:
|
101
|
-
formname =
|
102
|
+
formname = ffilename
|
102
103
|
try:
|
103
104
|
return uploadforms[filename]
|
104
105
|
except KeyError:
|
@@ -106,16 +107,22 @@ def get_upload_form(filename, data, formname, otherfields):
|
|
106
107
|
data = data.read()
|
107
108
|
except AttributeError:
|
108
109
|
pass
|
109
|
-
form =
|
110
|
+
form = b''
|
111
|
+
for ofield in otherfields:
|
112
|
+
tfield = otherfields[ofield]
|
113
|
+
xtra=''
|
114
|
+
if isinstance(tfield, dict):
|
115
|
+
tfield = json.dumps(tfield)
|
116
|
+
xtra = '\r\nContent-Type: application/json'
|
117
|
+
form += (b'--' + BND
|
118
|
+
+ '\r\nContent-Disposition: form-data; '
|
119
|
+
'name="{0}"{1}\r\n\r\n{2}\r\n'.format(
|
120
|
+
ofield, xtra, tfield).encode('utf-8'))
|
121
|
+
form += (b'--' + BND
|
110
122
|
+ '\r\nContent-Disposition: form-data; '
|
111
123
|
'name="{0}"; filename="{1}"\r\n'.format(
|
112
|
-
formname,
|
124
|
+
formname, ffilename).encode('utf-8'))
|
113
125
|
form += b'Content-Type: application/octet-stream\r\n\r\n' + data
|
114
|
-
for ofield in otherfields:
|
115
|
-
form += (b'\r\n--' + BND
|
116
|
-
+ '\r\nContent-Disposition: form-data; '
|
117
|
-
'name="{0}"\r\n\r\n{1}'.format(
|
118
|
-
ofield, otherfields[ofield]).encode('utf-8'))
|
119
126
|
form += b'\r\n--' + BND + b'--\r\n'
|
120
127
|
uploadforms[filename] = form
|
121
128
|
return form
|
@@ -272,6 +279,22 @@ class SecureHTTPConnection(httplib.HTTPConnection, object):
|
|
272
279
|
return json.loads(body) if body else {}, rsp.status
|
273
280
|
return body, rsp.status
|
274
281
|
|
282
|
+
def grab_rsp(self, url, data=None, referer=None, headers=None, method=None):
|
283
|
+
webclient = self.dupe()
|
284
|
+
if isinstance(data, dict):
|
285
|
+
data = json.dumps(data)
|
286
|
+
if data:
|
287
|
+
if not method:
|
288
|
+
method = 'POST'
|
289
|
+
webclient.request(method, url, data, referer=referer,
|
290
|
+
headers=headers)
|
291
|
+
else:
|
292
|
+
if not method:
|
293
|
+
method = 'GET'
|
294
|
+
webclient.request(method, url, referer=referer, headers=headers)
|
295
|
+
rsp = webclient.getresponse()
|
296
|
+
return rsp
|
297
|
+
|
275
298
|
def download(self, url, file):
|
276
299
|
"""Download a file to filename or file object
|
277
300
|
|
@@ -390,3 +413,4 @@ class SecureHTTPConnection(httplib.HTTPConnection, object):
|
|
390
413
|
except httplib.CannotSendRequest:
|
391
414
|
self.broken = True
|
392
415
|
raise
|
416
|
+
|
@@ -1,12 +1,11 @@
|
|
1
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
|
-
Platform: UNKNOWN
|
10
9
|
Classifier: Intended Audience :: Information Technology
|
11
10
|
Classifier: Intended Audience :: System Administrators
|
12
11
|
Classifier: License :: OSI Approved :: Apache Software License
|
@@ -18,14 +17,14 @@ Classifier: Programming Language :: Python :: 3
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.6
|
19
18
|
Classifier: Programming Language :: Python :: 3.7
|
20
19
|
Classifier: Programming Language :: Python :: 3.8
|
21
|
-
|
22
|
-
|
23
|
-
Requires-Dist:
|
20
|
+
License-File: LICENSE
|
21
|
+
License-File: AUTHORS
|
22
|
+
Requires-Dist: cryptography >=2.1
|
23
|
+
Requires-Dist: python-dateutil >=2.8.1
|
24
|
+
Requires-Dist: six >=1.10.0
|
24
25
|
|
25
26
|
This is a pure python implementation of IPMI protocol.
|
26
27
|
|
27
28
|
pyghmicons and pyghmiutil are example scripts to show how one may incorporate
|
28
29
|
this library into python code
|
29
30
|
|
30
|
-
|
31
|
-
|