Skip to content

Commit 6d6ab5a

Browse files
Fixed #2154 added more error handling around table output
1 parent c5e8d44 commit 6d6ab5a

File tree

6 files changed

+197
-71
lines changed

6 files changed

+197
-71
lines changed

SoftLayer/CLI/block/detail.py

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -83,25 +83,21 @@ def cli(env, volume_id):
8383
table.add_row(['Replication Status', "%s"
8484
% block_volume['replicationStatus']])
8585

86+
replicant_table = formatting.KeyValueTable(['Name', 'Value'])
87+
replicant_table.align['Name'] = 'r'
88+
replicant_table.align['Value'] = 'l'
8689
for replicant in block_volume['replicationPartners']:
87-
replicant_table = formatting.Table(['Name',
88-
'Value'])
89-
replicant_table.add_row(['Replicant Id', replicant['id']])
9090
replicant_table.add_row([
91-
'Volume Name',
92-
utils.lookup(replicant, 'username')])
91+
'Replicant Id', replicant['id']])
9392
replicant_table.add_row([
94-
'Target IP',
95-
utils.lookup(replicant, 'serviceResourceBackendIpAddress')])
93+
'Volume Name', utils.lookup(replicant, 'username')])
9694
replicant_table.add_row([
97-
'Data Center',
98-
utils.lookup(replicant,
99-
'serviceResource', 'datacenter', 'name')])
95+
'Target IP', utils.lookup(replicant, 'serviceResourceBackendIpAddress')])
10096
replicant_table.add_row([
101-
'Schedule',
102-
utils.lookup(replicant,
103-
'replicationSchedule', 'type', 'keyname')])
104-
table.add_row(['Replicant Volumes', replicant_table])
97+
'Data Center', utils.lookup(replicant, 'serviceResource', 'datacenter', 'name')])
98+
replicant_table.add_row([
99+
'Schedule', utils.lookup(replicant, 'replicationSchedule', 'type', 'keyname')])
100+
table.add_row(['Replicant Volumes', replicant_table])
105101

106102
if block_volume.get('originalVolumeSize'):
107103
original_volume_info = formatting.Table(['Property', 'Value'])

SoftLayer/CLI/file/detail.py

Lines changed: 22 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -37,54 +37,27 @@ def cli(env, volume_id):
3737
table.add_row(['Type', storage_type])
3838
table.add_row(['Capacity (GB)', get_capacity(file_volume)])
3939

40-
used_space = int(file_volume['bytesUsed']) \
41-
if file_volume['bytesUsed'] else 0
42-
if used_space < (1 << 10):
43-
table.add_row(['Used Space', "%dB" % used_space])
44-
elif used_space < (1 << 20):
45-
table.add_row(['Used Space', "%dKB" % (used_space / (1 << 10))])
46-
elif used_space < (1 << 30):
47-
table.add_row(['Used Space', "%dMB" % (used_space / (1 << 20))])
48-
else:
49-
table.add_row(['Used Space', "%dGB" % (used_space / (1 << 30))])
40+
used_space = formatting.convert_sizes(file_volume.get('bytes_used', 0), "GB", False)
41+
table.add_row(['Used Space', used_space])
5042

5143
if file_volume.get('provisionedIops'):
5244
table.add_row(['IOPs', float(file_volume['provisionedIops'])])
5345

5446
if file_volume.get('storageTierLevel'):
55-
table.add_row([
56-
'Endurance Tier',
57-
file_volume['storageTierLevel'],
58-
])
59-
60-
table.add_row([
61-
'Data Center',
62-
file_volume['serviceResource']['datacenter']['name'],
63-
])
64-
table.add_row([
65-
'Target IP',
66-
file_volume['serviceResourceBackendIpAddress'],
67-
])
47+
table.add_row(['Endurance Tier', file_volume['storageTierLevel']])
48+
49+
table.add_row(['Data Center', file_volume['serviceResource']['datacenter']['name']])
50+
table.add_row(['Target IP', file_volume['serviceResourceBackendIpAddress']])
6851

