pyghmi 1.5.77__py3-none-any.whl → 1.6.0__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.
pyghmi/ipmi/command.py CHANGED
@@ -654,6 +654,14 @@ class Command(object):
654
654
  return sel.EventHandler(self.init_sdr(), self).decode_pet(specifictrap,
655
655
  petdata)
656
656
 
657
+ def get_ikvm_methods(self):
658
+ self.oem_init()
659
+ return self._oem.get_ikvm_methods()
660
+
661
+ def get_ikvm_launchdata(self):
662
+ self.oem_init()
663
+ return self._oem.get_ikvm_launchdata()
664
+
657
665
  def get_inventory_descriptions(self):
658
666
  """Retrieve list of things that could be inventoried
659
667
 
@@ -1652,6 +1660,10 @@ class Command(object):
1652
1660
  r['privilege_level'] = privilege_levels[data[1] & 0b00001111]
1653
1661
  return r
1654
1662
 
1663
+ def get_screenshot(self, outfile):
1664
+ self.oem_init()
1665
+ return self._oem.get_screenshot(outfile)
1666
+
1655
1667
  def get_channel_info(self, channel=None):
1656
1668
  """Get channel info
1657
1669
 
@@ -2212,6 +2224,10 @@ class Command(object):
2212
2224
  self.oem_init()
2213
2225
  return self._oem.get_graphical_console()
2214
2226
 
2227
+ def get_update_status(self):
2228
+ self.oem_init()
2229
+ return self._oem.get_update_status()
2230
+
2215
2231
  def update_firmware(self, filename, data=None, progress=None, bank=None):
2216
2232
  """Send file to BMC to perform firmware update
2217
2233
 
@@ -42,12 +42,22 @@ class OEMHandler(object):
42
42
  """
43
43
  return {}
44
44
 
45
+ def get_screenshot(self, outfile):
46
+ return {}
47
+
45
48
  def get_system_power_watts(self, ipmicmd):
46
49
  # Use DCMI getpower reading command
47
50
  rsp = ipmicmd.xraw_command(netfn=0x2c, command=2, data=(0xdc, 1, 0, 0))
48
51
  wattage = struct.unpack('<H', rsp['data'][1:3])[0]
49
52
  return wattage
50
-
53
+
54
+ def get_ikvm_methods(self):
55
+ return []
56
+
57
+ def get_ikvm_launchdata(self):
58
+ # no standard ikvm behavior, must be oem defined
59
+ return {}
60
+
51
61
  def get_average_processor_temperature(self, ipmicmd):
52
62
  # DCMI suggests preferrence for 0x37 ('Air inlet')
53
63
  # If not that, then 0x40 ('Air inlet')
@@ -332,13 +342,17 @@ class OEMHandler(object):
332
342
  raise exc.UnsupportedFunctionality(
333
343
  'Remote storage configuration not supported on this platform')
334
344
 
345
+ def get_update_status(self):
346
+ raise exc.UnsupportedFunctionality(
347
+ 'Firmwore update not supported on this platform')
348
+
335
349
  def update_firmware(self, filename, data=None, progress=None, bank=None):
336
350
  raise exc.UnsupportedFunctionality(
337
351
  'Firmware update not supported on this platform')
338
352
 
339
353
  def reseat_bay(self, bay):
340
354
  raise exc.UnsupportedFunctionality(
341
- 'Bay reseat not supported on this platform')
355
+ 'Reseat not supported on this platform')
342
356
 
343
357
  def get_graphical_console(self):
344
358
  """Get graphical console launcher"""
@@ -51,7 +51,16 @@ class Unsupported(Exception):
51
51
  def fromstring(inputdata):
52
52
  if b'!entity' in inputdata.lower():
53
53
  raise Exception('Unsupported XML')
54
- return etree.fromstring(inputdata)
54
+ try:
55
+ return etree.fromstring(inputdata)
56
+ except etree.XMLSyntaxError:
57
+ inputdata = bytearray(inputdata.decode('utf8', errors='backslashreplace').encode())
58
+ for i in range(len(inputdata)):
59
+ if inputdata[i] < 0x20 and inputdata[i] not in (9, 0xa, 0xd):
60
+ inputdata[i] = 63
61
+ inputdata = bytes(inputdata)
62
+ return etree.fromstring(inputdata)
63
+
55
64
 
56
65
 
57
66
  def run_command_with_retry(connection, data):
@@ -211,11 +211,25 @@ class OEMHandler(generic.OEMHandler):
211
211
  self._mrethidx = rsp['data'][0]
212
212
  return self._mrethidx
213
213
 
214
+ def get_screenshot(self, outfile):
215
+ if self.has_xcc:
216
+ return self.immhandler.get_screenshot(outfile)
217
+ return {}
218
+
214
219
  def remove_storage_configuration(self, cfgspec):
215
220
  if self.has_xcc:
216
221
  return self.immhandler.remove_storage_configuration(cfgspec)
217
222
  return super(OEMHandler, self).remove_storage_configuration()
218
223
 
224
+ def get_ikvm_methods(self):
225
+ if self.has_xcc:
226
+ return ['url']
227
+
228
+ def get_ikvm_launchdata(self):
229
+ if self.has_xcc:
230
+ return self.immhandler.get_ikvm_launchdata()
231
+ return {}
232
+
219
233
  def clear_storage_arrays(self):
220
234
  if self.has_xcc:
221
235
  return self.immhandler.clear_storage_arrays()
@@ -1175,6 +1189,13 @@ class OEMHandler(generic.OEMHandler):
1175
1189
  else:
1176
1190
  raise
1177
1191
 
1192
+ def get_update_status(self):
1193
+ if self.is_fpc or self.has_tsma:
1194
+ return "ready"
1195
+ if self.has_xcc:
1196
+ return self.immhandler.get_update_status()
1197
+ return super(OEMHandler, self).get_update_status()
1198
+
1178
1199
  def update_firmware(self, filename, data=None, progress=None, bank=None):
