diff --git a/SoftLayer/CLI/block/order.py b/SoftLayer/CLI/block/order.py index 4393a2829..22430e429 100644 --- a/SoftLayer/CLI/block/order.py +++ b/SoftLayer/CLI/block/order.py @@ -7,7 +7,7 @@ from SoftLayer.CLI import exceptions -CONTEXT_SETTINGS = dict(token_normalize_func=lambda x: x.upper()) +CONTEXT_SETTINGS = {'token_normalize_func': lambda x: x.upper()} @click.command(context_settings=CONTEXT_SETTINGS) @@ -42,8 +42,14 @@ @click.option('--location', help='Datacenter short name (e.g.: dal09)', required=True) +@click.option('--snapshot-size', + type=int, + help='Optional parameter for ordering snapshot ' + 'space along with endurance block storage; specifies ' + 'the size (in GB) of snapshot space to order') @environment.pass_env -def cli(env, storage_type, size, iops, tier, os_type, location): +def cli(env, storage_type, size, iops, tier, os_type, + location, snapshot_size): """Order a block storage volume.""" block_manager = SoftLayer.BlockStorageManager(env.client) storage_type = storage_type.lower() @@ -62,6 +68,12 @@ def cli(env, storage_type, size, iops, tier, os_type, location): 'Option --iops must be a multiple of 100' ) + if snapshot_size is not None: + raise exceptions.CLIAbort( + 'Option --snapshot-size not allowed for performance volumes.' + ' Snapshots are only available for endurance storage.' + ) + try: order = block_manager.order_block_volume( storage_type='performance_storage_iscsi', @@ -84,7 +96,8 @@ def cli(env, storage_type, size, iops, tier, os_type, location): location=location, size=size, tier_level=float(tier), - os_type=os_type + os_type=os_type, + snapshot_size=snapshot_size ) except ValueError as ex: raise exceptions.ArgumentError(str(ex)) diff --git a/SoftLayer/CLI/file/order.py b/SoftLayer/CLI/file/order.py index bb200f275..0219efdbe 100644 --- a/SoftLayer/CLI/file/order.py +++ b/SoftLayer/CLI/file/order.py @@ -7,7 +7,7 @@ from SoftLayer.CLI import exceptions -CONTEXT_SETTINGS = dict(token_normalize_func=lambda x: x.upper()) +CONTEXT_SETTINGS = {'token_normalize_func': lambda x: x.upper()} @click.command(context_settings=CONTEXT_SETTINGS) @@ -42,8 +42,14 @@ @click.option('--location', help='Datacenter short name (e.g.: dal09)', required=True) +@click.option('--snapshot-size', + type=int, + help='Optional parameter for ordering snapshot ' + 'space along with endurance file storage; specifies ' + 'the size (in GB) of snapshot space to order') @environment.pass_env -def cli(env, storage_type, size, iops, tier, os_type, location): +def cli(env, storage_type, size, iops, tier, os_type, + location, snapshot_size): """Order a file storage volume.""" file_manager = SoftLayer.FileStorageManager(env.client) storage_type = storage_type.lower() @@ -62,6 +68,12 @@ def cli(env, storage_type, size, iops, tier, os_type, location): 'Option --iops must be a multiple of 100' ) + if snapshot_size is not None: + raise exceptions.CLIAbort( + 'Option --snapshot-size not allowed for performance volumes.' + ' Snapshots are only available for endurance storage.' + ) + try: order = file_manager.order_file_volume( storage_type='performance_storage_nfs', @@ -84,7 +96,8 @@ def cli(env, storage_type, size, iops, tier, os_type, location): location=location, size=size, tier_level=float(tier), - os_type=os_type + os_type=os_type, + snapshot_size=snapshot_size ) except ValueError as ex: raise exceptions.ArgumentError(str(ex)) diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index 10f170f33..308b43268 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -193,7 +193,7 @@ def delete_snapshot(self, snapshot_id): id=snapshot_id) def order_block_volume(self, storage_type, location, size, os_type, - iops=None, tier_level=None): + iops=None, tier_level=None, snapshot_size=None): """Places an order for a block volume. :param storage_type: "performance_storage_iscsi" (performance) @@ -203,6 +203,8 @@ def order_block_volume(self, storage_type, location, size, os_type, :param os_type: OS Type to use for volume alignment, see help for list :param iops: Number of IOPs for a "Performance" order :param tier_level: Tier level to use for an "Endurance" order + :param snapshot_size: The size of optional snapshot space, + if snapshot space should also be ordered (None if not ordered) """ try: @@ -239,6 +241,9 @@ def order_block_volume(self, storage_type, location, size, os_type, ), storage_utils.find_endurance_tier_price(package, tier_level), ] + if snapshot_size is not None: + prices.append(storage_utils.find_snapshot_space_price( + package, snapshot_size, tier_level)) else: raise exceptions.SoftLayerError( "Block volume storage_type must be either " diff --git a/SoftLayer/managers/file.py b/SoftLayer/managers/file.py index 9759b1c51..ce3a16dc9 100644 --- a/SoftLayer/managers/file.py +++ b/SoftLayer/managers/file.py @@ -196,7 +196,7 @@ def delete_snapshot(self, snapshot_id): id=snapshot_id) def order_file_volume(self, storage_type, location, size, os_type, - iops=None, tier_level=None): + iops=None, tier_level=None, snapshot_size=None): """Places an order for a file volume. :param storage_type: "performance_storage_iscsi" (performance) @@ -206,6 +206,8 @@ def order_file_volume(self, storage_type, location, size, os_type, :param os_type: OS Type to use for volume alignment, see help for list :param iops: Number of IOPs for a "Performance" order :param tier_level: Tier level to use for an "Endurance" order + :param snapshot_size: The size of optional snapshot space, + if snapshot space should also be ordered (None if not ordered) """ try: @@ -242,6 +244,9 @@ def order_file_volume(self, storage_type, location, size, os_type, ), storage_utils.find_endurance_tier_price(package, tier_level), ] + if snapshot_size is not None: + prices.append(storage_utils.find_snapshot_space_price( + package, snapshot_size, tier_level)) else: raise exceptions.SoftLayerError( "File volume storage_type must be either " diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index b413a6953..3998d03c3 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -101,29 +101,107 @@ def test_volume_list(self): }], json.loads(result.output)) + def test_volume_order_performance_iops_not_given(self): + result = self.run_command(['block', 'volume-order', + '--storage-type=performance', '--size=20', + '--os-type=linux', '--location=dal05']) + + self.assertEqual(2, result.exit_code) + + def test_volume_order_performance_iops_out_of_range(self): + result = self.run_command(['block', 'volume-order', + '--storage-type=performance', '--size=20', + '--iops=80000', '--os-type=linux', + '--location=dal05']) + + self.assertEqual(2, result.exit_code) + + def test_volume_order_performance_iops_not_multiple_of_100(self): + result = self.run_command(['block', 'volume-order', + '--storage-type=performance', '--size=20', + '--iops=122', '--os-type=linux', + '--location=dal05']) + + self.assertEqual(2, result.exit_code) + + def test_volume_order_performance_snapshot_error(self): + result = self.run_command(['block', 'volume-order', + '--storage-type=performance', '--size=20', + '--iops=100', '--os-type=linux', + '--location=dal05', '--snapshot-size=10']) + + self.assertEqual(2, result.exit_code) + + @mock.patch('SoftLayer.BlockStorageManager.order_block_volume') + def test_volume_order_performance(self, order_mock): + order_mock.return_value = { + 'placedOrder': { + 'id': 478, + 'items': [ + {'description': 'Performance Storage'}, + {'description': 'Block Storage'}, + {'description': '0.25 IOPS per GB'}, + {'description': '20 GB Storage Space'}] + } + } + + result = self.run_command(['block', 'volume-order', + '--storage-type=performance', '--size=20', + '--iops=100', '--os-type=linux', + '--location=dal05']) + + self.assert_no_fail(result) + self.assertEqual(result.output, + 'Order #478 placed successfully!\n' + ' > Performance Storage\n > Block Storage\n' + ' > 0.25 IOPS per GB\n > 20 GB Storage Space\n') + + def test_volume_order_endurance_tier_not_given(self): + result = self.run_command(['block', 'volume-order', + '--storage-type=endurance', '--size=20', + '--os-type=linux', '--location=dal05']) + + self.assertEqual(2, result.exit_code) + @mock.patch('SoftLayer.BlockStorageManager.order_block_volume') - def test_volume_order(self, order_mock): + def test_volume_order_endurance(self, order_mock): order_mock.return_value = { 'placedOrder': { 'id': 478, - 'items': [{'description': 'Endurance Storage'}, - {'description': 'Block Storage'}, - {'description': '0.25 IOPS per GB'}, - {'description': '20 GB Storage Space'}, - ] + 'items': [ + {'description': 'Endurance Storage'}, + {'description': 'Block Storage'}, + {'description': '0.25 IOPS per GB'}, + {'description': '20 GB Storage Space'}, + {'description': '10 GB Storage Space (Snapshot Space)'}] } } result = self.run_command(['block', 'volume-order', '--storage-type=endurance', '--size=20', '--tier=0.25', '--os-type=linux', - '--location=dal05']) + '--location=dal05', '--snapshot-size=10']) self.assert_no_fail(result) self.assertEqual(result.output, 'Order #478 placed successfully!\n' ' > Endurance Storage\n > Block Storage\n' - ' > 0.25 IOPS per GB\n > 20 GB Storage Space\n') + ' > 0.25 IOPS per GB\n > 20 GB Storage Space\n' + ' > 10 GB Storage Space (Snapshot Space)\n') + + @mock.patch('SoftLayer.BlockStorageManager.order_block_volume') + def test_volume_order_order_not_placed(self, order_mock): + order_mock.return_value = {} + + result = self.run_command(['block', 'volume-order', + '--storage-type=endurance', '--size=20', + '--tier=0.25', '--os-type=linux', + '--location=dal05']) + + self.assert_no_fail(result) + self.assertEqual(result.output, + 'Order could not be placed! Please verify ' + 'your options and try again.\n') def test_enable_snapshots(self): result = self.run_command(['block', 'snapshot-enable', '12345678', diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index 6f509d57c..8ae667ea6 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -131,29 +131,107 @@ def test_volume_detail(self): '# of Active Transactions': '0' }, json.loads(result.output)) + def test_volume_order_performance_iops_not_given(self): + result = self.run_command(['file', 'volume-order', + '--storage-type=performance', '--size=20', + '--os-type=linux', '--location=dal05']) + + self.assertEqual(2, result.exit_code) + + def test_volume_order_performance_iops_out_of_range(self): + result = self.run_command(['file', 'volume-order', + '--storage-type=performance', '--size=20', + '--iops=80000', '--os-type=linux', + '--location=dal05']) + + self.assertEqual(2, result.exit_code) + + def test_volume_order_performance_iops_not_multiple_of_100(self): + result = self.run_command(['file', 'volume-order', + '--storage-type=performance', '--size=20', + '--iops=122', '--os-type=linux', + '--location=dal05']) + + self.assertEqual(2, result.exit_code) + + def test_volume_order_performance_snapshot_error(self): + result = self.run_command(['file', 'volume-order', + '--storage-type=performance', '--size=20', + '--iops=100', '--os-type=linux', + '--location=dal05', '--snapshot-size=10']) + + self.assertEqual(2, result.exit_code) + + @mock.patch('SoftLayer.FileStorageManager.order_file_volume') + def test_volume_order_performance(self, order_mock): + order_mock.return_value = { + 'placedOrder': { + 'id': 478, + 'items': [ + {'description': 'Performance Storage'}, + {'description': 'File Storage'}, + {'description': '0.25 IOPS per GB'}, + {'description': '20 GB Storage Space'}] + } + } + + result = self.run_command(['file', 'volume-order', + '--storage-type=performance', '--size=20', + '--iops=100', '--os-type=linux', + '--location=dal05']) + + self.assert_no_fail(result) + self.assertEqual(result.output, + 'Order #478 placed successfully!\n' + ' > Performance Storage\n > File Storage\n' + ' > 0.25 IOPS per GB\n > 20 GB Storage Space\n') + + def test_volume_order_endurance_tier_not_given(self): + result = self.run_command(['file', 'volume-order', + '--storage-type=endurance', '--size=20', + '--os-type=linux', '--location=dal05']) + + self.assertEqual(2, result.exit_code) + @mock.patch('SoftLayer.FileStorageManager.order_file_volume') - def test_volume_order(self, order_mock): + def test_volume_order_endurance(self, order_mock): order_mock.return_value = { 'placedOrder': { 'id': 478, - 'items': [{'description': 'Endurance Storage'}, - {'description': 'File Storage'}, - {'description': '0.25 IOPS per GB'}, - {'description': '20 GB Storage Space'}, - ] + 'items': [ + {'description': 'Endurance Storage'}, + {'description': 'File Storage'}, + {'description': '0.25 IOPS per GB'}, + {'description': '20 GB Storage Space'}, + {'description': '10 GB Storage Space (Snapshot Space)'}] } } result = self.run_command(['file', 'volume-order', '--storage-type=endurance', '--size=20', '--tier=0.25', '--os-type=linux', - '--location=dal05']) + '--location=dal05', '--snapshot-size=10']) self.assert_no_fail(result) self.assertEqual(result.output, 'Order #478 placed successfully!\n' ' > Endurance Storage\n > File Storage\n' - ' > 0.25 IOPS per GB\n > 20 GB Storage Space\n') + ' > 0.25 IOPS per GB\n > 20 GB Storage Space\n' + ' > 10 GB Storage Space (Snapshot Space)\n') + + @mock.patch('SoftLayer.FileStorageManager.order_file_volume') + def test_volume_order_order_not_placed(self, order_mock): + order_mock.return_value = {} + + result = self.run_command(['file', 'volume-order', + '--storage-type=endurance', '--size=20', + '--tier=0.25', '--os-type=linux', + '--location=dal05']) + + self.assert_no_fail(result) + self.assertEqual(result.output, + 'Order could not be placed! Please verify ' + 'your options and try again.\n') def test_enable_snapshots(self): result = self.run_command(['file', 'snapshot-enable', '12345678', diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index 453374781..86b12e4e6 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -155,7 +155,7 @@ def test_order_block_volume_performance(self): "dal05", 100, "LINUX", - iops=100 + iops=100, ) self.assertEqual( @@ -231,7 +231,95 @@ def test_order_block_volume_endurance(self): "dal05", 100, "LINUX", - tier_level=0.25 + tier_level=0.25, + ) + + self.assertEqual( + result, + { + 'orderDate': '2013-08-01 15:23:45', + 'orderId': 1234, + 'prices': [{ + 'hourlyRecurringFee': '2', + 'id': 1, + 'item': {'description': 'this is a thing', 'id': 1}, + 'laborFee': '2', + 'oneTimeFee': '2', + 'oneTimeFeeTax': '.1', + 'quantity': 1, + 'recurringFee': '2', + 'recurringFeeTax': '.1', + 'setupFee': '1'}], + }, + ) + + def test_order_block_volume_endurance_with_snapshot(self): + mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') + mock.return_value = [{ + 'id': 1, + 'name': 'Endurance', + 'items': [{ + 'capacity': '1', + 'prices': [{ + 'id': 1, + 'locationGroupId': '', + 'categories': [{ + 'categoryCode': 'storage_block', + }], + }], + }, { + 'capacity': '1', + 'prices': [{ + 'id': 2, + 'locationGroupId': '', + 'categories': [{ + 'categoryCode': 'storage_service_enterprise', + }], + }], + }, { + 'capacity': '100', + 'prices': [{ + 'id': 3, + 'locationGroupId': '', + 'categories': [{ + 'categoryCode': 'performance_storage_space', + }], + 'capacityRestrictionMinimum': '100', + 'capacityRestrictionMaximum': '100', + }], + }, { + 'capacity': '100', + 'attributes': [{ + 'value': '100', + }], + 'prices': [{ + 'id': 4, + 'locationGroupId': '', + 'categories': [{ + 'categoryCode': 'storage_tier_level', + }], + }], + }, { + 'capacity': '10', + 'prices': [{ + 'id': 5, + 'locationGroupId': '', + 'categories': [{ + 'categoryCode': 'storage_snapshot_space', + }], + 'capacityRestrictionMinimum': '100', + 'capacityRestrictionMaximum': '100', + }], + }], + }] + + result = self.block.order_block_volume( + "storage_service_enterprise", + "dal05", + 100, + "LINUX", + tier_level=0.25, + snapshot_size=10, ) self.assertEqual( diff --git a/tests/managers/file_tests.py b/tests/managers/file_tests.py index 7187a7d87..42c6ff101 100644 --- a/tests/managers/file_tests.py +++ b/tests/managers/file_tests.py @@ -222,7 +222,7 @@ def test_order_file_volume_performance(self): "dal05", 100, "LINUX", - iops=100 + iops=100, ) self.assertEqual( @@ -306,7 +306,95 @@ def test_order_file_volume_endurance(self): "dal05", 100, "LINUX", - tier_level=0.25 + tier_level=0.25, + ) + + self.assertEqual( + result, + { + 'orderDate': '2013-08-01 15:23:45', + 'orderId': 1234, + 'prices': [{ + 'hourlyRecurringFee': '2', + 'id': 1, + 'item': {'description': 'this is a thing', 'id': 1}, + 'laborFee': '2', + 'oneTimeFee': '2', + 'oneTimeFeeTax': '.1', + 'quantity': 1, + 'recurringFee': '2', + 'recurringFeeTax': '.1', + 'setupFee': '1'}], + }, + ) + + def test_order_file_volume_endurance_with_snapshot(self): + mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') + mock.return_value = [{ + 'id': 1, + 'name': 'Endurance', + 'items': [{ + 'capacity': '1', + 'prices': [{ + 'id': 1, + 'locationGroupId': '', + 'categories': [{ + 'categoryCode': 'storage_file', + }], + }], + }, { + 'capacity': '1', + 'prices': [{ + 'id': 2, + 'locationGroupId': '', + 'categories': [{ + 'categoryCode': 'storage_service_enterprise', + }], + }], + }, { + 'capacity': '100', + 'prices': [{ + 'id': 3, + 'locationGroupId': '', + 'categories': [{ + 'categoryCode': 'performance_storage_space', + }], + 'capacityRestrictionMinimum': '100', + 'capacityRestrictionMaximum': '100', + }], + }, { + 'capacity': '100', + 'attributes': [{ + 'value': '100', + }], + 'prices': [{ + 'id': 4, + 'locationGroupId': '', + 'categories': [{ + 'categoryCode': 'storage_tier_level', + }], + }], + }, { + 'capacity': '10', + 'prices': [{ + 'id': 5, + 'locationGroupId': '', + 'categories': [{ + 'categoryCode': 'storage_snapshot_space', + }], + 'capacityRestrictionMinimum': '100', + 'capacityRestrictionMaximum': '100', + }], + }], + }] + + result = self.file.order_file_volume( + "storage_service_enterprise", + "dal05", + 100, + "LINUX", + tier_level=0.25, + snapshot_size=10, ) self.assertEqual(