From 84cea12bceb44a457c649afc8ba5c5cc0c83e5d9 Mon Sep 17 00:00:00 2001 From: David Pickle Date: Tue, 6 Jun 2017 14:16:33 -0500 Subject: [PATCH 1/4] Update file & block volume-detail commands to show duplicate volume info --- SoftLayer/CLI/block/detail.py | 9 ++++++ SoftLayer/CLI/file/detail.py | 9 ++++++ .../fixtures/SoftLayer_Network_Storage.py | 3 ++ SoftLayer/managers/block.py | 3 ++ SoftLayer/managers/file.py | 3 ++ tests/CLI/modules/block_tests.py | 9 +++++- tests/CLI/modules/file_tests.py | 8 ++++- tests/managers/block_tests.py | 27 ++++++++++++++++- tests/managers/file_tests.py | 29 ++++++++++++++++++- 9 files changed, 96 insertions(+), 4 deletions(-) diff --git a/SoftLayer/CLI/block/detail.py b/SoftLayer/CLI/block/detail.py index d1461df2e..b8ba8bed5 100644 --- a/SoftLayer/CLI/block/detail.py +++ b/SoftLayer/CLI/block/detail.py @@ -99,4 +99,13 @@ def cli(env, volume_id): replicant_list.append(replicant_table) table.add_row(['Replicant Volumes', replicant_list]) + if block_volume.get('originalVolumeSize'): + duplicate_info = formatting.Table(['Original Volume Name', + block_volume['originalVolumeName']]) + duplicate_info.add_row(['Original Volume Size', + block_volume['originalVolumeSize']]) + duplicate_info.add_row(['Original Snapshot Name', + block_volume['originalSnapshotName']]) + table.add_row(['Duplicate Volume Properties', duplicate_info]) + env.fout(table) diff --git a/SoftLayer/CLI/file/detail.py b/SoftLayer/CLI/file/detail.py index 8f7024aab..1fae6774d 100644 --- a/SoftLayer/CLI/file/detail.py +++ b/SoftLayer/CLI/file/detail.py @@ -114,4 +114,13 @@ def cli(env, volume_id): replicant_list.append(replicant_table) table.add_row(['Replicant Volumes', replicant_list]) + if file_volume.get('originalVolumeSize'): + duplicate_info = formatting.Table(['Original Volume Name', + file_volume['originalVolumeName']]) + duplicate_info.add_row(['Original Volume Size', + file_volume['originalVolumeSize']]) + duplicate_info.add_row(['Original Snapshot Name', + file_volume['originalSnapshotName']]) + table.add_row(['Duplicate Volume Properties', duplicate_info]) + env.fout(table) diff --git a/SoftLayer/fixtures/SoftLayer_Network_Storage.py b/SoftLayer/fixtures/SoftLayer_Network_Storage.py index ea5b91e6c..e596cc9d9 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Storage.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Storage.py @@ -25,6 +25,9 @@ 'snapshotCapacityGb': '10', 'parentVolume': {'snapshotSizeBytes': 1024}, 'osType': {'keyName': 'LINUX'}, + 'originalSnapshotName': 'test-origin-snapshot-name', + 'originalVolumeName': 'test-origin-volume-name', + 'originalVolumeSize': '20', 'schedules': [{ 'id': 978, 'type': {'keyname': 'SNAPSHOT_WEEKLY'}, diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index 89660d13e..9ab09d477 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -89,6 +89,9 @@ def get_block_volume_details(self, volume_id, **kwargs): 'storageTierLevel', 'iops', 'lunId', + 'originalVolumeName', + 'originalSnapshotName', + 'originalVolumeSize', 'activeTransactionCount', 'activeTransactions.transactionStatus[friendlyName]', 'replicationPartnerCount', diff --git a/SoftLayer/managers/file.py b/SoftLayer/managers/file.py index 22c73b45b..e100ddbb6 100644 --- a/SoftLayer/managers/file.py +++ b/SoftLayer/managers/file.py @@ -86,6 +86,9 @@ def get_file_volume_details(self, volume_id, **kwargs): 'storageTierLevel', 'iops', 'lunId', + 'originalVolumeName', + 'originalSnapshotName', + 'originalVolumeSize', 'activeTransactionCount', 'activeTransactions.transactionStatus[friendlyName]', 'replicationPartnerCount', diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index 58c46a0e9..b9634b121 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -67,6 +67,7 @@ def test_volume_cancel(self): def test_volume_detail(self): result = self.run_command(['block', 'volume-detail', '1234']) + self.assert_no_fail(result) self.assertEqual({ 'Username': 'username', @@ -94,7 +95,13 @@ def test_volume_detail(self): {'Replicant ID': 'Target IP', '1785': '10.3.177.84'}, {'Replicant ID': 'Data Center', '1785': 'dal01'}, {'Replicant ID': 'Schedule', '1785': 'REPLICATION_DAILY'}, - ]] + ]], + 'Duplicate Volume Properties': [ + {'Original Volume Name': 'Original Volume Size', + 'test-origin-volume-name': '20'}, + {'Original Volume Name': 'Original Snapshot Name', + 'test-origin-volume-name': 'test-origin-snapshot-name'} + ] }, json.loads(result.output)) def test_volume_list(self): diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index c3b3b7a40..74b25101e 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -145,7 +145,13 @@ def test_volume_detail(self): {'Replicant ID': 'Target IP', '1785': '10.3.177.84'}, {'Replicant ID': 'Data Center', '1785': 'dal01'}, {'Replicant ID': 'Schedule', '1785': 'REPLICATION_DAILY'}, - ]] + ]], + 'Duplicate Volume Properties': [ + {'Original Volume Name': 'Original Volume Size', + 'test-origin-volume-name': '20'}, + {'Original Volume Name': 'Original Snapshot Name', + 'test-origin-volume-name': 'test-origin-snapshot-name'} + ] }, json.loads(result.output)) def test_volume_order_performance_iops_not_given(self): diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index e336fbbbf..cb26244bb 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -30,10 +30,35 @@ def test_get_block_volume_details(self): self.assertEqual(fixtures.SoftLayer_Network_Storage.getObject, result) + expected_mask = 'id,'\ + 'username,'\ + 'password,'\ + 'capacityGb,'\ + 'snapshotCapacityGb,'\ + 'parentVolume.snapshotSizeBytes,'\ + 'storageType.keyName,'\ + 'serviceResource.datacenter[name],'\ + 'serviceResourceBackendIpAddress,'\ + 'storageTierLevel,'\ + 'iops,'\ + 'lunId,'\ + 'originalVolumeName,'\ + 'originalSnapshotName,'\ + 'originalVolumeSize,'\ + 'activeTransactionCount,'\ + 'activeTransactions.transactionStatus[friendlyName],'\ + 'replicationPartnerCount,'\ + 'replicationStatus,'\ + 'replicationPartners[id,username,'\ + 'serviceResourceBackendIpAddress,'\ + 'serviceResource[datacenter[name]],'\ + 'replicationSchedule[type[keyname]]]' + self.assert_called_with( 'SoftLayer_Network_Storage', 'getObject', - identifier=100 + identifier=100, + mask='mask[%s]' % expected_mask ) def test_list_block_volumes(self): diff --git a/tests/managers/file_tests.py b/tests/managers/file_tests.py index 3b761e0c1..63d893655 100644 --- a/tests/managers/file_tests.py +++ b/tests/managers/file_tests.py @@ -105,10 +105,37 @@ def test_get_file_volume_details(self): self.assertEqual(fixtures.SoftLayer_Network_Storage.getObject, result) + expected_mask = 'id,'\ + 'username,'\ + 'password,'\ + 'capacityGb,'\ + 'bytesUsed,'\ + 'snapshotCapacityGb,'\ + 'parentVolume.snapshotSizeBytes,'\ + 'storageType.keyName,'\ + 'serviceResource.datacenter[name],'\ + 'serviceResourceBackendIpAddress,'\ + 'fileNetworkMountAddress,'\ + 'storageTierLevel,'\ + 'iops,'\ + 'lunId,'\ + 'originalVolumeName,'\ + 'originalSnapshotName,'\ + 'originalVolumeSize,'\ + 'activeTransactionCount,'\ + 'activeTransactions.transactionStatus[friendlyName],'\ + 'replicationPartnerCount,'\ + 'replicationStatus,'\ + 'replicationPartners[id,username,'\ + 'serviceResourceBackendIpAddress,'\ + 'serviceResource[datacenter[name]],'\ + 'replicationSchedule[type[keyname]]]' + self.assert_called_with( 'SoftLayer_Network_Storage', 'getObject', - identifier=100) + identifier=100, + mask='mask[%s]' % expected_mask) def test_get_file_volume_snapshot_list(self): result = self.file.get_file_volume_snapshot_list(100) From e30f4d32b18132ebfcc3c96127a7807699f2369c Mon Sep 17 00:00:00 2001 From: David Pickle Date: Tue, 6 Jun 2017 08:29:48 -0500 Subject: [PATCH 2/4] Add volume-duplicate command for file & block storage --- SoftLayer/CLI/block/duplicate.py | 83 + SoftLayer/CLI/file/duplicate.py | 79 + SoftLayer/CLI/routes.py | 2 + .../fixtures/SoftLayer_Network_Storage.py | 34 +- .../fixtures/SoftLayer_Product_Package.py | 133 ++ SoftLayer/managers/block.py | 47 + SoftLayer/managers/file.py | 39 + SoftLayer/managers/storage_utils.py | 414 ++++ tests/CLI/modules/block_tests.py | 42 + tests/CLI/modules/file_tests.py | 42 + tests/managers/block_tests.py | 181 ++ tests/managers/file_tests.py | 164 ++ tests/managers/storage_utils_tests.py | 1715 +++++++++++++++++ 13 files changed, 2974 insertions(+), 1 deletion(-) create mode 100644 SoftLayer/CLI/block/duplicate.py create mode 100644 SoftLayer/CLI/file/duplicate.py create mode 100644 tests/managers/storage_utils_tests.py diff --git a/SoftLayer/CLI/block/duplicate.py b/SoftLayer/CLI/block/duplicate.py new file mode 100644 index 000000000..98ef1b793 --- /dev/null +++ b/SoftLayer/CLI/block/duplicate.py @@ -0,0 +1,83 @@ +"""Order a duplicate block storage volume.""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions + + +CONTEXT_SETTINGS = {'token_normalize_func': lambda x: x.upper()} + + +@click.command(context_settings=CONTEXT_SETTINGS) +@click.argument('origin-volume-id') +@click.option('--origin-snapshot-id', '-o', + type=int, + help="ID of an origin volume snapshot to use for duplcation.") +@click.option('--duplicate-size', '-c', + type=int, + help='Size of duplicate block volume in GB. ' + '***If no size is specified, the size of ' + 'the origin volume will be used.***\n' + 'Potential Sizes: [20, 40, 80, 100, 250, ' + '500, 1000, 2000, 4000, 8000, 12000] ' + 'Minimum: [the size of the origin volume] ' + 'Maximum: [the minimum of 12000 GB or ' + '10*(origin volume size)]') +@click.option('--duplicate-iops', '-i', + type=int, + help='Performance Storage IOPS, between 100 and 6000 in ' + 'multiples of 100 [only used for performance volumes] ' + '***If no IOPS value is specified, the IOPS value of the ' + 'origin volume will be used.***\n' + 'Requirements: [If IOPS/GB for the origin volume is less ' + 'than 0.3, IOPS/GB for the duplicate must also be less ' + 'than 0.3. If IOPS/GB for the origin volume is greater ' + 'than or equal to 0.3, IOPS/GB for the duplicate must ' + 'also be greater than or equal to 0.3.]') +@click.option('--duplicate-tier', '-t', + help='Endurance Storage Tier (IOPS per GB) [only used for ' + 'endurance volumes] ***If no tier is specified, the tier ' + 'of the origin volume will be used.***\n' + 'Requirements: [If IOPS/GB for the origin volume is 0.25, ' + 'IOPS/GB for the duplicate must also be 0.25. If IOPS/GB ' + 'for the origin volume is greater than 0.25, IOPS/GB ' + 'for the duplicate must also be greater than 0.25.]', + type=click.Choice(['0.25', '2', '4', '10'])) +@click.option('--duplicate-snapshot-size', '-s', + type=int, + help='The size of snapshot space to order for the duplicate. ' + '***If no snapshot space size is specified, the snapshot ' + 'space size of the origin volume will be used.***\n' + 'Input "0" for this parameter to order a duplicate volume ' + 'with no snapshot space.') +@environment.pass_env +def cli(env, origin_volume_id, origin_snapshot_id, duplicate_size, + duplicate_iops, duplicate_tier, duplicate_snapshot_size): + """Order a duplicate block storage volume.""" + block_manager = SoftLayer.BlockStorageManager(env.client) + + if duplicate_tier is not None: + duplicate_tier = float(duplicate_tier) + + try: + order = block_manager.order_duplicate_volume( + origin_volume_id, + origin_snapshot_id=origin_snapshot_id, + duplicate_size=duplicate_size, + duplicate_iops=duplicate_iops, + duplicate_tier_level=duplicate_tier, + duplicate_snapshot_size=duplicate_snapshot_size + ) + except ValueError as ex: + raise exceptions.ArgumentError(str(ex)) + + if 'placedOrder' in order.keys(): + click.echo("Order #{0} placed successfully!".format( + order['placedOrder']['id'])) + for item in order['placedOrder']['items']: + click.echo(" > %s" % item['description']) + else: + click.echo("Order could not be placed! Please verify your options " + + "and try again.") diff --git a/SoftLayer/CLI/file/duplicate.py b/SoftLayer/CLI/file/duplicate.py new file mode 100644 index 000000000..e02169771 --- /dev/null +++ b/SoftLayer/CLI/file/duplicate.py @@ -0,0 +1,79 @@ +"""Order a duplicate file storage volume.""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions + + +CONTEXT_SETTINGS = {'token_normalize_func': lambda x: x.upper()} + + +@click.command(context_settings=CONTEXT_SETTINGS) +@click.argument('origin-volume-id') +@click.option('--origin-snapshot-id', '-o', + type=int, + help="ID of an origin volume snapshot to use for duplcation.") +@click.option('--duplicate-size', '-c', + type=int, + help='Size of duplicate file volume in GB. ' + '***If no size is specified, the size of ' + 'the origin volume will be used.***\n' + 'Minimum: [the size of the origin volume]') +@click.option('--duplicate-iops', '-i', + type=int, + help='Performance Storage IOPS, between 100 and 6000 in ' + 'multiples of 100 [only used for performance volumes] ' + '***If no IOPS value is specified, the IOPS value of the ' + 'origin volume will be used.***\n' + 'Requirements: [If IOPS/GB for the origin volume is less ' + 'than 0.3, IOPS/GB for the duplicate must also be less ' + 'than 0.3. If IOPS/GB for the origin volume is greater ' + 'than or equal to 0.3, IOPS/GB for the duplicate must ' + 'also be greater than or equal to 0.3.]') +@click.option('--duplicate-tier', '-t', + help='Endurance Storage Tier (IOPS per GB) [only used for ' + 'endurance volumes] ***If no tier is specified, the tier ' + 'of the origin volume will be used.***\n' + 'Requirements: [If IOPS/GB for the origin volume is 0.25, ' + 'IOPS/GB for the duplicate must also be 0.25. If IOPS/GB ' + 'for the origin volume is greater than 0.25, IOPS/GB ' + 'for the duplicate must also be greater than 0.25.]', + type=click.Choice(['0.25', '2', '4', '10'])) +@click.option('--duplicate-snapshot-size', '-s', + type=int, + help='The size of snapshot space to order for the duplicate. ' + '***If no snapshot space size is specified, the snapshot ' + 'space size of the origin volume will be used.***\n' + 'Input "0" for this parameter to order a duplicate volume ' + 'with no snapshot space.') +@environment.pass_env +def cli(env, origin_volume_id, origin_snapshot_id, duplicate_size, + duplicate_iops, duplicate_tier, duplicate_snapshot_size): + """Order a duplicate file storage volume.""" + file_manager = SoftLayer.FileStorageManager(env.client) + + if duplicate_tier is not None: + duplicate_tier = float(duplicate_tier) + + try: + order = file_manager.order_duplicate_volume( + origin_volume_id, + origin_snapshot_id=origin_snapshot_id, + duplicate_size=duplicate_size, + duplicate_iops=duplicate_iops, + duplicate_tier_level=duplicate_tier, + duplicate_snapshot_size=duplicate_snapshot_size + ) + except ValueError as ex: + raise exceptions.ArgumentError(str(ex)) + + if 'placedOrder' in order.keys(): + click.echo("Order #{0} placed successfully!".format( + order['placedOrder']['id'])) + for item in order['placedOrder']['items']: + click.echo(" > %s" % item['description']) + else: + click.echo("Order could not be placed! Please verify your options " + + "and try again.") diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 3c4555db0..ae33226bc 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -76,6 +76,7 @@ ('block:snapshot-restore', 'SoftLayer.CLI.block.snapshot.restore:cli'), ('block:volume-cancel', 'SoftLayer.CLI.block.cancel:cli'), ('block:volume-detail', 'SoftLayer.CLI.block.detail:cli'), + ('block:volume-duplicate', 'SoftLayer.CLI.block.duplicate:cli'), ('block:volume-list', 'SoftLayer.CLI.block.list:cli'), ('block:volume-order', 'SoftLayer.CLI.block.order:cli'), @@ -98,6 +99,7 @@ ('file:snapshot-restore', 'SoftLayer.CLI.file.snapshot.restore:cli'), ('file:volume-cancel', 'SoftLayer.CLI.file.cancel:cli'), ('file:volume-detail', 'SoftLayer.CLI.file.detail:cli'), + ('file:volume-duplicate', 'SoftLayer.CLI.file.duplicate:cli'), ('file:volume-list', 'SoftLayer.CLI.file.list:cli'), ('file:volume-order', 'SoftLayer.CLI.file.order:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Network_Storage.py b/SoftLayer/fixtures/SoftLayer_Network_Storage.py index ea5b91e6c..0eb41531a 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Storage.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Storage.py @@ -1,3 +1,34 @@ +DUPLICATABLE_VOLUME = { + 'accountId': 1234, + 'activeTransactions': None, + 'activeTransactionCount': 0, + 'billingItem': { + 'activeChildren': [{ + 'categoryCode': 'storage_snapshot_space', + 'id': 125, + 'cancellationDate': '', + }], + 'cancellationDate': '', + 'id': 454, + 'location': {'id': 449500} + }, + 'capacityGb': 500, + 'id': 102, + 'iops': 1000, + 'lunId': 2, + 'osType': {'keyName': 'LINUX'}, + 'originalVolumeSize': '500', + 'parentVolume': {'snapshotSizeBytes': 1024}, + 'provisionedIops': '1000', + 'replicationPartnerCount': 0, + 'serviceResource': {'datacenter': {'id': 449500, 'name': 'dal05'}}, + 'serviceResourceBackendIpAddress': '10.1.2.3', + 'snapshotCapacityGb': '10', + 'storageTierLevel': 'READHEAVY_TIER', + 'storageType': {'keyName': 'ENDURANCE_BLOCK_STORAGE'}, + 'username': 'duplicatable_volume_username' +} + getObject = { 'accountId': 1234, 'billingItem': { @@ -8,7 +39,8 @@ 'categoryCode': 'storage_snapshot_space', 'id': 123, 'cancellationDate': '', - }] + }], + 'location': {'id': 449500} }, 'capacityGb': 20, 'createDate': '2015:50:15-04:00', diff --git a/SoftLayer/fixtures/SoftLayer_Product_Package.py b/SoftLayer/fixtures/SoftLayer_Product_Package.py index 1a5fbed05..237b64a70 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Package.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Package.py @@ -156,6 +156,139 @@ 'sort': 99}]}] +SAAS_PACKAGE = { + 'id': 759, + 'name': 'Storage As A Service (StaaS)', + 'categories': [{'categoryCode': 'storage_as_a_service'}], + 'items': [ + { + 'capacity': '0', + 'keyName': '', + 'prices': [{'id': 189433, + 'categories': [{ + 'categoryCode': 'storage_as_a_service'}], + 'locationGroupId': ''}] + }, { + 'capacity': '0', + 'keyName': '', + 'prices': [{'categories': [{'categoryCode': 'storage_block'}], + 'id': 189443, + 'locationGroupId': ''}] + }, { + 'capacity': '0', + 'keyName': '', + 'prices': [{'categories': [{'categoryCode': 'storage_file'}], + 'id': 189453, + 'locationGroupId': ''}] + }, { + 'capacity': '0', + 'capacityMaximum': '999', + 'capacityMinimum': '500', + 'itemCategory': {'categoryCode': 'performance_storage_space'}, + 'keyName': '500_999_GBS', + 'prices': [{'id': 189993, + 'categories': [{ + 'categoryCode': 'performance_storage_space'}], + 'locationGroupId': ''}] + }, { + 'capacity': '0', + 'capacityMaximum': '1999', + 'capacityMinimum': '1000', + 'itemCategory': {'categoryCode': 'performance_storage_space'}, + 'keyName': '1000_1999_GBS', + 'prices': [{'id': 190113, + 'categories': [{ + 'categoryCode': 'performance_storage_space'}], + 'locationGroupId': ''}] + }, { + 'capacity': '0', + 'capacityMaximum': '12000', + 'capacityMinimum': '1', + 'keyName': 'STORAGE_SPACE_FOR_2_IOPS_PER_GB', + 'prices': [{'id': 193433, + 'categories': [{ + 'categoryCode': 'performance_storage_space'}], + 'locationGroupId': ''}] + }, { + 'capacity': '0', + 'capacityMaximum': '12000', + 'capacityMinimum': '1', + 'keyName': 'STORAGE_SPACE_FOR_4_IOPS_PER_GB', + 'prices': [{'id': 194763, + 'categories': [{ + 'categoryCode': 'performance_storage_space'}], + 'locationGroupId': ''}] + }, { + 'capacity': '0', + 'capacityMaximum': '10000', + 'capacityMinimum': '100', + 'keyName': '', + 'itemCategory': {'categoryCode': 'performance_storage_iops'}, + 'prices': [{'capacityRestrictionMaximum': '999', + 'capacityRestrictionMinimum': '500', + 'capacityRestrictionType': 'STORAGE_SPACE', + 'categories': [{ + 'categoryCode': 'performance_storage_iops'}], + 'id': 190053, + 'locationGroupId': ''}] + }, { + 'capacity': '0', + 'capacityMaximum': '20000', + 'capacityMinimum': '100', + 'keyName': '', + 'itemCategory': {'categoryCode': 'performance_storage_iops'}, + 'prices': [{'capacityRestrictionMaximum': '1999', + 'capacityRestrictionMinimum': '1000', + 'capacityRestrictionType': 'STORAGE_SPACE', + 'categories': [{ + 'categoryCode': 'performance_storage_iops'}], + 'id': 190173, + 'locationGroupId': ''}] + }, { + 'capacity': '200', + 'itemCategory': {'categoryCode': 'storage_tier_level'}, + 'keyName': '', + 'prices': [{'id': 193373, + 'categories': [{ + 'categoryCode': 'storage_tier_level'}], + 'locationGroupId': ''}] + }, { + 'capacity': '300', + 'itemCategory': {'categoryCode': 'storage_tier_level'}, + 'keyName': '', + 'prices': [{'id': 194703, + 'categories': [{ + 'categoryCode': 'storage_tier_level'}], + 'locationGroupId': ''}] + }, { + 'capacity': '10', + 'keyName': '', + 'prices': [{'capacityRestrictionMaximum': '48000', + 'capacityRestrictionMinimum': '100', + 'capacityRestrictionType': 'IOPS', + 'categories': [{ + 'categoryCode': 'storage_snapshot_space'}], + 'id': 191193, + 'locationGroupId': ''}, + {'capacityRestrictionMaximum': '200', + 'capacityRestrictionMinimum': '200', + 'capacityRestrictionType': 'STORAGE_TIER_LEVEL', + 'categories': [{ + 'categoryCode': 'storage_snapshot_space'}], + 'id': 193613, + 'locationGroupId': ''}, + {'capacityRestrictionMaximum': '300', + 'capacityRestrictionMinimum': '300', + 'capacityRestrictionType': 'STORAGE_TIER_LEVEL', + 'categories': [{ + 'categoryCode': 'storage_snapshot_space'}], + 'id': 194943, + 'locationGroupId': ''}] + } + ] +} + + getAllObjects = [{ 'activePresets': [{ 'description': 'Single Xeon 1270, 8GB Ram, 2x1TB SATA disks, Non-RAID', diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index 89660d13e..aeccde7af 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -9,6 +9,8 @@ from SoftLayer.managers import storage_utils from SoftLayer import utils +# pylint: disable=too-many-public-methods + class BlockStorageManager(utils.IdentifierMixin, object): """Manages SoftLayer Block Storage volumes. @@ -252,6 +254,51 @@ def order_replicant_volume(self, volume_id, snapshot_schedule, return self.client.call('Product_Order', 'placeOrder', order) + def order_duplicate_volume(self, origin_volume_id, origin_snapshot_id=None, + duplicate_size=None, duplicate_iops=None, + duplicate_tier_level=None, + duplicate_snapshot_size=None): + """Places an order for a duplicate block volume. + + :param origin_volume_id: The ID of the origin volume to be duplicated + :param origin_snapshot_id: Origin snapshot ID to use for duplication + :param duplicate_size: Size/capacity for the duplicate volume + :param duplicate_iops: The IOPS per GB for the duplicate volume + :param duplicate_tier_level: Tier level for the duplicate volume + :param duplicate_snapshot_size: Snapshot space size for the duplicate + :return: Returns a SoftLayer_Container_Product_Order_Receipt + """ + + block_mask = 'id,billingItem[location],snapshotCapacityGb,'\ + 'storageType[keyName],capacityGb,originalVolumeSize,'\ + 'provisionedIops,storageTierLevel,osType[keyName]' + origin_volume = self.get_block_volume_details(origin_volume_id, + mask=block_mask) + + # 47474747 remove this if not used + # duplicate_parameters = self.client.call( + # 'Network_Storage', + # 'getVolumeDuplicateParameters', + # id=origin_volume_id) + + if isinstance(utils.lookup(origin_volume, 'osType', 'keyName'), str): + os_type = origin_volume['osType']['keyName'] + else: + raise exceptions.SoftLayerError( + "Cannot find origin volume's os-type") + + order = storage_utils.prepare_duplicate_order_object( + self, origin_volume, duplicate_iops, duplicate_tier_level, + duplicate_size, duplicate_snapshot_size, 'block' + ) + + order['osFormatType'] = {'keyName': os_type} + + if origin_snapshot_id is not None: + order['duplicateOriginSnapshotId'] = origin_snapshot_id + + return self.client.call('Product_Order', 'placeOrder', order) + def delete_snapshot(self, snapshot_id): """Deletes the specified snapshot object. diff --git a/SoftLayer/managers/file.py b/SoftLayer/managers/file.py index 22c73b45b..200f2b8e3 100644 --- a/SoftLayer/managers/file.py +++ b/SoftLayer/managers/file.py @@ -9,6 +9,8 @@ from SoftLayer.managers import storage_utils from SoftLayer import utils +# pylint: disable=too-many-public-methods + class FileStorageManager(utils.IdentifierMixin, object): """Manages file Storage volumes.""" @@ -240,6 +242,43 @@ def get_replication_locations(self, volume_id): 'getValidReplicationTargetDatacenterLocations', id=volume_id) + def order_duplicate_volume(self, origin_volume_id, origin_snapshot_id=None, + duplicate_size=None, duplicate_iops=None, + duplicate_tier_level=None, + duplicate_snapshot_size=None): + """Places an order for a duplicate file volume. + + :param origin_volume_id: The ID of the origin volume to be duplicated + :param origin_snapshot_id: Origin snapshot ID to use for duplication + :param duplicate_size: Size/capacity for the duplicate volume + :param duplicate_iops: The IOPS per GB for the duplicate volume + :param duplicate_tier_level: Tier level for the duplicate volume + :param duplicate_snapshot_size: Snapshot space size for the duplicate + :return: Returns a SoftLayer_Container_Product_Order_Receipt + """ + + file_mask = 'id,billingItem[location],snapshotCapacityGb,'\ + 'storageType[keyName],capacityGb,originalVolumeSize,'\ + 'provisionedIops,storageTierLevel' + origin_volume = self.get_file_volume_details(origin_volume_id, + mask=file_mask) + + # 47474747 remove this if not used + # duplicate_parameters = self.client.call( + # 'Network_Storage', + # 'getVolumeDuplicateParameters', + # id=origin_volume_id) + + order = storage_utils.prepare_duplicate_order_object( + self, origin_volume, duplicate_iops, duplicate_tier_level, + duplicate_size, duplicate_snapshot_size, 'file' + ) + + if origin_snapshot_id is not None: + order['duplicateOriginSnapshotId'] = origin_snapshot_id + + return self.client.call('Product_Order', 'placeOrder', order) + def delete_snapshot(self, snapshot_id): """Deletes the specified snapshot object. diff --git a/SoftLayer/managers/storage_utils.py b/SoftLayer/managers/storage_utils.py index dcd7f04fb..7cb46ab6b 100644 --- a/SoftLayer/managers/storage_utils.py +++ b/SoftLayer/managers/storage_utils.py @@ -350,6 +350,215 @@ def find_snapshot_space_price(package, size, tier_level): raise ValueError("Could not find price for snapshot space") +def find_saas_price_by_category(package, price_category): + """Find a price in the SaaS package with the specified category + + :param package: The Storage As A Service product package + :param price_category: The price category to search for + :return: Returns a price for the given category, or an error if not found + """ + for item in package['items']: + for price in item['prices']: + if price['locationGroupId'] != '': + continue + + if not _has_category(price['categories'], price_category): + continue + + return {'id': price['id']} + + raise ValueError("Could not find price with the category, %s" + % price_category) + + +def find_saas_endurance_space_price(package, size, tier_level): + """Find the SaaS endurance storage space price for the size and tier + + :param package: The Storage As A Service product package + :param size: The volume size for which a price is desired + :param tier_level: The endurance tier for which a price is desired + :return: Returns the price for the size and tier, or an error if not found + """ + key_name = 'STORAGE_SPACE_FOR_{0}_IOPS_PER_GB'.format(tier_level) + key_name = key_name.replace(".", "_") + for item in package['items']: + if item['keyName'] != key_name: + continue + + if 'capacityMinimum' not in item or 'capacityMaximum' not in item: + continue + + capacity_minimum = int(item['capacityMinimum']) + capacity_maximum = int(item['capacityMaximum']) + if size < capacity_minimum or size > capacity_maximum: + continue + + for price in item['prices']: + # Only collect prices from valid location groups. + if price['locationGroupId'] != '': + continue + + if not _has_category(price['categories'], + 'performance_storage_space'): + continue + + return {'id': price['id']} + + raise ValueError("Could not find price for endurance storage space") + + +def find_saas_endurance_tier_price(package, tier_level): + """Find the SaaS storage tier level price for the specified tier level + + :param package: The Storage As A Service product package + :param tier_level: The endurance tier for which a price is desired + :return: Returns the price for the given tier, or an error if not found + """ + target_capacity = ENDURANCE_TIERS.get(tier_level) + for item in package['items']: + if 'itemCategory' not in item\ + or 'categoryCode' not in item['itemCategory']\ + or item['itemCategory']['categoryCode']\ + != 'storage_tier_level': + continue + + if int(item['capacity']) != target_capacity: + continue + + for price in item['prices']: + # Only collect prices from valid location groups. + if price['locationGroupId'] != '': + continue + + if not _has_category(price['categories'], 'storage_tier_level'): + continue + + return {'id': price['id']} + + raise ValueError("Could not find price for endurance tier level") + + +def find_saas_perform_space_price(package, size): + """Find the SaaS performance storage space price for the given size + + :param package: The Storage As A Service product package + :param size: The volume size for which a price is desired + :return: Returns the price for the size and tier, or an error if not found + """ + for item in package['items']: + if 'itemCategory' not in item\ + or 'categoryCode' not in item['itemCategory']\ + or item['itemCategory']['categoryCode']\ + != 'performance_storage_space': + continue + + if 'capacityMinimum' not in item or 'capacityMaximum' not in item: + continue + + capacity_minimum = int(item['capacityMinimum']) + capacity_maximum = int(item['capacityMaximum']) + if size < capacity_minimum or size > capacity_maximum: + continue + + key_name = '{0}_{1}_GBS'.format(capacity_minimum, capacity_maximum) + if item['keyName'] != key_name: + continue + + for price in item['prices']: + # Only collect prices from valid location groups. + if price['locationGroupId'] != '': + continue + + if not _has_category(price['categories'], + 'performance_storage_space'): + continue + + return {'id': price['id']} + + raise ValueError("Could not find price for performance storage space") + + +def find_saas_perform_iops_price(package, size, iops): + """Find the SaaS IOPS price for the specified size and iops + + :param package: The Storage As A Service product package + :param size: The volume size for which a price is desired + :param iops: The number of IOPS for which a price is desired + :return: Returns the price for the size and IOPS, or an error if not found + """ + for item in package['items']: + if 'itemCategory' not in item\ + or 'categoryCode' not in item['itemCategory']\ + or item['itemCategory']['categoryCode']\ + != 'performance_storage_iops': + continue + + if 'capacityMinimum' not in item or 'capacityMaximum' not in item: + continue + + capacity_minimum = int(item['capacityMinimum']) + capacity_maximum = int(item['capacityMaximum']) + if iops < capacity_minimum or iops > capacity_maximum: + continue + + for price in item['prices']: + # Only collect prices from valid location groups. + if price['locationGroupId'] != '': + continue + + if not _has_category(price['categories'], + 'performance_storage_iops'): + continue + + if price['capacityRestrictionType'] != 'STORAGE_SPACE'\ + or size < int(price['capacityRestrictionMinimum'])\ + or size > int(price['capacityRestrictionMaximum']): + continue + + return {'id': price['id']} + + raise ValueError("Could not find price for iops for the given volume") + + +def find_saas_snapshot_space_price(package, size, tier_level=None, iops=None): + """Find the price in the SaaS package for the desired snapshot space size + + :param package: The product package of the endurance storage type + :param size: The snapshot space size for which a price is desired + :param tier_level: The tier of the volume for which space is being ordered + :param iops: The IOPS of the volume for which space is being ordered + :return: Returns the price for the given size, or an error if not found + """ + if tier_level is not None: + target_value = ENDURANCE_TIERS.get(tier_level) + target_restriction_type = 'STORAGE_TIER_LEVEL' + else: + target_value = iops + target_restriction_type = 'IOPS' + + for item in package['items']: + if int(item['capacity']) != size: + continue + + for price in item['prices']: + # Only collect prices from valid location groups. + if price['locationGroupId'] != '': + continue + + if target_restriction_type != price['capacityRestrictionType']\ + or target_value < int(price['capacityRestrictionMinimum'])\ + or target_value > int(price['capacityRestrictionMaximum']): + continue + + if not _has_category(price['categories'], + 'storage_snapshot_space'): + continue + + return {'id': price['id']} + + raise ValueError("Could not find price for snapshot space") + + def find_snapshot_schedule_id(volume, snapshot_schedule_keyname): """Find the snapshot schedule ID for the given volume and keyname @@ -446,6 +655,211 @@ def prepare_replicant_order_object(manager, volume_id, snapshot_schedule, return replicant_order +def prepare_duplicate_order_object(manager, origin_volume, iops, tier, + duplicate_size, + duplicate_snapshot_size, volume_type): + """Prepare the duplicate order to submit to SoftLayer_Product::placeOrder() + + :param manager: The File or Block manager calling this function + :param origin_volume: The origin volume which is being duplicated + :param iops: The IOPS per GB for the duplicant volume (performance) + :param tier: The tier level for the duplicant volume (endurance) + :param duplicate_size: The requested size for the duplicate volume + :param duplicate_snapshot_size: The size for the duplicate snapshot space + :param volume_type: The type of the origin volume ('file' or 'block') + :return: Returns the order object to be passed to the + placeOrder() method of the Product_Order service + """ + + # Verify that the origin volume has not been cancelled + if 'billingItem' not in origin_volume: + raise exceptions.SoftLayerError( + "The origin volume has been cancelled; " + "unable to order duplicate volume") + + # Verify that the origin volume has snapshot space (needed for duplication) + if isinstance(utils.lookup(origin_volume, 'snapshotCapacityGb'), str): + origin_snapshot_size = int(origin_volume['snapshotCapacityGb']) + else: + raise exceptions.SoftLayerError( + "Snapshot space not found for the origin volume. " + "Origin snapshot space is needed for duplication.") + + # Obtain the datacenter location ID for the duplicate + if isinstance(utils.lookup(origin_volume, 'billingItem', + 'location', 'id'), int): + location_id = origin_volume['billingItem']['location']['id'] + else: + raise exceptions.SoftLayerError( + "Cannot find origin volume's location") + + # If no specific snapshot space was requested for the duplicate, + # use the origin snapshot space size + if duplicate_snapshot_size is None: + duplicate_snapshot_size = origin_snapshot_size + + # Validate the requested duplicate size, and set the size if none was given + duplicate_size = _validate_duplicate_size( + origin_volume, duplicate_size, volume_type) + + # Get the appropriate package for the order + # ('storage_as_a_service' is currently used for duplicate volumes) + package = get_package(manager, 'storage_as_a_service') + + # Determine the IOPS or tier level for the duplicate volume, along with + # the type and prices for the order + origin_storage_type = origin_volume['storageType']['keyName'] + if origin_storage_type == 'PERFORMANCE_BLOCK_STORAGE'\ + or origin_storage_type == 'PERFORMANCE_BLOCK_STORAGE_REPLICANT'\ + or origin_storage_type == 'PERFORMANCE_FILE_STORAGE'\ + or origin_storage_type == 'PERFORMANCE_FILE_STORAGE_REPLICANT': + volume_is_performance = True + iops = _validate_dupl_performance_iops( + origin_volume, iops, duplicate_size) + # Set up the price array for the order + prices = [ + find_saas_price_by_category(package, 'storage_as_a_service'), + find_saas_price_by_category(package, 'storage_' + volume_type), + find_saas_perform_space_price(package, duplicate_size), + find_saas_perform_iops_price(package, duplicate_size, iops), + ] + # Add the price code for snapshot space as well, unless 0 GB was given + if duplicate_snapshot_size > 0: + prices.append(find_saas_snapshot_space_price( + package, duplicate_snapshot_size, iops=iops)) + + elif origin_storage_type == 'ENDURANCE_BLOCK_STORAGE'\ + or origin_storage_type == 'ENDURANCE_BLOCK_STORAGE_REPLICANT'\ + or origin_storage_type == 'ENDURANCE_FILE_STORAGE'\ + or origin_storage_type == 'ENDURANCE_FILE_STORAGE_REPLICANT': + volume_is_performance = False + tier = _validate_dupl_endurance_tier(origin_volume, tier) + # Set up the price array for the order + prices = [ + find_saas_price_by_category(package, 'storage_as_a_service'), + find_saas_price_by_category(package, 'storage_' + volume_type), + find_saas_endurance_space_price(package, duplicate_size, tier), + find_saas_endurance_tier_price(package, tier), + ] + # Add the price code for snapshot space as well, unless 0 GB was given + if duplicate_snapshot_size > 0: + prices.append(find_saas_snapshot_space_price( + package, duplicate_snapshot_size, tier_level=tier)) + + else: + raise exceptions.SoftLayerError( + "Origin volume does not have a valid storage type " + "(with an appropriate keyName to indicate the " + "volume is a PERFORMANCE or ENDURANCE volume)") + + duplicate_order = { + 'complexType': 'SoftLayer_Container_Product_Order_' + 'Network_Storage_AsAService', + 'packageId': package['id'], + 'prices': prices, + 'volumeSize': duplicate_size, + 'quantity': 1, + 'location': location_id, + 'duplicateOriginVolumeId': origin_volume['id'], + } + + if volume_is_performance: + duplicate_order['iops'] = iops + + return duplicate_order + + +def _validate_duplicate_size(origin_volume, duplicate_volume_size, + volume_type): + # Ensure the origin volume's size is found + if not isinstance(utils.lookup(origin_volume, 'capacityGb'), int): + raise exceptions.SoftLayerError("Cannot find origin volume's size.") + + # Determine the volume size/capacity for the duplicate + if duplicate_volume_size is None: + duplicate_volume_size = origin_volume['capacityGb'] + # Ensure the duplicate volume size is not below the minimum + elif duplicate_volume_size < origin_volume['capacityGb']: + raise exceptions.SoftLayerError( + "The requested duplicate volume size is too small. Duplicate " + "volumes must be at least as large as their origin volumes.") + + # Ensure the duplicate volume size is not above the maximum + if volume_type == 'block': + # Determine the base size for validating the requested duplicate size + if 'originalVolumeSize' in origin_volume: + base_volume_size = int(origin_volume['originalVolumeSize']) + else: + base_volume_size = origin_volume['capacityGb'] + + # Current limit for block volumes: 10*[origin size] + if duplicate_volume_size > base_volume_size * 10: + raise exceptions.SoftLayerError( + "The requested duplicate volume size is too large. The " + "maximum size for duplicate block volumes is 10 times the " + "size of the origin volume or, if the origin volume was also " + "a duplicate, 10 times the size of the initial origin volume " + "(i.e. the origin volume from which the first duplicate was " + "created in the chain of duplicates). " + "Requested: %s GB. Base origin size: %s GB." + % (duplicate_volume_size, base_volume_size)) + + return duplicate_volume_size + + +def _validate_dupl_performance_iops(origin_volume, duplicate_iops, + duplicate_size): + if not isinstance(utils.lookup(origin_volume, 'provisionedIops'), str): + raise exceptions.SoftLayerError( + "Cannot find origin volume's provisioned IOPS") + + if duplicate_iops is None: + duplicate_iops = int(origin_volume['provisionedIops']) + else: + origin_iops_per_gb = float(origin_volume['provisionedIops'])\ + / float(origin_volume['capacityGb']) + duplicate_iops_per_gb = float(duplicate_iops) / float(duplicate_size) + if origin_iops_per_gb < 0.3 and duplicate_iops_per_gb >= 0.3: + raise exceptions.SoftLayerError( + "Origin volume performance is < 0.3 IOPS/GB, " + "duplicate volume performance must also be < 0.3 " + "IOPS/GB. %s IOPS/GB (%s/%s) requested." + % (duplicate_iops_per_gb, duplicate_iops, duplicate_size)) + elif origin_iops_per_gb >= 0.3 and duplicate_iops_per_gb < 0.3: + raise exceptions.SoftLayerError( + "Origin volume performance is >= 0.3 IOPS/GB, " + "duplicate volume performance must also be >= 0.3 " + "IOPS/GB. %s IOPS/GB (%s/%s) requested." + % (duplicate_iops_per_gb, duplicate_iops, duplicate_size)) + return duplicate_iops + + +def _validate_dupl_endurance_tier(origin_volume, duplicate_tier): + try: + origin_tier = find_endurance_tier_iops_per_gb(origin_volume) + except ValueError: + raise exceptions.SoftLayerError( + "Cannot find origin volume's tier level") + + if duplicate_tier is None: + duplicate_tier = origin_tier + else: + if duplicate_tier != 0.25: + duplicate_tier = int(duplicate_tier) + + if origin_tier == 0.25 and duplicate_tier != 0.25: + raise exceptions.SoftLayerError( + "Origin volume performance tier is 0.25 IOPS/GB, " + "duplicate volume performance tier must also be 0.25 " + "IOPS/GB. %s IOPS/GB requested." % duplicate_tier) + elif origin_tier != 0.25 and duplicate_tier == 0.25: + raise exceptions.SoftLayerError( + "Origin volume performance tier is above 0.25 IOPS/GB, " + "duplicate volume performance tier must also be above 0.25 " + "IOPS/GB. %s IOPS/GB requested." % duplicate_tier) + return duplicate_tier + + def _has_category(categories, category_code): return any( True diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index 58c46a0e9..66021b4fc 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -462,3 +462,45 @@ 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') + + @mock.patch('SoftLayer.BlockStorageManager.order_duplicate_volume') + def test_duplicate_order_exception_caught(self, order_mock): + order_mock.side_effect = ValueError('order attempt failed, oh noooo!') + + result = self.run_command(['block', 'volume-duplicate', '102']) + + self.assertEqual(2, result.exit_code) + self.assertEqual('Argument Error: order attempt failed, oh noooo!', + result.exception.message) + + @mock.patch('SoftLayer.BlockStorageManager.order_duplicate_volume') + def test_duplicate_order_order_not_placed(self, order_mock): + order_mock.return_value = {} + + result = self.run_command(['block', 'volume-duplicate', '102', + '--duplicate-iops=1400']) + + self.assert_no_fail(result) + self.assertEqual(result.output, + 'Order could not be placed! Please verify ' + 'your options and try again.\n') + + @mock.patch('SoftLayer.BlockStorageManager.order_duplicate_volume') + def test_duplicate_order(self, order_mock): + order_mock.return_value = { + 'placedOrder': { + 'id': 24601, + 'items': [{'description': 'Storage as a Service'}] + } + } + + result = self.run_command(['block', 'volume-duplicate', '102', + '--origin-snapshot-id=470', + '--duplicate-size=250', + '--duplicate-tier=2', + '--duplicate-snapshot-size=20']) + + self.assert_no_fail(result) + self.assertEqual(result.output, + 'Order #24601 placed successfully!\n' + ' > Storage as a Service\n') diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index c3b3b7a40..9310cb09c 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -466,3 +466,45 @@ def test_replication_partners_unsuccessful(self, partners_mock): self.assertEqual( 'There are no replication partners for the given volume.\n', result.output) + + @mock.patch('SoftLayer.FileStorageManager.order_duplicate_volume') + def test_duplicate_order_exception_caught(self, order_mock): + order_mock.side_effect = ValueError('order attempt failed, oh noooo!') + + result = self.run_command(['file', 'volume-duplicate', '100']) + + self.assertEqual(2, result.exit_code) + self.assertEqual('Argument Error: order attempt failed, oh noooo!', + result.exception.message) + + @mock.patch('SoftLayer.FileStorageManager.order_duplicate_volume') + def test_duplicate_order_order_not_placed(self, order_mock): + order_mock.return_value = {} + + result = self.run_command(['file', 'volume-duplicate', '100', + '--duplicate-iops=1400']) + + self.assert_no_fail(result) + self.assertEqual(result.output, + 'Order could not be placed! Please verify ' + 'your options and try again.\n') + + @mock.patch('SoftLayer.FileStorageManager.order_duplicate_volume') + def test_duplicate_order(self, order_mock): + order_mock.return_value = { + 'placedOrder': { + 'id': 24602, + 'items': [{'description': 'Storage as a Service'}] + } + } + + result = self.run_command(['file', 'volume-duplicate', '100', + '--origin-snapshot-id=470', + '--duplicate-size=250', + '--duplicate-tier=2', + '--duplicate-snapshot-size=20']) + + self.assert_no_fail(result) + self.assertEqual(result.output, + 'Order #24602 placed successfully!\n' + ' > Storage as a Service\n') diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index e336fbbbf..7fd1de91c 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -969,3 +969,184 @@ def test_order_block_replicant(self): 'setupFee': '1'}], }, ) + + def test_order_block_duplicate_origin_os_type_not_found(self): + mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') + mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + + mock_volume = fixtures.SoftLayer_Network_Storage.DUPLICATABLE_VOLUME + prev_os_type = mock_volume['osType'] + del mock_volume['osType'] + mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') + mock.return_value = mock_volume + + exception = self.assertRaises( + exceptions.SoftLayerError, + self.block.order_duplicate_volume, + 102 + ) + + self.assertEqual(str(exception), + "Cannot find origin volume's os-type") + + mock_volume['osType'] = prev_os_type + + def test_order_block_duplicate_performance_no_duplicate_snapshot(self): + mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') + mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + + mock_volume = fixtures.SoftLayer_Network_Storage.DUPLICATABLE_VOLUME + prev_storage_type_keyname = mock_volume['storageType']['keyName'] + mock_volume['storageType']['keyName'] = 'PERFORMANCE_BLOCK_STORAGE' + mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') + mock.return_value = mock_volume + + result = self.block.order_duplicate_volume( + 102, + duplicate_snapshot_size=0) + + self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + + self.assert_called_with( + 'SoftLayer_Product_Order', + 'placeOrder', + args=({ + 'complexType': 'SoftLayer_Container_Product_Order_' + 'Network_Storage_AsAService', + 'packageId': 759, + 'prices': [ + {'id': 189433}, + {'id': 189443}, + {'id': 189993}, + {'id': 190053} + ], + 'volumeSize': 500, + 'quantity': 1, + 'location': 449500, + 'duplicateOriginVolumeId': 102, + 'osFormatType': {'keyName': 'LINUX'}, + 'iops': 1000 + },)) + + mock_volume['storageType']['keyName'] = prev_storage_type_keyname + + def test_order_block_duplicate_performance(self): + mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') + mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + + mock_volume = fixtures.SoftLayer_Network_Storage.DUPLICATABLE_VOLUME + prev_storage_type_keyname = mock_volume['storageType']['keyName'] + mock_volume['storageType']['keyName'] = 'PERFORMANCE_BLOCK_STORAGE' + mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') + mock.return_value = mock_volume + + result = self.block.order_duplicate_volume( + 102, + origin_snapshot_id=470, + duplicate_size=1000, + duplicate_iops=2000, + duplicate_tier_level=None, + duplicate_snapshot_size=10 + ) + + self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + + self.assert_called_with( + 'SoftLayer_Product_Order', + 'placeOrder', + args=({ + 'complexType': 'SoftLayer_Container_Product_Order_' + 'Network_Storage_AsAService', + 'packageId': 759, + 'prices': [ + {'id': 189433}, + {'id': 189443}, + {'id': 190113}, + {'id': 190173}, + {'id': 191193} + ], + 'volumeSize': 1000, + 'quantity': 1, + 'location': 449500, + 'duplicateOriginVolumeId': 102, + 'osFormatType': {'keyName': 'LINUX'}, + 'duplicateOriginSnapshotId': 470, + 'iops': 2000 + },)) + + mock_volume['storageType']['keyName'] = prev_storage_type_keyname + + def test_order_block_duplicate_endurance_no_duplicate_snapshot(self): + mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') + mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + + mock_volume = fixtures.SoftLayer_Network_Storage.DUPLICATABLE_VOLUME + mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') + mock.return_value = mock_volume + + result = self.block.order_duplicate_volume( + 102, + duplicate_snapshot_size=0) + + self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + + self.assert_called_with( + 'SoftLayer_Product_Order', + 'placeOrder', + args=({ + 'complexType': 'SoftLayer_Container_Product_Order_' + 'Network_Storage_AsAService', + 'packageId': 759, + 'prices': [ + {'id': 189433}, + {'id': 189443}, + {'id': 193433}, + {'id': 193373} + ], + 'volumeSize': 500, + 'quantity': 1, + 'location': 449500, + 'duplicateOriginVolumeId': 102, + 'osFormatType': {'keyName': 'LINUX'} + },)) + + def test_order_block_duplicate_endurance(self): + mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') + mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + + mock_volume = fixtures.SoftLayer_Network_Storage.DUPLICATABLE_VOLUME + mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') + mock.return_value = mock_volume + + result = self.block.order_duplicate_volume( + 102, + origin_snapshot_id=470, + duplicate_size=1000, + duplicate_iops=None, + duplicate_tier_level=4, + duplicate_snapshot_size=10 + ) + + self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + + self.assert_called_with( + 'SoftLayer_Product_Order', + 'placeOrder', + args=({ + 'complexType': 'SoftLayer_Container_Product_Order_' + 'Network_Storage_AsAService', + 'packageId': 759, + 'prices': [ + {'id': 189433}, + {'id': 189443}, + {'id': 194763}, + {'id': 194703}, + {'id': 194943} + ], + 'volumeSize': 1000, + 'quantity': 1, + 'location': 449500, + 'duplicateOriginVolumeId': 102, + 'osFormatType': {'keyName': 'LINUX'}, + 'duplicateOriginSnapshotId': 470 + },)) diff --git a/tests/managers/file_tests.py b/tests/managers/file_tests.py index 3b761e0c1..01f1c5dfe 100644 --- a/tests/managers/file_tests.py +++ b/tests/managers/file_tests.py @@ -902,3 +902,167 @@ def test_order_file_replicant(self): 'setupFee': '1'}], }, ) + + def test_order_file_duplicate_performance_no_duplicate_snapshot(self): + mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') + mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + + mock_volume = fixtures.SoftLayer_Network_Storage.DUPLICATABLE_VOLUME + prev_storage_type_keyname = mock_volume['storageType']['keyName'] + mock_volume['storageType']['keyName'] = 'PERFORMANCE_FILE_STORAGE' + mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') + mock.return_value = mock_volume + + result = self.file.order_duplicate_volume( + 102, + duplicate_snapshot_size=0) + + self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + + self.assert_called_with( + 'SoftLayer_Product_Order', + 'placeOrder', + args=({ + 'complexType': 'SoftLayer_Container_Product_Order_' + 'Network_Storage_AsAService', + 'packageId': 759, + 'prices': [ + {'id': 189433}, + {'id': 189453}, + {'id': 189993}, + {'id': 190053} + ], + 'volumeSize': 500, + 'quantity': 1, + 'location': 449500, + 'duplicateOriginVolumeId': 102, + 'iops': 1000 + },)) + + mock_volume['storageType']['keyName'] = prev_storage_type_keyname + + def test_order_file_duplicate_performance(self): + mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') + mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + + mock_volume = fixtures.SoftLayer_Network_Storage.DUPLICATABLE_VOLUME + prev_storage_type_keyname = mock_volume['storageType']['keyName'] + mock_volume['storageType']['keyName'] = 'PERFORMANCE_FILE_STORAGE' + mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') + mock.return_value = mock_volume + + result = self.file.order_duplicate_volume( + 102, + origin_snapshot_id=470, + duplicate_size=1000, + duplicate_iops=2000, + duplicate_tier_level=None, + duplicate_snapshot_size=10 + ) + + self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + + self.assert_called_with( + 'SoftLayer_Product_Order', + 'placeOrder', + args=({ + 'complexType': 'SoftLayer_Container_Product_Order_' + 'Network_Storage_AsAService', + 'packageId': 759, + 'prices': [ + {'id': 189433}, + {'id': 189453}, + {'id': 190113}, + {'id': 190173}, + {'id': 191193} + ], + 'volumeSize': 1000, + 'quantity': 1, + 'location': 449500, + 'duplicateOriginVolumeId': 102, + 'duplicateOriginSnapshotId': 470, + 'iops': 2000 + },)) + + mock_volume['storageType']['keyName'] = prev_storage_type_keyname + + def test_order_file_duplicate_endurance_no_duplicate_snapshot(self): + mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') + mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + + mock_volume = fixtures.SoftLayer_Network_Storage.DUPLICATABLE_VOLUME + prev_storage_type_keyname = mock_volume['storageType']['keyName'] + mock_volume['storageType']['keyName'] = 'ENDURANCE_FILE_STORAGE' + mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') + mock.return_value = mock_volume + + result = self.file.order_duplicate_volume( + 102, + duplicate_snapshot_size=0) + + self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + + self.assert_called_with( + 'SoftLayer_Product_Order', + 'placeOrder', + args=({ + 'complexType': 'SoftLayer_Container_Product_Order_' + 'Network_Storage_AsAService', + 'packageId': 759, + 'prices': [ + {'id': 189433}, + {'id': 189453}, + {'id': 193433}, + {'id': 193373} + ], + 'volumeSize': 500, + 'quantity': 1, + 'location': 449500, + 'duplicateOriginVolumeId': 102 + },)) + + mock_volume['storageType']['keyName'] = prev_storage_type_keyname + + def test_order_file_duplicate_endurance(self): + mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') + mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + + mock_volume = fixtures.SoftLayer_Network_Storage.DUPLICATABLE_VOLUME + prev_storage_type_keyname = mock_volume['storageType']['keyName'] + mock_volume['storageType']['keyName'] = 'ENDURANCE_FILE_STORAGE' + mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') + mock.return_value = mock_volume + + result = self.file.order_duplicate_volume( + 102, + origin_snapshot_id=470, + duplicate_size=1000, + duplicate_iops=None, + duplicate_tier_level=4, + duplicate_snapshot_size=10 + ) + + self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + + self.assert_called_with( + 'SoftLayer_Product_Order', + 'placeOrder', + args=({ + 'complexType': 'SoftLayer_Container_Product_Order_' + 'Network_Storage_AsAService', + 'packageId': 759, + 'prices': [ + {'id': 189433}, + {'id': 189453}, + {'id': 194763}, + {'id': 194703}, + {'id': 194943} + ], + 'volumeSize': 1000, + 'quantity': 1, + 'location': 449500, + 'duplicateOriginVolumeId': 102, + 'duplicateOriginSnapshotId': 470 + },)) + + mock_volume['storageType']['keyName'] = prev_storage_type_keyname diff --git a/tests/managers/storage_utils_tests.py b/tests/managers/storage_utils_tests.py new file mode 100644 index 000000000..b875cceea --- /dev/null +++ b/tests/managers/storage_utils_tests.py @@ -0,0 +1,1715 @@ +""" + SoftLayer.tests.managers.storage_utils_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" + +import SoftLayer +from SoftLayer import exceptions +from SoftLayer import fixtures +from SoftLayer.managers import storage_utils +from SoftLayer import testing + + +class StorageUtilsTests(testing.TestCase): + def set_up(self): + self.block = SoftLayer.BlockStorageManager(self.client) + self.file = SoftLayer.FileStorageManager(self.client) + + def test_find_saas_price_by_category_no_items_in_package(self): + package = { + 'items': []} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_price_by_category, + package, 'storage_as_a_service' + ) + + self.assertEqual(str(exception), + "Could not find price with the category, " + "storage_as_a_service") + + def test_find_saas_price_by_category_no_prices_in_items(self): + package = { + 'items': [ + {'capacity': '0', + 'prices': []} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_price_by_category, + package, 'storage_as_a_service' + ) + + self.assertEqual(str(exception), + "Could not find price with the category, " + "storage_as_a_service") + + def test_find_saas_price_by_category_empty_location_not_found(self): + package = { + 'items': [ + {'capacity': '0', + 'prices': [ + {'id': 189433, + 'categories': [ + {'categoryCode': 'storage_as_a_service'} + ], + 'locationGroupId': '77777777'} + ]} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_price_by_category, + package, 'storage_as_a_service' + ) + + self.assertEqual(str(exception), + "Could not find price with the category, " + "storage_as_a_service") + + def test_find_saas_price_by_category_category_not_found(self): + package = { + 'items': [ + {'capacity': '0', + 'prices': [ + {'id': 189433, + 'categories': [ + {'categoryCode': 'invalid_category_noooo'} + ], + 'locationGroupId': ''} + ]} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_price_by_category, + package, 'storage_as_a_service' + ) + + self.assertEqual(str(exception), + "Could not find price with the category, " + "storage_as_a_service") + + def test_find_saas_price_by_category(self): + package = { + 'items': [ + {'capacity': '0', + 'prices': [ + {'id': 189433, + 'categories': [ + {'categoryCode': 'storage_as_a_service'} + ], + 'locationGroupId': ''} + ]} + ]} + + result = storage_utils.find_saas_price_by_category( + package, 'storage_as_a_service') + + self.assertEqual({'id': 189433}, result) + + def test_find_saas_endurance_space_price_no_items_in_package(self): + package = { + 'items': []} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_endurance_space_price, + package, 8000, 0.25 + ) + + self.assertEqual(str(exception), + "Could not find price for endurance storage space") + + def test_find_saas_endurance_space_price_no_matching_keyname(self): + package = { + 'items': [ + {'capacity': '0', + 'keyName': 'STORAGE_SPACE_FOR_2_IOPS_PER_GB'} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_endurance_space_price, + package, 8000, 0.25 + ) + + self.assertEqual(str(exception), + "Could not find price for endurance storage space") + + def test_find_saas_endurance_space_price_no_capacity_maximum(self): + package = { + 'items': [ + {'capacity': '0', + 'capacityMinimum': '1', + 'keyName': 'STORAGE_SPACE_FOR_0_25_IOPS_PER_GB'} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_endurance_space_price, + package, 8000, 0.25 + ) + + self.assertEqual(str(exception), + "Could not find price for endurance storage space") + + def test_find_saas_endurance_space_price_no_capacity_minimum(self): + package = { + 'items': [ + {'capacity': '0', + 'capacityMaximum': '12000', + 'keyName': 'STORAGE_SPACE_FOR_0_25_IOPS_PER_GB'} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_endurance_space_price, + package, 8000, 0.25 + ) + + self.assertEqual(str(exception), + "Could not find price for endurance storage space") + + def test_find_saas_endurance_space_price_size_below_capacity(self): + package = { + 'items': [ + {'capacity': '0', + 'capacityMaximum': '12000', + 'capacityMinimum': '1', + 'keyName': 'STORAGE_SPACE_FOR_0_25_IOPS_PER_GB'} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_endurance_space_price, + package, 0, 0.25 + ) + + self.assertEqual(str(exception), + "Could not find price for endurance storage space") + + def test_find_saas_endurance_space_price_size_above_capacity(self): + package = { + 'items': [ + {'capacity': '0', + 'capacityMaximum': '12000', + 'capacityMinimum': '1', + 'keyName': 'STORAGE_SPACE_FOR_0_25_IOPS_PER_GB'} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_endurance_space_price, + package, 12001, 0.25 + ) + + self.assertEqual(str(exception), + "Could not find price for endurance storage space") + + def test_find_saas_endurance_space_price_no_prices_in_items(self): + package = { + 'items': [ + {'capacity': '0', + 'capacityMaximum': '12000', + 'capacityMinimum': '1', + 'keyName': 'STORAGE_SPACE_FOR_0_25_IOPS_PER_GB', + 'prices': []} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_endurance_space_price, + package, 8000, 0.25 + ) + + self.assertEqual(str(exception), + "Could not find price for endurance storage space") + + def test_find_saas_endurance_space_price_empty_location_not_found(self): + package = { + 'items': [ + {'capacity': '0', + 'capacityMaximum': '12000', + 'capacityMinimum': '1', + 'keyName': 'STORAGE_SPACE_FOR_0_25_IOPS_PER_GB', + 'prices': [ + {'id': 192103, + 'categories': [ + {'categoryCode': 'performance_storage_space'} + ], + 'locationGroupId': '77777777'} + ]} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_endurance_space_price, + package, 8000, 0.25 + ) + + self.assertEqual(str(exception), + "Could not find price for endurance storage space") + + def test_find_saas_endurance_space_price_category_not_found(self): + package = { + 'items': [ + {'capacity': '0', + 'capacityMaximum': '12000', + 'capacityMinimum': '1', + 'keyName': 'STORAGE_SPACE_FOR_0_25_IOPS_PER_GB', + 'prices': [ + {'id': 192103, + 'categories': [ + {'categoryCode': 'invalid_category_noooo'} + ], + 'locationGroupId': ''} + ]} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_endurance_space_price, + package, 8000, 0.25 + ) + + self.assertEqual(str(exception), + "Could not find price for endurance storage space") + + def test_find_saas_endurance_space_price(self): + package = { + 'items': [ + {'capacity': '0', + 'capacityMaximum': '12000', + 'capacityMinimum': '1', + 'keyName': 'STORAGE_SPACE_FOR_0_25_IOPS_PER_GB', + 'prices': [ + {'id': 192103, + 'categories': [ + {'categoryCode': 'performance_storage_space'} + ], + 'locationGroupId': ''} + ]} + ]} + + result = storage_utils.find_saas_endurance_space_price( + package, 8000, 0.25) + + self.assertEqual({'id': 192103}, result) + + def test_find_saas_endurance_tier_price_no_items_in_package(self): + package = { + 'items': []} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_endurance_tier_price, + package, 2 + ) + + self.assertEqual(str(exception), + "Could not find price for endurance tier level") + + def test_find_saas_endurance_tier_price_no_itemCategory(self): + package = { + 'items': [ + {'capacity': '200'} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_endurance_tier_price, + package, 2 + ) + + self.assertEqual(str(exception), + "Could not find price for endurance tier level") + + def test_find_saas_endurance_tier_price_no_itemCategory_code(self): + package = { + 'items': [ + {'capacity': '200', + 'itemCategory': {}} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_endurance_tier_price, + package, 2 + ) + + self.assertEqual(str(exception), + "Could not find price for endurance tier level") + + def test_find_saas_endurance_tier_price_no_matching_itemCategory(self): + package = { + 'items': [ + {'capacity': '200', + 'itemCategory': {'categoryCode': 'invalid_category_noooo'}} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_endurance_tier_price, + package, 2 + ) + + self.assertEqual(str(exception), + "Could not find price for endurance tier level") + + def test_find_saas_endurance_tier_price_no_matching_capacity(self): + package = { + 'items': [ + {'capacity': '200', + 'itemCategory': {'categoryCode': 'storage_tier_level'}} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_endurance_tier_price, + package, 10 + ) + + self.assertEqual(str(exception), + "Could not find price for endurance tier level") + + def test_find_saas_endurance_tier_price_no_prices_in_items(self): + package = { + 'items': [ + {'capacity': '200', + 'itemCategory': {'categoryCode': 'storage_tier_level'}, + 'prices': []} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_endurance_tier_price, + package, 2 + ) + + self.assertEqual(str(exception), + "Could not find price for endurance tier level") + + def test_find_saas_endurance_tier_price_empty_location_not_found(self): + package = { + 'items': [ + {'capacity': '200', + 'itemCategory': {'categoryCode': 'storage_tier_level'}, + 'prices': [ + {'id': 193373, + 'categories': [ + {'categoryCode': 'storage_tier_level'} + ], + 'locationGroupId': '77777777'} + ]} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_endurance_tier_price, + package, 2 + ) + + self.assertEqual(str(exception), + "Could not find price for endurance tier level") + + def test_find_saas_endurance_tier_price_category_not_found(self): + package = { + 'items': [ + {'capacity': '200', + 'itemCategory': {'categoryCode': 'storage_tier_level'}, + 'prices': [ + {'id': 193373, + 'categories': [ + {'categoryCode': 'invalid_category_noooo'} + ], + 'locationGroupId': ''} + ]} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_endurance_tier_price, + package, 2 + ) + + self.assertEqual(str(exception), + "Could not find price for endurance tier level") + + def test_find_saas_endurance_tier_price(self): + package = { + 'items': [ + {'capacity': '200', + 'itemCategory': {'categoryCode': 'storage_tier_level'}, + 'prices': [ + {'id': 193373, + 'categories': [ + {'categoryCode': 'storage_tier_level'} + ], + 'locationGroupId': ''} + ]} + ]} + + result = storage_utils.find_saas_endurance_tier_price( + package, 2) + + self.assertEqual({'id': 193373}, result) + + def test_find_saas_perform_space_price_no_items_in_package(self): + package = { + 'items': []} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_perform_space_price, + package, 500 + ) + + self.assertEqual(str(exception), + "Could not find price for performance storage space") + + def test_find_saas_perform_space_price_no_itemCategory(self): + package = { + 'items': [ + {'capacity': '0'} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_perform_space_price, + package, 500 + ) + + self.assertEqual(str(exception), + "Could not find price for performance storage space") + + def test_find_saas_perform_space_price_no_itemCategory_code(self): + package = { + 'items': [ + {'capacity': '0', + 'itemCategory': {}} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_perform_space_price, + package, 500 + ) + + self.assertEqual(str(exception), + "Could not find price for performance storage space") + + def test_find_saas_perform_space_price_no_matching_itemCategory(self): + package = { + 'items': [ + {'capacity': '0', + 'itemCategory': {'categoryCode': 'invalid_category_noooo'}} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_perform_space_price, + package, 500 + ) + + self.assertEqual(str(exception), + "Could not find price for performance storage space") + + def test_find_saas_perform_space_price_no_capacity_maximum(self): + package = { + 'items': [ + {'capacity': '0', + 'capacityMinimum': '500', + 'itemCategory': {'categoryCode': 'performance_storage_space'}, + 'keyName': '500_999_GBS'} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_perform_space_price, + package, 500 + ) + + self.assertEqual(str(exception), + "Could not find price for performance storage space") + + def test_find_saas_perform_space_price_no_capacity_minimum(self): + package = { + 'items': [ + {'capacity': '0', + 'capacityMaximum': '999', + 'itemCategory': {'categoryCode': 'performance_storage_space'}, + 'keyName': '500_999_GBS'} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_perform_space_price, + package, 500 + ) + + self.assertEqual(str(exception), + "Could not find price for performance storage space") + + def test_find_saas_perform_space_price_size_below_capacity(self): + package = { + 'items': [ + {'capacity': '0', + 'capacityMaximum': '999', + 'capacityMinimum': '500', + 'itemCategory': {'categoryCode': 'performance_storage_space'}, + 'keyName': '500_999_GBS'} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_perform_space_price, + package, 499 + ) + + self.assertEqual(str(exception), + "Could not find price for performance storage space") + + def test_find_saas_perform_space_price_size_above_capacity(self): + package = { + 'items': [ + {'capacity': '0', + 'capacityMaximum': '999', + 'capacityMinimum': '500', + 'itemCategory': {'categoryCode': 'performance_storage_space'}, + 'keyName': '500_999_GBS'} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_perform_space_price, + package, 1000 + ) + + self.assertEqual(str(exception), + "Could not find price for performance storage space") + + def test_find_saas_perform_space_price_no_matching_keyname(self): + package = { + 'items': [ + {'capacity': '0', + 'capacityMaximum': '999', + 'capacityMinimum': '500', + 'itemCategory': {'categoryCode': 'performance_storage_space'}, + 'keyName': 'NOT_THE_CORRECT_KEYNAME'} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_perform_space_price, + package, 500 + ) + + self.assertEqual(str(exception), + "Could not find price for performance storage space") + + def test_find_saas_perform_space_price_no_prices_in_items(self): + package = { + 'items': [ + {'capacity': '0', + 'capacityMaximum': '999', + 'capacityMinimum': '500', + 'itemCategory': {'categoryCode': 'performance_storage_space'}, + 'keyName': '500_999_GBS', + 'prices': []} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_perform_space_price, + package, 500 + ) + + self.assertEqual(str(exception), + "Could not find price for performance storage space") + + def test_find_saas_perform_space_price_empty_location_not_found(self): + package = { + 'items': [ + {'capacity': '0', + 'capacityMaximum': '999', + 'capacityMinimum': '500', + 'itemCategory': {'categoryCode': 'performance_storage_space'}, + 'keyName': '500_999_GBS', + 'prices': [ + {'id': 189993, + 'categories': [ + {'categoryCode': 'performance_storage_space'} + ], + 'locationGroupId': '77777777'} + ]} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_perform_space_price, + package, 500 + ) + + self.assertEqual(str(exception), + "Could not find price for performance storage space") + + def test_find_saas_perform_space_price_category_not_found(self): + package = { + 'items': [ + {'capacity': '0', + 'capacityMaximum': '999', + 'capacityMinimum': '500', + 'itemCategory': {'categoryCode': 'performance_storage_space'}, + 'keyName': '500_999_GBS', + 'prices': [ + {'id': 189993, + 'categories': [ + {'categoryCode': 'invalid_category_noooo'} + ], + 'locationGroupId': ''} + ]} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_perform_space_price, + package, 500 + ) + + self.assertEqual(str(exception), + "Could not find price for performance storage space") + + def test_find_saas_perform_space_price(self): + package = { + 'items': [ + {'capacity': '0', + 'capacityMaximum': '999', + 'capacityMinimum': '500', + 'itemCategory': {'categoryCode': 'performance_storage_space'}, + 'keyName': '500_999_GBS', + 'prices': [ + {'id': 189993, + 'categories': [ + {'categoryCode': 'performance_storage_space'} + ], + 'locationGroupId': ''} + ]} + ]} + + result = storage_utils.find_saas_perform_space_price( + package, 500) + + self.assertEqual({'id': 189993}, result) + + def test_find_saas_perform_iops_price_no_items_in_package(self): + package = { + 'items': []} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_perform_iops_price, + package, 500, 1700 + ) + + self.assertEqual(str(exception), + "Could not find price for iops for the given volume") + + def test_find_saas_perform_iops_price_no_itemCategory(self): + package = { + 'items': [ + {'capacity': '0'} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_perform_iops_price, + package, 500, 1700 + ) + + self.assertEqual(str(exception), + "Could not find price for iops for the given volume") + + def test_find_saas_perform_iops_price_no_itemCategory_code(self): + package = { + 'items': [ + {'capacity': '0', + 'itemCategory': {}} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_perform_iops_price, + package, 500, 1700 + ) + + self.assertEqual(str(exception), + "Could not find price for iops for the given volume") + + def test_find_saas_perform_iops_price_no_matching_itemCategory(self): + package = { + 'items': [ + {'capacity': '0', + 'itemCategory': {'categoryCode': 'invalid_category_noooo'}} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_perform_iops_price, + package, 500, 1700 + ) + + self.assertEqual(str(exception), + "Could not find price for iops for the given volume") + + def test_find_saas_perform_iops_price_no_capacity_maximum(self): + package = { + 'items': [ + {'capacity': '0', + 'capacityMinimum': '100', + 'itemCategory': {'categoryCode': 'performance_storage_iops'}} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_perform_iops_price, + package, 500, 1700 + ) + + self.assertEqual(str(exception), + "Could not find price for iops for the given volume") + + def test_find_saas_perform_iops_price_no_capacity_minimum(self): + package = { + 'items': [ + {'capacity': '0', + 'capacityMaximum': '10000', + 'itemCategory': {'categoryCode': 'performance_storage_iops'}} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_perform_iops_price, + package, 500, 1700 + ) + + self.assertEqual(str(exception), + "Could not find price for iops for the given volume") + + def test_find_saas_perform_iops_price_iops_below_capacity(self): + package = { + 'items': [ + {'capacity': '0', + 'capacityMaximum': '10000', + 'capacityMinimum': '100', + 'itemCategory': {'categoryCode': 'performance_storage_iops'}} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_perform_iops_price, + package, 500, 99 + ) + + self.assertEqual(str(exception), + "Could not find price for iops for the given volume") + + def test_find_saas_perform_iops_price_iops_above_capacity(self): + package = { + 'items': [ + {'capacity': '0', + 'capacityMaximum': '10000', + 'capacityMinimum': '100', + 'itemCategory': {'categoryCode': 'performance_storage_iops'}} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_perform_iops_price, + package, 500, 10001 + ) + + self.assertEqual(str(exception), + "Could not find price for iops for the given volume") + + def test_find_saas_perform_iops_price_no_prices_in_items(self): + package = { + 'items': [ + {'capacity': '0', + 'capacityMaximum': '10000', + 'capacityMinimum': '100', + 'itemCategory': {'categoryCode': 'performance_storage_iops'}, + 'prices': []} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_perform_iops_price, + package, 500, 1700 + ) + + self.assertEqual(str(exception), + "Could not find price for iops for the given volume") + + def test_find_saas_perform_iops_price_empty_location_not_found(self): + package = { + 'items': [ + {'capacity': '0', + 'capacityMaximum': '10000', + 'capacityMinimum': '100', + 'itemCategory': {'categoryCode': 'performance_storage_iops'}, + 'prices': [ + {'capacityRestrictionMaximum': '999', + 'capacityRestrictionMinimum': '500', + 'capacityRestrictionType': 'STORAGE_SPACE', + 'categories': [ + {'categoryCode': 'performance_storage_iops'} + ], + 'id': 190053, + 'locationGroupId': '77777777'} + ]} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_perform_iops_price, + package, 500, 1700 + ) + + self.assertEqual(str(exception), + "Could not find price for iops for the given volume") + + def test_find_saas_perform_iops_price_category_not_found(self): + package = { + 'items': [ + {'capacity': '0', + 'capacityMaximum': '10000', + 'capacityMinimum': '100', + 'itemCategory': {'categoryCode': 'performance_storage_iops'}, + 'prices': [ + {'capacityRestrictionMaximum': '999', + 'capacityRestrictionMinimum': '500', + 'capacityRestrictionType': 'STORAGE_SPACE', + 'categories': [ + {'categoryCode': 'invalid_category_noooo'} + ], + 'id': 190053, + 'locationGroupId': ''} + ]} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_perform_iops_price, + package, 500, 1700 + ) + + self.assertEqual(str(exception), + "Could not find price for iops for the given volume") + + def test_find_saas_perform_iops_price_wrong_capacity_restriction(self): + package = { + 'items': [ + {'capacity': '0', + 'capacityMaximum': '10000', + 'capacityMinimum': '100', + 'itemCategory': {'categoryCode': 'performance_storage_iops'}, + 'prices': [ + {'capacityRestrictionMaximum': '999', + 'capacityRestrictionMinimum': '500', + 'capacityRestrictionType': 'NOT_THE_CORRECT_TYPE', + 'categories': [ + {'categoryCode': 'performance_storage_iops'} + ], + 'id': 190053, + 'locationGroupId': ''} + ]} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_perform_iops_price, + package, 500, 1700 + ) + + self.assertEqual(str(exception), + "Could not find price for iops for the given volume") + + def test_find_saas_perform_iops_price_size_below_capacity(self): + package = { + 'items': [ + {'capacity': '0', + 'capacityMaximum': '10000', + 'capacityMinimum': '100', + 'itemCategory': {'categoryCode': 'performance_storage_iops'}, + 'prices': [ + {'capacityRestrictionMaximum': '999', + 'capacityRestrictionMinimum': '500', + 'capacityRestrictionType': 'STORAGE_SPACE', + 'categories': [ + {'categoryCode': 'performance_storage_iops'} + ], + 'id': 190053, + 'locationGroupId': ''} + ]} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_perform_iops_price, + package, 499, 1700 + ) + + self.assertEqual(str(exception), + "Could not find price for iops for the given volume") + + def test_find_saas_perform_iops_price_size_above_capacity(self): + package = { + 'items': [ + {'capacity': '0', + 'capacityMaximum': '10000', + 'capacityMinimum': '100', + 'itemCategory': {'categoryCode': 'performance_storage_iops'}, + 'prices': [ + {'capacityRestrictionMaximum': '999', + 'capacityRestrictionMinimum': '500', + 'capacityRestrictionType': 'STORAGE_SPACE', + 'categories': [ + {'categoryCode': 'performance_storage_iops'} + ], + 'id': 190053, + 'locationGroupId': ''} + ]} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_perform_iops_price, + package, 1000, 1700 + ) + + self.assertEqual(str(exception), + "Could not find price for iops for the given volume") + + def test_find_saas_perform_iops_price(self): + package = { + 'items': [ + {'capacity': '0', + 'capacityMaximum': '10000', + 'capacityMinimum': '100', + 'itemCategory': {'categoryCode': 'performance_storage_iops'}, + 'prices': [ + {'capacityRestrictionMaximum': '999', + 'capacityRestrictionMinimum': '500', + 'capacityRestrictionType': 'STORAGE_SPACE', + 'categories': [ + {'categoryCode': 'performance_storage_iops'} + ], + 'id': 190053, + 'locationGroupId': ''} + ]} + ]} + + result = storage_utils.find_saas_perform_iops_price( + package, 500, 1700) + + self.assertEqual({'id': 190053}, result) + + def test_find_saas_snapshot_space_price_no_items_in_package(self): + package = { + 'items': []} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_snapshot_space_price, + package, 10, iops=2100 + ) + + self.assertEqual(str(exception), + "Could not find price for snapshot space") + + def test_find_saas_snapshot_space_price_no_matching_capacity(self): + package = { + 'items': [ + {'capacity': '-1'} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_snapshot_space_price, + package, 10, iops=2100 + ) + + self.assertEqual(str(exception), + "Could not find price for snapshot space") + + def test_find_saas_snapshot_space_price_no_prices_in_items(self): + package = { + 'items': [ + {'capacity': '10', + 'prices': []} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_snapshot_space_price, + package, 10, iops=2100 + ) + + self.assertEqual(str(exception), + "Could not find price for snapshot space") + + def test_find_saas_snapshot_space_price_empty_location_not_found(self): + package = { + 'items': [ + {'capacity': '10', + 'prices': [ + {'capacityRestrictionMaximum': '48000', + 'capacityRestrictionMinimum': '100', + 'capacityRestrictionType': 'IOPS', + 'categories': [ + {'categoryCode': 'storage_snapshot_space'} + ], + 'id': 191193, + 'locationGroupId': '77777777'} + ]} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_snapshot_space_price, + package, 10, iops=2100 + ) + + self.assertEqual(str(exception), + "Could not find price for snapshot space") + + def test_find_saas_snapshot_space_price_wrong_capacity_restriction(self): + package = { + 'items': [ + {'capacity': '10', + 'prices': [ + {'capacityRestrictionMaximum': '48000', + 'capacityRestrictionMinimum': '100', + 'capacityRestrictionType': 'NOT_THE_CORRECT_CATEGORY', + 'categories': [ + {'categoryCode': 'storage_snapshot_space'} + ], + 'id': 191193, + 'locationGroupId': ''} + ]} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_snapshot_space_price, + package, 10, iops=2100 + ) + + self.assertEqual(str(exception), + "Could not find price for snapshot space") + + def test_find_saas_snapshot_space_price_target_value_below_capacity(self): + package = { + 'items': [ + {'capacity': '10', + 'prices': [ + {'capacityRestrictionMaximum': '48000', + 'capacityRestrictionMinimum': '100', + 'capacityRestrictionType': 'IOPS', + 'categories': [ + {'categoryCode': 'storage_snapshot_space'} + ], + 'id': 191193, + 'locationGroupId': ''} + ]} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_snapshot_space_price, + package, 10, iops=99 + ) + + self.assertEqual(str(exception), + "Could not find price for snapshot space") + + def test_find_saas_snapshot_space_price_target_value_above_capacity(self): + package = { + 'items': [ + {'capacity': '10', + 'prices': [ + {'capacityRestrictionMaximum': '48000', + 'capacityRestrictionMinimum': '100', + 'capacityRestrictionType': 'IOPS', + 'categories': [ + {'categoryCode': 'storage_snapshot_space'} + ], + 'id': 191193, + 'locationGroupId': ''} + ]} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_snapshot_space_price, + package, 10, iops=48001 + ) + + self.assertEqual(str(exception), + "Could not find price for snapshot space") + + def test_find_saas_snapshot_space_price_category_not_found(self): + package = { + 'items': [ + {'capacity': '10', + 'prices': [ + {'capacityRestrictionMaximum': '48000', + 'capacityRestrictionMinimum': '100', + 'capacityRestrictionType': 'IOPS', + 'categories': [ + {'categoryCode': 'invalid_category_noooooooo'} + ], + 'id': 191193, + 'locationGroupId': ''} + ]} + ]} + + exception = self.assertRaises( + ValueError, + storage_utils.find_saas_snapshot_space_price, + package, 10, iops=2100 + ) + + self.assertEqual(str(exception), + "Could not find price for snapshot space") + + def test_find_saas_snapshot_space_price_with_iops(self): + package = { + 'items': [ + {'capacity': '10', + 'prices': [ + {'capacityRestrictionMaximum': '48000', + 'capacityRestrictionMinimum': '100', + 'capacityRestrictionType': 'IOPS', + 'categories': [ + {'categoryCode': 'storage_snapshot_space'} + ], + 'id': 191193, + 'locationGroupId': ''} + ]} + ]} + + result = storage_utils.find_saas_snapshot_space_price( + package, 10, iops=2100) + + self.assertEqual({'id': 191193}, result) + + def test_find_saas_snapshot_space_price_with_tier_level(self): + package = { + 'items': [ + {'capacity': '10', + 'prices': [ + {'capacityRestrictionMaximum': '200', + 'capacityRestrictionMinimum': '200', + 'capacityRestrictionType': 'STORAGE_TIER_LEVEL', + 'categories': [ + {'categoryCode': 'storage_snapshot_space'} + ], + 'id': 193613, + 'locationGroupId': ''} + ]} + ]} + + result = storage_utils.find_saas_snapshot_space_price( + package, 10, tier_level=2) + + self.assertEqual({'id': 193613}, result) + + def test_prep_duplicate_order_origin_volume_cancelled(self): + mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') + mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + + mock_volume = fixtures.SoftLayer_Network_Storage.DUPLICATABLE_VOLUME + prev_billing_item = mock_volume['billingItem'] + del mock_volume['billingItem'] + + exception = self.assertRaises( + exceptions.SoftLayerError, + storage_utils.prepare_duplicate_order_object, + self.block, mock_volume, None, None, None, None, 'block' + ) + + self.assertEqual(str(exception), + "The origin volume has been cancelled; " + "unable to order duplicate volume") + + mock_volume['billingItem'] = prev_billing_item + + def test_prep_duplicate_order_origin_snapshot_capacity_not_found(self): + mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') + mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + + mock_volume = fixtures.SoftLayer_Network_Storage.DUPLICATABLE_VOLUME + prev_snapshot_capacity_gb = mock_volume['snapshotCapacityGb'] + del mock_volume['snapshotCapacityGb'] + + exception = self.assertRaises( + exceptions.SoftLayerError, + storage_utils.prepare_duplicate_order_object, + self.block, mock_volume, None, None, None, None, 'block' + ) + + self.assertEqual(str(exception), + "Snapshot space not found for the origin volume. " + "Origin snapshot space is needed for duplication.") + + mock_volume['snapshotCapacityGb'] = prev_snapshot_capacity_gb + + def test_prep_duplicate_order_origin_volume_location_not_found(self): + mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') + mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + + mock_volume = fixtures.SoftLayer_Network_Storage.DUPLICATABLE_VOLUME + prev_location = mock_volume['billingItem']['location'] + del mock_volume['billingItem']['location'] + + exception = self.assertRaises( + exceptions.SoftLayerError, + storage_utils.prepare_duplicate_order_object, + self.block, mock_volume, None, None, None, None, 'block' + ) + + self.assertEqual(str(exception), + "Cannot find origin volume's location") + + mock_volume['billingItem']['location'] = prev_location + + def test_prep_duplicate_order_origin_volume_capacity_not_found(self): + mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') + mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + + mock_volume = fixtures.SoftLayer_Network_Storage.DUPLICATABLE_VOLUME + prev_capacity_gb = mock_volume['capacityGb'] + mock_volume['capacityGb'] = None + + exception = self.assertRaises( + exceptions.SoftLayerError, + storage_utils.prepare_duplicate_order_object, + self.block, mock_volume, None, None, None, None, 'block' + ) + + self.assertEqual(str(exception), "Cannot find origin volume's size.") + + mock_volume['capacityGb'] = prev_capacity_gb + + def test_prep_duplicate_order_origin_originalVolumeSize_empty_block(self): + mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') + mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + + mock_volume = fixtures.SoftLayer_Network_Storage.DUPLICATABLE_VOLUME + prev_original_volume_size = mock_volume['originalVolumeSize'] + del mock_volume['originalVolumeSize'] + + expected_object = { + 'complexType': 'SoftLayer_Container_Product_Order_' + 'Network_Storage_AsAService', + 'packageId': 759, + 'prices': [ + {'id': 189433}, + {'id': 189443}, + {'id': 193433}, + {'id': 193373}, + {'id': 193613} + ], + 'volumeSize': 500, + 'quantity': 1, + 'location': 449500, + 'duplicateOriginVolumeId': 102} + + result = storage_utils.prepare_duplicate_order_object( + self.block, mock_volume, None, None, None, None, 'block') + + self.assertEqual(expected_object, result) + + mock_volume['originalVolumeSize'] = prev_original_volume_size + + def test_prep_duplicate_order_size_too_small(self): + mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') + mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + + mock_volume = fixtures.SoftLayer_Network_Storage.DUPLICATABLE_VOLUME + + exception = self.assertRaises( + exceptions.SoftLayerError, + storage_utils.prepare_duplicate_order_object, + self.block, mock_volume, None, None, 250, None, 'block' + ) + + self.assertEqual(str(exception), + "The requested duplicate volume size is too small. " + "Duplicate volumes must be at least as large as " + "their origin volumes.") + + def test_prep_duplicate_order_size_too_large_block(self): + mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') + mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + + mock_volume = fixtures.SoftLayer_Network_Storage.DUPLICATABLE_VOLUME + + exception = self.assertRaises( + exceptions.SoftLayerError, + storage_utils.prepare_duplicate_order_object, + self.block, mock_volume, None, None, 8000, None, 'block' + ) + + self.assertEqual(str(exception), + "The requested duplicate volume size is too large. " + "The maximum size for duplicate block volumes is 10 " + "times the size of the origin volume or, if the " + "origin volume was also a duplicate, 10 times the " + "size of the initial origin volume (i.e. the origin " + "volume from which the first duplicate was created " + "in the chain of duplicates). " + "Requested: 8000 GB. Base origin size: 500 GB.") + + def test_prep_duplicate_order_performance_origin_iops_not_found(self): + mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') + mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + + mock_volume = fixtures.SoftLayer_Network_Storage.DUPLICATABLE_VOLUME + prev_storage_type_keyname = mock_volume['storageType']['keyName'] + prev_provisioned_iops = mock_volume['provisionedIops'] + mock_volume['storageType']['keyName'] = 'PERFORMANCE_BLOCK_'\ + 'STORAGE_REPLICANT' + mock_volume['provisionedIops'] = None + + exception = self.assertRaises( + exceptions.SoftLayerError, + storage_utils.prepare_duplicate_order_object, + self.block, mock_volume, None, None, None, None, 'block' + ) + + self.assertEqual(str(exception), + "Cannot find origin volume's provisioned IOPS") + + mock_volume['storageType']['keyName'] = prev_storage_type_keyname + mock_volume['provisionedIops'] = prev_provisioned_iops + + def test_prep_duplicate_order_performance_iops_above_limit(self): + mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') + mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + + mock_volume = fixtures.SoftLayer_Network_Storage.DUPLICATABLE_VOLUME + prev_storage_type_keyname = mock_volume['storageType']['keyName'] + prev_provisioned_iops = mock_volume['provisionedIops'] + mock_volume['storageType']['keyName'] = 'PERFORMANCE_BLOCK_STORAGE' + mock_volume['provisionedIops'] = '100' + + exception = self.assertRaises( + exceptions.SoftLayerError, + storage_utils.prepare_duplicate_order_object, + self.block, mock_volume, 1000, None, 500, None, 'block' + ) + + self.assertEqual(str(exception), + "Origin volume performance is < 0.3 IOPS/GB, " + "duplicate volume performance must also be < 0.3 " + "IOPS/GB. 2.0 IOPS/GB (1000/500) requested.") + + mock_volume['storageType']['keyName'] = prev_storage_type_keyname + mock_volume['provisionedIops'] = prev_provisioned_iops + + def test_prep_duplicate_order_performance_iops_below_limit(self): + mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') + mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + + mock_volume = fixtures.SoftLayer_Network_Storage.DUPLICATABLE_VOLUME + prev_storage_type_keyname = mock_volume['storageType']['keyName'] + mock_volume['storageType']['keyName'] = 'PERFORMANCE_BLOCK_STORAGE' + + exception = self.assertRaises( + exceptions.SoftLayerError, + storage_utils.prepare_duplicate_order_object, + self.block, mock_volume, 200, None, 1000, None, 'block' + ) + + self.assertEqual(str(exception), + "Origin volume performance is >= 0.3 IOPS/GB, " + "duplicate volume performance must also be >= 0.3 " + "IOPS/GB. 0.2 IOPS/GB (200/1000) requested.") + + mock_volume['storageType']['keyName'] = prev_storage_type_keyname + + def test_prep_duplicate_order_performance_use_default_origin_values(self): + mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') + mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + + mock_volume = fixtures.SoftLayer_Network_Storage.DUPLICATABLE_VOLUME + prev_storage_type_keyname = mock_volume['storageType']['keyName'] + mock_volume['storageType']['keyName'] = 'PERFORMANCE_FILE_'\ + 'STORAGE_REPLICANT' + + expected_object = { + 'complexType': 'SoftLayer_Container_Product_Order_' + 'Network_Storage_AsAService', + 'packageId': 759, + 'prices': [ + {'id': 189433}, + {'id': 189453}, + {'id': 189993}, + {'id': 190053}, + {'id': 191193} + ], + 'volumeSize': 500, + 'quantity': 1, + 'location': 449500, + 'duplicateOriginVolumeId': 102, + 'iops': 1000} + + result = storage_utils.prepare_duplicate_order_object( + self.file, mock_volume, None, None, None, None, 'file') + + self.assertEqual(expected_object, result) + + mock_volume['storageType']['keyName'] = prev_storage_type_keyname + + def test_prep_duplicate_order_performance_block(self): + mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') + mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + + mock_volume = fixtures.SoftLayer_Network_Storage.DUPLICATABLE_VOLUME + prev_storage_type_keyname = mock_volume['storageType']['keyName'] + mock_volume['storageType']['keyName'] = 'PERFORMANCE_BLOCK_STORAGE' + + expected_object = { + 'complexType': 'SoftLayer_Container_Product_Order_' + 'Network_Storage_AsAService', + 'packageId': 759, + 'prices': [ + {'id': 189433}, + {'id': 189443}, + {'id': 190113}, + {'id': 190173}, + {'id': 191193} + ], + 'volumeSize': 1000, + 'quantity': 1, + 'location': 449500, + 'duplicateOriginVolumeId': 102, + 'iops': 2000} + + result = storage_utils.prepare_duplicate_order_object( + self.block, mock_volume, 2000, None, 1000, 10, 'block') + + self.assertEqual(expected_object, result) + + mock_volume['storageType']['keyName'] = prev_storage_type_keyname + + def test_prep_duplicate_order_performance_file(self): + mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') + mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + + mock_volume = fixtures.SoftLayer_Network_Storage.DUPLICATABLE_VOLUME + prev_storage_type_keyname = mock_volume['storageType']['keyName'] + mock_volume['storageType']['keyName'] = 'PERFORMANCE_FILE_STORAGE' + + expected_object = { + 'complexType': 'SoftLayer_Container_Product_Order_' + 'Network_Storage_AsAService', + 'packageId': 759, + 'prices': [ + {'id': 189433}, + {'id': 189453}, + {'id': 190113}, + {'id': 190173}, + {'id': 191193} + ], + 'volumeSize': 1000, + 'quantity': 1, + 'location': 449500, + 'duplicateOriginVolumeId': 102, + 'iops': 2000} + + result = storage_utils.prepare_duplicate_order_object( + self.file, mock_volume, 2000, None, 1000, 10, 'file') + + self.assertEqual(expected_object, result) + + mock_volume['storageType']['keyName'] = prev_storage_type_keyname + + def test_prep_duplicate_order_endurance_origin_tier_not_found(self): + mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') + mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + + mock_volume = fixtures.SoftLayer_Network_Storage.DUPLICATABLE_VOLUME + prev_tier_level = mock_volume['storageTierLevel'] + prev_storage_type_keyname = mock_volume['storageType']['keyName'] + mock_volume['storageTierLevel'] = 'NINJA_PENGUINS' + mock_volume['storageType']['keyName'] = 'ENDURANCE_BLOCK_'\ + 'STORAGE_REPLICANT' + + exception = self.assertRaises( + exceptions.SoftLayerError, + storage_utils.prepare_duplicate_order_object, + self.block, mock_volume, None, None, None, None, 'block' + ) + + self.assertEqual(str(exception), + "Cannot find origin volume's tier level") + + mock_volume['storageTierLevel'] = prev_tier_level + mock_volume['storageType']['keyName'] = prev_storage_type_keyname + + def test_prep_duplicate_order_endurance_tier_above_limit(self): + mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') + mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + + mock_volume = fixtures.SoftLayer_Network_Storage.DUPLICATABLE_VOLUME + prev_tier_level = mock_volume['storageTierLevel'] + mock_volume['storageTierLevel'] = 'LOW_INTENSITY_TIER' + + exception = self.assertRaises( + exceptions.SoftLayerError, + storage_utils.prepare_duplicate_order_object, + self.block, mock_volume, None, 2, None, None, 'block' + ) + + self.assertEqual(str(exception), + "Origin volume performance tier is 0.25 IOPS/GB, " + "duplicate volume performance tier must also be 0.25 " + "IOPS/GB. 2 IOPS/GB requested.") + + mock_volume['storageTierLevel'] = prev_tier_level + + def test_prep_duplicate_order_endurance_tier_below_limit(self): + mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') + mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + + mock_volume = fixtures.SoftLayer_Network_Storage.DUPLICATABLE_VOLUME + + exception = self.assertRaises( + exceptions.SoftLayerError, + storage_utils.prepare_duplicate_order_object, + self.block, mock_volume, None, 0.25, None, None, 'block' + ) + + self.assertEqual(str(exception), + "Origin volume performance tier is above 0.25 " + "IOPS/GB, duplicate volume performance tier must " + "also be above 0.25 IOPS/GB. 0.25 IOPS/GB requested.") + + def test_prep_duplicate_order_endurance_use_default_origin_values(self): + mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') + mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + + mock_volume = fixtures.SoftLayer_Network_Storage.DUPLICATABLE_VOLUME + prev_storage_type_keyname = mock_volume['storageType']['keyName'] + mock_volume['storageType']['keyName'] = 'ENDURANCE_FILE_'\ + 'STORAGE_REPLICANT' + + expected_object = { + 'complexType': 'SoftLayer_Container_Product_Order_' + 'Network_Storage_AsAService', + 'packageId': 759, + 'prices': [ + {'id': 189433}, + {'id': 189453}, + {'id': 193433}, + {'id': 193373}, + {'id': 193613} + ], + 'volumeSize': 500, + 'quantity': 1, + 'location': 449500, + 'duplicateOriginVolumeId': 102} + + result = storage_utils.prepare_duplicate_order_object( + self.file, mock_volume, None, None, None, None, 'file') + + self.assertEqual(expected_object, result) + + mock_volume['storageType']['keyName'] = prev_storage_type_keyname + + def test_prep_duplicate_order_endurance_block(self): + mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') + mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + + mock_volume = fixtures.SoftLayer_Network_Storage.DUPLICATABLE_VOLUME + + expected_object = { + 'complexType': 'SoftLayer_Container_Product_Order_' + 'Network_Storage_AsAService', + 'packageId': 759, + 'prices': [ + {'id': 189433}, + {'id': 189443}, + {'id': 194763}, + {'id': 194703}, + {'id': 194943} + ], + 'volumeSize': 1000, + 'quantity': 1, + 'location': 449500, + 'duplicateOriginVolumeId': 102} + + result = storage_utils.prepare_duplicate_order_object( + self.block, mock_volume, None, 4.0, 1000, 10, 'block') + + self.assertEqual(expected_object, result) + + def test_prep_duplicate_order_endurance_file(self): + mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') + mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + + mock_volume = fixtures.SoftLayer_Network_Storage.DUPLICATABLE_VOLUME + prev_storage_type_keyname = mock_volume['storageType']['keyName'] + mock_volume['storageType']['keyName'] = 'ENDURANCE_FILE_STORAGE' + + expected_object = { + 'complexType': 'SoftLayer_Container_Product_Order_' + 'Network_Storage_AsAService', + 'packageId': 759, + 'prices': [ + {'id': 189433}, + {'id': 189453}, + {'id': 194763}, + {'id': 194703}, + {'id': 194943} + ], + 'volumeSize': 1000, + 'quantity': 1, + 'location': 449500, + 'duplicateOriginVolumeId': 102} + + result = storage_utils.prepare_duplicate_order_object( + self.file, mock_volume, None, 4.0, 1000, 10, 'file') + + self.assertEqual(expected_object, result) + + mock_volume['storageType']['keyName'] = prev_storage_type_keyname + + def test_prep_duplicate_order_invalid_origin_storage_type(self): + mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') + mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + + mock_volume = fixtures.SoftLayer_Network_Storage.DUPLICATABLE_VOLUME + prev_storage_type_keyname = mock_volume['storageType']['keyName'] + mock_volume['storageType']['keyName'] = 'NINJA_CATS' + + exception = self.assertRaises( + exceptions.SoftLayerError, + storage_utils.prepare_duplicate_order_object, + self.block, mock_volume, None, None, None, None, 'block' + ) + + self.assertEqual(str(exception), + "Origin volume does not have a valid storage type " + "(with an appropriate keyName to indicate the " + "volume is a PERFORMANCE or ENDURANCE volume)") + + mock_volume['storageType']['keyName'] = prev_storage_type_keyname From efe06a2d1e859bdbcf9fefa62780741b93f0e60a Mon Sep 17 00:00:00 2001 From: David Pickle Date: Tue, 6 Jun 2017 15:36:38 -0500 Subject: [PATCH 3/4] Remove temporary comments --- SoftLayer/managers/block.py | 6 ------ SoftLayer/managers/file.py | 6 ------ 2 files changed, 12 deletions(-) diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index 8611466c5..6b2dc9d33 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -278,12 +278,6 @@ def order_duplicate_volume(self, origin_volume_id, origin_snapshot_id=None, origin_volume = self.get_block_volume_details(origin_volume_id, mask=block_mask) - # 47474747 remove this if not used - # duplicate_parameters = self.client.call( - # 'Network_Storage', - # 'getVolumeDuplicateParameters', - # id=origin_volume_id) - if isinstance(utils.lookup(origin_volume, 'osType', 'keyName'), str): os_type = origin_volume['osType']['keyName'] else: diff --git a/SoftLayer/managers/file.py b/SoftLayer/managers/file.py index 5820d7458..6caf1f5f1 100644 --- a/SoftLayer/managers/file.py +++ b/SoftLayer/managers/file.py @@ -266,12 +266,6 @@ def order_duplicate_volume(self, origin_volume_id, origin_snapshot_id=None, origin_volume = self.get_file_volume_details(origin_volume_id, mask=file_mask) - # 47474747 remove this if not used - # duplicate_parameters = self.client.call( - # 'Network_Storage', - # 'getVolumeDuplicateParameters', - # id=origin_volume_id) - order = storage_utils.prepare_duplicate_order_object( self, origin_volume, duplicate_iops, duplicate_tier_level, duplicate_size, duplicate_snapshot_size, 'file' From 9342eeddc2008f34305f89a4ae9e7f56851c6063 Mon Sep 17 00:00:00 2001 From: David Pickle Date: Fri, 9 Jun 2017 14:39:52 -0500 Subject: [PATCH 4/4] Fix bug for 'file volume-detail' command when 'bytesUsed' is empty --- SoftLayer/CLI/file/detail.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/file/detail.py b/SoftLayer/CLI/file/detail.py index 1fae6774d..3c9af3f23 100644 --- a/SoftLayer/CLI/file/detail.py +++ b/SoftLayer/CLI/file/detail.py @@ -16,7 +16,8 @@ def cli(env, volume_id): file_manager = SoftLayer.FileStorageManager(env.client) file_volume = file_manager.get_file_volume_details(volume_id) file_volume = utils.NestedDict(file_volume) - used_space = int(file_volume['bytesUsed']) + used_space = int(file_volume['bytesUsed'])\ + if file_volume['bytesUsed'] else 0 table = formatting.KeyValueTable(['Name', 'Value']) table.align['Name'] = 'r'