1179
1200
  if self.has_xcc:
1180
1201
  return self.immhandler.update_firmware(
@@ -617,7 +617,9 @@ class IMMClient(object):
617
617
  skipkeys.add(fwi['key'])
618
618
  if fwi.get('fw_status', 0) == 2:
619
619
  bdata = {}
620
- if 'fw_version_pend' in fwi:
620
+ if 'fw_pkg_version' in fwi and fwi['fw_pkg_version']:
621
+ bdata['version'] = fwi['fw_pkg_version']
622
+ elif 'fw_version_pend' in fwi:
621
623
  bdata['version'] = fwi['fw_version_pend']
622
624
  yield '{0} Pending Update'.format(aname), bdata
623
625
  for fwi in fwu.get('items', []):
@@ -914,6 +916,14 @@ class XCCClient(IMMClient):
914
916
  self.ipmicmd.ipmi_session.register_keepalive(self.keepalive, None)
915
917
  self.adp_referer = None
916
918
 
919
+ def get_screenshot(self, outfile):
920
+ self.wc.grab_json_response('/api/providers/rp_screenshot')
921
+ url = '/download/HostScreenShot.png'
922
+ fd = webclient.FileDownloader(self.wc, url, outfile)
923
+ fd.start()
924
+ fd.join()
925
+
926
+
917
927
  def get_user_privilege_level(self, uid):
918
928
  uid = uid - 1
919
929
  accurl = '/redfish/v1/AccountService/Accounts/{0}'.format(uid)
@@ -922,6 +932,18 @@ class XCCClient(IMMClient):
922
932
  return accinfo.get('RoleId', None)
923
933
  return None
924
934
 
935
+ def get_ikvm_methods(self):
936
+ return ['url']
937
+
938
+ def get_ikvm_launchdata(self):
939
+ access = self.grab_redfish_response_with_status('/redfish/v1/Managers/1/Oem/Lenovo/RemoteControl/Actions/LenovoRemoteControlService.GetRemoteConsoleToken', {})
940
+ if access[0].get('Token', None):
941
+ accessinfo = {
942
+ 'url': '/#/login?{}&context=remote&mode=multi'.format(access[0]['Token'])
943
+ }
944
+ return accessinfo
945
+ return {}
946
+
925
947
  def set_user_access(self, uid, privilege_level):
926
948
  uid = uid - 1
927
949
  role = None
@@ -940,8 +962,11 @@ class XCCClient(IMMClient):
940
962
 
941
963
  def reseat(self):
942
964
  wc = self.wc.dupe(timeout=5)
943
- rsp = wc.grab_json_response_with_status(
944
- '/api/providers/virt_reseat', '{}')
965
+ try:
966
+ rsp = wc.grab_json_response_with_status(
967
+ '/api/providers/virt_reseat', '{}')
968
+ except socket.timeout:
969
+ return # probably reseated itself and unable to reply
945
970
  if rsp[1] == 500 and rsp[0] == 'Target Unavailable':
946
971
  return
947
972
  if rsp[1] != 200 or rsp[0].get('return', 1) != 0:
@@ -1283,8 +1308,14 @@ class XCCClient(IMMClient):
1283
1308
  return True
1284
1309
 
1285
1310
  def get_diagnostic_data(self, savefile, progress=None, autosuffix=False):
1286
- self.wc.grab_json_response('/api/providers/ffdc',
1287
- {'Generate_FFDC': 1})
1311
+ result = self.wc.grab_json_response('/api/providers/ffdc',
1312
+ {'Generate_FFDC_status': 1})
1313
+ rsp = self.wc.grab_json_response('/api/providers/ffdc',
1314
+ {'Generate_FFDC': 1})
1315
+ if rsp.get('return', 0) == 4:
1316
+ rsp = self.wc.grab_json_response('/api/providers/ffdc',
1317
+ {'Generate_FFDC': 1,
1318
+ 'thermal_log': 0})
1288
1319
  percent = 0
1289
1320
  while percent != 100:
1290
1321
  ipmisession.Session.pause(3)
@@ -2075,14 +2106,18 @@ class XCCClient(IMMClient):
2075
2106
  # the validating phase; add a retry here so we don't exit the loop in this case
2076
2107
  retry = 3
2077
2108
  while not complete and retry > 0:
2078
- pgress, status = self.grab_redfish_response_with_status(
2079
- monitorurl)
2109
+ try:
2110
+ pgress, status = self.grab_redfish_response_with_status(
2111
+ monitorurl)
2112
+ except socket.timeout:
2113
+ pgress = None
2080
2114
  if status < 200 or status >= 300:
2081
2115
  raise Exception(pgress)
2082
2116
  if not pgress:
2083
2117
  retry -= 1
2084
2118
  ipmisession.Session.pause(3)
2085
2119
  continue
2120
+ retry = 3 # reset retry counter
2086
2121
  for msg in pgress.get('Messages', []):
2087
2122
  if 'Verify failed' in msg.get('Message', ''):
2088
2123
  raise Exception(msg['Message'])
@@ -2103,6 +2138,8 @@ class XCCClient(IMMClient):
2103
2138
  ipmisession.Session.pause(3)
2104
2139
  else:
2105
2140
  ipmisession.Session.pause(3)
2141
+ if not retry:
2142
+ raise Exception('Falied to monitor update progress due to excessive timeouts')
2106
2143
  if bank == 'backup':
2107
2144
  return 'complete'
2108
2145
  return 'pending'
@@ -2116,7 +2153,14 @@ class XCCClient(IMMClient):
2116
2153
 
2117
2154
  def set_custom_user_privilege(self, uid, privilege):
2118
2155
  return self.set_user_access(self, uid, privilege)
2119
-
2156
+
2157
+ def get_update_status(self):
2158
+ upd = self.grab_redfish_response_emptyonerror('/redfish/v1/UpdateService')
2159
+ health = upd.get('Status', {}).get('Health', 'bod')
2160
+ if health == 'OK':
2161
+ return 'ready'
2162
+ return 'unavailable'
2163
+
2120
2164
  def update_firmware(self, filename, data=None, progress=None, bank=None):
2121
2165
  usd = self.grab_redfish_response_emptyonerror(
2122
2166
  '/redfish/v1/UpdateService')
@@ -762,7 +762,13 @@ class SMMClient(object):
762
762
  rsp = {'data': [1]}
763
763
  rsp['data'] = bytearray(rsp['data'])
764
764
  if rsp['data'][0] == 2: # shared io
765
- rsp = self.ipmicmd.xraw_command(0x32, 0xa7, data=[bay - 1])
765
+ try:
766
+ rsp = self.ipmicmd.xraw_command(0x32, 0xa7, data=[bay - 1])
767
+ except Exception:
768
+ raise Exception('Shared IO detected trying to reseat {}, '
769
+ 'but unable to determine status of '
770
+ 'partner bay {}'.format(
771
+ bay, bay - 1))
766
772
  rsp['data'] = bytearray(rsp['data'])
767
773
  if rsp['data'][1] == 0x80:
768
774
  raise Exception('Unable to reseat bay {0} due to bay {1} '
pyghmi/redfish/command.py CHANGED
@@ -432,6 +432,15 @@ class Command(object):
432
432
  self._do_web_request(accinfo[0], userinfo, method='PATCH', etag=etag)
433
433
  return True
434
434
 
435
+ def get_screenshot(self, outfile):
436
+ return self.oem.get_screenshot(outfile)
437
+
438
+ def get_ikvm_methods(self):
439
+ return self.oem.get_ikvm_methods()
440
+
441
+ def get_ikvm_launchdata(self):
442
+ return self.oem.get_ikvm_launchdata()
443
+
435
444
  def user_delete(self, uid):
436
445
  self.oem.user_delete(uid)
437
446
 
@@ -1115,7 +1124,7 @@ class Command(object):
1115
1124
  yield res
1116
1125
 
1117
1126
  def _extract_fwinfo(self, inf):
1118
- currinf = {}
1127
+ currinf = self._oem._extract_fwinfo(inf)
1119
1128
  fwi, url = inf
1120
1129
  fwname = fwi.get('Name', 'Unknown')
1121
1130
  if fwname in self._fwnamemap:
@@ -1129,6 +1138,7 @@ class Command(object):
1129
1138
  currinf['id'] = fwi.get('Id', None)
1130
1139
  currinf['version'] = fwi.get('Version', 'Unknown')
1131
1140
  currinf['date'] = parse_time(fwi.get('ReleaseDate', ''))
1141
+ currinf['software_id'] = fwi.get('SoftwareId', '')
1132
1142
  if not (currinf['version'] or currinf['date']):
1133
1143
  return None, None
1134
1144
  # TODO(Jarrod Johnson): OEM extended data with buildid
@@ -1419,6 +1429,8 @@ class Command(object):
1419
1429
  self.wc.stdheaders['X-Auth-Token'] = self.xauthtoken
1420
1430
  if 'Authorization' in self.wc.stdheaders:
1421
1431
  del self.wc.stdheaders['Authorization']
1432
+ for res in self.oem.list_media(self, cache=False):
1433
+ pass
1422
1434
 
1423
1435
  def detach_remote_media(self):
1424
1436
  try:
@@ -1452,6 +1464,8 @@ class Command(object):
1452
1464
  method='PATCH')
1453
1465
  else:
1454
1466
  raise
1467
+ for res in self.oem.list_media(self, cache=False):
1468
+ pass
1455
1469
 
1456
1470
  def upload_media(self, filename, progress=None, data=None):
1457
1471
  """Upload a file to be hosted on the target BMC
@@ -1466,6 +1480,9 @@ class Command(object):
1466
1480
  """
1467
1481
  return self.oem.upload_media(filename, progress, data)
1468
1482
 
1483
+ def get_update_status(self):
1484
+ return self.oem.get_update_status()
1485
+
1469
1486
  def update_firmware(self, file, data=None, progress=None, bank=None):
1470
1487
  """Send file to BMC to perform firmware update
1471
1488
 
@@ -13,6 +13,7 @@
13
13
  # limitations under the License.
14
14
 
15
15
  import base64
16
+ import copy
16
17
  from fnmatch import fnmatch
17
18
  import json
18
19
  import os
@@ -193,6 +194,10 @@ class OEMHandler(object):
193
194
  self._urlcache = cache
194
195
  self.webclient = webclient
195
196
 
197
+ def get_screenshot(self, outfile):
198
+ raise exc.UnsupportedFunctionality(
199
+ 'Retrieving screenshot is not implemented for this platform')
200
+
196
201
  def supports_expand(self, url):
197
202
  # Unfortunately, the state of expand in redfish is pretty dicey,
198
203
  # so an OEM handler must opt into this behavior
@@ -569,13 +574,14 @@ class OEMHandler(object):
569
574
  pendingsettings, self.attrdeps, reginfo,
570
575
  fishclient._setbiosurl)
571
576
 
572
- def _set_redfish_settings(self, changeset, fishclient, currsettings,
577
+ def _set_redfish_settings(self, inchangeset, fishclient, currsettings,
573
578
  rawsettings, pendingsettings, attrdeps, reginfo,
574
579
  seturl):
575
580
  etag = pendingsettings.get('@odata.etag', None)
576
581
  pendingsettings = pendingsettings.get('Attributes', {})
577
582
  dephandler = AttrDependencyHandler(attrdeps, rawsettings,
578
583
  pendingsettings)
584
+ changeset = copy.deepcopy(inchangeset)
579
585
  for change in list(changeset):
580
586
  if change not in currsettings:
581
587
  found = False
@@ -650,6 +656,9 @@ class OEMHandler(object):
650
656
  return {}
651
657
 
652
658
 
659
+ def _extract_fwinfo(self, inf):
660
+ return {}
661
+
653
662
  def get_firmware_inventory(self, components, fishclient):
654
663
  return []
655
664
 
@@ -663,13 +672,13 @@ class OEMHandler(object):
663
672
  except AttributeError:
664
673
  self.password = password
665
674
 
666
- def list_media(self, fishclient):
667
- bmcinfo = fishclient._do_web_request(fishclient._bmcurl)
675
+ def list_media(self, fishclient, cache=True):
676
+ bmcinfo = fishclient._do_web_request(fishclient._bmcurl, cache=cache)
668
677
  vmcoll = bmcinfo.get('VirtualMedia', {}).get('@odata.id', None)
669
678
  if vmcoll:
670
- vmlist = fishclient._do_web_request(vmcoll)
679
+ vmlist = fishclient._do_web_request(vmcoll, cache=cache)
671
680
  vmurls = [x['@odata.id'] for x in vmlist.get('Members', [])]
672
- for vminfo in fishclient._do_bulk_requests(vmurls):
681
+ for vminfo in fishclient._do_bulk_requests(vmurls, cache=cache):
673
682
  vminfo = vminfo[0]
674
683
  if vminfo.get('Image', None):
675
684
  imageurl = vminfo['Image'].replace(
@@ -1018,6 +1027,12 @@ class OEMHandler(object):
1018
1027
  return topdata
1019
1028
  return topdata
1020
1029
 
1030
+ def get_ikvm_methods(self):
1031
+ return []
1032
+
1033
+ def get_ikvm_launchdata(self):
1034
+ return {}
1035
+
1021
1036
  def get_storage_configuration(self):
1022
1037
  raise exc.UnsupportedFunctionality(
1023
1038
  'Remote storage configuration not supported on this platform')
@@ -1038,6 +1053,15 @@ class OEMHandler(object):
1038
1053
  raise exc.UnsupportedFunctionality(
1039
1054
  'Remote media upload not supported on this platform')
1040
1055
 
1056
+ def get_update_status(self):
1057
+ upd = self._do_web_request('/redfish/v1/UpdateService')
1058
+ health = upd.get('Status', {}).get('Health', 'Unknown')
1059
+ if health == 'OK':
1060
+ return 'ready'
1061
+ if health == 'Unknown' and upd.get('ServiceEnabled'):
1062
+ return 'ready'
1063
+ return 'unavailable'
1064
+
1041
1065
  def update_firmware(self, filename, data=None, progress=None, bank=None, otherfields=()):
1042
1066
  # disable cache to make sure we trigger the token renewal logic if needed
1043
1067
  usd = self._do_web_request('/redfish/v1/UpdateService', cache=False)
@@ -1090,11 +1114,15 @@ class OEMHandler(object):
1090
1114
  retry = 3
1091
1115
  pct = 0.0
1092
1116
  while not complete and retry > 0:
1093
- pgress = self._do_web_request(monitorurl, cache=False)
1117
+ try:
1118
+ pgress = self._do_web_request(monitorurl, cache=False)
1119
+ except socket.timeout:
1120
+ pgress = None
1094
1121
  if not pgress:
1095
1122
  retry -= 1
1096
1123
  time.sleep(3)
1097
1124
  continue
1125
+ retry = 3 # reset retry counter
1098
1126
  for msg in pgress.get('Messages', []):
1099
1127
  if 'Verify failed' in msg.get('Message', ''):
1100
1128
  raise Exception(msg['Message'])
@@ -1119,6 +1147,8 @@ class OEMHandler(object):
1119
1147
  time.sleep(3)
1120
1148
  else:
1121
1149
  time.sleep(3)
1150
+ if not retry:
1151
+ raise Exception('Falied to monitor update progress due to excessive timeouts')
1122
1152
  return 'pending'
1123
1153
  finally:
1124
1154
  if 'HttpPushUriTargetsBusy' in usd:
@@ -1266,4 +1296,4 @@ class OEMHandler(object):
1266
1296
 
1267
1297
  def reseat_bay(self, bay):
1268
1298
  raise exc.UnsupportedFunctionality(
1269
- 'Bay reseat not supported on this platform')
1299
+ 'Reseat not supported on this platform')
@@ -12,8 +12,13 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ import copy
16
+ import os
15
17
  import pyghmi.redfish.oem.generic as generic
16
18
  import pyghmi.constants as pygconst
19
+ import pyghmi.util.webclient as webclient
20
+ import pyghmi.exceptions as exc
21
+ import time
17
22
 
18
23
  healthlookup = {
19
24
  'ok': pygconst.Health.Ok,
@@ -38,6 +43,9 @@ def _baytonumber(bay):
38
43
  def _baytolabel(bay):
39
44
  try:
40
45
  baynum = int(bay)
46
+ if baynum < 1:
47
+ raise exc.UnsupportedFunctionality(
48
+ 'Reseat not supported for whole chassis')
41
49
  # need to convert to 1a, 1b, etc...
42
50
  vertidx = ((baynum - 1) // 2 + 1) << 4
43
51
  horizidx = (baynum - 1) % 2 + 10
@@ -57,6 +65,58 @@ class OEMHandler(generic.OEMHandler):
57
65
  def get_system_configuration(self, hideadvanced=True, fishclient=None):
58
66
  return {}
59
67
 
68
+ def get_diagnostic_data(self, savefile, progress=None, autosuffix=False):
69
+ tsk = self._do_web_request(
70
+ '/redfish/v1/Managers/bmc/LogServices/Dump/Actions/LogService.CollectDiagnosticData',
71
+ {"DiagnosticDataType": "Manager"})
72
+ taskrunning = True
73
+ taskurl = tsk.get('@odata.id', None)
74
+ pct = 0 if taskurl else 100
75
+ durl = None
76
+ while pct < 100 and taskrunning:
77
+ status = self._do_web_request(taskurl)
78
+ durl = status.get('AdditionalDataURI', '')
79
+ pct = status.get('PercentComplete', 0)
80
+ taskrunning = status.get('TaskState', 'Complete') == 'Running'
81
+ if progress:
82
+ progress({'phase': 'initializing', 'progress': float(pct)})
83
+ if taskrunning:
84
+ time.sleep(3)
85
+ if not durl:
86
+ for hdr in status.get('Payload', {}).get('HttpHeaders', []):
87
+ if hdr.startswith('Location: '):
88
+
89
+ enturl = hdr.replace('Location: ', '')
90
+ entryinfo = self._do_web_request(enturl)
91
+ durl = entryinfo.get('AdditionalDataURI', None)
92
+ break
93
+ if not durl:
94
+ raise Exception("Failed getting service data url")
95
+ fname = os.path.basename(durl)
96
+ if autosuffix and not savefile.endswith('.tar.xz'):
97
+ savefile += time.strftime('-SMM3_%Y%m%d_%H%M%S.tar.xz')
98
+ fd = webclient.FileDownloader(self.webclient, durl, savefile)
99
+ fd.start()
100
+ while fd.isAlive():
101
+ fd.join(1)
102
+ if progress and self.webclient.get_download_progress():
103
+ progress({'phase': 'download',
104
+ 'progress': 100 * self.webclient.get_download_progress()})
105
+ if fd.exc:
106
+ raise fd.exc
107
+ if progress:
108
+ progress({'phase': 'complete'})
109
+ return savefile
110
+
111
+ def _extract_fwinfo(self, inf):
112
+ fwi, url = inf
113
+ currinf = {}
114
+ buildid = fwi.get('Oem', {}).get('Lenovo', {}).get('ExtendedVersion', None)
115
+ if buildid:
116
+ currinf['build'] = buildid
117
+ return currinf
118
+
119
+
60
120
  def _get_node_info(self):
61
121
  nodeinfo = self._varsysinfo
62
122
  if not nodeinfo:
@@ -69,9 +129,10 @@ class OEMHandler(generic.OEMHandler):
69
129
  if len(chassismembs) == 1:
70
130
  chassisurl = chassismembs[0]['@odata.id']
71
131
  nodeinfo = self._do_web_request(chassisurl)
72
- nodeinfo['SKU'] = nodeinfo['Model']
73
- nodeinfo['Model'] = 'N1380 Enclosure'
74
- return nodeinfo
132
+ newnodeinfo = copy.deepcopy(nodeinfo)
133
+ newnodeinfo['SKU'] = nodeinfo['Model']
134
+ newnodeinfo['Model'] = 'N1380 Enclosure'
135
+ return newnodeinfo
75
136
 
76
137
  def reseat_bay(self, bay):
77
138
  bayid = _baytolabel(bay)
@@ -799,7 +799,7 @@ class TsmHandler(generic.OEMHandler):
799
799
  raise exc.UnsupportedFunctionality(
800
800
  'Remote media upload not supported on this system')
801
801
 
802
- def list_media(self, fishclient=None):
802
+ def list_media(self, fishclient=None, cache=True):
803
803
  wc = self.wc
804
804
  rsp = wc.grab_json_response('/api/settings/media/general')
805
805
  cds = rsp['cd_remote_server_address']
@@ -150,6 +150,13 @@ class OEMHandler(generic.OEMHandler):
150
150
  self.fwc = None
151
151
  self.fwo = None
152
152
 
153
+ def get_screenshot(self, outfile):
154
+ self.wc.grab_json_response('/api/providers/rp_screenshot')
155
+ url = '/download/HostScreenShot.png'
156
+ fd = webclient.FileDownloader(self.wc, url, outfile)
157
+ fd.start()
158
+ fd.join()
159
+
153
160
  def get_extended_bmc_configuration(self, fishclient, hideadvanced=True):
154
161
  immsettings = self.get_system_configuration(fetchimm=True, hideadvanced=hideadvanced)
155
162
  for setting in list(immsettings):
@@ -157,6 +164,17 @@ class OEMHandler(generic.OEMHandler):
157
164
  del immsettings[setting]
158
165
  return immsettings
159
166
 
167
+ def get_ikvm_methods(self):
168
+ return ['url']
169
+
170
+ def get_ikvm_launchdata(self):
171
+ access = self._do_web_request('/redfish/v1/Managers/1/Oem/Lenovo/RemoteControl/Actions/LenovoRemoteControlService.GetRemoteConsoleToken', {})
172
+ if access.get('Token', None):
173
+ accessinfo = {
174
+ 'url': '/#/login?{}&context=remote&mode=multi'.format(access['Token'])
175
+ }
176
+ return accessinfo
177
+
160
178
  def get_system_configuration(self, hideadvanced=True, fishclient=None,
161
179
  fetchimm=False):
162
180
  if not self.fwc:
@@ -293,8 +311,12 @@ class OEMHandler(generic.OEMHandler):
293
311
  raise pygexc.UnsupportedFunctionality(
294
312
  'This is not an enclosure manager')
295
313
  wc = self.wc.dupe(timeout=5)
296
- rsp = wc.grab_json_response_with_status(
297
- '/api/providers/virt_reseat', '{}')
314
+ try:
315
+ rsp = wc.grab_json_response_with_status(
316
+ '/api/providers/virt_reseat', '{}')
317
+ except socket.timeout:
318
+ # Can't be certain, but most likely a timeout'
319
+ return
298
320
  if rsp[1] == 500 and rsp[0] == 'Target Unavailable':
299
321
  return
300
322
  if rsp[1] != 200 or rsp[0].get('return', 1) != 0:
@@ -638,7 +660,9 @@ class OEMHandler(generic.OEMHandler):
638
660
  skipkeys.add(fwi['key'])
639
661
  if fwi.get('fw_status', 0) == 2:
640
662
  bdata = {}
641
- if 'fw_version_pend' in fwi:
663
+ if 'fw_pkg_version' in fwi and fwi['fw_pkg_version']:
664
+ bdata['version'] = fwi['fw_pkg_version']
665
+ elif 'fw_version_pend' in fwi:
642
666
  bdata['version'] = fwi['fw_version_pend']
643
667
  yield '{0} Pending Update'.format(aname), bdata
644
668
  for fwi in fdata.get('items', []):
@@ -1127,7 +1151,7 @@ class OEMHandler(generic.OEMHandler):
1127
1151
  res = wc.grab_json_response_with_status(url, body, method=method)
1128
1152
  return res
1129
1153
 
1130
- def list_media(self, fishclient):
1154
+ def list_media(self, fishclient, cache=True):
1131
1155
  rt = self.wc.grab_json_response('/api/providers/rp_vm_remote_getdisk')
1132
1156
  if 'items' in rt:
1133
1157
  for mt in rt['items']:
@@ -1290,11 +1314,15 @@ class OEMHandler(generic.OEMHandler):
1290
1314
  # the validating phase; add a retry here so we don't exit the loop in this case
1291
1315
  retry = 3
1292
1316
  while not complete and retry > 0:
1293
- pgress = self._do_web_request(monitorurl, cache=False)
1317
+ try:
1318
+ pgress = self._do_web_request(monitorurl, cache=False)
1319
+ except socket.timeout:
1320
+ pgress = None
1294
1321
  if not pgress:
1295
1322
  retry -= 1
1296
1323
  time.sleep(3)
1297
1324
  continue
1325
+ retry = 3 # reset retry counter
1298
1326
  for msg in pgress.get('Messages', []):
1299
1327
  if 'Verify failed' in msg.get('Message', ''):
1300
1328
  raise Exception(msg['Message'])
@@ -1316,6 +1344,8 @@ class OEMHandler(generic.OEMHandler):
1316
1344
  time.sleep(3)
1317
1345
  else:
1318
1346
  time.sleep(3)
1347
+ if not retry:
1348
+ raise Exception('Falied to monitor update progress due to excessive timeouts')
1319
1349
  if bank == 'backup':
1320
1350
  return 'complete'
1321
1351
  return 'pending'
@@ -1530,8 +1560,12 @@ class OEMHandler(generic.OEMHandler):
1530
1560
  return 'pending'
1531
1561
 
1532
1562
  def get_diagnostic_data(self, savefile, progress=None, autosuffix=False):
1533
- self.wc.grab_json_response('/api/providers/ffdc',
1534
- {'Generate_FFDC': 1})
1563
+ rsp = self.wc.grab_json_response('/api/providers/ffdc',
1564
+ {'Generate_FFDC': 1})
1565
+ if rsp.get('return', 0) == 4:
1566
+ rsp = self.wc.grab_json_response('/api/providers/ffdc',
1567
+ {'Generate_FFDC': 1,
1568
+ 'thermal_log': 0})
1535
1569
  percent = 0
1536
1570
  while percent != 100:
1537
1571
  time.sleep(3)
@@ -42,6 +42,14 @@ class OEMHandler(generic.OEMHandler):
42
42
  def supports_expand(self, url):
43
43
  return True
44
44
 
45
+ def get_screenshot(self, outfile):
46
+ wc = self.webclient.dupe()
47
+ self._get_session_token(wc)
48
+ url = '/web_download/Mini_ScreenShot.jpg'
49
+ fd = webclient.FileDownloader(wc, url, outfile)
50
+ fd.start()
51
+ fd.join()
52
+
45
53
  def get_diagnostic_data(self, savefile, progress=None, autosuffix=False):
46
54
  tsk = self._do_web_request(
47
55
  '/redfish/v1/Systems/1/LogServices/DiagnosticLog/Actions/LogService.CollectDiagnosticData',
@@ -77,6 +85,17 @@ class OEMHandler(generic.OEMHandler):
77
85
  progress({'phase': 'complete'})
78
86
  return savefile
79
87
 
88
+ def get_ikvm_methods(self):
89
+ return ['openbmc', 'url']
90
+
91
+ def get_ikvm_launchdata(self):
92
+ access = self._do_web_request('/redfish/v1/Managers/1/Oem/Lenovo/RemoteControl/Actions/LenovoRemoteControlService.GetRemoteConsoleToken', {})
93
+ if access.get('Token', None):
94
+ accessinfo = {
95
+ 'url': '/#/login?{}&context=remote&mode=multi'.format(access['Token'])
96
+ }
97
+ return accessinfo
98
+
80
99
  def get_system_power_watts(self, fishclient):
81
100
  powerinfo = fishclient._do_web_request('/redfish/v1/Chassis/1/Sensors/power_Sys_Power')
82
101
  return powerinfo['Reading']
@@ -270,7 +289,7 @@ class OEMHandler(generic.OEMHandler):
270
289
  acctattribs['Oem']['Lenovo'][
271
290
  self.oemacctmap[key.lower()]] = currval
272
291
  if key.lower() == 'password_expiration':
273
- warntime = str(int(int(currval) * 0.08))
292
+ warntime = int(int(currval) * 0.08)
274
293
  acctattribs['Oem']['Lenovo'][
275
294
  'PasswordExpirationWarningPeriod'] = warntime
276
295
  elif key.lower() in self.acctmap:
@@ -413,6 +432,13 @@ class OEMHandler(generic.OEMHandler):
413
432
  progress({'phase': 'complete'})
414
433
 
415
434
  def get_firmware_inventory(self, components, fishclient):
435
+ sfs = fishclient._do_web_request('/api/providers/system_firmware_status')
436
+ pendingscm = sfs.get('fpga_scm_pending_build', None)
437
+ pendinghpm = sfs.get('fpga_hpm_pending_build', None)
438
+ if pendingscm == '*':
439
+ pendingscm = None
440
+ if pendinghpm == '*':
441
+ pendinghpm = None
416
442
  fwlist = fishclient._do_web_request(fishclient._fwinventory + '?$expand=.')
417
443
  fwlist = copy.deepcopy(fwlist.get('Members', []))
418
444
  self._fwnamemap = {}
@@ -432,9 +458,11 @@ class OEMHandler(generic.OEMHandler):
432
458
  swid = redres.get('SoftwareId', '')
433
459
  buildid = ''
434
460
  version = redres.get('Version', None)
435
- if swid.startswith('FPGA-') or swid.startswith('UEFI-') or swid.startswith('BMC-'):
436
- buildid = swid.split('-')[1] + version.split('-')[0]
437
- version = '-'.join(version.split('-')[1:])
461
+ for prefix in ['FPGA-', 'UEFI-', 'BMC-', 'LXPM-', 'DRVWN-', 'DRVLN-', 'LXUM']:
462
+ if swid.startswith(prefix):
463
+ buildid = swid.split('-')[1] + version.split('-')[0]
464
+ version = '-'.join(version.split('-')[1:])
465
+ break
438
466
  if version:
439
467
  redres['Version'] = version
440
468
  cres = fishclient._extract_fwinfo(res)
@@ -443,6 +471,14 @@ class OEMHandler(generic.OEMHandler):
443
471
  if buildid:
444
472
  cres[1]['build'] = buildid
445
473
  yield cres
474
+ if cres[0] == 'SCM-FPGA' and pendingscm:
475
+ yield 'SCM-FPGA Pending', {
476
+ 'Name': 'SCM-FPGA Pending',
477
+ 'build': pendingscm}
478
+ elif cres[0] == 'HPM-FPGA' and pendinghpm:
479
+ yield 'HPM-FPGA Pending', {
480
+ 'Name': 'HPM-FPGA Pending',
481
+ 'build': pendinghpm}
446
482
  raise pygexc.BypassGenericBehavior()
447
483
 
448
484
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyghmi
3
- Version: 1.5.77
3
+ Version: 1.6.0
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
@@ -11,25 +11,25 @@ pyghmi/cmd/pyghmiutil.py,sha256=hir7FFvwKDNxYGpOPCgIVSgHP4UsVKFIbVBgBWqkBxA,2917
11
11
  pyghmi/cmd/virshbmc.py,sha256=rNbRJrVnx5BmQQLVRV8JK3lW9YWUcP7Z8hCWfpfVLCM,5149
12
12
  pyghmi/ipmi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  pyghmi/ipmi/bmc.py,sha256=LJBF90msq8xFcZSUe3s3jVcW02Ib-0Fc5zWvQNtGmcQ,7283
14
- pyghmi/ipmi/command.py,sha256=qfoo_s2zosV7prL7IpuAaFwlBRM6ormXnPWqFj1LFoA,90521
14
+ pyghmi/ipmi/command.py,sha256=hSxL67zvlROMVyUtERc7UDRlJ7Bbk28seGG_RCtuWa0,90945
15
15
  pyghmi/ipmi/console.py,sha256=Jle7uJI3ZQS6cMwbEisFEvXjmu5MVqMs17BcAlygR_4,23369
16
16
  pyghmi/ipmi/events.py,sha256=zgUidJIARHomwxasgeYAzDO1AEMfEOzb6XVxzry22Us,22569
17
17
  pyghmi/ipmi/fru.py,sha256=sw5ZBMrEVSBDgOUPVU_ksehQMJvrl2v-r7rVyA9xoiE,14430
18
18
  pyghmi/ipmi/sdr.py,sha256=U4NH-ca1zwEmU_dKT2wvXY2vEvXh33dV9rIUlmf_5Xw,32999
19
19
  pyghmi/ipmi/oem/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
- pyghmi/ipmi/oem/generic.py,sha256=-5mIxczwkvOdRy2NSPbSgPerQ5AMez-KhC8AkOrwkL0,18614
20
+ pyghmi/ipmi/oem/generic.py,sha256=Ih8BoVebCCmgmHgbO1gJuprecRXG8vi2n4bZ-Oy6r7c,18966
21
21
  pyghmi/ipmi/oem/lookup.py,sha256=Ex00OEEolsdWCVhyP0QDGzOxHGEA7sKI8a8fW4kJPD8,3653
22
22
  pyghmi/ipmi/oem/lenovo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
- pyghmi/ipmi/oem/lenovo/config.py,sha256=gxFl5Z-RSDyEvbmcQ_RI2xbaW7gdSWm_ysJeXxV0P_0,26325
23
+ pyghmi/ipmi/oem/lenovo/config.py,sha256=aNN5dvbGlNpWlrrvlWthyAO0PGn6A9Pf75wjhVleu_k,26692
24
24
  pyghmi/ipmi/oem/lenovo/cpu.py,sha256=POZMP9n2S1v6r8iNStkCOVEiQYs3ut3RqL_9x-kgOFw,1651
25
25
  pyghmi/ipmi/oem/lenovo/dimm.py,sha256=L8k1aBgtvxqyubDBNKdDkz80pDE8Sck1eMLcMz1GhFI,1875
26
26
  pyghmi/ipmi/oem/lenovo/drive.py,sha256=MmVgaosEwJXcwi1kKYGnY-dbrx4Zp55941qWMvprUMA,2055
27
27
  pyghmi/ipmi/oem/lenovo/energy.py,sha256=THttIqlwpnj7ljbBWTHjelDLmDaQiCuMvNqJy9Ec7j8,6355
28
28
  pyghmi/ipmi/oem/lenovo/firmware.py,sha256=KS9uUBjFUzvdMw_e-kpr5sYIvFUaeg0yqyo69T94IVc,3747
29
- pyghmi/ipmi/oem/lenovo/handler.py,sha256=Y2c_IZph1VE5rP8d2fZASQulDtxL_yGv7bvJNtDxwL8,56632
30
- pyghmi/ipmi/oem/lenovo/imm.py,sha256=Yfrafthw1HU7Blw4JWnkryc2AEeUAoVok06A53hKvZc,111538
29
+ pyghmi/ipmi/oem/lenovo/handler.py,sha256=ubLUJS9c3yuZiCH1m2I5-NFI9CbpRh3_okijQxARoxM,57236
30
+ pyghmi/ipmi/oem/lenovo/imm.py,sha256=MFd26rim8d3yVrKWxXVchaXNch93ACelgDqmjxVZu9s,113461
31
31
  pyghmi/ipmi/oem/lenovo/inventory.py,sha256=FLJJinw-ibdHtf3KmrTzhWXbQrpxq3TSycVf96Hg7cw,5911
32
- pyghmi/ipmi/oem/lenovo/nextscale.py,sha256=ojLh17M87GnKUl-3yCTJcIJca1mpcrhlc7rQmhpby3A,43407
32
+ pyghmi/ipmi/oem/lenovo/nextscale.py,sha256=bvY7MLOGMioXT7F6HBt5BBftGAEYNo2GkBNjwysPAWs,43734
33
33
  pyghmi/ipmi/oem/lenovo/pci.py,sha256=S7p-5Q2qu2YhlffN-LEmIvjfXim6OlfYL7Q6r6VZqJ4,2020
34
34
  pyghmi/ipmi/oem/lenovo/psu.py,sha256=ISgGe7MdLd1Z7MiRcgJ0gyC92m-4CDNmhMs-H0T3GVY,3098
35
35
  pyghmi/ipmi/oem/lenovo/raid_controller.py,sha256=hr9W17FwgpG9B544eebkEH88gOlU6M4gIapZq7WOPsw,2335
@@ -43,19 +43,19 @@ pyghmi/ipmi/private/simplesession.py,sha256=cNGaoT0uWIKDut6gUG9kAOX_b_qTzdB26R6I
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=Rn2oooBAvnsGM46pS9TM7WVjDXGwrwlZY9mZqzC-97I,60741
46
+ pyghmi/redfish/command.py,sha256=GhGxHRUFLgXp3sEdusd2U92lJGgPnn7pUQVH7uDNsaw,61304
47
47
  pyghmi/redfish/oem/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
48
- pyghmi/redfish/oem/generic.py,sha256=dwad7SwV4zYka0oKrSIBfjXu3KyFbOidVGXAW08tfAE,55691
48
+ pyghmi/redfish/oem/generic.py,sha256=5RalOmcBlnvVx05sgo9xCYkTVZ8CrhKMb7pDTtuLAls,56718
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=FWNCR6eSK6a9ZgZ5G-HyAllYkUD5hZeO7oOVKNNslEE,3152
56
- pyghmi/redfish/oem/lenovo/tsma.py,sha256=puSj0fO5Dt5VpDoEMVTRY95CP9q18eXcAqq7TDK350E,34633
57
- pyghmi/redfish/oem/lenovo/xcc.py,sha256=DG0GVEl_n-k77p_CFzUISLQ3AlTgppc9_yPwPy20Q4o,82735
58
- pyghmi/redfish/oem/lenovo/xcc3.py,sha256=NYL_6akVaS5sYL6f3beHSZjWeRGlDTbAkzYojDK_G6I,19516
55
+ pyghmi/redfish/oem/lenovo/smm3.py,sha256=JnO89n59xK50MM7qKNofmLenmJ7e7rtaiuwGz-DxrNo,5598
56
+ pyghmi/redfish/oem/lenovo/tsma.py,sha256=6GELCuriumARj_kv7fgqtUpo9ekiWHpQcM9v_mnGILI,34645
57
+ pyghmi/redfish/oem/lenovo/xcc.py,sha256=78ksNj2-0jquj61lmAZldy3DdcR5KndqbLQ2Y4ZSFOM,84234
58
+ pyghmi/redfish/oem/lenovo/xcc3.py,sha256=KBJQLAIpaU32bfFzKxZ9jH7oHnGgJIBz0bMZT-TMKy8,20989
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.5.77.dist-info/AUTHORS,sha256=-0iHKtdQwAJfAGKcruCnvcQXrXuE_LgBZ3P15DJI1xY,2044
68
- pyghmi-1.5.77.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
69
- pyghmi-1.5.77.dist-info/METADATA,sha256=vGNVD3HFcrVNpj1IgdCPFTsS165Zmw0JLCSxagRV1ag,1137
70
- pyghmi-1.5.77.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
71
- pyghmi-1.5.77.dist-info/entry_points.txt,sha256=-OpJliDzATxmuPXK0VR3Ma-Yk_i4ZhfIIB-12A26dSI,168
72
- pyghmi-1.5.77.dist-info/pbr.json,sha256=bMLCDGwW8QTeMX-sRWFYm0VDMdoXgjoox4GwcdTx5hA,46
73
- pyghmi-1.5.77.dist-info/top_level.txt,sha256=aDtt6S9eVu6-tNdaUs4Pz9PbdUd69bziZZMhNvk9Ulc,7
74
- pyghmi-1.5.77.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.3.0)
2
+ Generator: setuptools (75.3.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -0,0 +1 @@
1
+ {"git_version": "4b5fbf5", "is_release": true}
@@ -1 +0,0 @@
1
- {"git_version": "4bbf63c", "is_release": true}