Repository: softlayer/softlayer-python
Pull request #866 (2017-08-28 15:59:22)

STAAS (File and Block) Hourly billing changes
Add the '--billing' flag to volume-order and volume-duplicate commands, and add logic within snapshot space and replication orders to use hourly billing if the original volume used hourly billing.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/13019252/badge)](https://coveralls.io/builds/13019252)

Coverage decreased (-0.05%) to 85.097% when pulling **cec667577d4254f6a7700c4204298533717fd340 on sunilvrajput:sunil-sl-master** into **05b95c7d91bf77c621d1860409fabe1f5c538c7b on softlayer:master**.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/13019487/badge)](https://coveralls.io/builds/13019487)

Coverage decreased (-0.05%) to 85.097% when pulling **f446e68c099b40fa7cbc2dd8084fda9a5bba7682 on sunilvrajput:sunil-sl-master** into **05b95c7d91bf77c621d1860409fabe1f5c538c7b on softlayer:master**.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/13019663/badge)](https://coveralls.io/builds/13019663)

Coverage decreased (-0.05%) to 85.097% when pulling **d85d7f5e844a8f3db09dded05084b3e094ce3f81 on sunilvrajput:sunil-sl-master** into **05b95c7d91bf77c621d1860409fabe1f5c538c7b on softlayer:master**.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/13019797/badge)](https://coveralls.io/builds/13019797)

Coverage decreased (-0.05%) to 85.097% when pulling **aba47a0b8725bae459d2aba391b8bb508032c919 on sunilvrajput:sunil-sl-master** into **05b95c7d91bf77c621d1860409fabe1f5c538c7b on softlayer:master**.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/13023305/badge)](https://coveralls.io/builds/13023305)

Coverage decreased (-0.05%) to 85.093% when pulling **35c48bdfdc155c2aeb8873383a19e24a5acfada8 on sunilvrajput:sunil-sl-master** into **05b95c7d91bf77c621d1860409fabe1f5c538c7b on softlayer:master**.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/13023386/badge)](https://coveralls.io/builds/13023386)

Coverage decreased (-0.05%) to 85.093% when pulling **df151ce648d2232ff75709c9bb448dbdb27b290f on sunilvrajput:sunil-sl-master** into **05b95c7d91bf77c621d1860409fabe1f5c538c7b on softlayer:master**.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/13024211/badge)](https://coveralls.io/builds/13024211)

Coverage decreased (-0.05%) to 85.093% when pulling **e39ac0d34b5c36e48a57297aaba41bad2d76ccc7 on sunilvrajput:sunil-sl-master** into **05b95c7d91bf77c621d1860409fabe1f5c538c7b on softlayer:master**.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/13024838/badge)](https://coveralls.io/builds/13024838)

Coverage increased (+0.004%) to 85.151% when pulling **e733e81a5ef31e5cf52ab4e9c4cea0c004c12c49 on sunilvrajput:sunil-sl-master** into **05b95c7d91bf77c621d1860409fabe1f5c538c7b on softlayer:master**.


Patch:
diff --git a/SoftLayer/CLI/block/duplicate.py b/SoftLayer/CLI/block/duplicate.py
index 98ef1b79..0ecf5911 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 865a38e2..abd7dd91 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 e0216977..a3b4c801 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 c9ba4abc..ae901339 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 f8dfae89..b4dd0b75 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 eb6d4926..76631201 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 7f12a7bf..2ba39cca 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 14ae573a..ba6dfc8c 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:


Test patch:
diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py
index b535a50a..d7ad2c3c 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,6 +574,31 @@ def test_duplicate_order(self, order_mock):
                          'Order #24601 placed successfully!\n'
                          ' > Storage as a Service\n')
 
+    @mock.patch('SoftLayer.BlockStorageManager.order_duplicate_volume')
+    def test_duplicate_order_hourly_billing(self, order_mock):
+        order_mock.return_value = {
+            'placedOrder': {
+                'id': 24602,
+                'items': [{'description': 'Storage as a Service'}]
+            }
+        }
+
+        result = self.run_command(['block', 'volume-duplicate', '100',
+                                   '--origin-snapshot-id=470',
+                                   '--duplicate-size=250',
+                                   '--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'
+                         ' > 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 df38ae53..6aad4564 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!')
@@ -514,3 +552,28 @@ def test_duplicate_order(self, order_mock):
         self.assertEqual(result.output,
                          'Order #24602 placed successfully!\n'
                          ' > Storage as a Service\n')
+
+    @mock.patch('SoftLayer.FileStorageManager.order_duplicate_volume')
+    def test_duplicate_order_hourly_billing(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', '--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'
+                         ' > Storage as a Service\n')
diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py
index 1c9da6d3..53dac03f 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,6 +370,7 @@ def test_order_block_volume_performance(self):
                 'quantity': 1,
                 'location': 449494,
                 'iops': 2000,
+                'useHourlyPricing': False,
                 'osFormatType': {'keyName': 'LINUX'}
             },)
         )