6952
if file_volume['fileNetworkMountAddress']:
70-
table.add_row([
71-
'Mount Address',
72-
file_volume['fileNetworkMountAddress'],
73-
])
53+
table.add_row(['Mount Address', file_volume['fileNetworkMountAddress']])
7454

7555
if file_volume['snapshotCapacityGb']:
76-
table.add_row([
77-
'Snapshot Capacity (GB)',
78-
file_volume['snapshotCapacityGb'],
79-
])
56+
table.add_row(['Snapshot Capacity (GB)', file_volume['snapshotCapacityGb']])
8057
if 'snapshotSizeBytes' in file_volume['parentVolume']:
81-
table.add_row([
82-
'Snapshot Used (Bytes)',
83-
file_volume['parentVolume']['snapshotSizeBytes'],
84-
])
58+
table.add_row(['Snapshot Used (Bytes)', file_volume['parentVolume']['snapshotSizeBytes']])
8559

86-
table.add_row(['# of Active Transactions', "%i"
87-
% file_volume['activeTransactionCount']])
60+
table.add_row(["# of Active Transactions", file_volume['activeTransactionCount']])
8861

8962
if file_volume['activeTransactions']:
9063
for trans in file_volume['activeTransactions']:
@@ -98,32 +71,25 @@ def cli(env, volume_id):
9871
# returns a string or object for 'replicationStatus'; it seems that
9972
# the type is string for File volumes and object for Block volumes
10073
if 'message' in file_volume['replicationStatus']:
101-
table.add_row(['Replication Status', "%s"
102-
% file_volume['replicationStatus']['message']])
74+
table.add_row(['Replication Status', file_volume['replicationStatus']['message']])
10375
else:
104-
table.add_row(['Replication Status', "%s"
105-
% file_volume['replicationStatus']])
76+
table.add_row(['Replication Status', file_volume['replicationStatus']])
10677

107-
replicant_list = []
78+
replicant_table = formatting.KeyValueTable(['Name', 'Value'])
79+
replicant_table.align['Name'] = 'r'
80+
replicant_table.align['Value'] = 'l'
10881
for replicant in file_volume['replicationPartners']:
109-
replicant_table = formatting.Table(['Replicant ID',
110-
replicant['id']])
11182
replicant_table.add_row([
112-
'Volume Name',
113-
utils.lookup(replicant, 'username')])
83+
'Volume ID', replicant.get('id')])
84+
replicant_table.add_row([
85+
'Volume Name', utils.lookup(replicant, 'username')])
11486
replicant_table.add_row([
115-
'Target IP',
116-
utils.lookup(replicant, 'serviceResourceBackendIpAddress')])
87+
'Target IP', utils.lookup(replicant, 'serviceResourceBackendIpAddress')])
11788
replicant_table.add_row([
118-
'Data Center',
119-
utils.lookup(replicant,
120-
'serviceResource', 'datacenter', 'name')])
89+
'Data Center', utils.lookup(replicant, 'serviceResource', 'datacenter', 'name')])
12190
replicant_table.add_row([
122-
'Schedule',
123-
utils.lookup(replicant,
124-
'replicationSchedule', 'type', 'keyname')])
125-
replicant_list.append(replicant_table)
126-
table.add_row(['Replicant Volumes', replicant_list])
91+
'Schedule', utils.lookup(replicant, 'replicationSchedule', 'type', 'keyname')])
92+
table.add_row(['Replicant Volumes', replicant_table])
12793

12894
if file_volume.get('originalVolumeSize'):
12995
original_volume_info = formatting.Table(['Property', 'Value'])

SoftLayer/CLI/formatting.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import click
1616
from rich import box
17+
from rich.errors import NotRenderableError
1718
from rich.table import Table as rTable
1819

1920
from SoftLayer.CLI import exceptions
@@ -392,7 +393,15 @@ def prettytable(self, fmt='table', theme=None):
392393
table.add_column(col, justify=justify, style=style)
393394

