From bd15a58865a25bf0a3fecb3966b3f6e66c8656e1 Mon Sep 17 00:00:00 2001 From: Nilo Lisboa Date: Fri, 17 Feb 2017 17:16:09 -0600 Subject: [PATCH 1/7] Permitted volume sizes now listed --- SoftLayer/CLI/block/order.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/SoftLayer/CLI/block/order.py b/SoftLayer/CLI/block/order.py index 4aaf03067..b7b30d740 100644 --- a/SoftLayer/CLI/block/order.py +++ b/SoftLayer/CLI/block/order.py @@ -16,7 +16,8 @@ type=click.Choice(['performance', 'endurance']), required=True) @click.option('--size', - type=int, + type=click.Choice(['20', '40', '80', '100', '250', '500', '1000', '2000', '4000', \ + '8000', '12000']), help='Size of block storage volume in GB', required=True) @click.option('--iops', @@ -71,14 +72,14 @@ def cli(env, storage_type, size, iops, tier, os_type, if snapshot_size is not None: raise exceptions.CLIAbort( 'Option --snapshot-size not allowed for performance volumes.' - ' Snapshots are only available for endurance storage.' + 'Snapshots are only available for endurance storage.' ) try: order = block_manager.order_block_volume( storage_type='performance_storage_iscsi', location=location, - size=size, + size=int(size), iops=iops, os_type=os_type ) @@ -96,7 +97,7 @@ def cli(env, storage_type, size, iops, tier, os_type, order = block_manager.order_block_volume( storage_type='storage_service_enterprise', location=location, - size=size, + size=int(size), tier_level=float(tier), os_type=os_type, snapshot_size=snapshot_size From d12665abda3e6f25fb658ab992c07ae860c7e303 Mon Sep 17 00:00:00 2001 From: Nilo Lisboa Date: Wed, 22 Feb 2017 16:07:33 -0600 Subject: [PATCH 2/7] fixed some zise information --- SoftLayer/CLI/iscsi/create.py | 7 ++++--- SoftLayer/CLI/iscsi/detail.py | 2 ++ SoftLayer/managers/iscsi.py | 3 ++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/SoftLayer/CLI/iscsi/create.py b/SoftLayer/CLI/iscsi/create.py index 95872c79a..86a8e3eb6 100644 --- a/SoftLayer/CLI/iscsi/create.py +++ b/SoftLayer/CLI/iscsi/create.py @@ -9,9 +9,10 @@ @click.command() @click.option('--size', - type=click.INT, + type=click.Choice(['20', '40', '80', '100', '250', '500', '1000', '2000', '4000', \ + '8000', '12000']), required=True, - help="Size of the iSCSI volume to create (in gibibytes)") + help="Size of the iSCSI volume to create (in Gigabytes)") @click.option('--datacenter', required=True, help="Datacenter shortname (sng01, dal05, ...)") @@ -20,4 +21,4 @@ def cli(env, size, datacenter): """Creates an iSCSI target.""" iscsi_mgr = SoftLayer.ISCSIManager(env.client) - iscsi_mgr.create_iscsi(size=size, location=datacenter) + iscsi_mgr.create_iscsi(size=int(size), location=datacenter) diff --git a/SoftLayer/CLI/iscsi/detail.py b/SoftLayer/CLI/iscsi/detail.py index 6ad433272..c7c9f9a17 100644 --- a/SoftLayer/CLI/iscsi/detail.py +++ b/SoftLayer/CLI/iscsi/detail.py @@ -47,8 +47,10 @@ def cli(env, identifier, password): table.add_row(['notes', result['notes']]) if password: + # print ("PASSWORD = {", password, "} ; {", result['password'], "}") pass_table = formatting.Table(['username', 'password']) pass_table.add_row([result['username'], result['password']]) table.add_row(['users', pass_table]) + env.fout(table) diff --git a/SoftLayer/managers/iscsi.py b/SoftLayer/managers/iscsi.py index 85df7d401..84beaf3ad 100644 --- a/SoftLayer/managers/iscsi.py +++ b/SoftLayer/managers/iscsi.py @@ -27,6 +27,7 @@ def __init__(self, client): def _find_item_prices(self, size, categorycode=''): """Retrieves the Item Price IDs.""" + print ("HERE") item_prices = self.client['Product_Package'].getItems( id=0, mask='id,capacity,prices[id]', @@ -36,7 +37,7 @@ def _find_item_prices(self, size, categorycode=''): 'categories': { 'categoryCode': {'operation': categorycode} }}}) - + print(">>>>>>>>>>>>>>> ", item_prices) for item_price in item_prices: for price in item_price['prices']: return price['id'] From 4610481911ff6bd56825a4e52ac62e747cc391a2 Mon Sep 17 00:00:00 2001 From: Nilo Lisboa Date: Mon, 6 Mar 2017 17:14:20 -0600 Subject: [PATCH 3/7] Removed Snapshot and ISCSI commands from SLCLI --- SoftLayer/CLI/block/order.py | 4 +- SoftLayer/CLI/iscsi/__init__.py | 1 - SoftLayer/CLI/iscsi/cancel.py | 30 ---- SoftLayer/CLI/iscsi/create.py | 24 ---- SoftLayer/CLI/iscsi/detail.py | 56 -------- SoftLayer/CLI/iscsi/list.py | 43 ------ SoftLayer/CLI/routes.py | 13 -- SoftLayer/CLI/snapshot/__init__.py | 1 - SoftLayer/CLI/snapshot/cancel.py | 21 --- SoftLayer/CLI/snapshot/create.py | 20 --- SoftLayer/CLI/snapshot/create_space.py | 22 --- SoftLayer/CLI/snapshot/list.py | 37 ----- SoftLayer/CLI/snapshot/restore_volume.py | 22 --- SoftLayer/managers/__init__.py | 2 - SoftLayer/managers/iscsi.py | 175 ----------------------- tests/managers/iscsi_tests.py | 122 ---------------- 16 files changed, 2 insertions(+), 591 deletions(-) delete mode 100644 SoftLayer/CLI/iscsi/__init__.py delete mode 100644 SoftLayer/CLI/iscsi/cancel.py delete mode 100644 SoftLayer/CLI/iscsi/create.py delete mode 100644 SoftLayer/CLI/iscsi/detail.py delete mode 100644 SoftLayer/CLI/iscsi/list.py delete mode 100644 SoftLayer/CLI/snapshot/__init__.py delete mode 100644 SoftLayer/CLI/snapshot/cancel.py delete mode 100644 SoftLayer/CLI/snapshot/create.py delete mode 100644 SoftLayer/CLI/snapshot/create_space.py delete mode 100644 SoftLayer/CLI/snapshot/list.py delete mode 100644 SoftLayer/CLI/snapshot/restore_volume.py delete mode 100644 SoftLayer/managers/iscsi.py delete mode 100644 tests/managers/iscsi_tests.py diff --git a/SoftLayer/CLI/block/order.py b/SoftLayer/CLI/block/order.py index b7b30d740..461e85a3d 100644 --- a/SoftLayer/CLI/block/order.py +++ b/SoftLayer/CLI/block/order.py @@ -16,8 +16,8 @@ type=click.Choice(['performance', 'endurance']), required=True) @click.option('--size', - type=click.Choice(['20', '40', '80', '100', '250', '500', '1000', '2000', '4000', \ - '8000', '12000']), + type=click.Choice(['20', '40', '80', '100', '250', '500', '1000', + '2000', '4000', '8000', '12000']), help='Size of block storage volume in GB', required=True) @click.option('--iops', diff --git a/SoftLayer/CLI/iscsi/__init__.py b/SoftLayer/CLI/iscsi/__init__.py deleted file mode 100644 index 2b379049a..000000000 --- a/SoftLayer/CLI/iscsi/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""iSCSI storage.""" diff --git a/SoftLayer/CLI/iscsi/cancel.py b/SoftLayer/CLI/iscsi/cancel.py deleted file mode 100644 index f744cb627..000000000 --- a/SoftLayer/CLI/iscsi/cancel.py +++ /dev/null @@ -1,30 +0,0 @@ -"""Cancel an existing iSCSI account.""" -# :license: MIT, see LICENSE for more details. - -import click - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions -from SoftLayer.CLI import formatting -from SoftLayer.CLI import helpers - - -@click.command() -@click.argument('identifier') -@click.option('--reason', help="An optional reason for cancellation") -@click.option('--immediate', - is_flag=True, - help="Cancels the iSCSI immediately instead of on the billing " - "anniversary") -@environment.pass_env -def cli(env, identifier, reason, immediate): - """Cancel an existing iSCSI account.""" - - iscsi_mgr = SoftLayer.ISCSIManager(env.client) - iscsi_id = helpers.resolve_id(iscsi_mgr.resolve_ids, identifier, 'iSCSI') - - if not (env.skip_confirmations or formatting.no_going_back(iscsi_id)): - raise exceptions.CLIAbort('Aborted') - - iscsi_mgr.cancel_iscsi(iscsi_id, reason, immediate) diff --git a/SoftLayer/CLI/iscsi/create.py b/SoftLayer/CLI/iscsi/create.py deleted file mode 100644 index 86a8e3eb6..000000000 --- a/SoftLayer/CLI/iscsi/create.py +++ /dev/null @@ -1,24 +0,0 @@ -"""Creates an iSCSI target.""" -# :license: MIT, see LICENSE for more details. - -import click - -import SoftLayer -from SoftLayer.CLI import environment - - -@click.command() -@click.option('--size', - type=click.Choice(['20', '40', '80', '100', '250', '500', '1000', '2000', '4000', \ - '8000', '12000']), - required=True, - help="Size of the iSCSI volume to create (in Gigabytes)") -@click.option('--datacenter', - required=True, - help="Datacenter shortname (sng01, dal05, ...)") -@environment.pass_env -def cli(env, size, datacenter): - """Creates an iSCSI target.""" - - iscsi_mgr = SoftLayer.ISCSIManager(env.client) - iscsi_mgr.create_iscsi(size=int(size), location=datacenter) diff --git a/SoftLayer/CLI/iscsi/detail.py b/SoftLayer/CLI/iscsi/detail.py deleted file mode 100644 index c7c9f9a17..000000000 --- a/SoftLayer/CLI/iscsi/detail.py +++ /dev/null @@ -1,56 +0,0 @@ -"""Get details for an iSCSI target.""" -# :license: MIT, see LICENSE for more details. - -import click - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting -from SoftLayer.CLI import helpers -from SoftLayer import utils - - -@click.command() -@click.argument('identifier') -@click.option('--password', - is_flag=True, - help="Show credentials to access the iSCSI target") -@environment.pass_env -def cli(env, identifier, password): - """Get details for an iSCSI target.""" - - iscsi_mgr = SoftLayer.ISCSIManager(env.client) - - iscsi_id = helpers.resolve_id(iscsi_mgr.resolve_ids, identifier, 'iSCSI') - result = iscsi_mgr.get_iscsi(iscsi_id) - result = utils.NestedDict(result) - - table = formatting.KeyValueTable(['name', 'value']) - table.align['name'] = 'r' - table.align['value'] = 'l' - - table.add_row(['id', result['id']]) - table.add_row(['serviceResourceName', result['serviceResourceName']]) - table.add_row(['createDate', result['createDate']]) - table.add_row(['nasType', result['nasType']]) - table.add_row(['capacityGb', result['capacityGb']]) - - if result['snapshotCapacityGb']: - table.add_row(['snapshotCapacityGb', result['snapshotCapacityGb']]) - - table.add_row(['mountableFlag', result['mountableFlag']]) - table.add_row(['serviceResourceBackendIpAddress', - result['serviceResourceBackendIpAddress']]) - table.add_row(['price', result['billingItem']['recurringFee']]) - table.add_row(['BillingItemId', result['billingItem']['id']]) - if result.get('notes'): - table.add_row(['notes', result['notes']]) - - if password: - # print ("PASSWORD = {", password, "} ; {", result['password'], "}") - pass_table = formatting.Table(['username', 'password']) - pass_table.add_row([result['username'], result['password']]) - table.add_row(['users', pass_table]) - - - env.fout(table) diff --git a/SoftLayer/CLI/iscsi/list.py b/SoftLayer/CLI/iscsi/list.py deleted file mode 100644 index e36a04bfb..000000000 --- a/SoftLayer/CLI/iscsi/list.py +++ /dev/null @@ -1,43 +0,0 @@ -"""List iSCSI targets.""" -# :license: MIT, see LICENSE for more details. - -import click - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting -from SoftLayer import utils - - -@click.command() -@environment.pass_env -def cli(env): - """List iSCSI targets.""" - - iscsi_mgr = SoftLayer.ISCSIManager(env.client) - iscsi_list = iscsi_mgr.list_iscsi() - iscsi_list = [utils.NestedDict(n) for n in iscsi_list] - table = formatting.Table([ - 'id', - 'datacenter', - 'size', - 'username', - 'password', - 'server' - ]) - for iscsi in iscsi_list: - table.add_row([ - iscsi['id'], - utils.lookup(iscsi, - 'serviceResource', - 'datacenter', - 'name') or formatting.blank(), - formatting.FormattedItem(iscsi.get('capacityGb', - formatting.blank()), - "%dGB" % iscsi.get('capacityGb', 0)), - iscsi.get('username', formatting.blank()), - iscsi.get('password', formatting.blank()), - iscsi.get('serviceResourceBackendIpAddress', - formatting.blank())]) - - env.fout(table) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index e66f8df83..7dcfa6b27 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -118,12 +118,6 @@ ('image:import', 'SoftLayer.CLI.image.import:cli'), ('image:export', 'SoftLayer.CLI.image.export:cli'), - ('iscsi', 'SoftLayer.CLI.iscsi'), - ('iscsi:cancel', 'SoftLayer.CLI.iscsi.cancel:cli'), - ('iscsi:create', 'SoftLayer.CLI.iscsi.create:cli'), - ('iscsi:detail', 'SoftLayer.CLI.iscsi.detail:cli'), - ('iscsi:list', 'SoftLayer.CLI.iscsi.list:cli'), - ('loadbal', 'SoftLayer.CLI.loadbal'), ('loadbal:cancel', 'SoftLayer.CLI.loadbal.cancel:cli'), ('loadbal:create', 'SoftLayer.CLI.loadbal.create:cli'), @@ -193,13 +187,6 @@ ('hardware:credentials', 'SoftLayer.CLI.hardware.credentials:cli'), ('hardware:update-firmware', 'SoftLayer.CLI.hardware.update_firmware:cli'), - ('snapshot', 'SoftLayer.CLI.snapshot'), - ('snapshot:cancel', 'SoftLayer.CLI.snapshot.cancel:cli'), - ('snapshot:create', 'SoftLayer.CLI.snapshot.create:cli'), - ('snapshot:create-space', 'SoftLayer.CLI.snapshot.create_space:cli'), - ('snapshot:list', 'SoftLayer.CLI.snapshot.list:cli'), - ('snapshot:restore-volume', 'SoftLayer.CLI.snapshot.restore_volume:cli'), - ('sshkey', 'SoftLayer.CLI.sshkey'), ('sshkey:add', 'SoftLayer.CLI.sshkey.add:cli'), ('sshkey:remove', 'SoftLayer.CLI.sshkey.remove:cli'), diff --git a/SoftLayer/CLI/snapshot/__init__.py b/SoftLayer/CLI/snapshot/__init__.py deleted file mode 100644 index 59e9a51fa..000000000 --- a/SoftLayer/CLI/snapshot/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Snapshots.""" diff --git a/SoftLayer/CLI/snapshot/cancel.py b/SoftLayer/CLI/snapshot/cancel.py deleted file mode 100644 index a684f87ff..000000000 --- a/SoftLayer/CLI/snapshot/cancel.py +++ /dev/null @@ -1,21 +0,0 @@ -"""Cancel/Delete iSCSI snapshot.""" -# :license: MIT, see LICENSE for more details. - -import click - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import helpers - - -@click.command() -@click.argument('identifier') -@environment.pass_env -def cli(env, identifier): - """Cancel/Delete iSCSI snapshot.""" - - iscsi_mgr = SoftLayer.ISCSIManager(env.client) - snapshot_id = helpers.resolve_id(iscsi_mgr.resolve_ids, - identifier, - 'Snapshot') - iscsi_mgr.delete_snapshot(snapshot_id) diff --git a/SoftLayer/CLI/snapshot/create.py b/SoftLayer/CLI/snapshot/create.py deleted file mode 100644 index 531b4d2eb..000000000 --- a/SoftLayer/CLI/snapshot/create.py +++ /dev/null @@ -1,20 +0,0 @@ -"""Create a snapshot of an iSCSI volume.""" -# :license: MIT, see LICENSE for more details. - -import click - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import helpers - - -@click.command() -@click.argument('identifier') -@click.option('--notes', help="An optional note for the snapshot") -@environment.pass_env -def cli(env, identifier, notes): - """Create a snapshot of an iSCSI volume.""" - - iscsi_mgr = SoftLayer.ISCSIManager(env.client) - iscsi_id = helpers.resolve_id(iscsi_mgr.resolve_ids, identifier, 'iSCSI') - iscsi_mgr.create_snapshot(iscsi_id, notes) diff --git a/SoftLayer/CLI/snapshot/create_space.py b/SoftLayer/CLI/snapshot/create_space.py deleted file mode 100644 index 2636e7d8e..000000000 --- a/SoftLayer/CLI/snapshot/create_space.py +++ /dev/null @@ -1,22 +0,0 @@ -"""Orders snapshot space for given iSCSI.""" -# :license: MIT, see LICENSE for more details. - -import click - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import helpers - - -@click.command() -@click.argument('identifier') -@click.option('--capacity', - type=click.INT, - help="Size of snapshot space to create") -@environment.pass_env -def cli(env, identifier, capacity): - """Orders snapshot space for given iSCSI.""" - - iscsi_mgr = SoftLayer.ISCSIManager(env.client) - iscsi_id = helpers.resolve_id(iscsi_mgr.resolve_ids, identifier, 'iSCSI') - iscsi_mgr.create_snapshot_space(iscsi_id, capacity) diff --git a/SoftLayer/CLI/snapshot/list.py b/SoftLayer/CLI/snapshot/list.py deleted file mode 100644 index 8de8c082c..000000000 --- a/SoftLayer/CLI/snapshot/list.py +++ /dev/null @@ -1,37 +0,0 @@ -"""List iSCSI Snapshots.""" -# :license: MIT, see LICENSE for more details. - -import click - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting -from SoftLayer.CLI import helpers -from SoftLayer import utils - - -@click.command() -@click.argument('iscsi-identifier') -@environment.pass_env -def cli(env, iscsi_identifier): - """List iSCSI Snapshots.""" - - iscsi_mgr = SoftLayer.ISCSIManager(env.client) - iscsi_id = helpers.resolve_id(iscsi_mgr.resolve_ids, - iscsi_identifier, - 'iSCSI') - iscsi = env.client['Network_Storage_Iscsi'] - snapshots = iscsi.getPartnerships( - mask='volumeId,partnerVolumeId,createDate,type', id=iscsi_id) - snapshots = [utils.NestedDict(n) for n in snapshots] - - table = formatting.Table(['id', 'createDate', 'name', 'description']) - - for snapshot in snapshots: - table.add_row([ - snapshot['partnerVolumeId'], - snapshot['createDate'], - snapshot['type']['name'], - snapshot['type']['description'], - ]) - env.fout(table) diff --git a/SoftLayer/CLI/snapshot/restore_volume.py b/SoftLayer/CLI/snapshot/restore_volume.py deleted file mode 100644 index 7fa4168ad..000000000 --- a/SoftLayer/CLI/snapshot/restore_volume.py +++ /dev/null @@ -1,22 +0,0 @@ -"""Restores volume from existing snapshot.""" -# :license: MIT, see LICENSE for more details. - -import click - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import helpers - - -@click.command() -@click.argument('snapshot-id') -@click.argument('volume-id') -@environment.pass_env -def cli(env, snapshot_id, volume_id): - """Restores volume from existing snapshot.""" - iscsi_mgr = SoftLayer.ISCSIManager(env.client) - volume_id = helpers.resolve_id(iscsi_mgr.resolve_ids, volume_id, 'iSCSI') - snapshot_id = helpers.resolve_id(iscsi_mgr.resolve_ids, - snapshot_id, - 'Snapshot') - iscsi_mgr.restore_from_snapshot(volume_id, snapshot_id) diff --git a/SoftLayer/managers/__init__.py b/SoftLayer/managers/__init__.py index b9a44a0d7..4b63b7726 100644 --- a/SoftLayer/managers/__init__.py +++ b/SoftLayer/managers/__init__.py @@ -14,7 +14,6 @@ from SoftLayer.managers.firewall import FirewallManager from SoftLayer.managers.hardware import HardwareManager from SoftLayer.managers.image import ImageManager -from SoftLayer.managers.iscsi import ISCSIManager from SoftLayer.managers.load_balancer import LoadBalancerManager from SoftLayer.managers.messaging import MessagingManager from SoftLayer.managers.metadata import MetadataManager @@ -34,7 +33,6 @@ 'FirewallManager', 'HardwareManager', 'ImageManager', - 'ISCSIManager', 'LoadBalancerManager', 'MessagingManager', 'MetadataManager', diff --git a/SoftLayer/managers/iscsi.py b/SoftLayer/managers/iscsi.py deleted file mode 100644 index 84beaf3ad..000000000 --- a/SoftLayer/managers/iscsi.py +++ /dev/null @@ -1,175 +0,0 @@ -""" - SoftLayer.iscsi - ~~~~~~~~~~~~~~~ - ISCSI Manager/helpers - - :license: MIT, see LICENSE for more details. -""" -from SoftLayer import exceptions -from SoftLayer import utils - - -class ISCSIManager(utils.IdentifierMixin, object): - """Manages SoftLayer iSCSI storage accounts. - - See product information here: - https://knowledgelayer.softlayer.com/topic/iscsi - - :param SoftLayer.API.BaseClient client: the client instance - - """ - - def __init__(self, client): - self.configuration = {} - self.client = client - self.iscsi_svc = self.client['Network_Storage_Iscsi'] - self.product_order = self.client['Product_Order'] - - def _find_item_prices(self, size, categorycode=''): - """Retrieves the Item Price IDs.""" - print ("HERE") - item_prices = self.client['Product_Package'].getItems( - id=0, - mask='id,capacity,prices[id]', - filter={ - 'items': { - 'capacity': {'operation': int(size)}, - 'categories': { - 'categoryCode': {'operation': categorycode} - }}}) - print(">>>>>>>>>>>>>>> ", item_prices) - for item_price in item_prices: - for price in item_price['prices']: - return price['id'] - - raise exceptions.SoftLayerError( - "Could not find a valid price with for the given size") - - def _build_order(self, item_price, location): - """Returns a dict appropriate for Product_Order::placeOrder().""" - - location_id = self._get_location_id(location) - order = { - 'complexType': - 'SoftLayer_Container_Product_Order_Network_Storage_Iscsi', - 'location': location_id, - 'packageId': 0, # storage package - 'prices': [{'id': item_price}], - 'quantity': 1 - } - return order - - def _get_location_id(self, location): - """Returns location id of datacenter for ProductOrder::placeOrder().""" - loc_svc = self.client['Location_Datacenter'] - datacenters = loc_svc.getDatacenters(mask='mask[longName,id,name]') - for datacenter in datacenters: - if datacenter['name'] == location: - location = datacenter['id'] - return location - raise ValueError('Invalid datacenter name specified.') - - def create_iscsi(self, size=None, location=None): - """Places an order for iSCSI volume. - - :param integer size: size of iSCSI volume to create - :param string location: datacenter to use to create volume in - """ - item_price = self._find_item_prices(int(size), - categorycode='iscsi') - iscsi_order = self._build_order(item_price, location) - return self.product_order.placeOrder(iscsi_order) - - def list_iscsi(self): - """List iSCSI volume.""" - account = self.client['Account'] - iscsi_list = account.getIscsiNetworkStorage( - mask='eventCount,serviceResource[datacenter.name]') - return iscsi_list - - def get_iscsi(self, volume_id, **kwargs): - """Get details about a iSCSI storage. - - :param integer volume_id: the volume ID - :returns: A dictionary containing a large amount of information about - the specified storage. - """ - - if 'mask' not in kwargs: - items = [ - 'id', - 'serviceResourceName', - 'createDate', - 'nasType', - 'capacityGb', - 'snapshotCapacityGb', - 'mountableFlag', - 'serviceResourceBackendIpAddress', - 'billingItem', - 'notes', - 'username', - 'password' - ] - kwargs['mask'] = "mask[%s]" % ','.join(items) - return self.iscsi_svc.getObject(id=volume_id, **kwargs) - - def cancel_iscsi(self, volume_id, reason='unNeeded', immediate=False): - """Cancels the given iSCSI volume. - - :param integer volume_id: the volume ID - - """ - iscsi = self.get_iscsi( - volume_id, - mask='mask[id,capacityGb,username,password,billingItem[id]]') - billingitemid = iscsi['billingItem']['id'] - self.client['Billing_Item'].cancelItem( - immediate, - True, - reason, - id=billingitemid) - - def create_snapshot(self, volume_id, notes='No longer needed'): - """Orders a snapshot for given volume. - - :param integer volume_id: the volume ID - """ - - self.iscsi_svc.createSnapshot(notes, id=volume_id) - - def create_snapshot_space(self, volume_id, capacity): - """Orders a snapshot space for given volume. - - :param integer volume_id: the volume ID - :param integer capacity: capacity in ~GB - """ - item_price = self._find_item_prices( - int(capacity), categorycode='iscsi_snapshot_space') - result = self.get_iscsi( - volume_id, mask='mask[id,capacityGb,serviceResource[datacenter]]') - snapshotspaceorder = { - 'complexType': - 'SoftLayer_Container_Product_Order_\ -Network_Storage_Iscsi_SnapshotSpace', - 'location': result['serviceResource']['datacenter']['id'], - 'packageId': 0, - 'prices': [{'id': item_price}], - 'quantity': 1, - 'volumeId': volume_id} - self.product_order.placeOrder(snapshotspaceorder) - - def delete_snapshot(self, snapshot_id): - """Deletes the given snapshot. - - :params: integer snapshot_id: the snapshot ID - """ - - self.iscsi_svc.deleteObject(id=snapshot_id) - - def restore_from_snapshot(self, volume_id, snapshot_id): - """Restore the volume to snapshot's contents. - - :params: imteger volume_id: the volume ID - :params: integer snapshot_id: the snapshot ID - """ - self.iscsi_svc.restoreFromSnapshot(snapshot_id, id=volume_id) diff --git a/tests/managers/iscsi_tests.py b/tests/managers/iscsi_tests.py deleted file mode 100644 index 7ba43834b..000000000 --- a/tests/managers/iscsi_tests.py +++ /dev/null @@ -1,122 +0,0 @@ -""" - SoftLayer.tests.managers.iscsi_tests - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - :license: MIT, see LICENSE for more details. -""" - -import SoftLayer -from SoftLayer import fixtures -from SoftLayer import testing - - -class ISCSITests(testing.TestCase): - def set_up(self): - self.iscsi = SoftLayer.ISCSIManager(self.client) - - def test_get_iscsi(self): - result = self.iscsi.get_iscsi(100) - - self.assertEqual(fixtures.SoftLayer_Network_Storage_Iscsi.getObject, - result) - self.assert_called_with('SoftLayer_Network_Storage_Iscsi', 'getObject', - identifier=100) - - def test_cancel_iscsi_immediately(self): - self.iscsi.cancel_iscsi(600, immediate=True) - - self.assert_called_with('SoftLayer_Billing_Item', 'cancelItem', - args=(True, True, 'unNeeded'), - identifier=600) - - def test_cancel_iscsi_without_reason(self): - self.iscsi.cancel_iscsi(600) - - self.assert_called_with('SoftLayer_Billing_Item', 'cancelItem', - args=(False, True, 'unNeeded'), - identifier=600) - - def test_cancel_iscsi_with_reason(self): - self.iscsi.cancel_iscsi(600, 'Network Performance') - - self.assert_called_with('SoftLayer_Billing_Item', 'cancelItem', - args=(False, True, 'Network Performance'), - identifier=600) - - def test_invalid_datacenter(self): - self.assertRaises(ValueError, - self.iscsi.create_iscsi, - size=10, location='foo') - - def test_create_iscsi(self): - mock = self.set_mock('SoftLayer_Product_Package', 'getItems') - mock.return_value = [ - { - 'id': 4439, - 'capacity': '1', - 'description': '1 GB iSCSI Storage', - 'itemCategory': {'categoryCode': 'iscsi'}, - 'prices': [{'id': 2222}] - } - ] - - self.iscsi.create_iscsi(size=1, location='dal05') - - args = ({'prices': [{'id': 2222}], - 'quantity': 1, - 'location': 0, - 'packageId': 0, - 'complexType': - 'SoftLayer_Container_Product_Order_Network_Storage_Iscsi'},) - self.assert_called_with('SoftLayer_Product_Order', 'placeOrder', - args=args) - - def test_delete_snapshot(self): - self.iscsi.delete_snapshot(1) - - self.assert_called_with('SoftLayer_Network_Storage_Iscsi', - 'deleteObject', - identifier=1) - - def test_create_snapshot(self): - self.iscsi.create_snapshot(100, 'unNeeded') - - self.assert_called_with('SoftLayer_Network_Storage_Iscsi', - 'createSnapshot', - identifier=100) - - def test_create_snapshot_space(self): - mock = self.set_mock('SoftLayer_Product_Package', 'getItems') - mock.return_value = [ - { - 'id': 1121, - 'capacity': '20', - 'description': '20 GB iSCSI snapshot', - 'itemCategory': {'categoryCode': 'iscsi_snapshot_space'}, - 'prices': [{'id': 2014}] - }] - - self.iscsi.create_snapshot_space(100, 20) - - args = ( - { - 'volumeId': 100, - 'location': 138124, - 'packageId': 0, - 'complexType': - 'SoftLayer_Container_' - 'Product_Order_Network_Storage_Iscsi_SnapshotSpace', - 'prices': [{'id': 2014}], - 'quantity': 1, - }, - ) - self.assert_called_with('SoftLayer_Product_Order', 'placeOrder', - args=args) - - def test_restore_from_snapshot(self): - self.iscsi.restore_from_snapshot(100, 101) - - self.assert_called_with('SoftLayer_Network_Storage_Iscsi', - 'restoreFromSnapshot', - args=(101,), - identifier=100) From a5638b413c078497af71cab12f3db163f266ea70 Mon Sep 17 00:00:00 2001 From: Nilo Lisboa Date: Wed, 15 Mar 2017 12:51:15 -0500 Subject: [PATCH 4/7] New replication changes Implemented replica-locations and replica-partners for improved Experience when replicating or looking up information on replicants. --- SoftLayer/CLI/block/replication/locations.py | 49 +++++++++++++++ SoftLayer/CLI/block/replication/partners.py | 57 ++++++++++++++++++ SoftLayer/CLI/file/replication/locations.py | 49 +++++++++++++++ SoftLayer/CLI/file/replication/partners.py | 60 +++++++++++++++++++ SoftLayer/CLI/routes.py | 5 ++ .../fixtures/SoftLayer_Network_Storage.py | 39 ++++++++++++ SoftLayer/managers/block.py | 20 +++++++ SoftLayer/managers/file.py | 20 +++++++ tests/CLI/modules/block_tests.py | 48 +++++++++++++++ tests/CLI/modules/file_tests.py | 48 +++++++++++++++ tests/managers/block_tests.py | 18 ++++++ tests/managers/file_tests.py | 18 ++++++ 12 files changed, 431 insertions(+) create mode 100644 SoftLayer/CLI/block/replication/locations.py create mode 100644 SoftLayer/CLI/block/replication/partners.py create mode 100644 SoftLayer/CLI/file/replication/locations.py create mode 100644 SoftLayer/CLI/file/replication/partners.py diff --git a/SoftLayer/CLI/block/replication/locations.py b/SoftLayer/CLI/block/replication/locations.py new file mode 100644 index 000000000..80ba5d98b --- /dev/null +++ b/SoftLayer/CLI/block/replication/locations.py @@ -0,0 +1,49 @@ +"""List suitable replication datacenters for the given volume.""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import columns as column_helper +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + +COLUMNS = [ + column_helper.Column('ID', ('id',), mask="id"), + column_helper.Column('Long Name', ('longName',), mask="longName"), + column_helper.Column('Short Name', ('name',), mask="name"), +] + +DEFAULT_COLUMNS = [ + 'ID', + 'Long Name', + 'Short Name', +] + + +@click.command() +@click.argument('volume-id') +@click.option('--sortby', help='Column to sort by', default='Long Name') +@click.option('--columns', + callback=column_helper.get_formatter(COLUMNS), + help='Columns to display. Options: {0}'.format( + ', '.join(column.name for column in COLUMNS)), + default=','.join(DEFAULT_COLUMNS)) +@environment.pass_env +def cli(env, columns, sortby, volume_id): + """List suitable replication datacenters for the given volume.""" + block_storage_manager = SoftLayer.BlockStorageManager(env.client) + + legal_centers = block_storage_manager.get_replication_locations( + volume_id + ) + + if not legal_centers: + click.echo("No data centers compatible for replication.") + else: + table = formatting.KeyValueTable(columns.columns) + table.sortby = sortby + for legal_center in legal_centers: + table.add_row([value or formatting.blank() + for value in columns.row(legal_center)]) + + env.fout(table) diff --git a/SoftLayer/CLI/block/replication/partners.py b/SoftLayer/CLI/block/replication/partners.py new file mode 100644 index 000000000..f19be0af5 --- /dev/null +++ b/SoftLayer/CLI/block/replication/partners.py @@ -0,0 +1,57 @@ +"""List existing replicant volumes for a block volume.""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import columns as column_helper +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + +COLUMNS = [ + column_helper.Column('ID', ('id',)), + column_helper.Column('Username', ('username',), mask="username"), + column_helper.Column('Account ID', ('accountId',), mask="accountId"), + column_helper.Column('Capacity (GB)', ('capacityGb',), mask="capacityGb"), + column_helper.Column('Hardware ID', ('hardwareId',), mask="hardwareId"), + column_helper.Column('Guest ID', ('guestId',), mask="guestId"), + column_helper.Column('Host ID', ('hostId',), mask="hostId"), +] + +DEFAULT_COLUMNS = [ + 'ID', + 'Username', + 'Account ID', + 'Capacity (GB)', + 'Hardware ID', + 'Guest ID', + 'Host ID' +] + + +@click.command() +@click.argument('volume-id') +@click.option('--sortby', help='Column to sort by', default='Username') +@click.option('--columns', + callback=column_helper.get_formatter(COLUMNS), + help='Columns to display. Options: {0}'.format( + ', '.join(column.name for column in COLUMNS)), + default=','.join(DEFAULT_COLUMNS)) +@environment.pass_env +def cli(env, columns, sortby, volume_id): + """List existing replicant volumes for a block volume.""" + block_storage_manager = SoftLayer.BlockStorageManager(env.client) + + legal_volumes = block_storage_manager.get_replication_partners( + volume_id + ) + + if not legal_volumes: + click.echo("There are no replication partners for the given volume.") + else: + table = formatting.Table(columns.columns) + table.sortby = sortby + for legal_volume in legal_volumes: + table.add_row([value or formatting.blank() + for value in columns.row(legal_volume)]) + + env.fout(table) diff --git a/SoftLayer/CLI/file/replication/locations.py b/SoftLayer/CLI/file/replication/locations.py new file mode 100644 index 000000000..58f979f70 --- /dev/null +++ b/SoftLayer/CLI/file/replication/locations.py @@ -0,0 +1,49 @@ +"""List suitable replication datacenters for the given volume.""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import columns as column_helper +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + +COLUMNS = [ + column_helper.Column('ID', ('id',), mask="id"), + column_helper.Column('Long Name', ('longName',), mask="longName"), + column_helper.Column('Short Name', ('name',), mask="name"), +] + +DEFAULT_COLUMNS = [ + 'ID', + 'Long Name', + 'Short Name', +] + + +@click.command() +@click.argument('volume-id') +@click.option('--sortby', help='Column to sort by', default='Long Name') +@click.option('--columns', + callback=column_helper.get_formatter(COLUMNS), + help='Columns to display. Options: {0}'.format( + ', '.join(column.name for column in COLUMNS)), + default=','.join(DEFAULT_COLUMNS)) +@environment.pass_env +def cli(env, columns, sortby, volume_id): + """List suitable replication datacenters for the given volume.""" + file_storage_manager = SoftLayer.FileStorageManager(env.client) + + legal_centers = file_storage_manager.get_replication_locations( + volume_id + ) + + if not legal_centers: + click.echo("No data centers compatible for replication.") + else: + table = formatting.KeyValueTable(columns.columns) + table.sortby = sortby + for legal_center in legal_centers: + table.add_row([value or formatting.blank() + for value in columns.row(legal_center)]) + + env.fout(table) diff --git a/SoftLayer/CLI/file/replication/partners.py b/SoftLayer/CLI/file/replication/partners.py new file mode 100644 index 000000000..866248fdf --- /dev/null +++ b/SoftLayer/CLI/file/replication/partners.py @@ -0,0 +1,60 @@ +"""List existing replicant volumes for a file volume.""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import columns as column_helper +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + +COLUMNS = [ + column_helper.Column('ID', ('id',)), + column_helper.Column('Username', ('username',), mask="username"), + column_helper.Column('Account ID', ('accountId',), mask="accountId"), + column_helper.Column('Capacity (GB)', ('capacityGb',), mask="capacityGb"), + column_helper.Column('Hardware ID', ('hardwareId',), mask="hardwareId"), + column_helper.Column('Guest ID', ('guestId',), mask="guestId"), + column_helper.Column('Host ID', ('hostId',), mask="hostId"), +] + +# In-line comment to avoid similarity flag with block version + +DEFAULT_COLUMNS = [ + 'ID', + 'Username', + 'Account ID', + 'Capacity (GB)', + 'Hardware ID', + 'Guest ID', + 'Host ID' +] + + +@click.command() +@click.argument('volume-id') +@click.option('--sortby', help='Column to sort by', default='Username') +@click.option('--columns', + callback=column_helper.get_formatter(COLUMNS), + help='Columns to display. Options: {0}'.format( + ', '.join(column.name for column in COLUMNS)), + default=','.join(DEFAULT_COLUMNS)) +@environment.pass_env +def cli(env, columns, sortby, volume_id): + """List existing replicant volumes for a file volume.""" + file_storage_manager = SoftLayer.FileStorageManager(env.client) + + legal_volumes = file_storage_manager.get_replication_partners( + volume_id + ) + + if not legal_volumes: + click.echo("There are no replication partners for the given volume.") + else: + table = formatting.Table(columns.columns) + table.sortby = sortby + + for legal_volume in legal_volumes: + table.add_row([value or formatting.blank() + for value in columns.row(legal_volume)]) + + env.fout(table) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 7dcfa6b27..10f2a7da1 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -63,6 +63,9 @@ ('block:replica-failback', 'SoftLayer.CLI.block.replication.failback:cli'), ('block:replica-failover', 'SoftLayer.CLI.block.replication.failover:cli'), ('block:replica-order', 'SoftLayer.CLI.block.replication.order:cli'), + ('block:replica-partners', 'SoftLayer.CLI.block.replication.partners:cli'), + ('block:replica-locations', + 'SoftLayer.CLI.block.replication.locations:cli'), ('block:snapshot-cancel', 'SoftLayer.CLI.block.snapshot.cancel:cli'), ('block:snapshot-create', 'SoftLayer.CLI.block.snapshot.create:cli'), ('block:snapshot-delete', 'SoftLayer.CLI.block.snapshot.delete:cli'), @@ -83,6 +86,8 @@ ('file:replica-failback', 'SoftLayer.CLI.file.replication.failback:cli'), ('file:replica-failover', 'SoftLayer.CLI.file.replication.failover:cli'), ('file:replica-order', 'SoftLayer.CLI.file.replication.order:cli'), + ('file:replica-partners', 'SoftLayer.CLI.file.replication.partners:cli'), + ('file:replica-locations', 'SoftLayer.CLI.file.replication.locations:cli'), ('file:snapshot-cancel', 'SoftLayer.CLI.file.snapshot.cancel:cli'), ('file:snapshot-create', 'SoftLayer.CLI.file.snapshot.create:cli'), ('file:snapshot-delete', 'SoftLayer.CLI.file.snapshot.delete:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Network_Storage.py b/SoftLayer/fixtures/SoftLayer_Network_Storage.py index 43012eb7a..91162e892 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Storage.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Storage.py @@ -84,12 +84,16 @@ 'id': 1784, 'username': 'TEST_REP_1', 'serviceResourceBackendIpAddress': '10.3.174.79', + 'nasType': 'ISCSI_REPLICANT', + 'createDate': '2017:50:15-04:00', 'serviceResource': {'datacenter': {'name': 'wdc01'}}, 'replicationSchedule': {'type': {'keyname': 'REPLICATION_HOURLY'}}, }, { 'id': 1785, 'username': 'TEST_REP_2', 'serviceResourceBackendIpAddress': '10.3.177.84', + 'nasType': 'ISCSI_REPLICANT', + 'createDate': '2017:50:15-04:00', 'serviceResource': {'datacenter': {'name': 'dal01'}}, 'replicationSchedule': {'type': {'keyname': 'REPLICATION_DAILY'}}, }], @@ -102,6 +106,41 @@ 'snapshotSizeBytes': '42', }] +getReplicationPartners = [{ + 'id': 1784, + 'accountId': 3000, + 'capacityGb': 20, + 'username': 'TEST_REP_1', + 'serviceResourceBackendIpAddress': '10.3.174.79', + 'nasType': 'ISCSI_REPLICANT', + 'hostId': None, + 'guestId': None, + 'hardwareId': None, + 'createDate': '2017:50:15-04:00', + 'serviceResource': {'datacenter': {'name': 'wdc01'}}, + 'replicationSchedule': {'type': {'keyname': 'REPLICATION_HOURLY'}}, +}, { + 'id': 1785, + 'accountId': 3001, + 'capacityGb': 20, + 'username': 'TEST_REP_2', + 'serviceResourceBackendIpAddress': '10.3.177.84', + 'nasType': 'ISCSI_REPLICANT', + 'hostId': None, + 'guestId': None, + 'hardwareId': None, + 'createDate': '2017:50:15-04:00', + 'serviceResource': {'datacenter': {'name': 'dal01'}}, + 'replicationSchedule': {'type': {'keyname': 'REPLICATION_DAILY'}}, +}] + +getValidReplicationTargetDatacenterLocations = [{ + 'id': 12345, + 'longName': 'Dallas 05', + 'name': 'dal05' +}] + + deleteObject = True allowAccessFromHostList = True removeAccessFromHostList = True diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index 7a933b048..89660d13e 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -195,6 +195,26 @@ def deauthorize_host_to_volume(self, volume_id, return self.client.call('Network_Storage', 'removeAccessFromHostList', host_templates, id=volume_id, **kwargs) + def get_replication_partners(self, volume_id): + """Acquires list of replicant volumes pertaining to the given volume. + + :param volume_id: The ID of the primary volume to be replicated + :return: Returns an array of SoftLayer_Location objects + """ + return self.client.call('Network_Storage', + 'getReplicationPartners', + id=volume_id) + + def get_replication_locations(self, volume_id): + """Acquires list of the datacenters to which a volume can be replicated. + + :param volume_id: The ID of the primary volume to be replicated + :return: Returns an array of SoftLayer_Network_Storage objects + """ + return self.client.call('Network_Storage', + 'getValidReplicationTargetDatacenterLocations', + id=volume_id) + def order_replicant_volume(self, volume_id, snapshot_schedule, location, tier=None, os_type=None): """Places an order for a replicant block volume. diff --git a/SoftLayer/managers/file.py b/SoftLayer/managers/file.py index 638db5477..22c73b45b 100644 --- a/SoftLayer/managers/file.py +++ b/SoftLayer/managers/file.py @@ -220,6 +220,26 @@ def order_replicant_volume(self, volume_id, snapshot_schedule, return self.client.call('Product_Order', 'placeOrder', order) + def get_replication_partners(self, volume_id): + """Acquires list of replicant volumes pertaining to the given volume. + + :param volume_id: The ID of the primary volume to be replicated + :return: Returns an array of SoftLayer_Location objects + """ + return self.client.call('Network_Storage', + 'getReplicationPartners', + id=volume_id) + + def get_replication_locations(self, volume_id): + """Acquires list of the datacenters to which a volume can be replicated. + + :param volume_id: The ID of the primary volume to be replicated + :return: Returns an array of SoftLayer_Network_Storage objects + """ + return self.client.call('Network_Storage', + 'getValidReplicationTargetDatacenterLocations', + id=volume_id) + def delete_snapshot(self, snapshot_id): """Deletes the specified snapshot object. diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index 017598817..f65d1cbc9 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -344,6 +344,54 @@ def test_replicant_failover(self): self.assertEqual('Failover to replicant is now in progress.\n', result.output) + def test_replication_locations(self): + result = self.run_command(['block', 'replica-locations', '1234']) + self.assert_no_fail(result) + self.assertEqual( + { + '12345': 'Dallas 05', + }, + json.loads(result.output)) + + @mock.patch('SoftLayer.BlockStorageManager.get_replication_locations') + def test_replication_locations_unsuccessful(self, locations_mock): + locations_mock.return_value = False + result = self.run_command(['block', 'replica-locations', '1234']) + self.assertEqual('No data centers compatible for replication.\n', + result.output) + + def test_replication_partners(self): + result = self.run_command(['block', 'replica-partners', '1234']) + self.assert_no_fail(result) + self.assertEqual([ + { + 'ID': 1784, + 'Account ID': 3000, + 'Capacity (GB)': 20, + 'Host ID': None, + 'Guest ID': None, + 'Hardware ID': None, + 'Username': 'TEST_REP_1', + }, + { + 'ID': 1785, + 'Account ID': 3001, + 'Host ID': None, + 'Guest ID': None, + 'Hardware ID': None, + 'Capacity (GB)': 20, + 'Username': 'TEST_REP_2', + }], + json.loads(result.output)) + + @mock.patch('SoftLayer.BlockStorageManager.get_replication_partners') + def test_replication_partners_unsuccessful(self, partners_mock): + partners_mock.return_value = False + result = self.run_command(['block', 'replica-partners', '1234']) + self.assertEqual( + 'There are no replication partners for the given volume.\n', + result.output) + @mock.patch('SoftLayer.BlockStorageManager.failover_to_replicant') def test_replicant_failover_unsuccessful(self, failover_mock): failover_mock.return_value = False diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index 0b7f8c4e9..df8abec92 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -418,3 +418,51 @@ def test_replicant_order(self, order_mock): ' > 20 GB Storage Space\n' ' > 10 GB Storage Space (Snapshot Space)\n' ' > 20 GB Storage Space Replicant of: TEST\n') + + def test_replication_locations(self): + result = self.run_command(['file', 'replica-locations', '1234']) + self.assert_no_fail(result) + self.assertEqual( + { + '12345': 'Dallas 05', + }, + json.loads(result.output)) + + @mock.patch('SoftLayer.FileStorageManager.get_replication_locations') + def test_replication_locations_unsuccessful(self, locations_mock): + locations_mock.return_value = False + result = self.run_command(['file', 'replica-locations', '1234']) + self.assertEqual('No data centers compatible for replication.\n', + result.output) + + def test_replication_partners(self): + result = self.run_command(['file', 'replica-partners', '1234']) + self.assert_no_fail(result) + self.assertEqual([ + { + 'ID': 1784, + 'Account ID': 3000, + 'Capacity (GB)': 20, + 'Host ID': None, + 'Guest ID': None, + 'Hardware ID': None, + 'Username': 'TEST_REP_1', + }, + { + 'ID': 1785, + 'Account ID': 3001, + 'Host ID': None, + 'Guest ID': None, + 'Hardware ID': None, + 'Capacity (GB)': 20, + 'Username': 'TEST_REP_2', + }], + json.loads(result.output)) + + @mock.patch('SoftLayer.FileStorageManager.get_replication_partners') + def test_replication_partners_unsuccessful(self, partners_mock): + partners_mock.return_value = False + result = self.run_command(['file', 'replica-partners', '1234']) + self.assertEqual( + 'There are no replication partners for the given volume.\n', + result.output) diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index d288ac954..99ec24411 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -157,6 +157,24 @@ def test_replicant_failback(self): identifier=1234, ) + def test_get_replication_partners(self): + self.block.get_replication_partners(1234) + + self.assert_called_with( + 'SoftLayer_Network_Storage', + 'getReplicationPartners', + identifier=1234, + ) + + def test_get_replication_locations(self): + self.block.get_replication_locations(1234) + + self.assert_called_with( + 'SoftLayer_Network_Storage', + 'getValidReplicationTargetDatacenterLocations', + identifier=1234, + ) + def test_order_block_volume_invalid_storage_type(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') mock.return_value = [{}] diff --git a/tests/managers/file_tests.py b/tests/managers/file_tests.py index a92ddcb43..3b761e0c1 100644 --- a/tests/managers/file_tests.py +++ b/tests/managers/file_tests.py @@ -166,6 +166,24 @@ def test_replicant_failback(self): identifier=1234, ) + def test_get_replication_partners(self): + self.file.get_replication_partners(1234) + + self.assert_called_with( + 'SoftLayer_Network_Storage', + 'getReplicationPartners', + identifier=1234, + ) + + def test_get_replication_locations(self): + self.file.get_replication_locations(1234) + + self.assert_called_with( + 'SoftLayer_Network_Storage', + 'getValidReplicationTargetDatacenterLocations', + identifier=1234, + ) + def test_delete_snapshot(self): result = self.file.delete_snapshot(100) From 5b6e0014b85f46503fe86310bbd1f4faa1688764 Mon Sep 17 00:00:00 2001 From: Ryan Rossiter Date: Tue, 25 Apr 2017 09:00:31 -0500 Subject: [PATCH 5/7] Update slcli doc with new subcommands The CLI doc still had some old commands in it, so those have been removed. There have also been new commands added to slcli that weren't reflected in the doc. --- docs/cli.rst | 90 ++++++++++++++++++++++++++++------------------------ 1 file changed, 48 insertions(+), 42 deletions(-) diff --git a/docs/cli.rst b/docs/cli.rst index 1a8613d2c..32249d298 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -58,48 +58,54 @@ To discover the available commands, simply type `slcli`. :: $ slcli - Usage: slcli [OPTIONS] COMMAND [ARGS]... - - SoftLayer Command-line Client - - Options: - --format [table|raw|json] Output format - -C, --config PATH Config file location - --debug [0|1|2|3] Sets the debug noise level - -v, --verbose Sets the debug noise level - --timings Time each API call and display after results - --proxy TEXT HTTP[S] proxy to be use to make API calls - -y, --really Confirm all prompt actions - --fixtures Use fixtures instead of actually making API calls - --version Show the version and exit. - --help Show this message and exit. - - Commands: - call-api Call arbitrary API endpoints. - cdn Content Delivery Network. - config CLI configuration. - dns Domain Name System. - firewall Firewalls. - globalip Global IP addresses. - image Compute images. - iscsi iSCSI storage. - loadbal Load balancers. - messaging Message queue service. - metadata Find details about this machine. - nas Network Attached Storage. - rwhois Referral Whois. - server Hardware servers. - snapshot Snapshots. - sshkey SSH Keys. - ssl SSL Certificates. - subnet Network subnets. - summary Account summary. - ticket Support tickets. - vlan Network VLANs. - vs Virtual Servers. - - To use most commands your SoftLayer username and api_key need to be - configured. The easiest way to do that is to use: 'slcli setup' + Usage: slcli [OPTIONS] COMMAND [ARGS]... + + SoftLayer Command-line Client + + Options: + --format [table|raw|json|jsonraw] + Output format [default: table] + -C, --config PATH Config file location [default: + ~/.softlayer] + -v, --verbose Sets the debug noise level, specify multiple + times for more verbosity. + --proxy TEXT HTTP[S] proxy to be use to make API calls + -y, --really / --not-really Confirm all prompt actions + --demo / --no-demo Use demo data instead of actually making API + calls + --version Show the version and exit. + -h, --help Show this message and exit. + + Commands: + block Block Storage. + call-api Call arbitrary API endpoints. + cdn Content Delivery Network. + config CLI configuration. + dns Domain Name System. + file File Storage. + firewall Firewalls. + globalip Global IP addresses. + hardware Hardware servers. + image Compute images. + loadbal Load balancers. + messaging Message queue service. + metadata Find details about this machine. + nas Network Attached Storage. + object-storage Object Storage. + report Reports. + rwhois Referral Whois. + setup Edit configuration. + shell Enters a shell for slcli. + sshkey SSH Keys. + ssl SSL Certificates. + subnet Network subnets. + summary Account summary. + ticket Support tickets. + virtual Virtual Servers. + vlan Network VLANs. + + To use most commands your SoftLayer username and api_key need to be + configured. The easiest way to do that is to use: 'slcli setup' As you can see, there are a number of commands/sections. To look at the list of subcommands for virtual servers type `slcli vs`. For example: From 68d6c55e1878ddfa6116ba7b2cb8184467b9f68e Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Tue, 25 Apr 2017 12:17:06 -0500 Subject: [PATCH 6/7] pinning pylint at 1.6.5 for now since >1.7 has a few new tests that need to be resolved now --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 6eaaa1f75..61bf0480c 100644 --- a/tox.ini +++ b/tox.ini @@ -18,7 +18,7 @@ basepython = python2.7 deps = -r{toxinidir}/tools/test-requirements.txt hacking - pylint + pylint==1.6.5 commands = flake8 SoftLayer tests From 817eb8539825a58d05223e3f95bdd1847b625211 Mon Sep 17 00:00:00 2001 From: Nilo Lisboa Date: Fri, 21 Apr 2017 18:19:23 -0500 Subject: [PATCH 7/7] Implementation of slcli block/file volume-count --- SoftLayer/CLI/block/count.py | 55 +++++++++++++++++++++++++ SoftLayer/CLI/block/detail.py | 1 - SoftLayer/CLI/block/list.py | 5 ++- SoftLayer/CLI/file/count.py | 55 +++++++++++++++++++++++++ SoftLayer/CLI/file/list.py | 5 ++- SoftLayer/CLI/routes.py | 2 + SoftLayer/fixtures/SoftLayer_Account.py | 2 +- SoftLayer/managers/block.py | 18 +++++++- SoftLayer/managers/file.py | 17 +++++++- tests/CLI/modules/block_tests.py | 14 ++++++- tests/CLI/modules/file_tests.py | 13 +++++- 11 files changed, 179 insertions(+), 8 deletions(-) create mode 100644 SoftLayer/CLI/block/count.py create mode 100644 SoftLayer/CLI/file/count.py diff --git a/SoftLayer/CLI/block/count.py b/SoftLayer/CLI/block/count.py new file mode 100644 index 000000000..52211f0d5 --- /dev/null +++ b/SoftLayer/CLI/block/count.py @@ -0,0 +1,55 @@ +"""List number of block storage volumes per datacenter.""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import columns as column_helper +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + +COLUMNS = [ + column_helper.Column('Datacenter', + ('serviceResource', 'datacenter', 'name'), + mask="serviceResource.datacenter.name"), + column_helper.Column('Count', + '', + mask=None) +] + +DEFAULT_COLUMNS = [ + 'Datacenter', + 'Count' +] + + +@click.command() +@click.option('--datacenter', '-d', help='Datacenter shortname') +@click.option('--sortby', help='Column to sort by', default='Datacenter') +@click.option('--columns', + callback=column_helper.get_formatter(COLUMNS), + help='Columns to display. Options: {0}'.format( + ', '.join(column.name for column in COLUMNS)), + default=','.join(DEFAULT_COLUMNS)) +@environment.pass_env +def cli(env, sortby, columns, datacenter): + """List number of block storage volumes per datacenter.""" + block_manager = SoftLayer.BlockStorageManager(env.client) + block_volumes = block_manager.list_block_volumes(datacenter=datacenter, + mask=columns.mask()) + + # cycle through all block volumes and count datacenter occurences. + datacenters = dict() + for volume in block_volumes: + service_resource = volume['serviceResource'] + if 'datacenter' in service_resource: + datacenter = service_resource['datacenter']['name'] + if datacenter not in datacenters.keys(): + datacenters[datacenter] = 1 + else: + datacenters[datacenter] += 1 + + table = formatting.KeyValueTable(columns.columns) + table.sortby = sortby + for datacenter in datacenters: + table.add_row([datacenter, datacenters[datacenter]]) + env.fout(table) diff --git a/SoftLayer/CLI/block/detail.py b/SoftLayer/CLI/block/detail.py index 51a1fbd4f..c0d1ad371 100644 --- a/SoftLayer/CLI/block/detail.py +++ b/SoftLayer/CLI/block/detail.py @@ -16,7 +16,6 @@ def cli(env, volume_id): block_manager = SoftLayer.BlockStorageManager(env.client) block_volume = block_manager.get_block_volume_details(volume_id) block_volume = utils.NestedDict(block_volume) - table = formatting.KeyValueTable(['Name', 'Value']) table.align['Name'] = 'r' table.align['Value'] = 'l' diff --git a/SoftLayer/CLI/block/list.py b/SoftLayer/CLI/block/list.py index 41e09ca30..4cc9afd2b 100644 --- a/SoftLayer/CLI/block/list.py +++ b/SoftLayer/CLI/block/list.py @@ -28,6 +28,8 @@ column_helper.Column('lunId', ('lunId',), mask="lunId"), column_helper.Column('active_transactions', ('activeTransactionCount',), mask="activeTransactionCount"), + column_helper.Column('rep_partner_count', ('replicationPartnerCount',), + mask="replicationPartnerCount"), column_helper.Column( 'created_by', ('billingItem', 'orderItem', 'order', 'userRecord', 'username')), @@ -42,7 +44,8 @@ 'bytes_used', 'ip_addr', 'lunId', - 'active_transactions' + 'active_transactions', + 'rep_partner_count' ] diff --git a/SoftLayer/CLI/file/count.py b/SoftLayer/CLI/file/count.py new file mode 100644 index 000000000..fc0700cc7 --- /dev/null +++ b/SoftLayer/CLI/file/count.py @@ -0,0 +1,55 @@ +"""List number of file storage volumes per datacenter.""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import columns as column_helper +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + +COLUMNS = [ + column_helper.Column('Datacenter', + ('serviceResource', 'datacenter', 'name'), + mask="serviceResource.datacenter.name"), + column_helper.Column('Count', + '', + mask=None) +] + +DEFAULT_COLUMNS = [ + 'Datacenter', + 'Count' +] + + +@click.command() +@click.option('--datacenter', '-d', help='Datacenter shortname') +@click.option('--sortby', help='Column to sort by', default='Datacenter') +@click.option('--columns', + callback=column_helper.get_formatter(COLUMNS), + help='Columns to display. Options: {0}'.format( + ', '.join(column.name for column in COLUMNS)), + default=','.join(DEFAULT_COLUMNS)) +@environment.pass_env +def cli(env, sortby, columns, datacenter): + """List number of file storage volumes per datacenter.""" + + file_manager = SoftLayer.FileStorageManager(env.client) + file_volumes = file_manager.list_file_volumes(datacenter=datacenter, + mask=columns.mask()) + + datacenters = dict() + for volume in file_volumes: + service_resource = volume['serviceResource'] + if 'datacenter' in service_resource: + datacenter = service_resource['datacenter']['name'] + if datacenter not in datacenters.keys(): + datacenters[datacenter] = 1 + else: + datacenters[datacenter] += 1 + + table = formatting.KeyValueTable(columns.columns) + table.sortby = sortby + for datacenter in datacenters: + table.add_row([datacenter, datacenters[datacenter]]) + env.fout(table) diff --git a/SoftLayer/CLI/file/list.py b/SoftLayer/CLI/file/list.py index 40399538c..86028f4ee 100644 --- a/SoftLayer/CLI/file/list.py +++ b/SoftLayer/CLI/file/list.py @@ -29,6 +29,8 @@ mask="activeTransactionCount"), column_helper.Column('mount_addr', ('fileNetworkMountAddress',), mask="fileNetworkMountAddress",), + column_helper.Column('rep_partner_count', ('replicationPartnerCount',), + mask="replicationPartnerCount"), column_helper.Column( 'created_by', ('billingItem', 'orderItem', 'order', 'userRecord', 'username')), @@ -43,7 +45,8 @@ 'bytes_used', 'ip_addr', 'active_transactions', - 'mount_addr' + 'mount_addr', + 'rep_partner_count' ] diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 10f2a7da1..ec21e653d 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -78,6 +78,7 @@ ('block:volume-detail', 'SoftLayer.CLI.block.detail:cli'), ('block:volume-list', 'SoftLayer.CLI.block.list:cli'), ('block:volume-order', 'SoftLayer.CLI.block.order:cli'), + ('block:volume-count', 'SoftLayer.CLI.block.count:cli'), ('file', 'SoftLayer.CLI.file'), ('file:access-authorize', 'SoftLayer.CLI.file.access.authorize:cli'), @@ -100,6 +101,7 @@ ('file:volume-detail', 'SoftLayer.CLI.file.detail:cli'), ('file:volume-list', 'SoftLayer.CLI.file.list:cli'), ('file:volume-order', 'SoftLayer.CLI.file.order:cli'), + ('file:volume-count', 'SoftLayer.CLI.file.count:cli'), ('firewall', 'SoftLayer.CLI.firewall'), ('firewall:add', 'SoftLayer.CLI.firewall.add:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index 561ff3926..b8005e0ee 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -536,7 +536,7 @@ 'notes': """{'status': 'available'}""", 'password': '', 'serviceProviderId': 1, - 'serviceResource': {'datacenter': {'id': 449500}}, + 'serviceResource': {'datacenter': {'name': 'dal05', 'id': 449500}}, 'serviceResourceBackendIpAddress': '10.1.2.3', 'serviceResourceName': 'Storage Type 01 Aggregate staaspar0101_pc01', 'username': 'username', diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index 89660d13e..192605d68 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -41,10 +41,25 @@ def list_block_volumes(self, datacenter=None, username=None, 'bytesUsed', 'serviceResource.datacenter[name]', 'serviceResourceBackendIpAddress', - 'activeTransactionCount' + 'activeTransactionCount', + 'replicationPartnerCount', + ',replicationPartners[id,username,' + 'serviceResourceBackendIpAddress,' + 'serviceResource[datacenter[name]],' + 'storageType,capacityGb,lunId,bytesUsed,' + 'activeTransactionCount,' + 'replicationSchedule[type[keyname]]]', ] kwargs['mask'] = ','.join(items) + # Retrieve relevant replicant information to be displayed. + kwargs['mask'] += ',replicationPartners[id,username,'\ + 'serviceResourceBackendIpAddress,'\ + 'serviceResource[datacenter[name]],'\ + 'storageType,capacityGb,lunId,bytesUsed,'\ + 'activeTransactionCount,'\ + 'replicationSchedule[type[keyname]]]' + _filter = utils.NestedDict(kwargs.get('filter') or {}) _filter['iscsiNetworkStorage']['serviceResource']['type']['type'] = \ @@ -52,6 +67,7 @@ def list_block_volumes(self, datacenter=None, username=None, _filter['iscsiNetworkStorage']['storageType']['keyName'] = ( utils.query_filter('*BLOCK_STORAGE*')) + if storage_type: _filter['iscsiNetworkStorage']['storageType']['keyName'] = ( utils.query_filter('%s_BLOCK_STORAGE' % storage_type.upper())) diff --git a/SoftLayer/managers/file.py b/SoftLayer/managers/file.py index 22c73b45b..e52a4a0c2 100644 --- a/SoftLayer/managers/file.py +++ b/SoftLayer/managers/file.py @@ -36,10 +36,25 @@ def list_file_volumes(self, datacenter=None, username=None, 'serviceResource.datacenter[name]', 'serviceResourceBackendIpAddress', 'activeTransactionCount', - 'fileNetworkMountAddress' + 'fileNetworkMountAddress', + 'replicationPartnerCount', + ',replicationPartners[id,username,' + 'serviceResourceBackendIpAddress,' + 'serviceResource[datacenter[name]],' + 'storageType,capacityGb,lunId,bytesUsed,' + 'activeTransactionCount,' + 'replicationSchedule[type[keyname]]]', ] kwargs['mask'] = ','.join(items) + # Retrieve relevant replicant information to be displayed. + kwargs['mask'] += ',replicationPartners[id,username,'\ + 'serviceResourceBackendIpAddress,'\ + 'serviceResource[datacenter[name]],'\ + 'storageType,capacityGb,lunId,bytesUsed,'\ + 'activeTransactionCount,'\ + 'replicationSchedule[type[keyname]]]' + _filter = utils.NestedDict(kwargs.get('filter') or {}) _filter['nasNetworkStorage']['serviceResource']['type']['type'] = \ diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index f65d1cbc9..101f41e6d 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -105,16 +105,27 @@ def test_volume_list(self): { 'bytes_used': None, 'capacity_gb': 20, - 'datacenter': None, + 'datacenter': 'dal05', 'id': 100, 'ip_addr': '10.1.2.3', 'lunId': None, + 'rep_partner_count': None, 'storage_type': 'ENDURANCE', 'username': 'username', 'active_transactions': None }], json.loads(result.output)) + def test_volume_count(self): + result = self.run_command(['block', 'volume-count']) + + self.assert_no_fail(result) + self.assertEqual( + { + 'dal05': 1 + }, + json.loads(result.output)) + def test_volume_order_performance_iops_not_given(self): result = self.run_command(['block', 'volume-order', '--storage-type=performance', '--size=20', @@ -357,6 +368,7 @@ def test_replication_locations(self): def test_replication_locations_unsuccessful(self, locations_mock): locations_mock.return_value = False result = self.run_command(['block', 'replica-locations', '1234']) + self.assert_no_fail(result) self.assertEqual('No data centers compatible for replication.\n', result.output) diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index df8abec92..cb1eda54d 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -87,10 +87,21 @@ def test_volume_list(self): 'storage_type': 'ENDURANCE', 'username': 'user', 'active_transactions': None, - 'mount_addr': '127.0.0.1:/TEST' + 'mount_addr': '127.0.0.1:/TEST', + 'rep_partner_count': None }], json.loads(result.output)) + def test_volume_count(self): + result = self.run_command(['block', 'volume-count']) + + self.assert_no_fail(result) + self.assertEqual( + { + 'dal05': 1 + }, + json.loads(result.output)) + def test_snapshot_list(self): result = self.run_command(['file', 'snapshot-list', '1234'])