@@ -357,6 +415,7 @@ def test_order_block_volume_endurance(self):
                 'volumeSize': 1000,
                 'quantity': 1,
                 'location': 449494,
+                'useHourlyPricing': False,
                 'osFormatType': {'keyName': 'LINUX'}
             },)
         )
@@ -461,7 +520,8 @@ def test_order_block_snapshot_space_upgrade(self):
                 ],
                 'quantity': 1,
                 'location': 449500,
-                'volumeId': 102
+                'volumeId': 102,
+                'useHourlyPricing': False
             },)
         )
 
@@ -491,7 +551,8 @@ def test_order_block_snapshot_space(self):
                 ],
                 'quantity': 1,
                 'location': 449500,
-                'volumeId': 102
+                'volumeId': 102,
+                'useHourlyPricing': False
             },)
         )
 
@@ -559,6 +620,7 @@ def test_order_block_replicant_performance_os_type_given(self):
                 'iops': 1000,
                 'originVolumeId': 102,
                 'originVolumeScheduleId': 978,
+                'useHourlyPricing': False,
                 'osFormatType': {'keyName': 'XEN'}
             },)
         )
@@ -600,6 +662,7 @@ def test_order_block_replicant_endurance(self):
                 'location': 449494,
                 'originVolumeId': 102,
                 'originVolumeScheduleId': 978,
+                'useHourlyPricing': False,
                 'osFormatType': {'keyName': 'LINUX'}
             },)
         )
@@ -659,7 +722,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 +769,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 +806,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 +848,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 67daca4b..bb4e2bfc 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 = {
@@ -385,7 +442,8 @@ def test_order_file_volume_performance(self):
                 'volumeSize': 1000,
                 'quantity': 1,
                 'location': 449494,
-                'iops': 2000
+                'iops': 2000,
+                'useHourlyPricing': False
             },)
         )
 
@@ -429,7 +487,8 @@ def test_order_file_volume_endurance(self):
                 ],
                 'volumeSize': 1000,
                 'quantity': 1,
-                'location': 449494
+                'location': 449494,
+                'useHourlyPricing': False
             },)
         )
 
@@ -461,7 +520,8 @@ def test_order_file_snapshot_space_upgrade(self):
                 ],
                 'quantity': 1,
                 'location': 449500,
-                'volumeId': 102
+                'volumeId': 102,
+                'useHourlyPricing': False
             },)
         )
 
@@ -493,7 +553,8 @@ def test_order_file_snapshot_space(self):
                 ],
                 'quantity': 1,
                 'location': 449500,
-                'volumeId': 102
+                'volumeId': 102,
+                'useHourlyPricing': False
             },)
         )
 
@@ -536,7 +597,8 @@ def test_order_file_replicant_performance(self):
                 'location': 449494,
                 'iops': 1000,
                 'originVolumeId': 102,
-                'originVolumeScheduleId': 978
+                'originVolumeScheduleId': 978,
+                'useHourlyPricing': False
             },)
         )
 
@@ -578,7 +640,8 @@ def test_order_file_replicant_endurance(self):
                 'quantity': 1,
                 'location': 449494,
                 'originVolumeId': 102,
-                'originVolumeScheduleId': 978
+                'originVolumeScheduleId': 978,
+                'useHourlyPricing': False
             },)
         )
 