394395
for row in self.rows:
395-
table.add_row(*row)
396+
try:
397+
table.add_row(*row)
398+
# Generally you will see this if one of the columns in the row is a list or dict
399+
except NotRenderableError:
400+
forced_row = []
401+
for i in row:
402+
forced_row.append(str(i))
403+
table.add_row(*forced_row)
404+
396405
return table
397406

398407

SoftLayer/fixtures/SoftLayer_Network_Storage.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,4 +334,107 @@
334334
'storageType': {
335335
'keyName': 'ENDURANCE_BLOCK_STORAGE'
336336
}
337+
}
338+
339+
340+
FILE_DETAIL_ISSUE2154 = {
341+
"capacityGb": 150,
342+
"id": 609491933,
343+
"username": "SL02SV1414935_187",
344+
"activeTransactionCount": 0,
345+
"replicationPartnerCount": 1,
346+
"fileNetworkMountAddress": "fsf-natestdal0505-fcb-fz.service.softlayer.com:/SL02SV1414935_187/data01",
347+
"originalVolumeSize": "20",
348+
"provisionedIops": "2000",
349+
"replicationStatus": "FAILOVER_COMPLETED",
350+
"serviceResourceBackendIpAddress": "fsf-natestdal0505-fcb-fz.service.softlayer.com",
351+
"snapshotCapacityGb": "5",
352+
"activeTransactions": [
353+
{
354+
"createDate": "",
355+
"elapsedSeconds": 111763,
356+
"guestId": "",
357+
"hardwareId": "",
358+
"id": "",
359+
"modifyDate": "",
360+
"statusChangeDate": "",
361+
"transactionGroup": {
362+
"name": "Volume Modification"
363+
},
364+
"transactionStatus": {
365+
"friendlyName": "In Progress"
366+
}
367+
}
368+
],
369+
"parentVolume": {
370+
"accountId": 1414935,
371+
"capacityGb": 120,
372+
"createDate": "2024-05-16T02:28:02-05:00",
373+
"guestId": "",
374+
"hardwareId": "",
375+
"hostId": "",
376+
"id": 609491967,
377+
"nasType": "SNAPSHOT",
378+
"notes": "vol_duplicate_snapshot_2024-05-16_0228",
379+
"serviceProviderId": 1,
380+
"storageTypeId": "16",
381+
"upgradableFlag": True,
382+
"username": "SL02SV1414935_187",
383+
"serviceResourceBackendIpAddress": "fsf-natestdal0505-fcb-fz.service.softlayer.com",
384+
"serviceResourceName": "Storage Type 02 Aggregate natestdal0505-fc-d",
385+
"snapshotSizeBytes": "0"
386+
},
387+
"replicationPartners": [
388+
{
389+
"id": 609491945,
390+
"username": "SL02SV1414935_187_REP_1",
391+
"serviceResourceBackendIpAddress": "fsf-natestdal0505-ffb-fz.service.softlayer.com",
392+
"replicationSchedule": {
393+
"active": 1,
394+
"createDate": "2024-05-16T01:20:19-05:00",
395+
"id": 666339,
396+
"modifyDate": "",
397+
"name": "SL02SV1414935_187_HOURLY_REP",
398+
"partnershipId": "",
399+
"typeId": 32,
400+
"volumeId": 609491933,
401+
"type": {
402+
"keyname": "REPLICATION_HOURLY"
403+
}
404+
},
405+
"serviceResource": {
406+
"backendIpAddress": "fsf-natestdal0505-ffb-fz.service.softlayer.com",
407+
"id": 57365,
408+
"name": "Storage Type 02 Aggregate natestdal0505-ff-d",
409+
"datacenter": {
410+
"name": "dal10"
411+
},
412+
"type": {
413+
"type": "NETAPP_STOR_AGGR"
414+
}
415+
}
416+
}
417+
],
418+
"serviceResource": {
419+
"backendIpAddress": "fsf-natestdal0505-fcb-fz.service.softlayer.com",
420+
"id": 52292,
421+
"name": "Storage Type 02 Aggregate natestdal0505-fc-d",
422+
"attributes": [
423+
{
424+
"value": "2",
425+
"attributeType": {
426+
"keyname": "STAAS_VERSION"
427+
}
428+
}
429+
],
430+
"datacenter": {
431+
"name": "lon02"
432+
},
433+
"type": {
434+
"type": "NETAPP_STOR_AGGR"
435+
}
436+
},
437+
"storageType": {
438+
"keyName": "PERFORMANCE_FILE_STORAGE"
337439
}
440+
}

