pyghmi 1.6.0__py3-none-any.whl → 1.6.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -108,7 +108,8 @@ class ServerSession(ipmisession.Session):
108
108
  # ignore null username for now
109
109
  return
110
110
  self.username = bytes(data[28:])
111
- if self.username.decode('utf-8') not in self.authdata:
111
+ password = self.authdata.get(self.username.decode('utf-8'))
112
+ if password is None:
112
113
  # don't think about invalid usernames for now
113
114
  return
114
115
  uuidbytes = self.uuid.bytes
@@ -118,8 +119,7 @@ class ServerSession(ipmisession.Session):
118
119
  + self.Rm + self.Rc + uuidbytes
119
120
  + bytearray([self.rolem, len(self.username)]))
120
121
  hmacdata += self.username
121
- self.kuid = self.authdata[self.username.decode('utf-8')].encode(
122
- 'utf-8')
122
+ self.kuid = password.encode('utf-8')
123
123
  if self.kg is None:
124
124
  self.kg = self.kuid
125
125
  authcode = hmac.new(
pyghmi/redfish/command.py CHANGED
@@ -522,10 +522,8 @@ class Command(object):
522
522
  raise exc.InvalidParameterValue(
523
523
  "Unknown power state %s requested" % powerstate)
524
524
  powerstate = powerstates[powerstate]
525
- result = self.wc.grab_json_response_with_status(
525
+ self._do_web_request(
526
526
  self.powerurl, {'ResetType': powerstate})
527
- if result[1] < 200 or result[1] >= 300:
528
- raise exc.PyghmiException(result[0])
529
527
  if wait and reqpowerstate in ('on', 'off', 'softoff', 'shutdown'):
530
528
  if reqpowerstate in ('softoff', 'shutdown'):
531
529
  reqpowerstate = 'off'
@@ -846,6 +844,8 @@ class Command(object):
846
844
  self._do_web_request(url, {'ResetType': action})
847
845
 
848
846
  def set_identify(self, on=True, blink=None):
847
+ if hasattr(self.oem, 'set_identify'):
848
+ return self.oem.set_identify(on, blink)
849
849
  targurl = self.sysurl
850
850
  if not targurl:
851
851
  root = self._do_web_request('/redfish/v1')
@@ -193,6 +193,7 @@ class OEMHandler(object):
193
193
  self._varsysurl = sysurl
194
194
  self._urlcache = cache
195
195
  self.webclient = webclient
196
+ self._hwnamemap = {}
196
197
 
197
198
  def get_screenshot(self, outfile):
198
199
  raise exc.UnsupportedFunctionality(
@@ -62,6 +62,19 @@ class OEMHandler(generic.OEMHandler):
62
62
  health = healthlookup.get(health, pygconst.Health.Critical)
63
63
  return {'health': health}
64
64
 
65
+ def set_identify(self, on=True, blink=False):
66
+ if on:
67
+ state = 'On'
68
+ elif blink:
69
+ state = 'Blinking'
70
+ else:
71
+ state = 'Off'
72
+ self._do_web_request('/redfish/v1/Chassis/chassis1', {
73
+ 'Oem': {'Lenovo': {'LED': {'IdentifyLED': {
74
+ 'State': state
75
+ }}}
76
+ }}, method='PATCH')
77
+
65
78
  def get_system_configuration(self, hideadvanced=True, fishclient=None):
66
79
  return {}
67
80
 
@@ -13,14 +13,39 @@
13
13
  # limitations under the License.
14
14
  import copy
15
15
  import json
16
+ import re
16
17
  import pyghmi.constants as pygconst
17
18
  import pyghmi.redfish.oem.generic as generic
18
19
  import pyghmi.exceptions as pygexc
19
20
  import pyghmi.util.webclient as webclient
21
+ import pyghmi.storage as storage
22
+ import pyghmi.ipmi.private.util as util
20
23
  import zipfile
21
24
  import time
22
25
  import os.path
23
26
 
27
+ numregex = re.compile('([0-9]+)')
28
+
29
+ def naturalize_string(key):
30
+ """Analyzes string in a human way to enable natural sort
31
+
32
+ :param key: string for the split
33
+ :returns: A structure that can be consumed by 'sorted'
34
+ """
35
+ return [int(text) if text.isdigit() else text.lower()
36
+ for text in re.split(numregex, key)]
37
+
38
+ def natural_sort(iterable):
39
+ """Return a sort using natural sort if possible
40
+
41
+ :param iterable:
42
+ :return:
43
+ """
44
+ try:
45
+ return sorted(iterable, key=naturalize_string)
46
+ except TypeError:
47
+ # The natural sort attempt failed, fallback to ascii sort
48
+ return sorted(iterable)
24
49
 
25
50
  class SensorReading(object):
26
51
  def __init__(self, healthinfo, sensor=None, value=None, units=None,
@@ -39,8 +64,647 @@ class SensorReading(object):
39
64
 
40
65
  class OEMHandler(generic.OEMHandler):
41
66
 
67
+ datacache = {}
68
+
42
69
  def supports_expand(self, url):
43
70
  return True
71
+
72
+ def weblogout(self):
73
+ if self.webclient:
74
+ try:
75
+ self.webclient.grab_json_response('/logout')
76
+ except Exception:
77
+ pass
78
+
79
+ def get_cached_data(self, attribute, age=30):
80
+ try:
81
+ kv = self.datacache[attribute]
82
+ if kv[1] > util._monotonic_time() - age:
83
+ return kv[0]
84
+ except KeyError:
85
+ return None
86
+
87
+ def get_inventory(self, withids=False):
88
+ sysinfo = {
89
+ 'UUID': self._varsysinfo.get('UUID', '').lower(),
90
+ 'Serial Number': self._varsysinfo.get('SerialNumber', ''),
91
+ 'Manufacturer': self._varsysinfo.get('Manufacturer', ''),
92
+ 'Product name': self._varsysinfo.get('Model', ''),
93
+ 'Model': self._varsysinfo.get(
94
+ 'SKU', self._varsysinfo.get('PartNumber', '')),
95
+ }
96
+ yield ('System', sysinfo)
97
+ for cpuinv in self._get_cpu_inventory():
98
+ yield cpuinv
99
+ for meminv in self._get_mem_inventory():
100
+ yield meminv
101
+ hwmap = self.hardware_inventory_map()
102
+ for key in natural_sort(hwmap):
103
+ yield (key, hwmap[key])
104
+
105
+ def hardware_inventory_map(self):
106
+ hwmap = self.get_cached_data('lenovo_cached_hwmap')
107
+ if hwmap:
108
+ return hwmap
109
+ hwmap = {}
110
+ for disk in self.disk_inventory(mode=1): # hardware mode
111
+ hwmap[disk[0]] = disk[1]
112
+ adapterdata = self.get_cached_data('lenovo_cached_adapters')
113
+ if not adapterdata:
114
+ # if self.updating:
115
+ # raise pygexc.TemporaryError(
116
+ # 'Cannot read extended inventory during firmware update')
117
+ if self.webclient:
118
+ adapterdata = self._do_bulk_requests([i['@odata.id'] for i in self.webclient.grab_json_response(
119
+ '/redfish/v1/Chassis/1')['Links']['PCIeDevices']])
120
+ if adapterdata:
121
+ self.datacache['lenovo_cached_adapters'] = (
122
+ adapterdata, util._monotonic_time())
123
+ if adapterdata:
124
+ anames = {}
125
+ for adata, _ in adapterdata:
126
+ skipadapter = False
127
+ clabel = adata['Slot']['Location']['PartLocation']['LocationType']
128
+ if clabel != 'Embedded':
129
+ aslot = adata['Slot']['Location']['PartLocation']['LocationOrdinalValue']
130
+ clabel = 'Slot {0}'.format(aslot)
131
+ aname = adata['Name']
132
+ bdata = {'location': clabel, 'name': aname}
133
+ if aname in anames:
134
+ anames[aname] += 1
135
+ aname = '{0} {1}'.format(aname, anames[aname])
136
+ else:
137
+ anames[aname] = 1
138
+ pcislot = adata['Id'].split('_')[1]
139
+ bdata['pcislot'] = '{0}:{1}.{2}'.format(
140
+ pcislot[:4].replace('0x',''), pcislot[4:6], pcislot[6:8]
141
+ )
142
+ serialdata = adata.get('SerialNumber', '')
143
+ if (serialdata and serialdata != 'N/A'
144
+ and '---' not in serialdata):
145
+ bdata['serial'] = serialdata
146
+ partnum = adata.get('PartNumber', '')
147
+ if partnum and partnum != 'N/A':
148
+ bdata['Part Number'] = partnum
149
+ fundata = self._get_expanded_data(adata['PCIeFunctions']['@odata.id'])
150
+ venid = fundata['Members'][0].get('VendorId', None)
151
+ if venid is not None:
152
+ bdata['PCI Vendor ID'] = venid.lower().split('0x')[-1]
153
+ devid = fundata['Members'][0].get('DeviceId', None)
154
+ if devid is not None and 'PCIE Device ID' not in bdata:
155
+ bdata['PCI Device ID'] = devid.lower().split('0x')[-1]
156
+ subvenid = fundata['Members'][0].get('SubsystemVendorId', None)
157
+ if subvenid is not None:
158
+ bdata['PCI Subsystem Vendor ID'] = subvenid.lower().split('0x')[-1]
159
+ subdevid = fundata['Members'][0].get('SubsystemId', None)
160
+ if subdevid is not None:
161
+ bdata['PCI Subsystem Device ID'] = subdevid.lower().split('0x')[-1]
162
+ bdata['FRU Number'] = adata.get('SKU', '')
163
+
164
+ # Could be identified also through Oem->Lenovo->FunctionClass
165
+ if fundata['Members'][0]['DeviceClass'] == 'NetworkController':
166
+ ports_data = self._get_expanded_data('{0}/Ports'.format(adata['@odata.id'].replace('PCIeDevices','NetworkAdapters')))
167
+ for port in ports_data['Members']:
168
+ bdata['MAC Address {0}'.format(port['Id'])] = port['Ethernet']['AssociatedMACAddresses'][0].lower()
169
+ hwmap[aname] = bdata
170
+ self.datacache['lenovo_cached_hwmap'] = (hwmap,
171
+ util._monotonic_time())
172
+ # self.weblogout()
173
+ return hwmap
174
+
175
+ def get_disk_firmware(self, diskent, prefix=''):
176
+ bdata = {}
177
+ if not prefix:
178
+ location = diskent.get('Name', '')
179
+ if location.startswith('M.2'):
180
+ prefix = 'M.2-'
181
+ elif location.startswith('7MM'):
182
+ prefix = '7MM-'
183
+ diskname = 'Disk {0}{1}'.format(prefix, diskent['PhysicalLocation']['PartLocation']['LocationOrdinalValue'])
184
+ bdata['model'] = '{0}_{1}'.format(diskent['Manufacturer'].rstrip(), diskent['Model'].rstrip())
185
+ bdata['version'] = diskent.get('FirmwareVersion','')
186
+ return (diskname, bdata)
187
+
188
+ def get_disk_hardware(self, diskent, prefix=''):
189
+ bdata = {}
190
+ if not prefix:
191
+ location = diskent.get('Name', '')
192
+ if location.startswith('M.2'):
193
+ prefix = 'M.2-'
194
+ elif location.startswith('7MM'):
195
+ prefix = '7MM-'
196
+ diskname = 'Disk {0}{1}'.format(prefix, diskent['PhysicalLocation']['PartLocation']['LocationOrdinalValue'])
197
+ bdata['Model'] = '{0}_{1}'.format(diskent['Manufacturer'].rstrip(), diskent['Model'].rstrip())
198
+ bdata['Serial Number'] = diskent['SerialNumber'].rstrip()
199
+ bdata['FRU Number'] = diskent['SKU'].rstrip()
200
+ bdata['Description'] = diskent['Oem']['Lenovo']['TypeString'].rstrip()
201
+ return (diskname, bdata)
202
+
203
+ def disk_inventory(self, mode=0):
204
+ # mode 0 is firmware, 1 is hardware
205
+ storagedata = self.get_cached_data('lenovo_cached_storage')
206
+ if not storagedata:
207
+ if self.webclient:
208
+ storagedata = self._do_bulk_requests([i['@odata.id'] for i in self.webclient.grab_json_response(
209
+ '/redfish/v1/Chassis/1')['Links']['Drives']])
210
+ if storagedata:
211
+ self.datacache['lenovo_cached_storage'] = (
212
+ storagedata, util._monotonic_time())
213
+ # Unmanaged disks cannot be retrieved through Redfish API
214
+ if storagedata:
215
+ for diskent, _ in storagedata:
216
+ if mode == 0:
217
+ yield self.get_disk_firmware(diskent)
218
+ elif mode == 1:
219
+ yield self.get_disk_hardware(diskent)
220
+
221
+ def get_storage_configuration(self, logout=True):
222
+ rsp = self._get_expanded_data("/redfish/v1/Systems/1/Storage")
223
+ standalonedisks = []
224
+ pools = []
225
+ for item in rsp.get('Members',[]):
226
+ cdisks = [item['Drives'][i]['@odata.id'] for i in range(len(item['Drives']))]
227
+ cid = '{0},{1}'.format(
228
+ item['Id'],
229
+ item['StorageControllers'][0]['Location']['PartLocation'].get('LocationOrdinalValue', -1))
230
+ storage_pools = self._get_expanded_data(item['StoragePools']['@odata.id'])
231
+ for p in storage_pools['Members']:
232
+ vols = self._get_expanded_data(p['AllocatedVolumes']['@odata.id'])
233
+ for vol in vols['Members']:
234
+ volumes=[]
235
+ disks=[]
236
+ spares=[]
237
+ volumes.append(
238
+ storage.Volume(name=vol['DisplayName'],
239
+ size=int(vol['CapacityBytes'])/1024//1024,
240
+ status=vol['Status']['Health'],
241
+ id=(cid,vol['Id'])))
242
+ for item_key, disk_ids in vol['Links'].items():
243
+ if isinstance(disk_ids, list) and 'drives' in item_key.lower():
244
+ for disk in disk_ids:
245
+ if disk['@odata.id'] in cdisks:
246
+ cdisks.remove(disk['@odata.id'])
247
+ disk_data = self.webclient.grab_json_response(disk['@odata.id'])
248
+ (spares if disk_data['Oem']['Lenovo']['DriveStatus']=="DedicatedHotspare" else disks).append(
249
+ storage.Disk(
250
+ name=disk_data['Name'], description=disk_data['Oem']['Lenovo']['TypeString'],
251
+ id=(cid, disk_data['Id']), status=disk_data['Oem']['Lenovo']['DriveStatus'],
252
+ serial=disk_data['SerialNumber'], fru=disk_data['SKU']))
253
+ raid=vol['RAIDType']
254
+ totalsize = int(p['Capacity']['Data']['AllocatedBytes'])/1024//1024
255
+ freesize = totalsize - int(p['Capacity']['Data']['ConsumedBytes'])/1024//1024
256
+ pools.append(storage.Array(
257
+ disks=disks, raid=raid, volumes=volumes,
258
+ id=(cid, p['Id']), hotspares=spares,
259
+ capacity=totalsize, available_capacity=freesize))
260
+ for d in cdisks:
261
+ disk_data = self.webclient.grab_json_response(d)
262
+ standalonedisks.append(
263
+ storage.Disk(
264
+ name=disk_data['Name'], description=disk_data['Oem']['Lenovo']['TypeString'],
265
+ id=(cid, disk_data['Id']), status=disk_data['Oem']['Lenovo']['DriveStatus'],
266
+ serial=disk_data['SerialNumber'], fru=disk_data['SKU']))
267
+ return storage.ConfigSpec(disks=standalonedisks, arrays=pools)
268
+
269
+ def check_storage_configuration(self, cfgspec=None):
270
+ rsp = self.webclient.grab_json_response(
271
+ '/api/providers/raidlink_GetStatus')
272
+ if rsp['return'] != 0 or rsp['status'] != 1:
273
+ raise pygexc.TemporaryError('Storage configuration unavailable in '
274
+ 'current state (try boot to setup or '
275
+ 'an OS)')
276
+ return True
277
+
278
+ def apply_storage_configuration(self, cfgspec):
279
+ realcfg = self.get_storage_configuration(False)
280
+ for disk in cfgspec.disks:
281
+ if disk.status.lower() == 'jbod':
282
+ self._make_jbod(disk, realcfg)
283
+ elif disk.status.lower() == 'hotspare':
284
+ self._make_global_hotspare(disk, realcfg)
285
+ elif disk.status.lower() in ('unconfigured', 'available', 'ugood',
286
+ 'unconfigured good'):
287
+ self._make_available(disk, realcfg)
288
+ for pool in cfgspec.arrays:
289
+ if pool.disks:
290
+ self._create_array(pool)
291
+
292
+ def _make_available(self, disk, realcfg):
293
+ currstatus = self._get_status(disk, realcfg)
294
+ newstate = None
295
+ if currstatus.lower() == 'unconfiguredgood':
296
+ return
297
+ elif currstatus.lower() == 'globalhotspare':
298
+ newstate = "None"
299
+ elif currstatus.lower() == 'jbod':
300
+ newstate = "MakeUnconfiguredGood"
301
+ self._set_drive_state(disk, newstate)
302
+
303
+ def _make_jbod(self, disk, realcfg):
304
+ currstatus = self._get_status(disk, realcfg)
305
+ if currstatus.lower() == 'jbod':
306
+ return
307
+ self._make_available(disk, realcfg)
308
+ self._set_drive_state(disk, "MakeJBOD")
309
+
310
+ def _make_global_hotspare(self, disk, realcfg):
311
+ currstatus = self._get_status(disk, realcfg)
312
+ if currstatus.lower() == 'globalhotspare':
313
+ return
314
+ self._make_available(disk, realcfg)
315
+ self._set_drive_state(disk, "Global")
316
+
317
+ def _set_drive_state(self, disk, state):
318
+ raid_alldevices = self.webclient.grab_json_response(
319
+ '/api/providers/raid_alldevices')
320
+ if raid_alldevices.get('return', -1) != 0:
321
+ raise Exception(
322
+ 'Unexpected return to get all RAID devices information')
323
+ for c in raid_alldevices.get('StorageComplexes',[]):
324
+ cslot = str(c.get('SlotNumber'))
325
+ if cslot == disk.id[0].split(',')[1]:
326
+ c_pciaddr = c.get('PCIeAddress',-1)
327
+ cdrives = c.get('Drives',[])
328
+ for d in cdrives:
329
+ if disk.id[1] == d.get('Id',''):
330
+ currstatus = d['Oem']['Lenovo']['DriveStatus']
331
+ d_resid = d['Internal']['ResourceId']
332
+ if state in ("Global", "None"):
333
+ data = {
334
+ "controller_address": c_pciaddr,
335
+ "drive_resource_id": d_resid,
336
+ "hotspare_type": state,
337
+ "pool_resource_ids": []}
338
+ raidlink_url = '/api/providers/raidlink_AssignHotSpare'
339
+ else:
340
+ data = {
341
+ "controller_address": c_pciaddr,
342
+ "drive_operation": state,
343
+ "drive_resource_ids": [d_resid]}
344
+ raidlink_url = '/api/providers/raidlink_DiskStateAction'
345
+ msg = self._do_web_request(raidlink_url, method='POST',
346
+ payload=data, cache=False)
347
+ if msg.get('return', -1) != 0:
348
+ raise Exception(
349
+ 'Unexpected return to set disk state: {0}'.format(
350
+ msg.get('return', -1)))
351
+ set_state_token = msg.get('token', '')
352
+ msg = self._do_web_request(
353
+ '/api/providers/raidlink_QueryAsyncStatus',
354
+ method='POST',
355
+ payload={"token": set_state_token},
356
+ cache=False)
357
+ while msg['status'] == 2:
358
+ time.sleep(1)
359
+ msg = self._do_web_request(
360
+ '/api/providers/raidlink_QueryAsyncStatus',
361
+ method='POST',
362
+ payload={"token": set_state_token},
363
+ cache=False)
364
+ if msg.get('return',-1) != 0 or msg.get('status',-1) != 0:
365
+ raise Exception(
366
+ 'Unexpected return to set disk state: {0}'.format(
367
+ msg.get('return', -1)))
368
+ disk_url=f"/redfish/v1/Systems/1/Storage/{disk.id[0].split(',')[0]}/Drives/{disk.id[1]}"
369
+ newstatus = self.webclient.grab_json_response(disk_url)
370
+ disk_converted = False
371
+ for _ in range(60):
372
+ if currstatus == newstatus['Oem']['Lenovo']['DriveStatus']:
373
+ time.sleep(1)
374
+ newstatus = self.webclient.grab_json_response(disk_url)
375
+ else:
376
+ disk_converted = True
377
+ break
378
+ if not disk_converted:
379
+ raise Exception(
380
+ 'Disk set command was successful, but the disk state is unchanged')
381
+
382
+ def _get_status(self, disk, realcfg):
383
+ for cfgdisk in realcfg.disks:
384
+ if disk.id == cfgdisk.id:
385
+ currstatus = cfgdisk.status
386
+ break
387
+ else:
388
+ raise pygexc.InvalidParameterValue('Requested disk not found')
389
+ return currstatus
390
+
391
+ def remove_storage_configuration(self, cfgspec):
392
+ realcfg = self.get_storage_configuration(False)
393
+ for pool in cfgspec.arrays:
394
+ for volume in pool.volumes:
395
+ cid = volume.id[0].split(',')[0]
396
+ vid = volume.id[1]
397
+ msg, code = self.webclient.grab_json_response_with_status(
398
+ f'/redfish/v1/Systems/1/Storage/{cid}/Volumes/{vid}',
399
+ method='DELETE')
400
+ if code == 500:
401
+ raise Exception(
402
+ 'Unexpected return to volume deletion: ' + repr(msg))
403
+ for disk in cfgspec.disks:
404
+ self._make_available(disk, realcfg)
405
+
406
+ def _parse_array_spec(self, arrayspec):
407
+ controller = None
408
+ if arrayspec.disks:
409
+ for disk in list(arrayspec.disks) + list(arrayspec.hotspares):
410
+ if controller is None:
411
+ controller = disk.id[0]
412
+ if controller != disk.id[0]:
413
+ raise pygexc.UnsupportedFunctionality(
414
+ 'Cannot span arrays across controllers')
415
+ raidmap = self._raid_number_map(controller)
416
+ if not raidmap:
417
+ raise pygexc.InvalidParameterValue(
418
+ 'No RAID Type supported on this controller')
419
+ requestedlevel = str(arrayspec.raid)
420
+ if requestedlevel not in raidmap:
421
+ raise pygexc.InvalidParameterValue(
422
+ 'Requested RAID Type "{0}" not available on this '
423
+ 'controller. Allowed values are: {1}'.format(
424
+ requestedlevel, [k for k in raidmap]))
425
+ rdinfo = raidmap[str(arrayspec.raid).lower()]
426
+ rdlvl = str(rdinfo[0])
427
+ defspan = 1 if rdinfo[1] == 1 else 2
428
+ spancount = defspan if arrayspec.spans is None else arrayspec.spans
429
+ drivesperspan = str(len(arrayspec.disks) // int(spancount))
430
+ hotspares = arrayspec.hotspares
431
+ drives = arrayspec.disks
432
+ minimal_conditions = {
433
+ "RAID0": (1,128,1),
434
+ "RAID1": (2,2,1),
435
+ "RAID1Triple": (3,3,1),
436
+ "RAID10": (4,128,2),
437
+ "RAID10Triple": (6,128,3),
438
+ "RAID5": (3,128,1),
439
+ "RAID50": (6,128,2),
440
+ "RAID6": (4,128,1),
441
+ "RAID60": (8, 128, 2)
442
+ }
443
+ raid_level = rdinfo[0]
444
+ min_pd = minimal_conditions[raid_level][0]
445
+ max_pd = minimal_conditions[raid_level][1]
446
+ disk_multiplier = minimal_conditions[raid_level][2]
447
+ if len(drives) < min_pd or \
448
+ len(drives) > max_pd or \
449
+ len(drives) % disk_multiplier != 0:
450
+ raise pygexc.InvalidParameterValue(
451
+ f'Number of disks for {rdinfo} must be between {min_pd} and {max_pd} and be a multiple of {disk_multiplier}')
452
+ if hotspares:
453
+ hstr = '|'.join([str(x.id[1]) for x in hotspares]) + '|'
454
+ else:
455
+ hstr = ''
456
+ drvstr = '|'.join([str(x.id[1]) for x in drives]) + '|'
457
+ return {
458
+ 'controller': controller,
459
+ 'drives': drvstr,
460
+ 'hotspares': hstr,
461
+ 'raidlevel': rdlvl,
462
+ 'spans': spancount,
463
+ 'perspan': drivesperspan,
464
+ }
465
+ else:
466
+ # TODO(Jarrod Johnson): adding new volume to
467
+ # existing array would be here
468
+ pass
469
+
470
+ def _raid_number_map(self, controller):
471
+ themap = {}
472
+ cid = controller.split(',')
473
+ rsp = self.webclient.grab_json_response(
474
+ '/redfish/v1/Systems/1/Storage/{0}'.format(cid[0]))
475
+ for rt in rsp['StorageControllers'][0]['SupportedRAIDTypes']:
476
+ rt_lower = rt.lower()
477
+ mapdata = (rt, 1)
478
+ themap[rt]=mapdata
479
+ themap[rt_lower] = mapdata
480
+ themap[rt_lower.replace('raid','r')] = mapdata
481
+ themap[rt_lower.replace('raid','')] = mapdata
482
+ return themap
483
+
484
+ def _create_array(self, pool):
485
+ params = self._parse_array_spec(pool)
486
+ cid = params['controller'].split(',')[0]
487
+ c_capabilities, code = self.webclient.grab_json_response_with_status(
488
+ f'/redfish/v1/Systems/1/Storage/{cid}/Volumes/Capabilities')
489
+ if code == 404:
490
+ c_capabilities, code = self.webclient.grab_json_response_with_status(
491
+ f'/redfish/v1/Systems/1/Storage/{cid}/Volumes/Oem/Lenovo/Capabilities')
492
+ if code == 404:
493
+ # If none of the endpoints exist, maybe it should be printed that
494
+ # no capabilities found, therefore default values will be used
495
+ # whatever they are
496
+ pass
497
+ volumes = pool.volumes
498
+ drives = [d for d in params['drives'].split("|") if d != '']
499
+ hotspares = [h for h in params['hotspares'].split("|") if h != '']
500
+ raidlevel = params['raidlevel']
501
+ nameappend = 1
502
+ currvolnames = None
503
+ currcfg = self.get_storage_configuration(False)
504
+ for vol in volumes:
505
+ if vol.name is None:
506
+ # need to iterate while there exists a volume of that name
507
+ if currvolnames is None:
508
+ currvolnames = set([])
509
+ for pool in currcfg.arrays:
510
+ for volume in pool.volumes:
511
+ currvolnames.add(volume.name)
512
+ name = 'Volume_{0}'.format(nameappend)
513
+ nameappend += 1
514
+ while name in currvolnames:
515
+ name = 'Volume_{0}'.format(nameappend)
516
+ nameappend += 1
517
+ else:
518
+ name = vol.name
519
+
520
+ # Won't check against Redfish allowable values as they not trustworthy yet
521
+ # Some values show in Redfish, but may not be accepted by UEFI/controller or vice versa
522
+ stripsize_map = {
523
+ '4': 4096, '4096': 4096,
524
+ '16': 16384, '16384': 16384,
525
+ '32': 32768, '32768': 32768,
526
+ '64': 65536, '65536': 65536,
527
+ '128': 131072, '131072': 131072,
528
+ '256': 262144, '262144': 262144,
529
+ '512': 524288, '524288': 524288,
530
+ '1024': 1048576, '1048576': 1048576
531
+ }
532
+ stripsize = stripsize_map[str(vol.stripsize).lower().replace('k','')] if vol.stripsize is not None else None
533
+
534
+ readpolicy_map = {'0': 'Off', '1': 'ReadAhead'}
535
+ read_policy = None
536
+ read_cache_possible = c_capabilities.get("ReadCachePolicy@Redfish.AllowableValues",[])
537
+ if read_cache_possible:
538
+ if vol.read_policy is not None:
539
+ if str(vol.read_policy) in readpolicy_map:
540
+ vol.read_policy = readpolicy_map[str(vol.read_policy)]
541
+ if vol.read_policy in read_cache_possible:
542
+ read_policy = vol.read_policy
543
+ else:
544
+ raise pygexc.InvalidParameterValue(
545
+ f'{vol.read_policy} Read Cache Policy is not supported. Allowed values are: {read_cache_possible}')
546
+
547
+ writepolicy_map = {'0': 'WriteThrough', '1': 'UnprotectedWriteBack',
548
+ '2': 'ProtectedWriteBack', '3': 'Off'}
549
+ write_policy = None
550
+ write_cache_possible = c_capabilities.get("WriteCachePolicy@Redfish.AllowableValues",[])
551
+ if write_cache_possible:
552
+ if vol.write_policy is not None:
553
+ if str(vol.write_policy) in writepolicy_map:
554
+ vol.write_policy = writepolicy_map[str(vol.write_policy)]
555
+ if vol.write_policy in write_cache_possible:
556
+ write_policy = vol.write_policy
557
+ else:
558
+ raise pygexc.InvalidParameterValue(
559
+ f'{vol.write_policy} Write Cache Policy is not supported. Allowed values are: {write_cache_possible}')
560
+
561
+ defaultinit_map = {'0': 'No', '1': 'Fast', '2': 'Full'}
562
+ default_init = None
563
+ default_init_possible = c_capabilities.get("InitializationType@Redfish.AllowableValues",[])
564
+ if default_init_possible:
565
+ if vol.default_init is not None:
566
+ if str(vol.default_init) in defaultinit_map:
567
+ vol.default_init = defaultinit_map[str(vol.default_init)]
568
+ if vol.default_init in default_init_possible:
569
+ default_init = vol.default_init
570
+ else:
571
+ raise pygexc.InvalidParameterValue(
572
+ f'{vol.default_init} Initialization Type is not supported. Allowed values are: {default_init_possible}')
573
+
574
+ volsize = None
575
+ spec_disks = sorted(drives)
576
+ spec_hotspares = hotspares
577
+ for array in currcfg.arrays:
578
+ in_use_disks = sorted([d.id[1] for d in array.disks])
579
+ in_use_hotspares = [h.id[1] for h in array.hotspares]
580
+ if spec_disks == in_use_disks:
581
+ if vol.size is None:
582
+ volsize = array.available_capacity
583
+ array.available_capacity = 0
584
+ break
585
+ else:
586
+ strsize = str(vol.size)
587
+ if strsize in ('all','100%'):
588
+ raise pygexc.InvalidParameterValue(
589
+ f'Requested size for volume {name} exceeds available capacity. Available capacity is {array.available_capacity} MiB')
590
+ elif strsize.endswith('%'):
591
+ volsize = int(array.capacity
592
+ * float(strsize.replace('%', ''))
593
+ / 100.0)
594
+ if volsize > array.available_capacity:
595
+ raise pygexc.InvalidParameterValue(
596
+ f'Requested size for volume {name} exceeds available capacity. Available capacity is {array.available_capacity} MiB')
597
+ else:
598
+ array.available_capacity-=volsize
599
+ else:
600
+ try:
601
+ volsize = int(strsize)
602
+ if volsize > array.available_capacity:
603
+ raise pygexc.InvalidParameterValue(
604
+ f'Requested size for volume {name} exceeds available capacity. Available capacity is {array.available_capacity} MiB')
605
+ else:
606
+ array.available_capacity-=volsize
607
+ except ValueError:
608
+ raise pygexc.InvalidParameterValue(
609
+ 'Unrecognized size ' + strsize)
610
+ elif any(d in in_use_disks for d in spec_disks):
611
+ raise pygexc.InvalidParameterValue(
612
+ f'At least one disk from provided config is in use by another volume. To create a volume using the remaining capacity, configure the new volume to use all the following disks: {in_use_disks}')
613
+ else:
614
+ disks_capacities = {}
615
+ for d in spec_disks:
616
+ disks_capacities[d] = self.webclient.grab_json_response(
617
+ f'/redfish/v1/Systems/1/Storage/{cid}/Drives/{d}')["CapacityBytes"]
618
+ max_capacity = sum(v for k,v in disks_capacities.items())
619
+ min_disk = min([v for k,v in disks_capacities.items()])
620
+ disk_count = len(disks_capacities)
621
+ max_capacity_per_raid = {
622
+ "RAID0": max_capacity,
623
+ "RAID1": min_disk,
624
+ "RAID1Triple": min_disk,
625
+ "RAID10": (disk_count//2)*min_disk,
626
+ "RAID10Triple": (disk_count//3)*min_disk,
627
+ "RAID5": (disk_count-1)*min_disk,
628
+ "RAID50": (disk_count-2)*min_disk,
629
+ "RAID6": (disk_count-2)*min_disk,
630
+ "RAID60": (disk_count-4)*min_disk
631
+ }
632
+ if vol.size is not None:
633
+ strsize = str(vol.size)
634
+ if strsize.endswith('%'):
635
+ volsize = int(max_capacity_per_raid[raidlevel]
636
+ * float(strsize.replace('%', ''))
637
+ / 100.0)
638
+ else:
639
+ try:
640
+ volsize = int(strsize)
641
+ if volsize > max_capacity_per_raid[raidlevel]:
642
+ raise pygexc.InvalidParameterValue(
643
+ f'Requested size for volume {name} exceeds available capacity. Available capacity is {max_capacity_per_raid[raidlevel]} bytes')
644
+ except ValueError:
645
+ raise pygexc.InvalidParameterValue(
646
+ 'Unrecognized size ' + strsize)
647
+ for h in spec_hotspares:
648
+ if h in in_use_hotspares:
649
+ raise pygexc.InvalidParameterValue(
650
+ f'Hotspare {h} from provided config is in use by another volume.')
651
+
652
+ request_data = {
653
+ "Name":name,
654
+ "RAIDType":raidlevel,
655
+ "Links":{
656
+ "Drives":[
657
+ {'@odata.id': f'/redfish/v1/Systems/1/Storage/{cid}/Drives/{did}'} for did in spec_disks]}}
658
+ if spec_hotspares:
659
+ request_data["Links"]["DedicatedSpareDrives"] = {[
660
+ {'@odata.id': f'/redfish/v1/Systems/1/Storage/{cid}/Drives/{hid}' for hid in spec_hotspares}]}
661
+ if volsize:
662
+ request_data["CapacityBytes"] = volsize
663
+ if stripsize:
664
+ request_data["StripSizeBytes"] = stripsize
665
+ if read_policy:
666
+ request_data["ReadCachePolicy"] = read_policy
667
+ if write_policy:
668
+ request_data["WriteCachePolicy"] = write_policy
669
+
670
+ msg, code=self.webclient.grab_json_response_with_status(
671
+ f'/redfish/v1/Systems/1/Storage/{cid}/Volumes',
672
+ method='POST',
673
+ data=request_data)
674
+ if code == 500:
675
+ raise Exception("Unexpected response to volume creation: " + repr(msg))
676
+ time.sleep(60)
677
+ #Even if in web the volume appears immediately, get_storage_configuration does not see it that fast
678
+ newcfg = self.get_storage_configuration(False)
679
+ newvols = [v.id for p in newcfg.arrays for v in p.volumes]
680
+ currvols = [v.id for p in currcfg.arrays for v in p.volumes]
681
+ newvol = list(set(newvols) - set(currvols))[0]
682
+ if default_init:
683
+ msg, code = self.webclient.grab_json_response_with_status(
684
+ f'/redfish/v1/Systems/1/Storage/{cid}/Volumes/{newvol[1]}/Actions/Volume.Initialize',
685
+ method='POST',
686
+ data = {"InitializeType": default_init})
687
+ if code == 500:
688
+ raise Exception("Unexpected response to volume initialization: " + repr(msg))
689
+
690
+ def attach_remote_media(self, url, user, password, vmurls):
691
+ for vmurl in vmurls:
692
+ if 'EXT' not in vmurl:
693
+ continue
694
+ vminfo = self._do_web_request(vmurl, cache=False)
695
+ if vminfo['ConnectedVia'] != 'NotConnected':
696
+ continue
697
+ msg,code = self.webclient.grab_json_response_with_status(
698
+ vmurl,
699
+ data={'Image': url, 'Inserted': True},
700
+ method='PATCH')
701
+ if code == 500:
702
+ raise Exception("Unexpected response when attaching remote media: " + repr(msg))
703
+ raise pygexc.BypassGenericBehavior()
704
+ break
705
+ else:
706
+ raise pygexc.InvalidParameterValue(
707
+ 'XCC does not have required license for operation')
44
708
 
45
709
  def get_screenshot(self, outfile):
46
710
  wc = self.webclient.dupe()
@@ -480,5 +1144,3 @@ class OEMHandler(generic.OEMHandler):
480
1144
  'Name': 'HPM-FPGA Pending',
481
1145
  'build': pendinghpm}
482
1146
  raise pygexc.BypassGenericBehavior()
483
-
484
-
@@ -1,6 +1,7 @@
1
1
  Akira Yoshiyama <akirayoshiyama@gmail.com>
2
2
  Allan Vidal <avidal@lenovo.com>
3
3
  Andreas Jaeger <aj@suse.com>
4
+ Andrei Machedon <amachedon@lenovo.com>
4
5
  Christian Arnold <the-fr3ak@gmx.net>
5
6
  Connor Reed <conman2305@hotmail.com>
6
7
  Daniel Speichert <Daniel_Speichert@comcast.com>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyghmi
3
- Version: 1.6.0
3
+ Version: 1.6.2
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
@@ -37,25 +37,25 @@ pyghmi/ipmi/oem/lenovo/raid_drive.py,sha256=NZoQrgipBXrxYhSXShpHkaylv9aTxwGNmv7P
37
37
  pyghmi/ipmi/private/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
38
  pyghmi/ipmi/private/constants.py,sha256=ViuB_nXsMM5LjkT9FGf2it2KQKvEehd5cZJgewjmVds,66936
39
39
  pyghmi/ipmi/private/localsession.py,sha256=DThuwgvsAJT2AQ8byg4TA8AZG4hBBHnT-pxIJRsISCE,4879
40
- pyghmi/ipmi/private/serversession.py,sha256=X1QhlKW47zi710MHHRkjX7Agrj-RQ1nFXdV2tA0YtYg,16426
40
+ pyghmi/ipmi/private/serversession.py,sha256=H5toINVu7sEuc13MWJHVG3zqNCyMY1tYXjQ4ZOOjQfE,16411
41
41
  pyghmi/ipmi/private/session.py,sha256=276g11vswSuXxEkXaCqfBE68eStZ4KymKS5uDGHzjJQ,81797
42
42
  pyghmi/ipmi/private/simplesession.py,sha256=cNGaoT0uWIKDut6gUG9kAOX_b_qTzdB26R6I6Qk7cns,58834
43
43
  pyghmi/ipmi/private/spd.py,sha256=oEPSXm19X2eNXDiyW_6fVjBFqhuuMAtBI9quRJgclH4,27094
44
44
  pyghmi/ipmi/private/util.py,sha256=ayYodiSydlrrt0_pQppoRB1T6n-KNOiHZSfAlCMcpG0,3847
45
45
  pyghmi/redfish/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
- pyghmi/redfish/command.py,sha256=GhGxHRUFLgXp3sEdusd2U92lJGgPnn7pUQVH7uDNsaw,61304
46
+ pyghmi/redfish/command.py,sha256=0mLgsynfiyfi_i4wtL3ZqPR6CeeHajj-azqTW7EUE3I,61278
47
47
  pyghmi/redfish/oem/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
48
- pyghmi/redfish/oem/generic.py,sha256=5RalOmcBlnvVx05sgo9xCYkTVZ8CrhKMb7pDTtuLAls,56718
48
+ pyghmi/redfish/oem/generic.py,sha256=OvkQV3-p6aoY-oeVNSpfChpAuh1iDOV8OXdWuz7tW9Q,56747
49
49
  pyghmi/redfish/oem/lookup.py,sha256=pfJW5xSkUY61OirMeYy0b1SbjBFz6IDfN5ZOYog_Yq4,1530
50
50
  pyghmi/redfish/oem/dell/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
51
51
  pyghmi/redfish/oem/dell/idrac.py,sha256=pNnmqdV1sOP3ABw0xq0wF1QEO2L8onT7Osc_-sDO8EU,2146
52
52
  pyghmi/redfish/oem/dell/main.py,sha256=g8773SShUpbYxXB9zVx2pD5z1xP04wB_sXAxcAs6_xY,793
53
53
  pyghmi/redfish/oem/lenovo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
54
  pyghmi/redfish/oem/lenovo/main.py,sha256=bnx8LuC_C4_OluNR8JSHIxtSlM4_jdBb4cUzJM6mazE,2597
55
- pyghmi/redfish/oem/lenovo/smm3.py,sha256=JnO89n59xK50MM7qKNofmLenmJ7e7rtaiuwGz-DxrNo,5598
55
+ pyghmi/redfish/oem/lenovo/smm3.py,sha256=W3PKHpFNj5Ujql-neWTH3UgYGDZXQlBvC1aRX97GV8A,5982
56
56
  pyghmi/redfish/oem/lenovo/tsma.py,sha256=6GELCuriumARj_kv7fgqtUpo9ekiWHpQcM9v_mnGILI,34645
57
57
  pyghmi/redfish/oem/lenovo/xcc.py,sha256=78ksNj2-0jquj61lmAZldy3DdcR5KndqbLQ2Y4ZSFOM,84234
58
- pyghmi/redfish/oem/lenovo/xcc3.py,sha256=KBJQLAIpaU32bfFzKxZ9jH7oHnGgJIBz0bMZT-TMKy8,20989
58
+ pyghmi/redfish/oem/lenovo/xcc3.py,sha256=j5zd1QAF5fokEFAsXtaMkIInjvwgSw-BcpNphPT1h-Q,54587
59
59
  pyghmi/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
60
60
  pyghmi/tests/unit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
61
61
  pyghmi/tests/unit/base.py,sha256=xWImA7zPRgfrEe2xAdRZ6w_dLwExGRBJ5CBybssUQGg,744
@@ -64,11 +64,11 @@ pyghmi/tests/unit/ipmi/test_sdr.py,sha256=vb3iLY0cnHJ2K_m4xgYUjEcbPd_ZYhYx-uBowB
64
64
  pyghmi/util/__init__.py,sha256=GZLBWJiun2Plb_VE9dDSh4_PQMCha3gA7QLUqx3oSYI,25
65
65
  pyghmi/util/parse.py,sha256=6VlyBCEcE8gy8PJWmEDdtCyWATaKwPaTswCdioPCWOE,2120
66
66
  pyghmi/util/webclient.py,sha256=782_yMuy_LuN9E2vh2EJ-R64X_EyvLLRuurE__jfn20,15371
67
- pyghmi-1.6.0.dist-info/AUTHORS,sha256=-0iHKtdQwAJfAGKcruCnvcQXrXuE_LgBZ3P15DJI1xY,2044
68
- pyghmi-1.6.0.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
69
- pyghmi-1.6.0.dist-info/METADATA,sha256=jXiO4-aURNuuFgBYbGHrNV29OfV0W8_vVh0Q9pdFtsI,1136
70
- pyghmi-1.6.0.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
71
- pyghmi-1.6.0.dist-info/entry_points.txt,sha256=-OpJliDzATxmuPXK0VR3Ma-Yk_i4ZhfIIB-12A26dSI,168
72
- pyghmi-1.6.0.dist-info/pbr.json,sha256=hh4S35BO_VBD-i-FKARTIoOpcIB6KZ5PAmA-FL5fLYs,46
73
- pyghmi-1.6.0.dist-info/top_level.txt,sha256=aDtt6S9eVu6-tNdaUs4Pz9PbdUd69bziZZMhNvk9Ulc,7
74
- pyghmi-1.6.0.dist-info/RECORD,,
67
+ pyghmi-1.6.2.dist-info/AUTHORS,sha256=yv4aQom_PII-SNqbeeKrfH-spcG85PRPsZ71Iqjl_fU,2083
68
+ pyghmi-1.6.2.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
69
+ pyghmi-1.6.2.dist-info/METADATA,sha256=dFiGyB-IAQ3tFzBHsgS1NmQLn9mX82IJxFknP4Bmq8o,1136
70
+ pyghmi-1.6.2.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
71
+ pyghmi-1.6.2.dist-info/entry_points.txt,sha256=-OpJliDzATxmuPXK0VR3Ma-Yk_i4ZhfIIB-12A26dSI,168
72
+ pyghmi-1.6.2.dist-info/pbr.json,sha256=KQJXkpzIDiMZ-dqqjT_r8x1mofvwXWnVAUoUJc484J4,46
73
+ pyghmi-1.6.2.dist-info/top_level.txt,sha256=aDtt6S9eVu6-tNdaUs4Pz9PbdUd69bziZZMhNvk9Ulc,7
74
+ pyghmi-1.6.2.dist-info/RECORD,,
@@ -0,0 +1 @@
1
+ {"git_version": "6bf4e17", "is_release": true}
@@ -1 +0,0 @@
1
- {"git_version": "4b5fbf5", "is_release": true}
File without changes