@@ -617,7 +680,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 +726,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 +764,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 +809,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 f07e21f2..1df9547c 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(
@@ -2743,7 +2744,8 @@ def test_prep_snapshot_order_saas_endurance_upgrade(self):
             'prices': [{'id': 193853}],
             'quantity': 1,
             'location': 449500,
-            'volumeId': 102
+            'volumeId': 102,
+            'useHourlyPricing': False
         }
 
         result = storage_utils.prepare_snapshot_order_object(
@@ -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,8 @@ 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(
@@ -2874,6 +2879,33 @@ def test_prep_snapshot_order_enterprise(self):
 
         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(
+            self.block, mock_volume, 20, None, False
+        )
+
+        self.assertEqual(expected_object, result)
+
+        mock_volume['billingItem']['hourlyFlag'] = prev_hourly_flag
+
     # ---------------------------------------------------------------------
     # Tests for prepare_volume_order_object()
     # ---------------------------------------------------------------------
@@ -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,7 +3069,8 @@ def test_prep_volume_order_saas_endurance(self):
             ],
             'quantity': 1,
             'location': 29,
-            'volumeSize': 1000
+            'volumeSize': 1000,
+            'useHourlyPricing': False
         }
 
         result = storage_utils.prepare_volume_order_object(
@@ -3064,7 +3099,8 @@ def test_prep_volume_order_saas_endurance_with_snapshot(self):
             ],
             'quantity': 1,
             'location': 29,
-            'volumeSize': 1000
+            'volumeSize': 1000,
+            'useHourlyPricing': False
         }
 
         result = storage_utils.prepare_volume_order_object(
@@ -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,7 +3375,8 @@ 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(
@@ -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(
@@ -3535,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()
     # ---------------------------------------------------------------------
@@ -3657,7 +3740,8 @@ def test_prep_duplicate_order_origin_originalVolumeSize_empty_block(self):
             'volumeSize': 500,
             'quantity': 1,
             'location': 449500,
-            'duplicateOriginVolumeId': 102}
+            'duplicateOriginVolumeId': 102,
+            'useHourlyPricing': False}
 
         result = storage_utils.prepare_duplicate_order_object(
             self.block, mock_volume, None, None, None, None, 'block')
@@ -3797,7 +3881,8 @@ def test_prep_duplicate_order_performance_use_default_origin_values(self):
             'quantity': 1,
             'location': 449500,
             'duplicateOriginVolumeId': 102,
-            'iops': 1000}
+            'iops': 1000,
+            'useHourlyPricing': False}
 
         result = storage_utils.prepare_duplicate_order_object(
             self.file, mock_volume, None, None, None, None, 'file')
@@ -3829,7 +3914,8 @@ def test_prep_duplicate_order_performance_block(self):
             'quantity': 1,
             'location': 449500,
             'duplicateOriginVolumeId': 102,
-            'iops': 2000}
+            'iops': 2000,
+            'useHourlyPricing': False}
 
         result = storage_utils.prepare_duplicate_order_object(
             self.block, mock_volume, 2000, None, 1000, 10, 'block')
@@ -3861,7 +3947,8 @@ def test_prep_duplicate_order_performance_file(self):
             'quantity': 1,
             'location': 449500,
             'duplicateOriginVolumeId': 102,
-            'iops': 2000}
+            'iops': 2000,
+            'useHourlyPricing': False}
 
         result = storage_utils.prepare_duplicate_order_object(
             self.file, mock_volume, 2000, None, 1000, 10, 'file')
@@ -3954,7 +4041,8 @@ def test_prep_duplicate_order_endurance_use_default_origin_values(self):
             'volumeSize': 500,
             'quantity': 1,
             'location': 449500,
-            'duplicateOriginVolumeId': 102}
+            'duplicateOriginVolumeId': 102,
+            'useHourlyPricing': False}
 
         result = storage_utils.prepare_duplicate_order_object(
             self.file, mock_volume, None, None, None, None, 'file')
@@ -3983,7 +4071,8 @@ def test_prep_duplicate_order_endurance_block(self):
             'volumeSize': 1000,
             'quantity': 1,
             'location': 449500,
-            'duplicateOriginVolumeId': 102}
+            'duplicateOriginVolumeId': 102,
+            'useHourlyPricing': False}
 
         result = storage_utils.prepare_duplicate_order_object(
             self.block, mock_volume, None, 4.0, 1000, 10, 'block')
@@ -4012,7 +4101,8 @@ def test_prep_duplicate_order_endurance_file(self):
             'volumeSize': 1000,
             'quantity': 1,
             'location': 449500,
-            'duplicateOriginVolumeId': 102}
+            'duplicateOriginVolumeId': 102,
+            'useHourlyPricing': False}
 
         result = storage_utils.prepare_duplicate_order_object(
             self.file, mock_volume, None, 4.0, 1000, 10, 'file')