tests/CLI/formatting_table_tests.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
55
:license: MIT, see LICENSE for more details.
66
"""
7+
from rich.console import Console
8+
79

810
from SoftLayer.CLI import exceptions
911
from SoftLayer.CLI import formatting
@@ -21,6 +23,48 @@ def test_boolean_table(self):
2123
table.add_row(["entry1"])
2224
self.assertTrue(table)
2325

26+
def test_key_value_table(self):
27+
expected = """┌───────────┬─────────────────────────┐
28+
│ Key │ Value │
29+
├───────────┼─────────────────────────┤
30+
│ First │ One │
31+
│ Sub Table │ ┌─────────┬───────────┐ │
32+
│ │ │ Sub Key │ Sub Value │ │
33+
│ │ ├─────────┼───────────┤ │
34+
│ │ │ Second │ Two │ │
35+
│ │ └─────────┴───────────┘ │
36+
└───────────┴─────────────────────────┘
37+
"""
38+
table = formatting.KeyValueTable(["Key", "Value"])
39+
table.add_row(["First", "One"])
40+
sub_table = formatting.KeyValueTable(["Sub Key", "Sub Value"])
41+
sub_table.add_row(["Second", "Two"])
42+
table.add_row(["Sub Table", sub_table])
43+
console = Console()
44+
45+
with console.capture() as capture:
46+
to_print = formatting.format_output(table)
47+
console.print(to_print)
48+
result = capture.get()
49+
self.assertEqual(expected, result)
50+
51+
def test_unrenderable_recovery_table(self):
52+
expected = """│ Sub Table │ [<rich.table.Table object at"""
53+
table = formatting.KeyValueTable(["Key", "Value"])
54+
table.add_row(["First", "One"])
55+
sub_table = formatting.KeyValueTable(["Sub Key", "Sub Value"])
56+
sub_table.add_row(["Second", "Two"])
57+
# You can't have a list of tables, that isn't handled very well.
58+
listable = [sub_table]
59+
table.add_row(["Sub Table", listable])
60+
console = Console()
61+
with console.capture() as capture:
62+
to_print = formatting.format_output(table)
63+
console.print(to_print)
64+
result = capture.get()
65+
print(result)
66+
self.assertIn(expected, result)
67+
2468

2569
class IterToTableTests(testing.TestCase):
2670

tests/CLI/modules/file_tests.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
:license: MIT, see LICENSE for more details.
66
"""
77
from SoftLayer.CLI import exceptions
8+
from SoftLayer.fixtures import SoftLayer_Network_Storage
89
from SoftLayer import SoftLayerError
910
from SoftLayer import testing
1011

@@ -220,6 +221,13 @@ def test_volume_detail_name_identifier(self):
220221
self.assert_called_with('SoftLayer_Network_Storage', 'getObject', identifier=1)
221222
self.assert_no_fail(result)
222223

224+
def test_volume_detail_issues2154(self):
225+
lun_mock = self.set_mock('SoftLayer_Network_Storage', 'getObject')
226+
lun_mock.return_value = SoftLayer_Network_Storage.FILE_DETAIL_ISSUE2154
227+
result = self.run_command(['--format=table', 'file', 'volume-detail', '1234'])
228+
self.assert_no_fail(result)
229+
self.assertIn("SL02SV1414935_187", result.output)
230+
223231
def test_volume_order_performance_iops_not_given(self):
224232
result = self.run_command(['file', 'volume-order',
225233
'--storage-type=performance', '--size=20',

0 commit comments

Comments
 (0)