Skip to content

Commit 1361a62

Browse files
Merge pull request softlayer#722 from sudorandom/iodine53-block-storage-reviewed
Add block storage Support
2 parents e35107a + 2bf9320 commit 1361a62

16 files changed

Lines changed: 1260 additions & 5 deletions

File tree

SoftLayer/CLI/block/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Block Storage."""

SoftLayer/CLI/block/access_list.py

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
"""List hosts with access to volume."""
2+
# :license: MIT, see LICENSE for more details.
3+
4+
import click
5+
import SoftLayer
6+
from SoftLayer.CLI import columns as column_helper
7+
from SoftLayer.CLI import environment
8+
from SoftLayer.CLI import formatting
9+
10+
11+
def _format_name(obj):
12+
if obj['type'] == 'VIRTUAL':
13+
return "{0}.{1}".format(obj['hostname'], obj['domain'])
14+
15+
elif obj['type'] == 'HARDWARE':
16+
return "{0}.{1}".format(obj['hostname'], obj['domain'])
17+
18+
elif obj['type'] == 'SUBNET':
19+
name = "{0}/{1}".format(
20+
obj['networkIdentifier'],
21+
obj['cidr']
22+
)
23+
if 'note' in obj.keys():
24+
name = "{0} ({1})".format(name, obj['note'])
25+
26+
return name
27+
28+
elif obj['type'] == 'IP':
29+
name = obj['ipAddress']
30+
if 'note' in obj.keys():
31+
name = "{0} ({1})".format(name, obj['note'])
32+
33+
return name
34+
else:
35+
raise Exception('Unknown type %s' % obj['type'])
36+
37+
38+
COLUMNS = [
39+
column_helper.Column('id', ('id',)),
40+
column_helper.Column('name', _format_name, """
41+
allowedVirtualGuests[hostname,domain],
42+
allowedHardware[hostname,domain],
43+
allowedSubnets[networkIdentifier,cidr,note],
44+
allowedIpAddresses[ipAddress,note],
45+
"""),
46+
column_helper.Column('type', ('type',)),
47+
column_helper.Column(
48+
'private_ip_address',
49+
('primaryBackendIpAddress',),
50+
"""
51+
allowedVirtualGuests.primaryBackendIpAddress
52+
allowedHardware.primaryBackendIpAddress
53+
allowedSubnets.primaryBackendIpAddress
54+
allowedIpAddresses.primaryBackendIpAddress
55+
"""),
56+
column_helper.Column(
57+
'host_iqn',
58+
('allowedHost', 'name',),
59+
"""
60+
allowedVirtualGuests.allowedHost.name
61+
allowedHardware.allowedHost.name
62+
allowedSubnets.allowedHost.name
63+
allowedIpAddresses.allowedHost.name
64+
"""),
65+
column_helper.Column(
66+
'username',
67+
('allowedHost', 'credential', 'username',),
68+
"""
69+
allowedVirtualGuests.allowedHost.credential.username
70+
allowedHardware.allowedHost.credential.username
71+
allowedSubnets.allowedHost.credential.username
72+
allowedIpAddresses.allowedHost.credential.username
73+
"""),
74+
column_helper.Column(
75+
'password',
76+
('allowedHost', 'credential', 'password',),
77+
"""
78+
allowedVirtualGuests.allowedHost.credential.password
79+
allowedHardware.allowedHost.credential.password
80+
allowedSubnets.allowedHost.credential.password
81+
allowedIpAddresses.allowedHost.credential.password
82+
"""),
83+
]
84+
85+
86+
DEFAULT_COLUMNS = [
87+
'id',
88+
'name',
89+
'type',
90+
'private_ip_address',
91+
'host_iqn',
92+
'username',
93+
'password',
94+
]
95+
96+
97+
@click.command()
98+
@click.argument('volume_id')
99+
@click.option('--sortby', help='Column to sort by', default='name')
100+
@click.option('--columns',
101+
callback=column_helper.get_formatter(COLUMNS),
102+
help='Columns to display. Options: {0}'.format(
103+
', '.join(column.name for column in COLUMNS)),
104+
default=','.join(DEFAULT_COLUMNS))
105+
@environment.pass_env
106+
def cli(env, columns, sortby, volume_id):
107+
"""List ACLs."""
108+
block_manager = SoftLayer.BlockStorageManager(env.client)
109+
access_list = block_manager.get_block_volume_access_list(
110+
volume_id=volume_id)
111+
table = formatting.Table(columns.columns)
112+
table.sortby = sortby
113+
114+
for key, type_name in [('allowedVirtualGuests', 'VIRTUAL'),
115+
('allowedHardware', 'HARDWARE'),
116+
('allowedSubnets', 'SUBNET'),
117+
('allowedIpAddresses', 'IP')]:
118+
for obj in access_list.get(key, []):
119+
obj['type'] = type_name
120+
table.add_row([value or formatting.blank()
121+
for value in columns.row(obj)])
122+
123+
env.fout(table)

SoftLayer/CLI/block/cancel.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
"""Cancel an existing iSCSI account."""
2+
# :license: MIT, see LICENSE for more details.
3+
4+
import click
5+
6+
import SoftLayer
7+
from SoftLayer.CLI import environment
8+
from SoftLayer.CLI import exceptions
9+
from SoftLayer.CLI import formatting
10+
11+
12+
@click.command()
13+
@click.argument('volume-id')
14+
@click.option('--reason', help="An optional reason for cancellation")
15+
@click.option('--immediate',
16+
is_flag=True,
17+
help="Cancels the block storage volume immediately instead "
18+
"of on the billing anniversary")
19+
@environment.pass_env
20+
def cli(env, volume_id, reason, immediate):
21+
"""Cancel an existing block storage volume."""
22+
23+
block_storage_manager = SoftLayer.BlockStorageManager(env.client)
24+
25+
if not (env.skip_confirmations or formatting.no_going_back(volume_id)):
26+
raise exceptions.CLIAbort('Aborted')
27+
28+
block_storage_manager.cancel_block_volume(volume_id, reason, immediate)

SoftLayer/CLI/block/detail.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
"""Display details for a specified volume."""
2+
# :license: MIT, see LICENSE for more details.
3+
4+
import click
5+
import SoftLayer
6+
from SoftLayer.CLI import environment
7+
from SoftLayer.CLI import formatting
8+
from SoftLayer import utils
9+
10+
11+
@click.command()
12+
@click.argument('volume_id')
13+
@environment.pass_env
14+
def cli(env, volume_id):
15+
"""Display details for a specified volume."""
16+
block_manager = SoftLayer.BlockStorageManager(env.client)
17+
block_volume = block_manager.get_block_volume_details(volume_id)
18+
block_volume = utils.NestedDict(block_volume)
19+
20+
table = formatting.KeyValueTable(['Name', 'Value'])
21+
table.align['Name'] = 'r'
22+
table.align['Value'] = 'l'
23+
24+
storage_type = block_volume['storageType']['keyName'].split('_').pop(0)
25+
table.add_row(['ID', block_volume['id']])
26+
table.add_row(['Username', block_volume['username']])
27+
table.add_row(['Type', storage_type])
28+
table.add_row(['Capacity (GB)', "%iGB" % block_volume['capacityGb']])
29+
table.add_row(['LUN Id', "%s" % block_volume['lunId']])
30+
31+
if block_volume.get('iops'):
32+
table.add_row(['IOPs', block_volume['iops']])
33+
34+
if block_volume.get('storageTierLevel'):
35+
table.add_row([
36+
'Endurance Tier',
37+
block_volume['storageTierLevel']['description'],
38+
])
39+
40+
table.add_row([
41+
'Data Center',
42+
block_volume['serviceResource']['datacenter']['name'],
43+
])
44+
table.add_row([
45+
'Target IP',
46+
block_volume['serviceResourceBackendIpAddress'],
47+
])
48+
49+
if block_volume['snapshotCapacityGb']:
50+
table.add_row([
51+
'Snapshot Capacity (GB)',
52+
block_volume['snapshotCapacityGb'],
53+
])
54+
table.add_row([
55+
'Snapshot Used (Bytes)',
56+
block_volume['parentVolume']['snapshotSizeBytes'],
57+
])
58+
59+
env.fout(table)

SoftLayer/CLI/block/list.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
"""List block storage volumes."""
2+
# :license: MIT, see LICENSE for more details.
3+
4+
import click
5+
import SoftLayer
6+
from SoftLayer.CLI import columns as column_helper
7+
from SoftLayer.CLI import environment
8+
from SoftLayer.CLI import formatting
9+
10+
11+
COLUMNS = [
12+
column_helper.Column('id', ('id',), mask="id"),
13+
column_helper.Column('username', ('username',), mask="username"),
14+
column_helper.Column('datacenter',
15+
('serviceResource', 'datacenter', 'name'),
16+
mask="serviceResource.datacenter.name"),
17+
column_helper.Column(
18+
'storage_type',
19+
lambda b: b['storageType']['keyName'].split('_').pop(0),
20+
mask="storageType.keyName"),
21+
column_helper.Column('capacity_gb', ('capacityGb',), mask="capacityGb"),
22+
column_helper.Column('bytes_used', ('bytesUsed',), mask="bytesUsed"),
23+
column_helper.Column('ip_addr', ('serviceResourceBackendIpAddress',),
24+
mask="serviceResourceBackendIpAddress"),
25+
]
26+
27+
DEFAULT_COLUMNS = [
28+
'id',
29+
'username',
30+
'datacenter',
31+
'storage_type',
32+
'capacity_gb',
33+
'bytes_used',
34+
'ip_addr'
35+
]
36+
37+
38+
@click.command()
39+
@click.option('--username', '-u', help='Volume username')
40+
@click.option('--datacenter', '-d', help='Datacenter shortname')
41+
@click.option('--storage-type',
42+
help='Type of storage volume',
43+
type=click.Choice(['performance', 'endurance']))
44+
@click.option('--sortby', help='Column to sort by', default='username')
45+
@click.option('--columns',
46+
callback=column_helper.get_formatter(COLUMNS),
47+
help='Columns to display. Options: {0}'.format(
48+
', '.join(column.name for column in COLUMNS)),
49+
default=','.join(DEFAULT_COLUMNS))
50+
@environment.pass_env
51+
def cli(env, sortby, columns, datacenter, username, storage_type):
52+
"""List block storage."""
53+
block_manager = SoftLayer.BlockStorageManager(env.client)
54+
block_volumes = block_manager.list_block_volumes(datacenter=datacenter,
55+
username=username,
56+
storage_type=storage_type,
57+
mask=columns.mask())
58+
59+
table = formatting.Table(columns.columns)
60+
table.sortby = sortby
61+
62+
for block_volume in block_volumes:
63+
table.add_row([value or formatting.blank()
64+
for value in columns.row(block_volume)])
65+
66+
env.fout(table)

SoftLayer/CLI/block/order.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
"""Order a block storage volume."""
2+
# :license: MIT, see LICENSE for more details.
3+
4+
import click
5+
import SoftLayer
6+
from SoftLayer.CLI import environment
7+
from SoftLayer.CLI import exceptions
8+
9+
10+
CONTEXT_SETTINGS = dict(token_normalize_func=lambda x: x.upper())
11+
12+
13+
@click.command(context_settings=CONTEXT_SETTINGS)
14+
@click.option('--storage-type',
15+
help='Type of storage volume',
16+
type=click.Choice(['performance', 'endurance']),
17+
required=True)
18+
@click.option('--size',
19+
type=int,
20+
help='Size of storage volume in GB',
21+
required=True)
22+
@click.option('--iops',
23+
type=int,
24+
help='Performance Storage IOPs,'
25+
' between 100 and 6000 in multiples of 100'
26+
' [required for storage-type performance]')
27+
@click.option('--tier',
28+
help='Endurance Storage Tier (IOP per GB)'
29+
' [required for storage-type endurance]',
30+
type=click.Choice(['0.25', '2', '4']))
31+
@click.option('--os-type',
32+
help='Operating System',
33+
type=click.Choice([
34+
'HYPER_V',
35+
'LINUX',
36+
'VMWARE',
37+
'WINDOWS_2008',
38+
'WINDOWS_GPT',
39+
'WINDOWS',
40+
'XEN']),
41+
required=True)
42+
@click.option('--location',
43+
help='Datacenter short name (e.g.: dal09)',
44+
required=True)
45+
@environment.pass_env
46+
def cli(env, storage_type, size, iops, tier, os_type, location):
47+
"""Order a block storage volume."""
48+
block_manager = SoftLayer.BlockStorageManager(env.client)
49+
storage_type = storage_type.lower()
50+
51+
if storage_type == 'performance':
52+
if iops is None:
53+
raise exceptions.CLIAbort(
54+
'Option --iops required with Performance')
55+
56+
if iops < 100 or iops > 6000:
57+
raise exceptions.CLIAbort(
58+
'Option --iops must be between 100 and 6000, inclusive')
59+
60+
if iops % 100 != 0:
61+
raise exceptions.CLIAbort(
62+
'Option --iops must be a multiple of 100'
63+
)
64+
65+
try:
66+
order = block_manager.order_block_volume(
67+
storage_type='performance_storage_iscsi',
68+
location=location,
69+
size=size,
70+
iops=iops,
71+
os_type=os_type
72+
)
73+
except ValueError as ex:
74+
raise exceptions.ArgumentError(str(ex))
75+
76+
if storage_type == 'endurance':
77+
if tier is None:
78+
raise exceptions.CLIAbort(
79+
'Option --tier required with Endurance in IOPS/GB [0.25,2,4]')
80+
81+
try:
82+
order = block_manager.order_block_volume(
83+
storage_type='storage_service_enterprise',
84+
location=location,
85+
size=size,
86+
tier_level=float(tier),
87+
os_type=os_type
88+
)
89+
except ValueError as ex:
90+
raise exceptions.ArgumentError(str(ex))
91+
92+
if 'placedOrder' in order.keys():
93+
click.echo("Order #{0} placed successfully!".format(
94+
order['placedOrder']['id']))
95+
for item in order['placedOrder']['items']:
96+
click.echo(" > %s" % item['description'])
97+
else:
98+
click.echo("Order could not be placed! Please verify your options " +
99+
"and try again.")
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
"""Create a block storage snapshot."""
2+
# :license: MIT, see LICENSE for more details.
3+
4+
import click
5+
import SoftLayer
6+
from SoftLayer.CLI import environment
7+
8+
9+
@click.command()
10+
@click.argument('snapshot_id')
11+
@environment.pass_env
12+
def cli(env, snapshot_id):
13+
"""Deletes a snapshot on a given volume"""
14+
block_manager = SoftLayer.BlockStorageManager(env.client)
15+
block_manager.delete_snapshot(snapshot_id)

0 commit comments

Comments
 (0)