From d7a9b89d39e08f46163136fa99ba0f1b151e8273 Mon Sep 17 00:00:00 2001 From: Sunil Rajput Date: Mon, 28 Aug 2017 10:18:45 -0500 Subject: [PATCH 1/2] Add hourly billing logic for File and Block ordering commands --- SoftLayer/CLI/block/duplicate.py | 15 ++- SoftLayer/CLI/block/order.py | 22 ++++- SoftLayer/CLI/file/duplicate.py | 15 ++- SoftLayer/CLI/file/order.py | 22 ++++- .../fixtures/SoftLayer_Network_Storage.py | 1 + SoftLayer/managers/block.py | 39 +++++--- SoftLayer/managers/file.py | 39 +++++--- SoftLayer/managers/storage_utils.py | 26 ++++- tests/CLI/modules/block_tests.py | 62 ++++++++++++ tests/CLI/modules/file_tests.py | 57 +++++++++++ tests/managers/block_tests.py | 30 ++++-- tests/managers/file_tests.py | 30 ++++-- tests/managers/storage_utils_tests.py | 98 ++++++++++++++----- 13 files changed, 372 insertions(+), 84 deletions(-) diff --git a/SoftLayer/CLI/block/duplicate.py b/SoftLayer/CLI/block/duplicate.py index 98ef1b793..0ecf59110 100644 --- a/SoftLayer/CLI/block/duplicate.py +++ b/SoftLayer/CLI/block/duplicate.py @@ -49,15 +49,23 @@ 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' + 'space size of the origin block volume will be used.***\n' 'Input "0" for this parameter to order a duplicate volume ' 'with no snapshot space.') +@click.option('--billing', + type=click.Choice(['hourly', 'monthly']), + default='monthly', + help="Optional parameter for Billing rate (default to monthly)") @environment.pass_env def cli(env, origin_volume_id, origin_snapshot_id, duplicate_size, - duplicate_iops, duplicate_tier, duplicate_snapshot_size): + duplicate_iops, duplicate_tier, duplicate_snapshot_size, billing): """Order a duplicate block storage volume.""" block_manager = SoftLayer.BlockStorageManager(env.client) + hourly_billing_flag = False + if billing.lower() == "hourly": + hourly_billing_flag = True + if duplicate_tier is not None: duplicate_tier = float(duplicate_tier) @@ -68,7 +76,8 @@ def cli(env, origin_volume_id, origin_snapshot_id, duplicate_size, duplicate_size=duplicate_size, duplicate_iops=duplicate_iops, duplicate_tier_level=duplicate_tier, - duplicate_snapshot_size=duplicate_snapshot_size + duplicate_snapshot_size=duplicate_snapshot_size, + hourly_billing_flag=hourly_billing_flag ) except ValueError as ex: raise exceptions.ArgumentError(str(ex)) diff --git a/SoftLayer/CLI/block/order.py b/SoftLayer/CLI/block/order.py index 865a38e23..abd7dd91e 100644 --- a/SoftLayer/CLI/block/order.py +++ b/SoftLayer/CLI/block/order.py @@ -56,13 +56,27 @@ 'storage_as_a_service', 'enterprise', 'performance'])) +@click.option('--billing', + type=click.Choice(['hourly', 'monthly']), + default='monthly', + help="Optional parameter for Billing rate (default to monthly)") @environment.pass_env def cli(env, storage_type, size, iops, tier, os_type, - location, snapshot_size, service_offering): + location, snapshot_size, service_offering, billing): """Order a block storage volume.""" block_manager = SoftLayer.BlockStorageManager(env.client) storage_type = storage_type.lower() + hourly_billing_flag = False + if billing.lower() == "hourly": + hourly_billing_flag = True + + if hourly_billing_flag and service_offering != 'storage_as_a_service': + raise exceptions.CLIAbort( + 'Hourly billing is only available for the storage_as_a_service ' + 'service offering' + ) + if storage_type == 'performance': if iops is None: raise exceptions.CLIAbort( @@ -87,7 +101,8 @@ def cli(env, storage_type, size, iops, tier, os_type, iops=iops, os_type=os_type, snapshot_size=snapshot_size, - service_offering=service_offering + service_offering=service_offering, + hourly_billing_flag=hourly_billing_flag ) except ValueError as ex: raise exceptions.ArgumentError(str(ex)) @@ -107,7 +122,8 @@ def cli(env, storage_type, size, iops, tier, os_type, tier_level=float(tier), os_type=os_type, snapshot_size=snapshot_size, - service_offering=service_offering + service_offering=service_offering, + hourly_billing_flag=hourly_billing_flag ) except ValueError as ex: raise exceptions.ArgumentError(str(ex)) diff --git a/SoftLayer/CLI/file/duplicate.py b/SoftLayer/CLI/file/duplicate.py index e02169771..a3b4c801a 100644 --- a/SoftLayer/CLI/file/duplicate.py +++ b/SoftLayer/CLI/file/duplicate.py @@ -45,15 +45,23 @@ 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' + 'space size of the origin file volume will be used.***\n' 'Input "0" for this parameter to order a duplicate volume ' 'with no snapshot space.') +@click.option('--billing', + type=click.Choice(['hourly', 'monthly']), + default='monthly', + help="Optional parameter for Billing rate (default to monthly)") @environment.pass_env def cli(env, origin_volume_id, origin_snapshot_id, duplicate_size, - duplicate_iops, duplicate_tier, duplicate_snapshot_size): + duplicate_iops, duplicate_tier, duplicate_snapshot_size, billing): """Order a duplicate file storage volume.""" file_manager = SoftLayer.FileStorageManager(env.client) + hourly_billing_flag = False + if billing.lower() == "hourly": + hourly_billing_flag = True + if duplicate_tier is not None: duplicate_tier = float(duplicate_tier) @@ -64,7 +72,8 @@ def cli(env, origin_volume_id, origin_snapshot_id, duplicate_size, duplicate_size=duplicate_size, duplicate_iops=duplicate_iops, duplicate_tier_level=duplicate_tier, - duplicate_snapshot_size=duplicate_snapshot_size + duplicate_snapshot_size=duplicate_snapshot_size, + hourly_billing_flag=hourly_billing_flag ) except ValueError as ex: raise exceptions.ArgumentError(str(ex)) diff --git a/SoftLayer/CLI/file/order.py b/SoftLayer/CLI/file/order.py index c9ba4abcd..ae9013392 100644 --- a/SoftLayer/CLI/file/order.py +++ b/SoftLayer/CLI/file/order.py @@ -44,13 +44,27 @@ 'storage_as_a_service', 'enterprise', 'performance'])) +@click.option('--billing', + type=click.Choice(['hourly', 'monthly']), + default='monthly', + help="Optional parameter for Billing rate (default to monthly)") @environment.pass_env def cli(env, storage_type, size, iops, tier, - location, snapshot_size, service_offering): + location, snapshot_size, service_offering, billing): """Order a file storage volume.""" file_manager = SoftLayer.FileStorageManager(env.client) storage_type = storage_type.lower() + hourly_billing_flag = False + if billing.lower() == "hourly": + hourly_billing_flag = True + + if hourly_billing_flag and service_offering != 'storage_as_a_service': + raise exceptions.CLIAbort( + 'Hourly billing is only available for the storage_as_a_service ' + 'service offering' + ) + if storage_type == 'performance': if iops is None: raise exceptions.CLIAbort( @@ -74,7 +88,8 @@ def cli(env, storage_type, size, iops, tier, size=size, iops=iops, snapshot_size=snapshot_size, - service_offering=service_offering + service_offering=service_offering, + hourly_billing_flag=hourly_billing_flag ) except ValueError as ex: raise exceptions.ArgumentError(str(ex)) @@ -93,7 +108,8 @@ def cli(env, storage_type, size, iops, tier, size=size, tier_level=float(tier), snapshot_size=snapshot_size, - service_offering=service_offering + service_offering=service_offering, + hourly_billing_flag=hourly_billing_flag ) except ValueError as ex: raise exceptions.ArgumentError(str(ex)) diff --git a/SoftLayer/fixtures/SoftLayer_Network_Storage.py b/SoftLayer/fixtures/SoftLayer_Network_Storage.py index f8dfae898..b4dd0b751 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Storage.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Storage.py @@ -10,6 +10,7 @@ }], 'cancellationDate': '', 'categoryCode': 'storage_as_a_service', + 'hourlyFlag': None, 'id': 454, 'location': {'id': 449500} }, diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index eb6d49264..766312012 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -234,9 +234,10 @@ def order_replicant_volume(self, volume_id, snapshot_schedule, :return: Returns a SoftLayer_Container_Product_Order_Receipt """ - block_mask = 'billingItem[activeChildren],storageTierLevel,osType,'\ - 'staasVersion,hasEncryptionAtRest,snapshotCapacityGb,'\ - 'schedules,hourlySchedule,dailySchedule,weeklySchedule,'\ + block_mask = 'billingItem[activeChildren,hourlyFlag],'\ + 'storageTierLevel,osType,staasVersion,'\ + 'hasEncryptionAtRest,snapshotCapacityGb,schedules,'\ + 'hourlySchedule,dailySchedule,weeklySchedule,'\ 'storageType[keyName],provisionedIops' block_volume = self.get_block_volume_details(volume_id, mask=block_mask) @@ -261,7 +262,8 @@ def order_replicant_volume(self, volume_id, snapshot_schedule, 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): + duplicate_snapshot_size=None, + hourly_billing_flag=False): """Places an order for a duplicate block volume. :param origin_volume_id: The ID of the origin volume to be duplicated @@ -270,10 +272,12 @@ def order_duplicate_volume(self, origin_volume_id, origin_snapshot_id=None, :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 + :param hourly_billing_flag: Billing type, monthly (False) + or hourly (True), default to monthly. :return: Returns a SoftLayer_Container_Product_Order_Receipt """ - block_mask = 'id,billingItem[location],snapshotCapacityGb,'\ + block_mask = 'id,billingItem[location,hourlyFlag],snapshotCapacityGb,'\ 'storageType[keyName],capacityGb,originalVolumeSize,'\ 'provisionedIops,storageTierLevel,osType[keyName],'\ 'staasVersion,hasEncryptionAtRest' @@ -288,7 +292,8 @@ def order_duplicate_volume(self, origin_volume_id, origin_snapshot_id=None, order = storage_utils.prepare_duplicate_order_object( self, origin_volume, duplicate_iops, duplicate_tier_level, - duplicate_size, duplicate_snapshot_size, 'block' + duplicate_size, duplicate_snapshot_size, 'block', + hourly_billing_flag ) order['osFormatType'] = {'keyName': os_type} @@ -308,7 +313,8 @@ def delete_snapshot(self, snapshot_id): def order_block_volume(self, storage_type, location, size, os_type, iops=None, tier_level=None, snapshot_size=None, - service_offering='storage_as_a_service'): + service_offering='storage_as_a_service', + hourly_billing_flag=False): """Places an order for a block volume. :param storage_type: 'performance' or 'endurance' @@ -321,10 +327,12 @@ def order_block_volume(self, storage_type, location, size, os_type, if snapshot space should also be ordered (None if not ordered) :param service_offering: Requested offering package to use in the order ('storage_as_a_service', 'enterprise', or 'performance') + :param hourly_billing_flag: Billing type, monthly (False) + or hourly (True), default to monthly. """ order = storage_utils.prepare_volume_order_object( self, storage_type, location, size, iops, tier_level, - snapshot_size, service_offering, 'block' + snapshot_size, service_offering, 'block', hourly_billing_flag ) order['osFormatType'] = {'keyName': os_type} @@ -352,8 +360,9 @@ def order_snapshot_space(self, volume_id, capacity, tier, :param boolean upgrade: Flag to indicate if this order is an upgrade :return: Returns a SoftLayer_Container_Product_Order_Receipt """ - block_mask = 'id,billingItem[location],storageType[keyName],'\ - 'storageTierLevel,provisionedIops,staasVersion,hasEncryptionAtRest' + block_mask = 'id,billingItem[location,hourlyFlag],'\ + 'storageType[keyName],storageTierLevel,provisionedIops,'\ + 'staasVersion,hasEncryptionAtRest' block_volume = self.get_block_volume_details(volume_id, mask=block_mask, **kwargs) @@ -376,7 +385,7 @@ def cancel_snapshot_space(self, volume_id, block_volume = self.get_block_volume_details( volume_id, - mask='mask[id,billingItem[activeChildren]]') + mask='mask[id,billingItem[activeChildren,hourlyFlag]]') if 'activeChildren' not in block_volume['billingItem']: raise exceptions.SoftLayerError( @@ -394,6 +403,9 @@ def cancel_snapshot_space(self, volume_id, raise exceptions.SoftLayerError( 'No snapshot space found to cancel') + if utils.lookup(block_volume, 'billingItem', 'hourlyFlag'): + immediate = True + return self.client['Billing_Item'].cancelItem( immediate, True, @@ -456,9 +468,12 @@ def cancel_block_volume(self, volume_id, """ block_volume = self.get_block_volume_details( volume_id, - mask='mask[id,billingItem[id]]') + mask='mask[id,billingItem[id,hourlyFlag]]') billing_item_id = block_volume['billingItem']['id'] + if utils.lookup(block_volume, 'billingItem', 'hourlyFlag'): + immediate = True + return self.client['Billing_Item'].cancelItem( immediate, True, diff --git a/SoftLayer/managers/file.py b/SoftLayer/managers/file.py index 7f12a7bf4..2ba39cca4 100644 --- a/SoftLayer/managers/file.py +++ b/SoftLayer/managers/file.py @@ -213,9 +213,10 @@ def order_replicant_volume(self, volume_id, snapshot_schedule, :return: Returns a SoftLayer_Container_Product_Order_Receipt """ - file_mask = 'billingItem[activeChildren],storageTierLevel,'\ - 'staasVersion,hasEncryptionAtRest,snapshotCapacityGb,'\ - 'schedules,hourlySchedule,dailySchedule,weeklySchedule,'\ + file_mask = 'billingItem[activeChildren,hourlyFlag],'\ + 'storageTierLevel,staasVersion,'\ + 'hasEncryptionAtRest,snapshotCapacityGb,schedules,'\ + 'hourlySchedule,dailySchedule,weeklySchedule,'\ 'storageType[keyName],provisionedIops' file_volume = self.get_file_volume_details(volume_id, mask=file_mask) @@ -249,7 +250,8 @@ def get_replication_locations(self, 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): + duplicate_snapshot_size=None, + hourly_billing_flag=False): """Places an order for a duplicate file volume. :param origin_volume_id: The ID of the origin volume to be duplicated @@ -258,10 +260,12 @@ def order_duplicate_volume(self, origin_volume_id, origin_snapshot_id=None, :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 + :param hourly_billing_flag: Billing type, monthly (False) + or hourly (True), default to monthly. :return: Returns a SoftLayer_Container_Product_Order_Receipt """ - file_mask = 'id,billingItem[location],snapshotCapacityGb,'\ + file_mask = 'id,billingItem[location,hourlyFlag],snapshotCapacityGb,'\ 'storageType[keyName],capacityGb,originalVolumeSize,'\ 'provisionedIops,storageTierLevel,'\ 'staasVersion,hasEncryptionAtRest' @@ -270,7 +274,8 @@ def order_duplicate_volume(self, origin_volume_id, origin_snapshot_id=None, order = storage_utils.prepare_duplicate_order_object( self, origin_volume, duplicate_iops, duplicate_tier_level, - duplicate_size, duplicate_snapshot_size, 'file' + duplicate_size, duplicate_snapshot_size, 'file', + hourly_billing_flag ) if origin_snapshot_id is not None: @@ -288,7 +293,8 @@ def delete_snapshot(self, snapshot_id): def order_file_volume(self, storage_type, location, size, iops=None, tier_level=None, snapshot_size=None, - service_offering='storage_as_a_service'): + service_offering='storage_as_a_service', + hourly_billing_flag=False): """Places an order for a file volume. :param storage_type: 'performance' or 'endurance' @@ -300,10 +306,12 @@ def order_file_volume(self, storage_type, location, size, if snapshot space should also be ordered (None if not ordered) :param service_offering: Requested offering package to use in the order ('storage_as_a_service', 'enterprise', or 'performance') + :param hourly_billing_flag: Billing type, monthly (False) + or hourly (True), default to monthly. """ order = storage_utils.prepare_volume_order_object( self, storage_type, location, size, iops, tier_level, - snapshot_size, service_offering, 'file' + snapshot_size, service_offering, 'file', hourly_billing_flag ) return self.client.call('Product_Order', 'placeOrder', order) @@ -367,8 +375,9 @@ def order_snapshot_space(self, volume_id, capacity, tier, :param boolean upgrade: Flag to indicate if this order is an upgrade :return: Returns a SoftLayer_Container_Product_Order_Receipt """ - file_mask = 'id,billingItem[location],storageType[keyName],'\ - 'storageTierLevel,provisionedIops,staasVersion,hasEncryptionAtRest' + file_mask = 'id,billingItem[location,hourlyFlag],'\ + 'storageType[keyName],storageTierLevel,provisionedIops,'\ + 'staasVersion,hasEncryptionAtRest' file_volume = self.get_file_volume_details(volume_id, mask=file_mask, **kwargs) @@ -391,7 +400,7 @@ def cancel_snapshot_space(self, volume_id, file_volume = self.get_file_volume_details( volume_id, - mask='mask[id,billingItem[activeChildren]]') + mask='mask[id,billingItem[activeChildren,hourlyFlag]]') if 'activeChildren' not in file_volume['billingItem']: raise exceptions.SoftLayerError( @@ -409,6 +418,9 @@ def cancel_snapshot_space(self, volume_id, raise exceptions.SoftLayerError( 'No snapshot space found to cancel') + if utils.lookup(file_volume, 'billingItem', 'hourlyFlag'): + immediate = True + return self.client['Billing_Item'].cancelItem( immediate, True, @@ -438,9 +450,12 @@ def cancel_file_volume(self, volume_id, """ file_volume = self.get_file_volume_details( volume_id, - mask='mask[id,billingItem[id]]') + mask='mask[id,billingItem[id,hourlyFlag]]') billing_item_id = file_volume['billingItem']['id'] + if utils.lookup(file_volume, 'billingItem', 'hourlyFlag'): + immediate = True + return self.client['Billing_Item'].cancelItem( immediate, True, diff --git a/SoftLayer/managers/storage_utils.py b/SoftLayer/managers/storage_utils.py index 14ae573a7..ba6dfc8c0 100644 --- a/SoftLayer/managers/storage_utils.py +++ b/SoftLayer/managers/storage_utils.py @@ -581,6 +581,11 @@ def prepare_snapshot_order_object(manager, volume, capacity, tier, upgrade): complex_type = 'SoftLayer_Container_Product_Order_'\ 'Network_Storage_Enterprise_SnapshotSpace' + # Determine if hourly billing should be used + hourly_billing_flag = utils.lookup(volume, 'billingItem', 'hourlyFlag') + if hourly_billing_flag is None: + hourly_billing_flag = False + # Build and return the order object snapshot_space_order = { 'complexType': complex_type, @@ -588,15 +593,16 @@ def prepare_snapshot_order_object(manager, volume, capacity, tier, upgrade): 'prices': prices, 'quantity': 1, 'location': volume['billingItem']['location']['id'], - 'volumeId': volume['id'] + 'volumeId': volume['id'], + 'useHourlyPricing': hourly_billing_flag } return snapshot_space_order def prepare_volume_order_object(manager, storage_type, location, size, - iops, tier, snapshot_size, - service_offering, volume_type): + iops, tier, snapshot_size, service_offering, + volume_type, hourly_billing_flag=False): """Prepare the order object which is submitted to the placeOrder() method :param manager: The File or Block manager calling this function @@ -608,6 +614,7 @@ def prepare_volume_order_object(manager, storage_type, location, size, :param snapshot_size: The size of snapshot space for the volume (optional) :param service_offering: Requested offering package to use for the order :param volume_type: The type of the volume to order ('file' or 'block') + :param hourly_billing_flag: Billing type, monthly (False) or hourly (True) :return: Returns the order object for the Product_Order service's placeOrder() method """ @@ -689,6 +696,7 @@ def prepare_volume_order_object(manager, storage_type, location, size, 'prices': prices, 'quantity': 1, 'location': location_id, + 'useHourlyPricing': hourly_billing_flag } if order_type_is_saas: @@ -847,6 +855,11 @@ def prepare_replicant_order_object(manager, snapshot_schedule, location, find_ent_space_price(package, 'replication', volume_size, tier) ] + # Determine if hourly billing should be used + hourly_billing_flag = utils.lookup(volume, 'billingItem', 'hourlyFlag') + if hourly_billing_flag is None: + hourly_billing_flag = False + # Build and return the order object replicant_order = { 'complexType': complex_type, @@ -856,6 +869,7 @@ def prepare_replicant_order_object(manager, snapshot_schedule, location, 'location': location_id, 'originVolumeId': volume['id'], 'originVolumeScheduleId': snapshot_schedule_id, + 'useHourlyPricing': hourly_billing_flag } if order_type_is_saas: @@ -867,8 +881,8 @@ def prepare_replicant_order_object(manager, snapshot_schedule, location, def prepare_duplicate_order_object(manager, origin_volume, iops, tier, - duplicate_size, - duplicate_snapshot_size, volume_type): + duplicate_size, duplicate_snapshot_size, + volume_type, hourly_billing_flag=False): """Prepare the duplicate order to submit to SoftLayer_Product::placeOrder() :param manager: The File or Block manager calling this function @@ -878,6 +892,7 @@ def prepare_duplicate_order_object(manager, origin_volume, iops, tier, :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') + :param hourly_billing_flag: Billing type, monthly (False) or hourly (True) :return: Returns the order object to be passed to the placeOrder() method of the Product_Order service """ @@ -979,6 +994,7 @@ def prepare_duplicate_order_object(manager, origin_volume, iops, tier, 'quantity': 1, 'location': location_id, 'duplicateOriginVolumeId': origin_volume['id'], + 'useHourlyPricing': hourly_billing_flag } if volume_is_performance: diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index b535a50a7..7f07b799a 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -542,3 +542,65 @@ def test_duplicate_order(self, order_mock): def test_set_password(self): result = self.run_command(['block', 'access-password', '1234', '--password=AAAAA']) self.assert_no_fail(result) + + def test_block_volume_order_performance_hourly_billing_not_available(self): + result = self.run_command(['block', 'volume-order', + '--storage-type=performance', '--size=20', + '--os-type=LINUX' + '--location=dal10', + '--service-offering=performance', + '--billing=hourly', + '--iops=200']) + + self.assertEqual(2, result.exit_code) + + @mock.patch('SoftLayer.BlockStorageManager.order_block_volume') + def test_volume_order_performance_hourly(self, order_mock): + order_mock.return_value = { + 'placedOrder': { + 'id': 478, + 'items': [ + {'description': 'Storage as a Service'}, + {'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=performance', + '--size=20', + '--iops=100', + '--os-type=LINUX', + '--location=dal10', + '--snapshot-size=10', + '--billing=hourly']) + + self.assert_no_fail(result) + self.assertEqual(result.output, + 'Order #478 placed successfully!\n' + ' > Storage as a Service\n > Block Storage\n' + ' > 0.25 IOPS per GB\n > 20 GB Storage Space\n' + ' > 10 GB Storage Space (Snapshot Space)\n') + + @mock.patch('SoftLayer.BlockStorageManager.order_duplicate_volume') + def test_duplicate_order_hourly(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', + '--billing=hourly']) + + 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 df38ae531..9bffb5e34 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -514,3 +514,60 @@ def test_duplicate_order(self, order_mock): self.assertEqual(result.output, 'Order #24602 placed successfully!\n' ' > Storage as a Service\n') + + def test_volume_order_performance_hourly_billing_not_available(self): + result = self.run_command(['file', 'volume-order', + '--storage-type=performance', '--size=20', + '--location=dal10', '--iops=200', + '--service-offering=performance', + '--billing=hourly']) + + self.assertEqual(2, result.exit_code) + + @mock.patch('SoftLayer.FileStorageManager.order_file_volume') + def test_volume_order_performance_hourly(self, order_mock): + order_mock.return_value = { + 'placedOrder': { + 'id': 478, + 'items': [ + {'description': 'Storage as a Service'}, + {'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=performance', '--size=20', + '--iops=100', '--location=dal05', + '--snapshot-size=10', + '--billing=hourly']) + + self.assert_no_fail(result) + self.assertEqual(result.output, + 'Order #478 placed successfully!\n' + ' > Storage as a Service\n > File Storage\n' + ' > 0.25 IOPS per GB\n > 20 GB Storage Space\n' + ' > 10 GB Storage Space (Snapshot Space)\n') + + @mock.patch('SoftLayer.FileStorageManager.order_duplicate_volume') + def test_duplicate_order_hourly(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', + '--billing=hourly']) + + 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 1c9da6d35..e3d237ecb 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -313,7 +313,8 @@ def test_order_block_volume_performance(self): 'quantity': 1, 'location': 449494, 'iops': 2000, - 'osFormatType': {'keyName': 'LINUX'} + 'osFormatType': {'keyName': 'LINUX'}, + 'useHourlyPricing': False, },) ) @@ -357,7 +358,8 @@ def test_order_block_volume_endurance(self): 'volumeSize': 1000, 'quantity': 1, 'location': 449494, - 'osFormatType': {'keyName': 'LINUX'} + 'osFormatType': {'keyName': 'LINUX'}, + 'useHourlyPricing': False, },) ) @@ -461,7 +463,8 @@ def test_order_block_snapshot_space_upgrade(self): ], 'quantity': 1, 'location': 449500, - 'volumeId': 102 + 'volumeId': 102, + 'useHourlyPricing': False, },) ) @@ -491,7 +494,8 @@ def test_order_block_snapshot_space(self): ], 'quantity': 1, 'location': 449500, - 'volumeId': 102 + 'volumeId': 102, + 'useHourlyPricing': False, },) ) @@ -559,7 +563,8 @@ def test_order_block_replicant_performance_os_type_given(self): 'iops': 1000, 'originVolumeId': 102, 'originVolumeScheduleId': 978, - 'osFormatType': {'keyName': 'XEN'} + 'osFormatType': {'keyName': 'XEN'}, + 'useHourlyPricing': False, },) ) @@ -600,7 +605,8 @@ def test_order_block_replicant_endurance(self): 'location': 449494, 'originVolumeId': 102, 'originVolumeScheduleId': 978, - 'osFormatType': {'keyName': 'LINUX'} + 'osFormatType': {'keyName': 'LINUX'}, + 'useHourlyPricing': False, },) ) @@ -659,7 +665,8 @@ def test_order_block_duplicate_performance_no_duplicate_snapshot(self): 'location': 449500, 'duplicateOriginVolumeId': 102, 'osFormatType': {'keyName': 'LINUX'}, - 'iops': 1000 + 'iops': 1000, + 'useHourlyPricing': False, },)) mock_volume['storageType']['keyName'] = prev_storage_type_keyname @@ -705,7 +712,8 @@ def test_order_block_duplicate_performance(self): 'duplicateOriginVolumeId': 102, 'osFormatType': {'keyName': 'LINUX'}, 'duplicateOriginSnapshotId': 470, - 'iops': 2000 + 'iops': 2000, + 'useHourlyPricing': False, },)) mock_volume['storageType']['keyName'] = prev_storage_type_keyname @@ -741,7 +749,8 @@ def test_order_block_duplicate_endurance_no_duplicate_snapshot(self): 'quantity': 1, 'location': 449500, 'duplicateOriginVolumeId': 102, - 'osFormatType': {'keyName': 'LINUX'} + 'osFormatType': {'keyName': 'LINUX'}, + 'useHourlyPricing': False, },)) def test_order_block_duplicate_endurance(self): @@ -782,7 +791,8 @@ def test_order_block_duplicate_endurance(self): 'location': 449500, 'duplicateOriginVolumeId': 102, 'osFormatType': {'keyName': 'LINUX'}, - 'duplicateOriginSnapshotId': 470 + 'duplicateOriginSnapshotId': 470, + 'useHourlyPricing': False, },)) def test_setCredentialPassword(self): diff --git a/tests/managers/file_tests.py b/tests/managers/file_tests.py index 67daca4b0..00dc6b848 100644 --- a/tests/managers/file_tests.py +++ b/tests/managers/file_tests.py @@ -385,7 +385,8 @@ def test_order_file_volume_performance(self): 'volumeSize': 1000, 'quantity': 1, 'location': 449494, - 'iops': 2000 + 'iops': 2000, + 'useHourlyPricing': False, },) ) @@ -429,7 +430,8 @@ def test_order_file_volume_endurance(self): ], 'volumeSize': 1000, 'quantity': 1, - 'location': 449494 + 'location': 449494, + 'useHourlyPricing': False, },) ) @@ -461,7 +463,8 @@ def test_order_file_snapshot_space_upgrade(self): ], 'quantity': 1, 'location': 449500, - 'volumeId': 102 + 'volumeId': 102, + 'useHourlyPricing': False },) ) @@ -493,7 +496,8 @@ def test_order_file_snapshot_space(self): ], 'quantity': 1, 'location': 449500, - 'volumeId': 102 + 'volumeId': 102, + 'useHourlyPricing': False },) ) @@ -536,7 +540,8 @@ def test_order_file_replicant_performance(self): 'location': 449494, 'iops': 1000, 'originVolumeId': 102, - 'originVolumeScheduleId': 978 + 'originVolumeScheduleId': 978, + 'useHourlyPricing': False },) ) @@ -578,7 +583,8 @@ def test_order_file_replicant_endurance(self): 'quantity': 1, 'location': 449494, 'originVolumeId': 102, - 'originVolumeScheduleId': 978 + 'originVolumeScheduleId': 978, + 'useHourlyPricing': False },) ) @@ -617,7 +623,8 @@ def test_order_file_duplicate_performance_no_duplicate_snapshot(self): 'quantity': 1, 'location': 449500, 'duplicateOriginVolumeId': 102, - 'iops': 1000 + 'iops': 1000, + 'useHourlyPricing': False },)) mock_volume['storageType']['keyName'] = prev_storage_type_keyname @@ -662,7 +669,8 @@ def test_order_file_duplicate_performance(self): 'location': 449500, 'duplicateOriginVolumeId': 102, 'duplicateOriginSnapshotId': 470, - 'iops': 2000 + 'iops': 2000, + 'useHourlyPricing': False, },)) mock_volume['storageType']['keyName'] = prev_storage_type_keyname @@ -699,7 +707,8 @@ def test_order_file_duplicate_endurance_no_duplicate_snapshot(self): 'volumeSize': 500, 'quantity': 1, 'location': 449500, - 'duplicateOriginVolumeId': 102 + 'duplicateOriginVolumeId': 102, + 'useHourlyPricing': False, },)) mock_volume['storageType']['keyName'] = prev_storage_type_keyname @@ -743,7 +752,8 @@ def test_order_file_duplicate_endurance(self): 'quantity': 1, 'location': 449500, 'duplicateOriginVolumeId': 102, - 'duplicateOriginSnapshotId': 470 + 'duplicateOriginSnapshotId': 470, + 'useHourlyPricing': False, },)) mock_volume['storageType']['keyName'] = prev_storage_type_keyname diff --git a/tests/managers/storage_utils_tests.py b/tests/managers/storage_utils_tests.py index f07e21f2d..21ce9edd0 100644 --- a/tests/managers/storage_utils_tests.py +++ b/tests/managers/storage_utils_tests.py @@ -2721,7 +2721,8 @@ def test_prep_snapshot_order_saas_endurance_tier_is_not_none(self): 'prices': [{'id': 193613}], 'quantity': 1, 'location': 449500, - 'volumeId': 102 + 'volumeId': 102, + 'useHourlyPricing': False, } result = storage_utils.prepare_snapshot_order_object( @@ -2740,14 +2741,15 @@ def test_prep_snapshot_order_saas_endurance_upgrade(self): 'complexType': 'SoftLayer_Container_Product_Order_' 'Network_Storage_Enterprise_SnapshotSpace_Upgrade', 'packageId': 759, - 'prices': [{'id': 193853}], + 'prices': [{'id': 193613}], 'quantity': 1, 'location': 449500, - 'volumeId': 102 + 'volumeId': 102, + 'useHourlyPricing': False, } result = storage_utils.prepare_snapshot_order_object( - self.block, mock_volume, 20, None, True + self.block, mock_volume, 10, 2, True ) self.assertEqual(expected_object, result) @@ -2792,7 +2794,8 @@ def test_prep_snapshot_order_saas_performance(self): 'prices': [{'id': 191193}], 'quantity': 1, 'location': 449500, - 'volumeId': 102 + 'volumeId': 102, + 'useHourlyPricing': False } result = storage_utils.prepare_snapshot_order_object( @@ -2841,7 +2844,8 @@ def test_prep_snapshot_order_enterprise_tier_is_not_none(self): 'prices': [{'id': 46160}], 'quantity': 1, 'location': 449500, - 'volumeId': 100 + 'volumeId': 100, + 'useHourlyPricing': False, } result = storage_utils.prepare_snapshot_order_object( @@ -2865,7 +2869,33 @@ def test_prep_snapshot_order_enterprise(self): 'prices': [{'id': 45860}], 'quantity': 1, 'location': 449500, - 'volumeId': 100 + 'volumeId': 100, + 'useHourlyPricing': False, + } + + result = storage_utils.prepare_snapshot_order_object( + self.block, mock_volume, 20, None, False + ) + + self.assertEqual(expected_object, result) + + def test_prep_snapshot_order_hourly_billing(self): + mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') + mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + + mock_volume = fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME + prev_hourly_flag = mock_volume['billingItem']['hourlyFlag'] + mock_volume['billingItem']['hourlyFlag'] = True + + expected_object = { + 'complexType': 'SoftLayer_Container_Product_Order_' + 'Network_Storage_Enterprise_SnapshotSpace', + 'packageId': 759, + 'prices': [{'id': 193853}], + 'quantity': 1, + 'location': 449500, + 'volumeId': 102, + 'useHourlyPricing': True } result = storage_utils.prepare_snapshot_order_object( @@ -2874,6 +2904,8 @@ def test_prep_snapshot_order_enterprise(self): self.assertEqual(expected_object, result) + mock_volume['billingItem']['hourlyFlag'] = prev_hourly_flag + # --------------------------------------------------------------------- # Tests for prepare_volume_order_object() # --------------------------------------------------------------------- @@ -2882,7 +2914,7 @@ def test_prep_volume_order_invalid_storage_type(self): exceptions.SoftLayerError, storage_utils.prepare_volume_order_object, self.block, 'saxophone_cat', 'dal09', 1000, - None, 4, None, 'enterprise', 'block' + None, 4, None, 'enterprise', 'block', False ) self.assertEqual( @@ -2898,7 +2930,7 @@ def test_prep_volume_order_invalid_location(self): exceptions.SoftLayerError, storage_utils.prepare_volume_order_object, self.block, 'endurance', 'hoth01', 1000, - None, 4, None, 'enterprise', 'block' + None, 4, None, 'enterprise', 'block', False ) self.assertEqual( @@ -2915,7 +2947,7 @@ def test_prep_volume_order_enterprise_offering_invalid_storage_type(self): exceptions.SoftLayerError, storage_utils.prepare_volume_order_object, self.block, 'performance', 'dal09', 1000, - None, 4, None, 'enterprise', 'block' + None, 4, None, 'enterprise', 'block', False ) self.assertEqual( @@ -2932,7 +2964,7 @@ def test_prep_volume_order_performance_offering_invalid_storage_type(self): exceptions.SoftLayerError, storage_utils.prepare_volume_order_object, self.block, 'endurance', 'dal09', 1000, - 800, None, None, 'performance', 'block' + 800, None, None, 'performance', 'block', False ) self.assertEqual( @@ -2977,7 +3009,8 @@ def test_prep_volume_order_saas_performance(self): 'quantity': 1, 'location': 29, 'volumeSize': 1000, - 'iops': 800 + 'iops': 800, + 'useHourlyPricing': False } result = storage_utils.prepare_volume_order_object( @@ -3007,7 +3040,8 @@ def test_prep_volume_order_saas_performance_with_snapshot(self): 'quantity': 1, 'location': 29, 'volumeSize': 1000, - 'iops': 800 + 'iops': 800, + 'useHourlyPricing': False } result = storage_utils.prepare_volume_order_object( @@ -3035,6 +3069,7 @@ def test_prep_volume_order_saas_endurance(self): ], 'quantity': 1, 'location': 29, + 'useHourlyPricing': False, 'volumeSize': 1000 } @@ -3064,6 +3099,7 @@ def test_prep_volume_order_saas_endurance_with_snapshot(self): ], 'quantity': 1, 'location': 29, + 'useHourlyPricing': False, 'volumeSize': 1000 } @@ -3092,7 +3128,8 @@ def test_prep_volume_order_perf_performance_block(self): {'id': 41562} ], 'quantity': 1, - 'location': 29 + 'location': 29, + 'useHourlyPricing': False } result = storage_utils.prepare_volume_order_object( @@ -3120,7 +3157,8 @@ def test_prep_volume_order_perf_performance_file(self): {'id': 41562} ], 'quantity': 1, - 'location': 29 + 'location': 29, + 'useHourlyPricing': False } result = storage_utils.prepare_volume_order_object( @@ -3149,7 +3187,8 @@ def test_prep_volume_order_ent_endurance(self): {'id': 45088} ], 'quantity': 1, - 'location': 29 + 'location': 29, + 'useHourlyPricing': False } result = storage_utils.prepare_volume_order_object( @@ -3179,7 +3218,8 @@ def test_prep_volume_order_ent_endurance_with_snapshot(self): {'id': 46170} ], 'quantity': 1, - 'location': 29 + 'location': 29, + 'useHourlyPricing': False } result = storage_utils.prepare_volume_order_object( @@ -3335,11 +3375,12 @@ def test_prep_replicant_order_saas_endurance(self): 'location': 51, 'originVolumeId': 102, 'originVolumeScheduleId': 978, - 'volumeSize': 500 + 'volumeSize': 500, + 'useHourlyPricing': False, } result = storage_utils.prepare_replicant_order_object( - self.block, 'WEEKLY', 'wdc04', None, mock_volume, 'block' + self.block, 'WEEKLY', 'wdc04', 2, mock_volume, 'block' ) self.assertEqual(expected_object, result) @@ -3368,7 +3409,8 @@ def test_prep_replicant_order_saas_endurance_tier_is_not_none(self): 'location': 51, 'originVolumeId': 102, 'originVolumeScheduleId': 978, - 'volumeSize': 500 + 'volumeSize': 500, + 'useHourlyPricing': False, } result = storage_utils.prepare_replicant_order_object( @@ -3431,7 +3473,8 @@ def test_prep_replicant_order_saas_performance(self): 'originVolumeId': 102, 'originVolumeScheduleId': 978, 'volumeSize': 500, - 'iops': 1000 + 'iops': 1000, + 'useHourlyPricing': False, } result = storage_utils.prepare_replicant_order_object( @@ -3492,7 +3535,8 @@ def test_prep_replicant_order_ent_endurance(self): 'quantity': 1, 'location': 51, 'originVolumeId': 100, - 'originVolumeScheduleId': 978 + 'originVolumeScheduleId': 978, + 'useHourlyPricing': False } result = storage_utils.prepare_replicant_order_object( @@ -3526,7 +3570,8 @@ def test_prep_replicant_order_ent_endurance_tier_is_not_none(self): 'quantity': 1, 'location': 51, 'originVolumeId': 100, - 'originVolumeScheduleId': 978 + 'originVolumeScheduleId': 978, + 'useHourlyPricing': False } result = storage_utils.prepare_replicant_order_object( @@ -3657,6 +3702,7 @@ def test_prep_duplicate_order_origin_originalVolumeSize_empty_block(self): 'volumeSize': 500, 'quantity': 1, 'location': 449500, + 'useHourlyPricing': False, 'duplicateOriginVolumeId': 102} result = storage_utils.prepare_duplicate_order_object( @@ -3797,6 +3843,7 @@ def test_prep_duplicate_order_performance_use_default_origin_values(self): 'quantity': 1, 'location': 449500, 'duplicateOriginVolumeId': 102, + 'useHourlyPricing': False, 'iops': 1000} result = storage_utils.prepare_duplicate_order_object( @@ -3829,6 +3876,7 @@ def test_prep_duplicate_order_performance_block(self): 'quantity': 1, 'location': 449500, 'duplicateOriginVolumeId': 102, + 'useHourlyPricing': False, 'iops': 2000} result = storage_utils.prepare_duplicate_order_object( @@ -3861,6 +3909,7 @@ def test_prep_duplicate_order_performance_file(self): 'quantity': 1, 'location': 449500, 'duplicateOriginVolumeId': 102, + 'useHourlyPricing': False, 'iops': 2000} result = storage_utils.prepare_duplicate_order_object( @@ -3954,6 +4003,7 @@ def test_prep_duplicate_order_endurance_use_default_origin_values(self): 'volumeSize': 500, 'quantity': 1, 'location': 449500, + 'useHourlyPricing': False, 'duplicateOriginVolumeId': 102} result = storage_utils.prepare_duplicate_order_object( @@ -3983,6 +4033,7 @@ def test_prep_duplicate_order_endurance_block(self): 'volumeSize': 1000, 'quantity': 1, 'location': 449500, + 'useHourlyPricing': False, 'duplicateOriginVolumeId': 102} result = storage_utils.prepare_duplicate_order_object( @@ -4012,6 +4063,7 @@ def test_prep_duplicate_order_endurance_file(self): 'volumeSize': 1000, 'quantity': 1, 'location': 449500, + 'useHourlyPricing': False, 'duplicateOriginVolumeId': 102} result = storage_utils.prepare_duplicate_order_object( From 879dd19ee472446908fe269c95a93dc5d9a71498 Mon Sep 17 00:00:00 2001 From: David Pickle Date: Tue, 29 Aug 2017 16:33:04 -0500 Subject: [PATCH 2/2] Update unit tests to increase code coverage --- tests/CLI/modules/block_tests.py | 102 +++++++++++++------------- tests/CLI/modules/file_tests.py | 86 ++++++++++++---------- tests/managers/block_tests.py | 77 ++++++++++++++++--- tests/managers/file_tests.py | 67 +++++++++++++++-- tests/managers/storage_utils_tests.py | 102 ++++++++++++++++++-------- 5 files changed, 295 insertions(+), 139 deletions(-) diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index 7f07b799a..d7ad2c3c1 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -228,6 +228,41 @@ def test_volume_order_order_not_placed(self, order_mock): 'Order could not be placed! Please verify ' 'your options and try again.\n') + def test_volume_order_hourly_billing_not_available(self): + result = self.run_command(['block', 'volume-order', + '--storage-type=endurance', '--size=20', + '--tier=0.25', '--os-type=linux', + '--location=dal10', '--billing=hourly', + '--service-offering=enterprise']) + + self.assertEqual(2, result.exit_code) + + @mock.patch('SoftLayer.BlockStorageManager.order_block_volume') + def test_volume_order_hourly_billing(self, order_mock): + order_mock.return_value = { + 'placedOrder': { + 'id': 10983647, + 'items': [ + {'description': 'Storage as a Service'}, + {'description': 'Block Storage'}, + {'description': '20 GB Storage Space'}, + {'description': '200 IOPS'}] + } + } + + result = self.run_command(['block', 'volume-order', + '--storage-type=endurance', '--size=20', + '--tier=0.25', '--os-type=linux', + '--location=dal10', '--billing=hourly', + '--service-offering=storage_as_a_service']) + self.assert_no_fail(result) + self.assertEqual(result.output, + 'Order #10983647 placed successfully!\n' + ' > Storage as a Service\n' + ' > Block Storage\n' + ' > 20 GB Storage Space\n' + ' > 200 IOPS\n') + @mock.patch('SoftLayer.BlockStorageManager.order_block_volume') def test_volume_order_performance_manager_error(self, order_mock): order_mock.side_effect = ValueError('failure!') @@ -539,68 +574,31 @@ def test_duplicate_order(self, order_mock): 'Order #24601 placed successfully!\n' ' > Storage as a Service\n') - def test_set_password(self): - result = self.run_command(['block', 'access-password', '1234', '--password=AAAAA']) - self.assert_no_fail(result) - - def test_block_volume_order_performance_hourly_billing_not_available(self): - result = self.run_command(['block', 'volume-order', - '--storage-type=performance', '--size=20', - '--os-type=LINUX' - '--location=dal10', - '--service-offering=performance', - '--billing=hourly', - '--iops=200']) - - self.assertEqual(2, result.exit_code) - - @mock.patch('SoftLayer.BlockStorageManager.order_block_volume') - def test_volume_order_performance_hourly(self, order_mock): - order_mock.return_value = { - 'placedOrder': { - 'id': 478, - 'items': [ - {'description': 'Storage as a Service'}, - {'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=performance', - '--size=20', - '--iops=100', - '--os-type=LINUX', - '--location=dal10', - '--snapshot-size=10', - '--billing=hourly']) - - self.assert_no_fail(result) - self.assertEqual(result.output, - 'Order #478 placed successfully!\n' - ' > Storage as a Service\n > Block Storage\n' - ' > 0.25 IOPS per GB\n > 20 GB Storage Space\n' - ' > 10 GB Storage Space (Snapshot Space)\n') - @mock.patch('SoftLayer.BlockStorageManager.order_duplicate_volume') - def test_duplicate_order_hourly(self, order_mock): + def test_duplicate_order_hourly_billing(self, order_mock): order_mock.return_value = { 'placedOrder': { - 'id': 24601, + 'id': 24602, 'items': [{'description': 'Storage as a Service'}] } } - result = self.run_command(['block', 'volume-duplicate', '102', + result = self.run_command(['block', 'volume-duplicate', '100', '--origin-snapshot-id=470', '--duplicate-size=250', - '--duplicate-tier=2', - '--duplicate-snapshot-size=20', - '--billing=hourly']) + '--duplicate-tier=2', '--billing=hourly', + '--duplicate-snapshot-size=20']) + order_mock.assert_called_with('100', origin_snapshot_id=470, + duplicate_size=250, duplicate_iops=None, + duplicate_tier_level=2, + duplicate_snapshot_size=20, + hourly_billing_flag=True) self.assert_no_fail(result) self.assertEqual(result.output, - 'Order #24601 placed successfully!\n' + 'Order #24602 placed successfully!\n' ' > Storage as a Service\n') + + def test_set_password(self): + result = self.run_command(['block', 'access-password', '1234', '--password=AAAAA']) + self.assert_no_fail(result) diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index 9bffb5e34..6aad4564d 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -229,6 +229,44 @@ def test_volume_order_order_not_placed(self, order_mock): 'Order could not be placed! Please verify ' 'your options and try again.\n') + def test_volume_order_hourly_billing_not_available(self): + result = self.run_command(['file', 'volume-order', + '--storage-type=endurance', '--size=20', + '--tier=0.25', '--location=dal10', + '--billing=hourly', + '--service-offering=enterprise']) + + self.assertEqual(2, result.exit_code) + + @mock.patch('SoftLayer.FileStorageManager.order_file_volume') + def test_volume_order_hourly_billing(self, order_mock): + order_mock.return_value = { + 'placedOrder': { + 'id': 479, + 'items': [ + {'description': 'Storage as a Service'}, + {'description': 'File Storage'}, + {'description': '20 GB Storage Space'}, + {'description': '0.25 IOPS per GB'}, + {'description': '10 GB Storage Space (Snapshot Space)'}] + } + } + + result = self.run_command(['file', 'volume-order', + '--storage-type=endurance', '--size=20', + '--tier=0.25', '--location=dal05', + '--service-offering=storage_as_a_service', + '--billing=hourly', '--snapshot-size=10']) + + self.assert_no_fail(result) + self.assertEqual(result.output, + 'Order #479 placed successfully!\n' + ' > Storage as a Service\n' + ' > File Storage\n' + ' > 20 GB Storage Space\n' + ' > 0.25 IOPS per GB\n' + ' > 10 GB Storage Space (Snapshot Space)\n') + @mock.patch('SoftLayer.FileStorageManager.order_file_volume') def test_volume_order_performance_manager_error(self, order_mock): order_mock.side_effect = ValueError('failure!') @@ -515,44 +553,8 @@ def test_duplicate_order(self, order_mock): 'Order #24602 placed successfully!\n' ' > Storage as a Service\n') - def test_volume_order_performance_hourly_billing_not_available(self): - result = self.run_command(['file', 'volume-order', - '--storage-type=performance', '--size=20', - '--location=dal10', '--iops=200', - '--service-offering=performance', - '--billing=hourly']) - - self.assertEqual(2, result.exit_code) - - @mock.patch('SoftLayer.FileStorageManager.order_file_volume') - def test_volume_order_performance_hourly(self, order_mock): - order_mock.return_value = { - 'placedOrder': { - 'id': 478, - 'items': [ - {'description': 'Storage as a Service'}, - {'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=performance', '--size=20', - '--iops=100', '--location=dal05', - '--snapshot-size=10', - '--billing=hourly']) - - self.assert_no_fail(result) - self.assertEqual(result.output, - 'Order #478 placed successfully!\n' - ' > Storage as a Service\n > File Storage\n' - ' > 0.25 IOPS per GB\n > 20 GB Storage Space\n' - ' > 10 GB Storage Space (Snapshot Space)\n') - @mock.patch('SoftLayer.FileStorageManager.order_duplicate_volume') - def test_duplicate_order_hourly(self, order_mock): + def test_duplicate_order_hourly_billing(self, order_mock): order_mock.return_value = { 'placedOrder': { 'id': 24602, @@ -563,10 +565,14 @@ def test_duplicate_order_hourly(self, order_mock): result = self.run_command(['file', 'volume-duplicate', '100', '--origin-snapshot-id=470', '--duplicate-size=250', - '--duplicate-tier=2', - '--duplicate-snapshot-size=20', - '--billing=hourly']) + '--duplicate-tier=2', '--billing=hourly', + '--duplicate-snapshot-size=20']) + order_mock.assert_called_with('100', origin_snapshot_id=470, + duplicate_size=250, duplicate_iops=None, + duplicate_tier_level=2, + duplicate_snapshot_size=20, + hourly_billing_flag=True) self.assert_no_fail(result) self.assertEqual(result.output, 'Order #24602 placed successfully!\n' diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index e3d237ecb..53dac03fe 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -25,6 +25,21 @@ def test_cancel_block_volume_immediately(self): identifier=449, ) + def test_cancel_block_volume_immediately_hourly_billing(self): + mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') + mock.return_value = { + 'billingItem': {'hourlyFlag': True, 'id': 449}, + } + + self.block.cancel_block_volume(123, immediate=False) + + self.assert_called_with( + 'SoftLayer_Billing_Item', + 'cancelItem', + args=(True, True, 'No longer needed'), + identifier=449, + ) + def test_get_block_volume_details(self): result = self.block.get_block_volume_details(100) @@ -181,6 +196,48 @@ def test_cancel_snapshot_immediately(self): identifier=123, ) + def test_cancel_snapshot_hourly_billing_immediate_true(self): + mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') + mock.return_value = { + 'billingItem': { + 'activeChildren': [ + {'categoryCode': 'storage_snapshot_space', 'id': 417} + ], + 'hourlyFlag': True, + 'id': 449 + }, + } + + self.block.cancel_snapshot_space(1234, immediate=True) + + self.assert_called_with( + 'SoftLayer_Billing_Item', + 'cancelItem', + args=(True, True, 'No longer needed'), + identifier=417, + ) + + def test_cancel_snapshot_hourly_billing_immediate_false(self): + mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') + mock.return_value = { + 'billingItem': { + 'activeChildren': [ + {'categoryCode': 'storage_snapshot_space', 'id': 417} + ], + 'hourlyFlag': True, + 'id': 449 + }, + } + + self.block.cancel_snapshot_space(1234, immediate=False) + + self.assert_called_with( + 'SoftLayer_Billing_Item', + 'cancelItem', + args=(True, True, 'No longer needed'), + identifier=417, + ) + def test_cancel_snapshot_exception_no_billing_item_active_children(self): mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = { @@ -313,8 +370,8 @@ def test_order_block_volume_performance(self): 'quantity': 1, 'location': 449494, 'iops': 2000, - 'osFormatType': {'keyName': 'LINUX'}, 'useHourlyPricing': False, + 'osFormatType': {'keyName': 'LINUX'} },) ) @@ -358,8 +415,8 @@ def test_order_block_volume_endurance(self): 'volumeSize': 1000, 'quantity': 1, 'location': 449494, - 'osFormatType': {'keyName': 'LINUX'}, 'useHourlyPricing': False, + 'osFormatType': {'keyName': 'LINUX'} },) ) @@ -464,7 +521,7 @@ def test_order_block_snapshot_space_upgrade(self): 'quantity': 1, 'location': 449500, 'volumeId': 102, - 'useHourlyPricing': False, + 'useHourlyPricing': False },) ) @@ -495,7 +552,7 @@ def test_order_block_snapshot_space(self): 'quantity': 1, 'location': 449500, 'volumeId': 102, - 'useHourlyPricing': False, + 'useHourlyPricing': False },) ) @@ -563,8 +620,8 @@ def test_order_block_replicant_performance_os_type_given(self): 'iops': 1000, 'originVolumeId': 102, 'originVolumeScheduleId': 978, - 'osFormatType': {'keyName': 'XEN'}, 'useHourlyPricing': False, + 'osFormatType': {'keyName': 'XEN'} },) ) @@ -605,8 +662,8 @@ def test_order_block_replicant_endurance(self): 'location': 449494, 'originVolumeId': 102, 'originVolumeScheduleId': 978, - 'osFormatType': {'keyName': 'LINUX'}, 'useHourlyPricing': False, + 'osFormatType': {'keyName': 'LINUX'} },) ) @@ -666,7 +723,7 @@ def test_order_block_duplicate_performance_no_duplicate_snapshot(self): 'duplicateOriginVolumeId': 102, 'osFormatType': {'keyName': 'LINUX'}, 'iops': 1000, - 'useHourlyPricing': False, + 'useHourlyPricing': False },)) mock_volume['storageType']['keyName'] = prev_storage_type_keyname @@ -713,7 +770,7 @@ def test_order_block_duplicate_performance(self): 'osFormatType': {'keyName': 'LINUX'}, 'duplicateOriginSnapshotId': 470, 'iops': 2000, - 'useHourlyPricing': False, + 'useHourlyPricing': False },)) mock_volume['storageType']['keyName'] = prev_storage_type_keyname @@ -750,7 +807,7 @@ def test_order_block_duplicate_endurance_no_duplicate_snapshot(self): 'location': 449500, 'duplicateOriginVolumeId': 102, 'osFormatType': {'keyName': 'LINUX'}, - 'useHourlyPricing': False, + 'useHourlyPricing': False },)) def test_order_block_duplicate_endurance(self): @@ -792,7 +849,7 @@ def test_order_block_duplicate_endurance(self): 'duplicateOriginVolumeId': 102, 'osFormatType': {'keyName': 'LINUX'}, 'duplicateOriginSnapshotId': 470, - 'useHourlyPricing': False, + 'useHourlyPricing': False },)) def test_setCredentialPassword(self): diff --git a/tests/managers/file_tests.py b/tests/managers/file_tests.py index 00dc6b848..bb4e2bfc3 100644 --- a/tests/managers/file_tests.py +++ b/tests/managers/file_tests.py @@ -25,6 +25,21 @@ def test_cancel_file_volume_immediately(self): identifier=449, ) + def test_cancel_file_volume_immediately_hourly_billing(self): + mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') + mock.return_value = { + 'billingItem': {'hourlyFlag': True, 'id': 449}, + } + + self.file.cancel_file_volume(123, immediate=False) + + self.assert_called_with( + 'SoftLayer_Billing_Item', + 'cancelItem', + args=(True, True, 'No longer needed'), + identifier=449, + ) + def test_authorize_host_to_volume(self): result = self.file.authorize_host_to_volume( 50, @@ -166,6 +181,48 @@ def test_cancel_snapshot_immediately(self): identifier=123, ) + def test_cancel_snapshot_hourly_billing_immediate_true(self): + mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') + mock.return_value = { + 'billingItem': { + 'activeChildren': [ + {'categoryCode': 'storage_snapshot_space', 'id': 417} + ], + 'hourlyFlag': True, + 'id': 449 + }, + } + + self.file.cancel_snapshot_space(1234, immediate=True) + + self.assert_called_with( + 'SoftLayer_Billing_Item', + 'cancelItem', + args=(True, True, 'No longer needed'), + identifier=417, + ) + + def test_cancel_snapshot_hourly_billing_immediate_false(self): + mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') + mock.return_value = { + 'billingItem': { + 'activeChildren': [ + {'categoryCode': 'storage_snapshot_space', 'id': 417} + ], + 'hourlyFlag': True, + 'id': 449 + }, + } + + self.file.cancel_snapshot_space(1234, immediate=False) + + self.assert_called_with( + 'SoftLayer_Billing_Item', + 'cancelItem', + args=(True, True, 'No longer needed'), + identifier=417, + ) + def test_cancel_snapshot_exception_no_billing_item_active_children(self): mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = { @@ -386,7 +443,7 @@ def test_order_file_volume_performance(self): 'quantity': 1, 'location': 449494, 'iops': 2000, - 'useHourlyPricing': False, + 'useHourlyPricing': False },) ) @@ -431,7 +488,7 @@ def test_order_file_volume_endurance(self): 'volumeSize': 1000, 'quantity': 1, 'location': 449494, - 'useHourlyPricing': False, + 'useHourlyPricing': False },) ) @@ -670,7 +727,7 @@ def test_order_file_duplicate_performance(self): 'duplicateOriginVolumeId': 102, 'duplicateOriginSnapshotId': 470, 'iops': 2000, - 'useHourlyPricing': False, + 'useHourlyPricing': False },)) mock_volume['storageType']['keyName'] = prev_storage_type_keyname @@ -708,7 +765,7 @@ def test_order_file_duplicate_endurance_no_duplicate_snapshot(self): 'quantity': 1, 'location': 449500, 'duplicateOriginVolumeId': 102, - 'useHourlyPricing': False, + 'useHourlyPricing': False },)) mock_volume['storageType']['keyName'] = prev_storage_type_keyname @@ -753,7 +810,7 @@ def test_order_file_duplicate_endurance(self): 'location': 449500, 'duplicateOriginVolumeId': 102, 'duplicateOriginSnapshotId': 470, - 'useHourlyPricing': False, + 'useHourlyPricing': False },)) mock_volume['storageType']['keyName'] = prev_storage_type_keyname diff --git a/tests/managers/storage_utils_tests.py b/tests/managers/storage_utils_tests.py index 21ce9edd0..1df9547c5 100644 --- a/tests/managers/storage_utils_tests.py +++ b/tests/managers/storage_utils_tests.py @@ -2722,7 +2722,7 @@ def test_prep_snapshot_order_saas_endurance_tier_is_not_none(self): 'quantity': 1, 'location': 449500, 'volumeId': 102, - 'useHourlyPricing': False, + 'useHourlyPricing': False } result = storage_utils.prepare_snapshot_order_object( @@ -2741,15 +2741,15 @@ def test_prep_snapshot_order_saas_endurance_upgrade(self): 'complexType': 'SoftLayer_Container_Product_Order_' 'Network_Storage_Enterprise_SnapshotSpace_Upgrade', 'packageId': 759, - 'prices': [{'id': 193613}], + 'prices': [{'id': 193853}], 'quantity': 1, 'location': 449500, 'volumeId': 102, - 'useHourlyPricing': False, + 'useHourlyPricing': False } result = storage_utils.prepare_snapshot_order_object( - self.block, mock_volume, 10, 2, True + self.block, mock_volume, 20, None, True ) self.assertEqual(expected_object, result) @@ -2845,7 +2845,7 @@ def test_prep_snapshot_order_enterprise_tier_is_not_none(self): 'quantity': 1, 'location': 449500, 'volumeId': 100, - 'useHourlyPricing': False, + 'useHourlyPricing': False } result = storage_utils.prepare_snapshot_order_object( @@ -2870,7 +2870,7 @@ def test_prep_snapshot_order_enterprise(self): 'quantity': 1, 'location': 449500, 'volumeId': 100, - 'useHourlyPricing': False, + 'useHourlyPricing': False } result = storage_utils.prepare_snapshot_order_object( @@ -2914,7 +2914,7 @@ def test_prep_volume_order_invalid_storage_type(self): exceptions.SoftLayerError, storage_utils.prepare_volume_order_object, self.block, 'saxophone_cat', 'dal09', 1000, - None, 4, None, 'enterprise', 'block', False + None, 4, None, 'enterprise', 'block' ) self.assertEqual( @@ -2930,7 +2930,7 @@ def test_prep_volume_order_invalid_location(self): exceptions.SoftLayerError, storage_utils.prepare_volume_order_object, self.block, 'endurance', 'hoth01', 1000, - None, 4, None, 'enterprise', 'block', False + None, 4, None, 'enterprise', 'block' ) self.assertEqual( @@ -2947,7 +2947,7 @@ def test_prep_volume_order_enterprise_offering_invalid_storage_type(self): exceptions.SoftLayerError, storage_utils.prepare_volume_order_object, self.block, 'performance', 'dal09', 1000, - None, 4, None, 'enterprise', 'block', False + None, 4, None, 'enterprise', 'block' ) self.assertEqual( @@ -2964,7 +2964,7 @@ def test_prep_volume_order_performance_offering_invalid_storage_type(self): exceptions.SoftLayerError, storage_utils.prepare_volume_order_object, self.block, 'endurance', 'dal09', 1000, - 800, None, None, 'performance', 'block', False + 800, None, None, 'performance', 'block' ) self.assertEqual( @@ -3069,8 +3069,8 @@ def test_prep_volume_order_saas_endurance(self): ], 'quantity': 1, 'location': 29, - 'useHourlyPricing': False, - 'volumeSize': 1000 + 'volumeSize': 1000, + 'useHourlyPricing': False } result = storage_utils.prepare_volume_order_object( @@ -3099,8 +3099,8 @@ def test_prep_volume_order_saas_endurance_with_snapshot(self): ], 'quantity': 1, 'location': 29, - 'useHourlyPricing': False, - 'volumeSize': 1000 + 'volumeSize': 1000, + 'useHourlyPricing': False } result = storage_utils.prepare_volume_order_object( @@ -3376,11 +3376,11 @@ def test_prep_replicant_order_saas_endurance(self): 'originVolumeId': 102, 'originVolumeScheduleId': 978, 'volumeSize': 500, - 'useHourlyPricing': False, + 'useHourlyPricing': False } result = storage_utils.prepare_replicant_order_object( - self.block, 'WEEKLY', 'wdc04', 2, mock_volume, 'block' + self.block, 'WEEKLY', 'wdc04', None, mock_volume, 'block' ) self.assertEqual(expected_object, result) @@ -3410,7 +3410,7 @@ def test_prep_replicant_order_saas_endurance_tier_is_not_none(self): 'originVolumeId': 102, 'originVolumeScheduleId': 978, 'volumeSize': 500, - 'useHourlyPricing': False, + 'useHourlyPricing': False } result = storage_utils.prepare_replicant_order_object( @@ -3474,7 +3474,7 @@ def test_prep_replicant_order_saas_performance(self): 'originVolumeScheduleId': 978, 'volumeSize': 500, 'iops': 1000, - 'useHourlyPricing': False, + 'useHourlyPricing': False } result = storage_utils.prepare_replicant_order_object( @@ -3580,6 +3580,44 @@ def test_prep_replicant_order_ent_endurance_tier_is_not_none(self): self.assertEqual(expected_object, result) + def test_prep_replicant_order_hourly_billing(self): + mock = self.set_mock('SoftLayer_Location_Datacenter', 'getDatacenters') + mock.return_value = [{'id': 51, 'name': 'wdc04'}] + mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') + mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + + mock_volume = fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME + prev_hourly_flag = mock_volume['billingItem']['hourlyFlag'] + mock_volume['billingItem']['hourlyFlag'] = True + + expected_object = { + 'complexType': 'SoftLayer_Container_Product_Order_' + 'Network_Storage_AsAService', + 'packageId': 759, + 'prices': [ + {'id': 189433}, + {'id': 189443}, + {'id': 193433}, + {'id': 193373}, + {'id': 193613}, + {'id': 194693} + ], + 'quantity': 1, + 'location': 51, + 'originVolumeId': 102, + 'originVolumeScheduleId': 978, + 'volumeSize': 500, + 'useHourlyPricing': True + } + + result = storage_utils.prepare_replicant_order_object( + self.block, 'WEEKLY', 'wdc04', None, mock_volume, 'block' + ) + + self.assertEqual(expected_object, result) + + mock_volume['billingItem']['hourlyFlag'] = prev_hourly_flag + # --------------------------------------------------------------------- # Tests for prepare_duplicate_order_object() # --------------------------------------------------------------------- @@ -3702,8 +3740,8 @@ def test_prep_duplicate_order_origin_originalVolumeSize_empty_block(self): 'volumeSize': 500, 'quantity': 1, 'location': 449500, - 'useHourlyPricing': False, - 'duplicateOriginVolumeId': 102} + 'duplicateOriginVolumeId': 102, + 'useHourlyPricing': False} result = storage_utils.prepare_duplicate_order_object( self.block, mock_volume, None, None, None, None, 'block') @@ -3843,8 +3881,8 @@ def test_prep_duplicate_order_performance_use_default_origin_values(self): 'quantity': 1, 'location': 449500, 'duplicateOriginVolumeId': 102, - 'useHourlyPricing': False, - 'iops': 1000} + 'iops': 1000, + 'useHourlyPricing': False} result = storage_utils.prepare_duplicate_order_object( self.file, mock_volume, None, None, None, None, 'file') @@ -3876,8 +3914,8 @@ def test_prep_duplicate_order_performance_block(self): 'quantity': 1, 'location': 449500, 'duplicateOriginVolumeId': 102, - 'useHourlyPricing': False, - 'iops': 2000} + 'iops': 2000, + 'useHourlyPricing': False} result = storage_utils.prepare_duplicate_order_object( self.block, mock_volume, 2000, None, 1000, 10, 'block') @@ -3909,8 +3947,8 @@ def test_prep_duplicate_order_performance_file(self): 'quantity': 1, 'location': 449500, 'duplicateOriginVolumeId': 102, - 'useHourlyPricing': False, - 'iops': 2000} + 'iops': 2000, + 'useHourlyPricing': False} result = storage_utils.prepare_duplicate_order_object( self.file, mock_volume, 2000, None, 1000, 10, 'file') @@ -4003,8 +4041,8 @@ def test_prep_duplicate_order_endurance_use_default_origin_values(self): 'volumeSize': 500, 'quantity': 1, 'location': 449500, - 'useHourlyPricing': False, - 'duplicateOriginVolumeId': 102} + 'duplicateOriginVolumeId': 102, + 'useHourlyPricing': False} result = storage_utils.prepare_duplicate_order_object( self.file, mock_volume, None, None, None, None, 'file') @@ -4033,8 +4071,8 @@ def test_prep_duplicate_order_endurance_block(self): 'volumeSize': 1000, 'quantity': 1, 'location': 449500, - 'useHourlyPricing': False, - 'duplicateOriginVolumeId': 102} + 'duplicateOriginVolumeId': 102, + 'useHourlyPricing': False} result = storage_utils.prepare_duplicate_order_object( self.block, mock_volume, None, 4.0, 1000, 10, 'block') @@ -4063,8 +4101,8 @@ def test_prep_duplicate_order_endurance_file(self): 'volumeSize': 1000, 'quantity': 1, 'location': 449500, - 'useHourlyPricing': False, - 'duplicateOriginVolumeId': 102} + 'duplicateOriginVolumeId': 102, + 'useHourlyPricing': False} result = storage_utils.prepare_duplicate_order_object( self.file, mock_volume, None, 4.0, 1000, 10, 'file')