Repository: softlayer/softlayer-python
Pull request #752 (2016-08-17 01:33:10)

Add File & Block replication commands and output
This pull request includes several new commands and updates
to CLI output for supporting File and Block volume replication.

Code developed collaboratively by:
    ccorales95  Cristina Corales <ccorales95@gmail.com>
    kyubifire   Nilo Lisboa <nilo_bl@hotmail.com>
    VuKevin     Kevin Vu <VuKevin@icloud.com>
    dpickle2    David Pickle <dpickle29@gmail.com>

* Add support for ordering replicant volumes [Block & File]

* Add support for replicant failover & failback [Block & File]

* Add additional output for commands:
    Add replicant info to volume-detail output [Block & File]
    Show replicant volumes in volume-list output [Block & File]

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

Coverage increased (+0.5%) to 81.507% when pulling **8a62c2c55fdf5e6572be9c8ae22eedd62e8eb738 on kyubifire:replication** into **b07a1908540da81b6415d734908986427161a835 on softlayer:master**.


Patch:
diff --git a/CONTRIBUTORS b/CONTRIBUTORS
index 88442234..6197a85f 100644
--- a/CONTRIBUTORS
+++ b/CONTRIBUTORS
@@ -11,6 +11,7 @@ Jason Johnson <spligak@gmail.com>
 Kevin Landreth <klandreth@softlayer.com>
 Kevin McDonald <kmcdonald@softlayer.com>
 Łukasz Oleś <loles@mirantis.com>
+Michael Fork <mjfork@us.ibm.com>
 Nathan Beittenmiller <nbeittenmiller@softlayer.com>
 Neetu Jain <njain@softlayer.com>
 Paul Sroufe <psroufe@softlayer.com>
diff --git a/SoftLayer/CLI/block/detail.py b/SoftLayer/CLI/block/detail.py
index 37a68430..51a1fbd4 100644
--- a/SoftLayer/CLI/block/detail.py
+++ b/SoftLayer/CLI/block/detail.py
@@ -66,4 +66,37 @@ def cli(env, volume_id):
                 'Ongoing Transactions',
                 trans['transactionStatus']['friendlyName']])
 
+    table.add_row(['Replicant Count', "%u"
+                   % block_volume['replicationPartnerCount']])
+
+    if block_volume['replicationPartnerCount'] > 0:
+        # This if/else temporarily handles a bug in which the SL API
+        # returns a string or object for 'replicationStatus'; it seems that
+        # the type is string for File volumes and object for Block volumes
+        if 'message' in block_volume['replicationStatus']:
+            table.add_row(['Replication Status', "%s"
+                           % block_volume['replicationStatus']['message']])
+        else:
+            table.add_row(['Replication Status', "%s"
+                           % block_volume['replicationStatus']])
+
+        replicant_list = []
+        for replicant in block_volume['replicationPartners']:
+            replicant_table = formatting.Table(['Replicant ID',
+                                                replicant['id']])
+            replicant_table.add_row([
+                'Volume Name',
+                replicant['username']])
+            replicant_table.add_row([
+                'Target IP',
+                replicant['serviceResourceBackendIpAddress']])
+            replicant_table.add_row([
+                'Data Center',
+                replicant['serviceResource']['datacenter']['name']])
+            replicant_table.add_row([
+                'Schedule',
+                replicant['replicationSchedule']['type']['keyname']])
+            replicant_list.append(replicant_table)
+        table.add_row(['Replicant Volumes', replicant_list])
+
     env.fout(table)
diff --git a/SoftLayer/CLI/block/list.py b/SoftLayer/CLI/block/list.py
index afb008a9..41e09ca3 100644
--- a/SoftLayer/CLI/block/list.py
+++ b/SoftLayer/CLI/block/list.py
@@ -28,6 +28,9 @@
     column_helper.Column('lunId', ('lunId',), mask="lunId"),
     column_helper.Column('active_transactions', ('activeTransactionCount',),
                          mask="activeTransactionCount"),
+    column_helper.Column(
+        'created_by',
+        ('billingItem', 'orderItem', 'order', 'userRecord', 'username')),
 ]
 
 DEFAULT_COLUMNS = [
diff --git a/SoftLayer/CLI/block/order.py b/SoftLayer/CLI/block/order.py
index 4393a282..22430e42 100644
--- a/SoftLayer/CLI/block/order.py
+++ b/SoftLayer/CLI/block/order.py
@@ -7,7 +7,7 @@
 from SoftLayer.CLI import exceptions
 
 
-CONTEXT_SETTINGS = dict(token_normalize_func=lambda x: x.upper())
+CONTEXT_SETTINGS = {'token_normalize_func': lambda x: x.upper()}
 
 
 @click.command(context_settings=CONTEXT_SETTINGS)
@@ -42,8 +42,14 @@
 @click.option('--location',
               help='Datacenter short name (e.g.: dal09)',
               required=True)
+@click.option('--snapshot-size',
+              type=int,
+              help='Optional parameter for ordering snapshot '
+              'space along with endurance block storage; specifies '
+              'the size (in GB) of snapshot space to order')
 @environment.pass_env
-def cli(env, storage_type, size, iops, tier, os_type, location):
+def cli(env, storage_type, size, iops, tier, os_type,
+        location, snapshot_size):
     """Order a block storage volume."""
     block_manager = SoftLayer.BlockStorageManager(env.client)
     storage_type = storage_type.lower()
@@ -62,6 +68,12 @@ def cli(env, storage_type, size, iops, tier, os_type, location):
                 'Option --iops must be a multiple of 100'
             )
 
+        if snapshot_size is not None:
+            raise exceptions.CLIAbort(
+                'Option --snapshot-size not allowed for performance volumes.'
+                ' Snapshots are only available for endurance storage.'
+            )
+
         try:
             order = block_manager.order_block_volume(
                 storage_type='performance_storage_iscsi',
@@ -84,7 +96,8 @@ def cli(env, storage_type, size, iops, tier, os_type, location):
                 location=location,
                 size=size,
                 tier_level=float(tier),
-                os_type=os_type
+                os_type=os_type,
+                snapshot_size=snapshot_size
             )
         except ValueError as ex:
             raise exceptions.ArgumentError(str(ex))
diff --git a/SoftLayer/CLI/block/replication/__init__.py b/SoftLayer/CLI/block/replication/__init__.py
new file mode 100644
index 00000000..ac0a44b7
--- /dev/null
+++ b/SoftLayer/CLI/block/replication/__init__.py
@@ -0,0 +1,1 @@
+"""Block Storage Replication Control."""
diff --git a/SoftLayer/CLI/block/replication/failback.py b/SoftLayer/CLI/block/replication/failback.py
new file mode 100644
index 00000000..3887c29c
--- /dev/null
+++ b/SoftLayer/CLI/block/replication/failback.py
@@ -0,0 +1,25 @@
+"""Failback from a replicant volume."""
+# :license: MIT, see LICENSE for more details.
+
+import click
+import SoftLayer
+from SoftLayer.CLI import environment
+
+
+@click.command()
+@click.argument('volume-id')
+@click.option('--replicant-id', help="ID of the replicant volume")
+@environment.pass_env
+def cli(env, volume_id, replicant_id):
+    """Failback a block volume from the given replicant volume."""
+    block_storage_manager = SoftLayer.BlockStorageManager(env.client)
+
+    success = block_storage_manager.failback_from_replicant(
+        volume_id,
+        replicant_id
+    )
+
+    if success:
+        click.echo("Failback from replicant is now in progress.")
+    else:
+        click.echo("Failback operation could not be initiated.")
diff --git a/SoftLayer/CLI/block/replication/failover.py b/SoftLayer/CLI/block/replication/failover.py
new file mode 100644
index 00000000..545175c4
--- /dev/null
+++ b/SoftLayer/CLI/block/replication/failover.py
@@ -0,0 +1,30 @@
+"""Failover to a replicant volume."""
+# :license: MIT, see LICENSE for more details.
+
+import click
+import SoftLayer
+from SoftLayer.CLI import environment
+
+
+@click.command()
+@click.argument('volume-id')
+@click.option('--replicant-id', help="ID of the replicant volume")
+@click.option('--immediate',
+              is_flag=True,
+              default=False,
+              help="Failover to replicant immediately.")
+@environment.pass_env
+def cli(env, volume_id, replicant_id, immediate):
+    """Failover a block volume to the given replicant volume."""
+    block_storage_manager = SoftLayer.BlockStorageManager(env.client)
+
+    success = block_storage_manager.failover_to_replicant(
+        volume_id,
+        replicant_id,
+        immediate
+    )
+
+    if success:
+        click.echo("Failover to replicant is now in progress.")
+    else:
+        click.echo("Failover operation could not be initiated.")
diff --git a/SoftLayer/CLI/block/replication/order.py b/SoftLayer/CLI/block/replication/order.py
new file mode 100644
index 00000000..c25a8beb
--- /dev/null
+++ b/SoftLayer/CLI/block/replication/order.py
@@ -0,0 +1,65 @@
+"""Order a block storage replica volume."""
+# :license: MIT, see LICENSE for more details.
+
+import click
+import SoftLayer
+from SoftLayer.CLI import environment
+from SoftLayer.CLI import exceptions
+
+
+CONTEXT_SETTINGS = {'token_normalize_func': lambda x: x.upper()}
+
+
+@click.command(context_settings=CONTEXT_SETTINGS)
+@click.argument('volume_id')
+@click.option('--snapshot-schedule', '-s',
+              help='Snapshot schedule to use for replication, '
+              '(HOURLY | DAILY | WEEKLY)',
+              required=True,
+              type=click.Choice(['HOURLY', 'DAILY', 'WEEKLY']))
+@click.option('--location', '-l',
+              help='Short name of the data center for the replicant '
+              '(e.g.: dal09)',
+              required=True)
+@click.option('--tier',
+              help='Endurance Storage Tier (IOPS per GB) of the primary'
+              ' volume for which a replicant is ordered [optional]',
+              type=click.Choice(['0.25', '2', '4']))
+@click.option('--os-type',
+              help='Operating System Type (e.g.: LINUX) of the primary'
+              ' volume for which a replica is ordered [optional]',
+              type=click.Choice([
+                  'HYPER_V',
+                  'LINUX',
+                  'VMWARE',
+                  'WINDOWS_2008',
+                  'WINDOWS_GPT',
+                  'WINDOWS',
+                  'XEN']))
+@environment.pass_env
+def cli(env, volume_id, snapshot_schedule, location, tier, os_type):
+    """Order a block storage replica volume."""
+    block_manager = SoftLayer.BlockStorageManager(env.client)
+
+    if tier is not None:
+        tier = float(tier)
+
+    try:
+        order = block_manager.order_replicant_volume(
+            volume_id,
+            snapshot_schedule=snapshot_schedule,
+            location=location,
+            tier=tier,
+            os_type=os_type,
+        )
+    except ValueError as ex:
+        raise exceptions.ArgumentError(str(ex))
+
+    if 'placedOrder' in order.keys():
+        click.echo("Order #{0} placed successfully!".format(
+            order['placedOrder']['id']))
+        for item in order['placedOrder']['items']:
+            click.echo(" > %s" % item['description'])
+    else:
+        click.echo("Order could not be placed! Please verify your options " +
+                   "and try again.")
diff --git a/SoftLayer/CLI/block/snapshot/create.py b/SoftLayer/CLI/block/snapshot/create.py
index 4e327b0d..68dc976f 100644
--- a/SoftLayer/CLI/block/snapshot/create.py
+++ b/SoftLayer/CLI/block/snapshot/create.py
@@ -16,5 +16,9 @@ def cli(env, volume_id, notes):
     block_manager = SoftLayer.BlockStorageManager(env.client)
     snapshot = block_manager.create_snapshot(volume_id, notes=notes)
 
-    if snapshot['id']:
+    if 'id' in snapshot:
         click.echo('New snapshot created with id: %s' % snapshot['id'])
+    else:
+        click.echo('Error occurred while creating snapshot.\n'
+                   'Ensure volume is not failed over or in another '
+                   'state which prevents taking snapshots.')
diff --git a/SoftLayer/CLI/file/detail.py b/SoftLayer/CLI/file/detail.py
index e8ceda92..b73ecc0d 100644
--- a/SoftLayer/CLI/file/detail.py
+++ b/SoftLayer/CLI/file/detail.py
@@ -75,4 +75,37 @@ def cli(env, volume_id):
                 'Ongoing Transactions',
                 trans['transactionStatus']['friendlyName']])
 
+    table.add_row(['Replicant Count', "%u"
+                   % file_volume['replicationPartnerCount']])
+
+    if file_volume['replicationPartnerCount'] > 0:
+        # This if/else temporarily handles a bug in which the SL API
+        # returns a string or object for 'replicationStatus'; it seems that
+        # the type is string for File volumes and object for Block volumes
+        if 'message' in file_volume['replicationStatus']:
+            table.add_row(['Replication Status', "%s"
+                           % file_volume['replicationStatus']['message']])
+        else:
+            table.add_row(['Replication Status', "%s"
+                           % file_volume['replicationStatus']])
+
+        replicant_list = []
+        for replicant in file_volume['replicationPartners']:
+            replicant_table = formatting.Table(['Replicant ID',
+                                                replicant['id']])
+            replicant_table.add_row([
+                'Volume Name',
+                replicant['username']])
+            replicant_table.add_row([
+                'Target IP',
+                replicant['serviceResourceBackendIpAddress']])
+            replicant_table.add_row([
+                'Data Center',
+                replicant['serviceResource']['datacenter']['name']])
+            replicant_table.add_row([
+                'Schedule',
+                replicant['replicationSchedule']['type']['keyname']])
+            replicant_list.append(replicant_table)
+        table.add_row(['Replicant Volumes', replicant_list])
+
     env.fout(table)
diff --git a/SoftLayer/CLI/file/list.py b/SoftLayer/CLI/file/list.py
index 4b12de18..a3266362 100644
--- a/SoftLayer/CLI/file/list.py
+++ b/SoftLayer/CLI/file/list.py
@@ -27,6 +27,9 @@
                          mask="serviceResourceBackendIpAddress"),
     column_helper.Column('active_transactions', ('activeTransactionCount',),
                          mask="activeTransactionCount"),
+    column_helper.Column(
+        'created_by',
+        ('billingItem', 'orderItem', 'order', 'userRecord', 'username')),
 ]
 
 DEFAULT_COLUMNS = [
diff --git a/SoftLayer/CLI/file/order.py b/SoftLayer/CLI/file/order.py
index bb200f27..0219efdb 100644
--- a/SoftLayer/CLI/file/order.py
+++ b/SoftLayer/CLI/file/order.py
@@ -7,7 +7,7 @@
 from SoftLayer.CLI import exceptions
 
 
-CONTEXT_SETTINGS = dict(token_normalize_func=lambda x: x.upper())
+CONTEXT_SETTINGS = {'token_normalize_func': lambda x: x.upper()}
 
 
 @click.command(context_settings=CONTEXT_SETTINGS)
@@ -42,8 +42,14 @@
 @click.option('--location',
               help='Datacenter short name (e.g.: dal09)',
               required=True)
+@click.option('--snapshot-size',
+              type=int,
+              help='Optional parameter for ordering snapshot '
+              'space along with endurance file storage; specifies '
+              'the size (in GB) of snapshot space to order')
 @environment.pass_env
-def cli(env, storage_type, size, iops, tier, os_type, location):
+def cli(env, storage_type, size, iops, tier, os_type,
+        location, snapshot_size):
     """Order a file storage volume."""
     file_manager = SoftLayer.FileStorageManager(env.client)
     storage_type = storage_type.lower()
@@ -62,6 +68,12 @@ def cli(env, storage_type, size, iops, tier, os_type, location):
                 'Option --iops must be a multiple of 100'
             )
 
+        if snapshot_size is not None:
+            raise exceptions.CLIAbort(
+                'Option --snapshot-size not allowed for performance volumes.'
+                ' Snapshots are only available for endurance storage.'
+            )
+
         try:
             order = file_manager.order_file_volume(
                 storage_type='performance_storage_nfs',
@@ -84,7 +96,8 @@ def cli(env, storage_type, size, iops, tier, os_type, location):
                 location=location,
                 size=size,
                 tier_level=float(tier),
-                os_type=os_type
+                os_type=os_type,
+                snapshot_size=snapshot_size
             )
         except ValueError as ex:
             raise exceptions.ArgumentError(str(ex))
diff --git a/SoftLayer/CLI/file/replication/__init__.py b/SoftLayer/CLI/file/replication/__init__.py
new file mode 100644
index 00000000..11030ec3
--- /dev/null
+++ b/SoftLayer/CLI/file/replication/__init__.py
@@ -0,0 +1,1 @@
+"""File Storage Replication Control."""
diff --git a/SoftLayer/CLI/file/replication/failback.py b/SoftLayer/CLI/file/replication/failback.py
new file mode 100644
index 00000000..6fc2e9b7
--- /dev/null
+++ b/SoftLayer/CLI/file/replication/failback.py
@@ -0,0 +1,26 @@
+"""Failback from a replicant volume."""
+# :license: MIT, see LICENSE for more details.
+
+import click
+
+import SoftLayer
+from SoftLayer.CLI import environment
+
+
+@click.command()
+@click.argument('volume-id')
+@click.option('--replicant-id', help="ID of the replicant volume")
+@environment.pass_env
+def cli(env, volume_id, replicant_id):
+    """Failback a file volume from the given replicant volume."""
+    file_storage_manager = SoftLayer.FileStorageManager(env.client)
+
+    success = file_storage_manager.failback_from_replicant(
+        volume_id,
+        replicant_id
+    )
+
+    if success:
+        click.echo("Failback from replicant is now in progress.")
+    else:
+        click.echo("Failback operation could not be initiated.")
diff --git a/SoftLayer/CLI/file/replication/failover.py b/SoftLayer/CLI/file/replication/failover.py
new file mode 100644
index 00000000..d5695fb5
--- /dev/null
+++ b/SoftLayer/CLI/file/replication/failover.py
@@ -0,0 +1,30 @@
+"""Failover to a replicant volume."""
+# :license: MIT, see LICENSE for more details.
+
+import click
+import SoftLayer
+from SoftLayer.CLI import environment
+
+
+@click.command()
+@click.argument('volume-id')
+@click.option('--replicant-id', help="ID of the replicant volume")
+@click.option('--immediate',
+              is_flag=True,
+              default=False,
+              help="Failover to replicant immediately.")
+@environment.pass_env
+def cli(env, volume_id, replicant_id, immediate):
+    """Failover a file volume to the given replicant volume."""
+    file_storage_manager = SoftLayer.FileStorageManager(env.client)
+
+    success = file_storage_manager.failover_to_replicant(
+        volume_id,
+        replicant_id,
+        immediate
+    )
+
+    if success:
+        click.echo("Failover to replicant is now in progress.")
+    else:
+        click.echo("Failover operation could not be initiated.")
diff --git a/SoftLayer/CLI/file/replication/order.py b/SoftLayer/CLI/file/replication/order.py
new file mode 100644
index 00000000..7c4617fa
--- /dev/null
+++ b/SoftLayer/CLI/file/replication/order.py
@@ -0,0 +1,53 @@
+"""Order a file storage replica volume."""
+# :license: MIT, see LICENSE for more details.
+
+import click
+import SoftLayer
+from SoftLayer.CLI import environment
+from SoftLayer.CLI import exceptions
+
+
+CONTEXT_SETTINGS = {'token_normalize_func': lambda x: x.upper()}
+
+
+@click.command(context_settings=CONTEXT_SETTINGS)
+@click.argument('volume_id')
+@click.option('--snapshot-schedule', '-s',
+              help='Snapshot schedule to use for replication, '
+              '(HOURLY | DAILY | WEEKLY)',
+              required=True,
+              type=click.Choice(['HOURLY', 'DAILY', 'WEEKLY']))
+@click.option('--location', '-l',
+              help='Short name of the data center for the replicant '
+              '(e.g.: dal09)',
+              required=True)
+@click.option('--tier',
+              help='Endurance Storage Tier (IOPS per GB) of the primary'
+              ' volume for which a replicant is ordered [optional]',
+              type=click.Choice(['0.25', '2', '4']))
+@environment.pass_env
+def cli(env, volume_id, snapshot_schedule, location, tier):
+    """Order a file storage replica volume."""
+    file_manager = SoftLayer.FileStorageManager(env.client)
+
+    if tier is not None:
+        tier = float(tier)
+
+    try:
+        order = file_manager.order_replicant_volume(
+            volume_id,
+            snapshot_schedule=snapshot_schedule,
+            location=location,
+            tier=tier,
+        )
+    except ValueError as ex:
+        raise exceptions.ArgumentError(str(ex))
+
+    if 'placedOrder' in order.keys():
+        click.echo("Order #{0} placed successfully!".format(
+            order['placedOrder']['id']))
+        for item in order['placedOrder']['items']:
+            click.echo(" > %s" % item['description'])
+    else:
+        click.echo("Order could not be placed! Please verify your options " +
+                   "and try again.")
diff --git a/SoftLayer/CLI/file/snapshot/create.py b/SoftLayer/CLI/file/snapshot/create.py
index 9c33d026..ec7ff1ff 100644
--- a/SoftLayer/CLI/file/snapshot/create.py
+++ b/SoftLayer/CLI/file/snapshot/create.py
@@ -16,5 +16,9 @@ def cli(env, volume_id, notes):
     file_storage_manager = SoftLayer.FileStorageManager(env.client)
     snapshot = file_storage_manager.create_snapshot(volume_id, notes=notes)
 
-    if snapshot['id']:
+    if 'id' in snapshot:
         click.echo('New snapshot created with id: %s' % snapshot['id'])
+    else:
+        click.echo('Error occurred while creating snapshot.\n'
+                   'Ensure volume is not failed over or in another '
+                   'state which prevents taking snapshots.')
diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py
index 4e5f0ecf..03175c77 100644
--- a/SoftLayer/CLI/routes.py
+++ b/SoftLayer/CLI/routes.py
@@ -60,6 +60,9 @@
     ('block:access-authorize', 'SoftLayer.CLI.block.access.authorize:cli'),
     ('block:access-list', 'SoftLayer.CLI.block.access.list:cli'),
     ('block:access-revoke', 'SoftLayer.CLI.block.access.revoke:cli'),
+    ('block:replica-failback', 'SoftLayer.CLI.block.replication.failback:cli'),
+    ('block:replica-failover', 'SoftLayer.CLI.block.replication.failover:cli'),
+    ('block:replica-order', 'SoftLayer.CLI.block.replication.order:cli'),
     ('block:snapshot-cancel', 'SoftLayer.CLI.block.snapshot.cancel:cli'),
     ('block:snapshot-create', 'SoftLayer.CLI.block.snapshot.create:cli'),
     ('block:snapshot-delete', 'SoftLayer.CLI.block.snapshot.delete:cli'),
@@ -77,6 +80,9 @@
     ('file:access-authorize', 'SoftLayer.CLI.file.access.authorize:cli'),
     ('file:access-list', 'SoftLayer.CLI.file.access.list:cli'),
     ('file:access-revoke', 'SoftLayer.CLI.file.access.revoke:cli'),
+    ('file:replica-failback', 'SoftLayer.CLI.file.replication.failback:cli'),
+    ('file:replica-failover', 'SoftLayer.CLI.file.replication.failover:cli'),
+    ('file:replica-order', 'SoftLayer.CLI.file.replication.order:cli'),
     ('file:snapshot-cancel', 'SoftLayer.CLI.file.snapshot.cancel:cli'),
     ('file:snapshot-create', 'SoftLayer.CLI.file.snapshot.create:cli'),
     ('file:snapshot-delete', 'SoftLayer.CLI.file.snapshot.delete:cli'),
diff --git a/SoftLayer/fixtures/SoftLayer_Network_Storage.py b/SoftLayer/fixtures/SoftLayer_Network_Storage.py
index b87b85fa..52a178e3 100644
--- a/SoftLayer/fixtures/SoftLayer_Network_Storage.py
+++ b/SoftLayer/fixtures/SoftLayer_Network_Storage.py
@@ -2,10 +2,12 @@
     'accountId': 1234,
     'billingItem': {
         'id': 449,
+        'cancellationDate': '',
         'categoryCode': 'storage_service_enterprise',
         'activeChildren': [{
             'categoryCode': 'storage_snapshot_space',
-            'id': 123
+            'id': 123,
+            'cancellationDate': '',
         }]
     },
     'capacityGb': 20,
@@ -20,8 +22,13 @@
     'serviceProviderId': 1,
     'iops': 1000,
     'storageTierLevel': {'description': '2 IOPS per GB'},
-    'snapshotCapacityGb': 10,
+    'snapshotCapacityGb': '10',
     'parentVolume': {'snapshotSizeBytes': 1024},
+    'osType': {'keyName': 'LINUX'},
+    'schedules': [{
+        'id': 978,
+        'type': {'keyname': 'SNAPSHOT_WEEKLY'},
+    }],
     'serviceResource': {'datacenter': {'id': 449500, 'name': 'dal05'}},
     'serviceResourceBackendIpAddress': '10.1.2.3',
     'serviceResourceName': 'Storage Type 01 Aggregate staaspar0101_pc01',
@@ -70,6 +77,21 @@
             'credential': {'username': 'joe', 'password': '12345'},
         },
     }],
+    'replicationStatus': 'Replicant Volume Provisioning has completed.',
+    'replicationPartnerCount': 1,
+    'replicationPartners': [{
+        'id': 1784,
+        'username': 'TEST_REP_1',
+        'serviceResourceBackendIpAddress': '10.3.174.79',
+        'serviceResource': {'datacenter': {'name': 'wdc01'}},
+        'replicationSchedule': {'type': {'keyname': 'REPLICATION_HOURLY'}},
+    }, {
+        'id': 1785,
+        'username': 'TEST_REP_2',
+        'serviceResourceBackendIpAddress': '10.3.177.84',
+        'serviceResource': {'datacenter': {'name': 'dal01'}},
+        'replicationSchedule': {'type': {'keyname': 'REPLICATION_DAILY'}},
+    }],
 }
 
 getSnapshots = [{
@@ -82,7 +104,8 @@
 deleteObject = True
 allowAccessFromHostList = True
 removeAccessFromHostList = True
-
+failoverToReplicant = True
+failbackFromReplicant = True
 restoreFromSnapshot = True
 
 createSnapshot = {
diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py
index 10f170f3..5a79e311 100644
--- a/SoftLayer/managers/block.py
+++ b/SoftLayer/managers/block.py
@@ -46,7 +46,7 @@ def list_block_volumes(self, datacenter=None, username=None,
             (utils.query_filter('!~ ISCSI'))
 
         _filter['iscsiNetworkStorage']['storageType']['keyName'] = (
-            utils.query_filter('*BLOCK_STORAGE'))
+            utils.query_filter('*BLOCK_STORAGE*'))
         if storage_type:
             _filter['iscsiNetworkStorage']['storageType']['keyName'] = (
                 utils.query_filter('%s_BLOCK_STORAGE' % storage_type.upper()))
@@ -86,6 +86,12 @@ def get_block_volume_details(self, volume_id, **kwargs):
                 'lunId',
                 'activeTransactionCount',
                 'activeTransactions.transactionStatus[friendlyName]',
+                'replicationPartnerCount',
+                'replicationStatus',
+                'replicationPartners[id,username,'
+                'serviceResourceBackendIpAddress,'
+                'serviceResource[datacenter[name]],'
+                'replicationSchedule[type[keyname]]]',
             ]
             kwargs['mask'] = ','.join(items)
         return self.client.call('Network_Storage', 'getObject',
@@ -184,6 +190,43 @@ def deauthorize_host_to_volume(self, volume_id,
         return self.client.call('Network_Storage', 'removeAccessFromHostList',
                                 host_templates, id=volume_id, **kwargs)
 
+    def order_replicant_volume(self, volume_id, snapshot_schedule,
+                               location, tier=None, os_type=None):
+        """Places an order for a replicant block volume.
+
+        :param volume_id: The ID of the primary volume to be replicated
+        :param snapshot_schedule: The primary volume's snapshot
+                                  schedule to use for replication
+        :param location: The location for the ordered replicant volume
+        :param tier: The tier (IOPS per GB) of the primary volume
+        :param os_type: The OS type of the primary volume
+        :return: Returns a SoftLayer_Container_Product_Order_Receipt
+        """
+
+        block_mask = 'billingItem[activeChildren],storageTierLevel,'\
+                     'osType,snapshotCapacityGb,schedules,'\
+                     'hourlySchedule,dailySchedule,weeklySchedule'
+        block_volume = self.get_block_volume_details(volume_id,
+                                                     mask=block_mask)
+
+        if os_type is None:
+            if isinstance(utils.lookup(block_volume, 'osType', 'keyName'),
+                          str):
+                os_type = block_volume['osType']['keyName']
+            else:
+                raise exceptions.SoftLayerError(
+                    "Cannot find primary volume's os-type "
+                    "automatically; must specify manually")
+
+        order = storage_utils.prepare_replicant_order_object(
+            self, volume_id, snapshot_schedule, location, tier,
+            block_volume, 'block'
+        )
+
+        order['osFormatType'] = {'keyName': os_type}
+
+        return self.client.call('Product_Order', 'placeOrder', order)
+
     def delete_snapshot(self, snapshot_id):
         """Deletes the specified snapshot object.
 
@@ -193,7 +236,7 @@ def delete_snapshot(self, snapshot_id):
                                 id=snapshot_id)
 
     def order_block_volume(self, storage_type, location, size, os_type,
-                           iops=None, tier_level=None):
+                           iops=None, tier_level=None, snapshot_size=None):
         """Places an order for a block volume.
 
         :param storage_type: "performance_storage_iscsi" (performance)
@@ -203,6 +246,8 @@ def order_block_volume(self, storage_type, location, size, os_type,
         :param os_type: OS Type to use for volume alignment, see help for list
         :param iops: Number of IOPs for a "Performance" order
         :param tier_level: Tier level to use for an "Endurance" order
+        :param snapshot_size: The size of optional snapshot space,
+        if snapshot space should also be ordered (None if not ordered)
         """
 
         try:
@@ -239,6 +284,9 @@ def order_block_volume(self, storage_type, location, size, os_type,
                     ),
                 storage_utils.find_endurance_tier_price(package, tier_level),
             ]
+            if snapshot_size is not None:
+                prices.append(storage_utils.find_snapshot_space_price(
+                    package, snapshot_size, tier_level))
         else:
             raise exceptions.SoftLayerError(
                 "Block volume storage_type must be either "
@@ -413,3 +461,26 @@ def cancel_block_volume(self, volume_id,
             True,
             reason,
             id=billing_item_id)
+
+    def failover_to_replicant(self, volume_id, replicant_id, immediate=False):
+        """Failover to a volume replicant.
+
+        :param integer volume_id: The id of the volume
+        :param integer replicant_id: ID of replicant to failover to
+        :param boolean immediate: Flag indicating if failover is immediate
+        :return: Returns whether failover was successful or not
+        """
+
+        return self.client.call('Network_Storage', 'failoverToReplicant',
+                                replicant_id, immediate, id=volume_id)
+
+    def failback_from_replicant(self, volume_id, replicant_id):
+        """Failback from a volume replicant.
+
+        :param integer volume_id: The id of the volume
+        :param integer: ID of replicant to failback from
+        :return: Returns whether failback was successful or not
+        """
+
+        return self.client.call('Network_Storage', 'failbackFromReplicant',
+                                replicant_id, id=volume_id)
diff --git a/SoftLayer/managers/file.py b/SoftLayer/managers/file.py
index 9759b1c5..ae179206 100644
--- a/SoftLayer/managers/file.py
+++ b/SoftLayer/managers/file.py
@@ -45,7 +45,7 @@ def list_file_volumes(self, datacenter=None, username=None,
             (utils.query_filter('!~ NAS'))
 
         _filter['nasNetworkStorage']['storageType']['keyName'] = (
-            utils.query_filter('*FILE_STORAGE'))
+            utils.query_filter('*FILE_STORAGE*'))
         if storage_type:
             _filter['nasNetworkStorage']['storageType']['keyName'] = (
                 utils.query_filter('%s_FILE_STORAGE' % storage_type.upper()))
@@ -86,6 +86,12 @@ def get_file_volume_details(self, volume_id, **kwargs):
                 'lunId',
                 'activeTransactionCount',
                 'activeTransactions.transactionStatus[friendlyName]',
+                'replicationPartnerCount',
+                'replicationStatus',
+                'replicationPartners[id,username,'
+                'serviceResourceBackendIpAddress,'
+                'serviceResource[datacenter[name]],'
+                'replicationSchedule[type[keyname]]]',
             ]
             kwargs['mask'] = ','.join(items)
         return self.client.call('Network_Storage', 'getObject',
@@ -187,6 +193,31 @@ def deauthorize_host_to_volume(self, volume_id,
         return self.client.call('Network_Storage', 'removeAccessFromHostList',
                                 host_templates, id=volume_id, **kwargs)
 
+    def order_replicant_volume(self, volume_id, snapshot_schedule,
+                               location, tier=None):
+        """Places an order for a replicant file volume.
+
+        :param volume_id: The ID of the primary volume to be replicated
+        :param snapshot_schedule: The primary volume's snapshot
+                                  schedule to use for replication
+        :param location: The location for the ordered replicant volume
+        :param tier: The tier (IOPS per GB) of the primary volume
+        :return: Returns a SoftLayer_Container_Product_Order_Receipt
+        """
+
+        file_mask = 'billingItem[activeChildren],storageTierLevel,'\
+                    'osType,snapshotCapacityGb,schedules,'\
+                    'hourlySchedule,dailySchedule,weeklySchedule'
+        file_volume = self.get_file_volume_details(volume_id,
+                                                   mask=file_mask)
+
+        order = storage_utils.prepare_replicant_order_object(
+            self, volume_id, snapshot_schedule, location, tier,
+            file_volume, 'file'
+        )
+
+        return self.client.call('Product_Order', 'placeOrder', order)
+
     def delete_snapshot(self, snapshot_id):
         """Deletes the specified snapshot object.
 
@@ -196,7 +227,7 @@ def delete_snapshot(self, snapshot_id):
                                 id=snapshot_id)
 
     def order_file_volume(self, storage_type, location, size, os_type,
-                          iops=None, tier_level=None):
+                          iops=None, tier_level=None, snapshot_size=None):
         """Places an order for a file volume.
 
         :param storage_type: "performance_storage_iscsi" (performance)
@@ -206,6 +237,8 @@ def order_file_volume(self, storage_type, location, size, os_type,
         :param os_type: OS Type to use for volume alignment, see help for list
         :param iops: Number of IOPs for a "Performance" order
         :param tier_level: Tier level to use for an "Endurance" order
+        :param snapshot_size: The size of optional snapshot space,
+        if snapshot space should also be ordered (None if not ordered)
         """
 
         try:
@@ -242,6 +275,9 @@ def order_file_volume(self, storage_type, location, size, os_type,
                     ),
                 storage_utils.find_endurance_tier_price(package, tier_level),
             ]
+            if snapshot_size is not None:
+                prices.append(storage_utils.find_snapshot_space_price(
+                    package, snapshot_size, tier_level))
         else:
             raise exceptions.SoftLayerError(
                 "File volume storage_type must be either "
@@ -308,6 +344,7 @@ 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
         """
+
         package = storage_utils.get_package(self, 'storage_service_enterprise')
         file_mask = 'serviceResource.datacenter[id],'\
             'storageTierLevel,billingItem'
@@ -411,3 +448,26 @@ def cancel_file_volume(self, volume_id,
             True,
             reason,
             id=billing_item_id)
+
+    def failover_to_replicant(self, volume_id, replicant_id, immediate=False):
+        """Failover to a volume replicant.
+
+        :param integer volume_id: The id of the volume
+        :param integer replicant_id: ID of replicant to failover to
+        :param boolean immediate: Flag indicating if failover is immediate
+        :return: Returns whether failover was successful or not
+        """
+
+        return self.client.call('Network_Storage', 'failoverToReplicant',
+                                replicant_id, immediate, id=volume_id)
+
+    def failback_from_replicant(self, volume_id, replicant_id):
+        """Failback from a volume replicant.
+
+        :param integer volume_id: The id of the volume
+        :param integer: ID of replicant to failback from
+        :return: Returns whether failback was successful or not
+        """
+
+        return self.client.call('Network_Storage', 'failbackFromReplicant',
+                                replicant_id, id=volume_id)
diff --git a/SoftLayer/managers/storage_utils.py b/SoftLayer/managers/storage_utils.py
index bb23703b..0ee01ef9 100644
--- a/SoftLayer/managers/storage_utils.py
+++ b/SoftLayer/managers/storage_utils.py
@@ -5,6 +5,7 @@
 
     :license: MIT, see LICENSE for more details.
 """
+from SoftLayer import exceptions
 from SoftLayer import utils
 
 
@@ -282,6 +283,39 @@ def find_performance_iops_price(package, size, iops):
     raise ValueError("Could not find price for iops for the given volume")
 
 
+def find_replication_price(package, capacity, tier_level):
+    """Find the price in the given package for the desired replicant volume
+
+    :param package: The product package of the endurance storage type
+    :param capacity: The capacity of the primary storage volume
+    :param tier_level: The tier of the primary storage volume
+    :return: Returns the price for the given size, or an error if not found
+    """
+    for item in package['items']:
+        if int(item['capacity']) != capacity:
+            continue
+
+        for price in item['prices']:
+            # Only collect prices from valid location groups.
+            if price['locationGroupId'] != '':
+                continue
+
+            if not _has_category(price['categories'],
+                                 'performance_storage_replication'):
+                continue
+
+            level = ENDURANCE_TIERS.get(tier_level)
+            if level < int(price['capacityRestrictionMinimum']):
+                continue
+
+            if level > int(price['capacityRestrictionMaximum']):
+                continue
+
+            return price
+
+    raise ValueError("Could not find price for replicant volume")
+
+
 def find_snapshot_space_price(package, size, tier_level):
     """Find the price in the given package for the desired snapshot space size
 
@@ -315,6 +349,102 @@ def find_snapshot_space_price(package, size, tier_level):
     raise ValueError("Could not find price for snapshot space")
 
 
+def find_snapshot_schedule_id(volume, snapshot_schedule_keyname):
+    """Find the snapshot schedule ID for the given volume and keyname
+
+    :param volume: The volume for which the snapshot ID is desired
+    :param snapshot_schedule_keyname: The keyname of the snapshot schedule
+    :return: Returns an int value indicating the volume's snapshot schedule ID
+    """
+    for schedule in volume['schedules']:
+        if 'type' in schedule and 'keyname' in schedule['type']:
+            if schedule['type']['keyname'] == snapshot_schedule_keyname:
+                return schedule['id']
+
+    raise ValueError("The given snapshot schedule ID was not found for "
+                     "the given storage volume")
+
+
+def prepare_replicant_order_object(manager, volume_id, snapshot_schedule,
+                                   location, tier, volume, volume_type):
+    """Prepare the order object which is submitted to the placeOrder() method
+
+    :param manager: The File or Block manager calling this function
+    :param volume_id: The ID of the primary volume to be replicated
+    :param snapshot_schedule: The primary volume's snapshot
+                              schedule to use for replication
+    :param location: The location for the ordered replicant volume
+    :param tier: The tier (IOPS per GB) of the primary volume
+    :param volume: The primary volume as a SoftLayer_Network_Storage object
+    :param volume_type: The type of the primary volume ('file' or 'block')
+    :return: Returns the order object for the
+             Product_Order service's placeOrder() method
+    """
+
+    try:
+        location_id = get_location_id(manager, location)
+    except ValueError:
+        raise exceptions.SoftLayerError(
+            "Invalid data center name specified. "
+            "Please provide the lower case short name (e.g.: dal09)")
+
+    volume_capacity = int(volume['capacityGb'])
+    storage_type = volume['billingItem']['categoryCode']
+
+    if storage_type != 'storage_service_enterprise':
+        raise exceptions.SoftLayerError(
+            "Primary volume storage_type must be Endurance")
+
+    if 'snapshotCapacityGb' in volume:
+        volume_snapshot_capacity = int(volume['snapshotCapacityGb'])
+    else:
+        raise exceptions.SoftLayerError(
+            "Snapshot capacity not found for the given primary volume")
+
+    snapshot_schedule_id = find_snapshot_schedule_id(
+        volume,
+        'SNAPSHOT_' + snapshot_schedule
+    )
+
+    if volume['billingItem']['cancellationDate'] != '':
+        raise exceptions.SoftLayerError(
+            'This volume is set for cancellation; '
+            'unable to order replicant volume')
+
+    for child in volume['billingItem']['activeChildren']:
+        if child['categoryCode'] == 'storage_snapshot_space'\
+                and child['cancellationDate'] != '':
+            raise exceptions.SoftLayerError(
+                'The snapshot space for this volume is set for '
+                'cancellation; unable to order replicant volume')
+
+    if tier is None:
+        tier = find_endurance_tier_iops_per_gb(volume)
+
+    package = get_package(manager, storage_type)
+    prices = [
+        find_endurance_price(package, 'storage_service_enterprise'),
+        find_endurance_price(package, 'storage_' + volume_type),
+        find_endurance_tier_price(package, tier),
+        find_endurance_space_price(package, volume_capacity, tier),
+        find_snapshot_space_price(package, volume_snapshot_capacity, tier),
+        find_replication_price(package, volume_capacity, tier),
+    ]
+
+    replicant_order = {
+        'complexType': 'SoftLayer_Container_Product_Order_'
+                       'Network_Storage_Enterprise',
+        'packageId': package['id'],
+        'prices': prices,
+        'quantity': 1,
+        'location': location_id,
+        'originVolumeId': int(volume_id),
+        'originVolumeScheduleId': snapshot_schedule_id,
+    }
+
+    return replicant_order
+
+
 def _has_category(categories, category_code):
     return any(
         True


Test patch:
diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py
index b413a695..fbb225a0 100644
--- a/tests/CLI/modules/block_tests.py
+++ b/tests/CLI/modules/block_tests.py
@@ -73,14 +73,28 @@ def test_volume_detail(self):
             'LUN Id': '2',
             'Endurance Tier': '2 IOPS per GB',
             'IOPs': 1000,
-            'Snapshot Capacity (GB)': 10,
+            'Snapshot Capacity (GB)': '10',
             'Snapshot Used (Bytes)': 1024,
             'Capacity (GB)': '20GB',
             'Target IP': '10.1.2.3',
             'Data Center': 'dal05',
             'Type': 'ENDURANCE',
             'ID': 100,
-            '# of Active Transactions': '0'
+            '# of Active Transactions': '0',
+            'Replicant Count': '1',
+            'Replication Status': 'Replicant Volume Provisioning '
+                                  'has completed.',
+            'Replicant Volumes': [[
+                {'Replicant ID': 'Volume Name', '1784': 'TEST_REP_1'},
+                {'Replicant ID': 'Target IP', '1784': '10.3.174.79'},
+                {'Replicant ID': 'Data Center', '1784': 'wdc01'},
+                {'Replicant ID': 'Schedule', '1784': 'REPLICATION_HOURLY'},
+            ], [
+                {'Replicant ID': 'Volume Name', '1785': 'TEST_REP_2'},
+                {'Replicant ID': 'Target IP', '1785': '10.3.177.84'},
+                {'Replicant ID': 'Data Center', '1785': 'dal01'},
+                {'Replicant ID': 'Schedule', '1785': 'REPLICATION_DAILY'},
+            ]]
         }, json.loads(result.output))
 
     def test_volume_list(self):
@@ -101,29 +115,107 @@ def test_volume_list(self):
             }],
             json.loads(result.output))
 
+    def test_volume_order_performance_iops_not_given(self):
+        result = self.run_command(['block', 'volume-order',
+                                   '--storage-type=performance', '--size=20',
+                                   '--os-type=linux', '--location=dal05'])
+
+        self.assertEqual(2, result.exit_code)
+
+    def test_volume_order_performance_iops_out_of_range(self):
+        result = self.run_command(['block', 'volume-order',
+                                   '--storage-type=performance', '--size=20',
+                                   '--iops=80000', '--os-type=linux',
+                                   '--location=dal05'])
+
+        self.assertEqual(2, result.exit_code)
+
+    def test_volume_order_performance_iops_not_multiple_of_100(self):
+        result = self.run_command(['block', 'volume-order',
+                                   '--storage-type=performance', '--size=20',
+                                   '--iops=122', '--os-type=linux',
+                                   '--location=dal05'])
+
+        self.assertEqual(2, result.exit_code)
+
+    def test_volume_order_performance_snapshot_error(self):
+        result = self.run_command(['block', 'volume-order',
+                                   '--storage-type=performance', '--size=20',
+                                   '--iops=100', '--os-type=linux',
+                                   '--location=dal05', '--snapshot-size=10'])
+
+        self.assertEqual(2, result.exit_code)
+
     @mock.patch('SoftLayer.BlockStorageManager.order_block_volume')
-    def test_volume_order(self, order_mock):
+    def test_volume_order_performance(self, order_mock):
         order_mock.return_value = {
             'placedOrder': {
                 'id': 478,
-                'items': [{'description': 'Endurance Storage'},
-                          {'description': 'Block Storage'},
-                          {'description': '0.25 IOPS per GB'},
-                          {'description': '20 GB Storage Space'},
-                          ]
+                'items': [
+                    {'description': 'Performance Storage'},
+                    {'description': 'Block Storage'},
+                    {'description': '0.25 IOPS per GB'},
+                    {'description': '20 GB Storage Space'}]
+            }
+        }
+
+        result = self.run_command(['block', 'volume-order',
+                                   '--storage-type=performance', '--size=20',
+                                   '--iops=100', '--os-type=linux',
+                                   '--location=dal05'])
+
+        self.assert_no_fail(result)
+        self.assertEqual(result.output,
+                         'Order #478 placed successfully!\n'
+                         ' > Performance Storage\n > Block Storage\n'
+                         ' > 0.25 IOPS per GB\n > 20 GB Storage Space\n')
+
+    def test_volume_order_endurance_tier_not_given(self):
+        result = self.run_command(['block', 'volume-order',
+                                   '--storage-type=endurance', '--size=20',
+                                   '--os-type=linux', '--location=dal05'])
+
+        self.assertEqual(2, result.exit_code)
+
+    @mock.patch('SoftLayer.BlockStorageManager.order_block_volume')
+    def test_volume_order_endurance(self, order_mock):
+        order_mock.return_value = {
+            'placedOrder': {
+                'id': 478,
+                'items': [
+                    {'description': 'Endurance Storage'},
+                    {'description': 'Block Storage'},
+                    {'description': '0.25 IOPS per GB'},
+                    {'description': '20 GB Storage Space'},
+                    {'description': '10 GB Storage Space (Snapshot Space)'}]
                 }
         }
 
         result = self.run_command(['block', 'volume-order',
                                    '--storage-type=endurance', '--size=20',
                                    '--tier=0.25', '--os-type=linux',
-                                   '--location=dal05'])
+                                   '--location=dal05', '--snapshot-size=10'])
 
         self.assert_no_fail(result)
         self.assertEqual(result.output,
                          'Order #478 placed successfully!\n'
                          ' > Endurance Storage\n > Block Storage\n'
-                         ' > 0.25 IOPS per GB\n > 20 GB Storage Space\n')
+                         ' > 0.25 IOPS per GB\n > 20 GB Storage Space\n'
+                         ' > 10 GB Storage Space (Snapshot Space)\n')
+
+    @mock.patch('SoftLayer.BlockStorageManager.order_block_volume')
+    def test_volume_order_order_not_placed(self, order_mock):
+        order_mock.return_value = {}
+
+        result = self.run_command(['block', 'volume-order',
+                                   '--storage-type=endurance', '--size=20',
+                                   '--tier=0.25', '--os-type=linux',
+                                   '--location=dal05'])
+
+        self.assert_no_fail(result)
+        self.assertEqual(result.output,
+                         'Order could not be placed! Please verify '
+                         'your options and try again.\n')
 
     def test_enable_snapshots(self):
         result = self.run_command(['block', 'snapshot-enable', '12345678',
@@ -142,6 +234,17 @@ def test_create_snapshot(self):
 
         self.assert_no_fail(result)
 
+    @mock.patch('SoftLayer.BlockStorageManager.create_snapshot')
+    def test_create_snapshot_unsuccessful(self, snapshot_mock):
+        snapshot_mock.return_value = []
+
+        result = self.run_command(['block', 'snapshot-create', '8', '-n=note'])
+
+        self.assertEqual('Error occurred while creating snapshot.\n'
+                         'Ensure volume is not failed over or in another '
+                         'state which prevents taking snapshots.\n',
+                         result.output)
+
     def test_snapshot_list(self):
         result = self.run_command(['block', 'snapshot-list', '12345678'])
 
@@ -208,3 +311,82 @@ def test_deauthorize_host_to_volume(self):
                                    '--ip-address=192.3.2.1'])
 
         self.assert_no_fail(result)
+
+    def test_replicant_failover(self):
+        result = self.run_command(['block', 'replica-failover', '12345678',
+                                   '--replicant-id=5678', '--immediate'])
+
+        self.assert_no_fail(result)
+        self.assertEqual('Failover to replicant is now in progress.\n',
+                         result.output)
+
+    @mock.patch('SoftLayer.BlockStorageManager.failover_to_replicant')
+    def test_replicant_failover_unsuccessful(self, failover_mock):
+        failover_mock.return_value = False
+
+        result = self.run_command(['block', 'replica-failover', '12345678',
+                                   '--replicant-id=5678'])
+
+        self.assertEqual('Failover operation could not be initiated.\n',
+                         result.output)
+
+    def test_replicant_failback(self):
+        result = self.run_command(['block', 'replica-failback', '12345678',
+                                   '--replicant-id=5678'])
+
+        self.assert_no_fail(result)
+        self.assertEqual('Failback from replicant is now in progress.\n',
+                         result.output)
+
+    @mock.patch('SoftLayer.BlockStorageManager.failback_from_replicant')
+    def test_replicant_failback_unsuccessful(self, failback_mock):
+        failback_mock.return_value = False
+
+        result = self.run_command(['block', 'replica-failback', '12345678',
+                                   '--replicant-id=5678'])
+
+        self.assertEqual('Failback operation could not be initiated.\n',
+                         result.output)
+
+    @mock.patch('SoftLayer.BlockStorageManager.order_replicant_volume')
+    def test_replicant_order_order_not_placed(self, order_mock):
+        order_mock.return_value = {}
+
+        result = self.run_command(['block', 'replica-order', '100',
+                                   '--snapshot-schedule=DAILY',
+                                   '--location=dal05'])
+
+        self.assert_no_fail(result)
+        self.assertEqual(result.output,
+                         'Order could not be placed! Please verify '
+                         'your options and try again.\n')
+
+    @mock.patch('SoftLayer.BlockStorageManager.order_replicant_volume')
+    def test_replicant_order(self, order_mock):
+        order_mock.return_value = {
+            'placedOrder': {
+                'id': 91604,
+                'items': [
+                    {'description': 'Endurance Storage'},
+                    {'description': '2 IOPS per GB'},
+                    {'description': 'Block Storage'},
+                    {'description': '20 GB Storage Space'},
+                    {'description': '10 GB Storage Space (Snapshot Space)'},
+                    {'description': '20 GB Storage Space Replicant of: TEST'},
+                ],
+            }
+        }
+
+        result = self.run_command(['block', 'replica-order', '100',
+                                   '--snapshot-schedule=DAILY',
+                                   '--location=dal05', '--tier=2'])
+
+        self.assert_no_fail(result)
+        self.assertEqual(result.output,
+                         'Order #91604 placed successfully!\n'
+                         ' > Endurance Storage\n'
+                         ' > 2 IOPS per GB\n'
+                         ' > Block Storage\n'
+                         ' > 20 GB Storage Space\n'
+                         ' > 10 GB Storage Space (Snapshot Space)\n'
+                         ' > 20 GB Storage Space Replicant of: TEST\n')
diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py
index 6f509d57..3cbce526 100644
--- a/tests/CLI/modules/file_tests.py
+++ b/tests/CLI/modules/file_tests.py
@@ -117,43 +117,136 @@ def test_volume_detail(self):
         result = self.run_command(['file', 'volume-detail', '1234'])
 
         self.assert_no_fail(result)
-        self.assertEqual({'Username': 'username',
-                          'Used Space': '0B',
-                          'Endurance Tier': '2 IOPS per GB',
-                          'IOPs': 1000,
-                          'Snapshot Capacity (GB)': 10,
-                          'Snapshot Used (Bytes)': 1024,
-                          'Capacity (GB)': '20GB',
-                          'Target IP': '10.1.2.3',
-                          'Data Center': 'dal05',
-                          'Type': 'ENDURANCE',
-                          'ID': 100,
-                          '# of Active Transactions': '0'
-                          }, json.loads(result.output))
+        self.assertEqual({
+            'Username': 'username',
+            'Used Space': '0B',
+            'Endurance Tier': '2 IOPS per GB',
+            'IOPs': 1000,
+            'Snapshot Capacity (GB)': '10',
+            'Snapshot Used (Bytes)': 1024,
+            'Capacity (GB)': '20GB',
+            'Target IP': '10.1.2.3',
+            'Data Center': 'dal05',
+            'Type': 'ENDURANCE',
+            'ID': 100,
+            '# of Active Transactions': '0',
+            'Replicant Count': '1',
+            'Replication Status': 'Replicant Volume Provisioning '
+                                  'has completed.',
+            'Replicant Volumes': [[
+                {'Replicant ID': 'Volume Name', '1784': 'TEST_REP_1'},
+                {'Replicant ID': 'Target IP', '1784': '10.3.174.79'},
+                {'Replicant ID': 'Data Center', '1784': 'wdc01'},
+                {'Replicant ID': 'Schedule', '1784': 'REPLICATION_HOURLY'},
+            ], [
+                {'Replicant ID': 'Volume Name', '1785': 'TEST_REP_2'},
+                {'Replicant ID': 'Target IP', '1785': '10.3.177.84'},
+                {'Replicant ID': 'Data Center', '1785': 'dal01'},
+                {'Replicant ID': 'Schedule', '1785': 'REPLICATION_DAILY'},
+            ]]
+        }, json.loads(result.output))
+
+    def test_volume_order_performance_iops_not_given(self):
+        result = self.run_command(['file', 'volume-order',
+                                   '--storage-type=performance', '--size=20',
+                                   '--os-type=linux', '--location=dal05'])
+
+        self.assertEqual(2, result.exit_code)
+
+    def test_volume_order_performance_iops_out_of_range(self):
+        result = self.run_command(['file', 'volume-order',
+                                   '--storage-type=performance', '--size=20',
+                                   '--iops=80000', '--os-type=linux',
+                                   '--location=dal05'])
+
+        self.assertEqual(2, result.exit_code)
+
+    def test_volume_order_performance_iops_not_multiple_of_100(self):
+        result = self.run_command(['file', 'volume-order',
+                                   '--storage-type=performance', '--size=20',
+                                   '--iops=122', '--os-type=linux',
+                                   '--location=dal05'])
+
+        self.assertEqual(2, result.exit_code)
+
+    def test_volume_order_performance_snapshot_error(self):
+        result = self.run_command(['file', 'volume-order',
+                                   '--storage-type=performance', '--size=20',
+                                   '--iops=100', '--os-type=linux',
+                                   '--location=dal05', '--snapshot-size=10'])
+
+        self.assertEqual(2, result.exit_code)
 
     @mock.patch('SoftLayer.FileStorageManager.order_file_volume')
-    def test_volume_order(self, order_mock):
+    def test_volume_order_performance(self, order_mock):
         order_mock.return_value = {
             'placedOrder': {
                 'id': 478,
-                'items': [{'description': 'Endurance Storage'},
-                          {'description': 'File Storage'},
-                          {'description': '0.25 IOPS per GB'},
-                          {'description': '20 GB Storage Space'},
-                          ]
+                'items': [
+                    {'description': 'Performance Storage'},
+                    {'description': 'File Storage'},
+                    {'description': '0.25 IOPS per GB'},
+                    {'description': '20 GB Storage Space'}]
+            }
+        }
+
+        result = self.run_command(['file', 'volume-order',
+                                   '--storage-type=performance', '--size=20',
+                                   '--iops=100', '--os-type=linux',
+                                   '--location=dal05'])
+
+        self.assert_no_fail(result)
+        self.assertEqual(result.output,
+                         'Order #478 placed successfully!\n'
+                         ' > Performance Storage\n > File Storage\n'
+                         ' > 0.25 IOPS per GB\n > 20 GB Storage Space\n')
+
+    def test_volume_order_endurance_tier_not_given(self):
+        result = self.run_command(['file', 'volume-order',
+                                   '--storage-type=endurance', '--size=20',
+                                   '--os-type=linux', '--location=dal05'])
+
+        self.assertEqual(2, result.exit_code)
+
+    @mock.patch('SoftLayer.FileStorageManager.order_file_volume')
+    def test_volume_order_endurance(self, order_mock):
+        order_mock.return_value = {
+            'placedOrder': {
+                'id': 478,
+                'items': [
+                    {'description': 'Endurance Storage'},
+                    {'description': 'File Storage'},
+                    {'description': '0.25 IOPS per GB'},
+                    {'description': '20 GB Storage Space'},
+                    {'description': '10 GB Storage Space (Snapshot Space)'}]
                 }
         }
 
         result = self.run_command(['file', 'volume-order',
                                    '--storage-type=endurance', '--size=20',
                                    '--tier=0.25', '--os-type=linux',
-                                   '--location=dal05'])
+                                   '--location=dal05', '--snapshot-size=10'])
 
         self.assert_no_fail(result)
         self.assertEqual(result.output,
                          'Order #478 placed successfully!\n'
                          ' > Endurance Storage\n > File Storage\n'
-                         ' > 0.25 IOPS per GB\n > 20 GB Storage Space\n')
+                         ' > 0.25 IOPS per GB\n > 20 GB Storage Space\n'
+                         ' > 10 GB Storage Space (Snapshot Space)\n')
+
+    @mock.patch('SoftLayer.FileStorageManager.order_file_volume')
+    def test_volume_order_order_not_placed(self, order_mock):
+        order_mock.return_value = {}
+
+        result = self.run_command(['file', 'volume-order',
+                                   '--storage-type=endurance', '--size=20',
+                                   '--tier=0.25', '--os-type=linux',
+                                   '--location=dal05'])
+
+        self.assert_no_fail(result)
+        self.assertEqual(result.output,
+                         'Order could not be placed! Please verify '
+                         'your options and try again.\n')
 
     def test_enable_snapshots(self):
         result = self.run_command(['file', 'snapshot-enable', '12345678',
@@ -173,6 +266,17 @@ def test_create_snapshot(self):
         self.assert_no_fail(result)
         self.assertEqual('New snapshot created with id: 449\n', result.output)
 
+    @mock.patch('SoftLayer.FileStorageManager.create_snapshot')
+    def test_create_snapshot_unsuccessful(self, snapshot_mock):
+        snapshot_mock.return_value = []
+
+        result = self.run_command(['file', 'snapshot-create', '8', '-n=note'])
+
+        self.assertEqual('Error occurred while creating snapshot.\n'
+                         'Ensure volume is not failed over or in another '
+                         'state which prevents taking snapshots.\n',
+                         result.output)
+
     def test_snapshot_restore(self):
         result = self.run_command(['file', 'snapshot-restore', '12345678',
                                    '--snapshot-id=87654321'])
@@ -215,3 +319,82 @@ def test_snapshot_cancel(self):
                          ' for snapshot cancellation\n', result.output)
         self.assert_called_with('SoftLayer_Billing_Item', 'cancelItem',
                                 args=(False, True, None))
+
+    def test_replicant_failover(self):
+        result = self.run_command(['file', 'replica-failover', '12345678',
+                                   '--replicant-id=5678', '--immediate'])
+
+        self.assert_no_fail(result)
+        self.assertEqual('Failover to replicant is now in progress.\n',
+                         result.output)
+
+    @mock.patch('SoftLayer.FileStorageManager.failover_to_replicant')
+    def test_replicant_failover_unsuccessful(self, failover_mock):
+        failover_mock.return_value = False
+
+        result = self.run_command(['file', 'replica-failover', '12345678',
+                                   '--replicant-id=5678'])
+
+        self.assertEqual('Failover operation could not be initiated.\n',
+                         result.output)
+
+    def test_replicant_failback(self):
+        result = self.run_command(['file', 'replica-failback', '12345678',
+                                   '--replicant-id=5678'])
+
+        self.assert_no_fail(result)
+        self.assertEqual('Failback from replicant is now in progress.\n',
+                         result.output)
+
+    @mock.patch('SoftLayer.FileStorageManager.failback_from_replicant')
+    def test_replicant_failback_unsuccessful(self, failback_mock):
+        failback_mock.return_value = False
+
+        result = self.run_command(['file', 'replica-failback', '12345678',
+                                   '--replicant-id=5678'])
+
+        self.assertEqual('Failback operation could not be initiated.\n',
+                         result.output)
+
+    @mock.patch('SoftLayer.FileStorageManager.order_replicant_volume')
+    def test_replicant_order_order_not_placed(self, order_mock):
+        order_mock.return_value = {}
+
+        result = self.run_command(['file', 'replica-order', '100',
+                                   '--snapshot-schedule=DAILY',
+                                   '--location=dal05'])
+
+        self.assert_no_fail(result)
+        self.assertEqual(result.output,
+                         'Order could not be placed! Please verify '
+                         'your options and try again.\n')
+
+    @mock.patch('SoftLayer.FileStorageManager.order_replicant_volume')
+    def test_replicant_order(self, order_mock):
+        order_mock.return_value = {
+            'placedOrder': {
+                'id': 77309,
+                'items': [
+                    {'description': 'Endurance Storage'},
+                    {'description': '2 IOPS per GB'},
+                    {'description': 'File Storage'},
+                    {'description': '20 GB Storage Space'},
+                    {'description': '10 GB Storage Space (Snapshot Space)'},
+                    {'description': '20 GB Storage Space Replicant of: TEST'},
+                ],
+            }
+        }
+
+        result = self.run_command(['file', 'replica-order', '100',
+                                   '--snapshot-schedule=DAILY',
+                                   '--location=dal05', '--tier=2'])
+
+        self.assert_no_fail(result)
+        self.assertEqual(result.output,
+                         'Order #77309 placed successfully!\n'
+                         ' > Endurance Storage\n'
+                         ' > 2 IOPS per GB\n'
+                         ' > File Storage\n'
+                         ' > 20 GB Storage Space\n'
+                         ' > 10 GB Storage Space (Snapshot Space)\n'
+                         ' > 20 GB Storage Space Replicant of: TEST\n')
diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py
index 45337478..091e6e66 100644
--- a/tests/managers/block_tests.py
+++ b/tests/managers/block_tests.py
@@ -6,6 +6,7 @@
 """
 
 import SoftLayer
+from SoftLayer import exceptions
 from SoftLayer import fixtures
 from SoftLayer import testing
 
@@ -113,6 +114,30 @@ def test_cancel_snapshot_immediately(self):
             identifier=123,
         )
 
+    def test_replicant_failover(self):
+        result = self.block.failover_to_replicant(1234, 5678, immediate=True)
+
+        self.assertEqual(
+            fixtures.SoftLayer_Network_Storage.failoverToReplicant, result)
+        self.assert_called_with(
+            'SoftLayer_Network_Storage',
+            'failoverToReplicant',
+            args=(5678, True),
+            identifier=1234,
+        )
+
+    def test_replicant_failback(self):
+        result = self.block.failback_from_replicant(1234, 5678)
+
+        self.assertEqual(
+            fixtures.SoftLayer_Network_Storage.failbackFromReplicant, result)
+        self.assert_called_with(
+            'SoftLayer_Network_Storage',
+            'failbackFromReplicant',
+            args=(5678,),
+            identifier=1234,
+        )
+
     def test_order_block_volume_performance(self):
         mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects')
         mock.return_value = [{
@@ -155,7 +180,7 @@ def test_order_block_volume_performance(self):
             "dal05",
             100,
             "LINUX",
-            iops=100
+            iops=100,
             )
 
         self.assertEqual(
@@ -231,7 +256,95 @@ def test_order_block_volume_endurance(self):
             "dal05",
             100,
             "LINUX",
-            tier_level=0.25
+            tier_level=0.25,
+            )
+
+        self.assertEqual(
+            result,
+            {
+                'orderDate': '2013-08-01 15:23:45',
+                'orderId': 1234,
+                'prices': [{
+                    'hourlyRecurringFee': '2',
+                    'id': 1,
+                    'item': {'description': 'this is a thing', 'id': 1},
+                    'laborFee': '2',
+                    'oneTimeFee': '2',
+                    'oneTimeFeeTax': '.1',
+                    'quantity': 1,
+                    'recurringFee': '2',
+                    'recurringFeeTax': '.1',
+                    'setupFee': '1'}],
+                },
+            )
+
+    def test_order_block_volume_endurance_with_snapshot(self):
+        mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects')
+        mock.return_value = [{
+            'id': 1,
+            'name': 'Endurance',
+            'items': [{
+                'capacity': '1',
+                'prices': [{
+                    'id': 1,
+                    'locationGroupId': '',
+                    'categories': [{
+                        'categoryCode': 'storage_block',
+                    }],
+                }],
+            }, {
+                'capacity': '1',
+                'prices': [{
+                    'id': 2,
+                    'locationGroupId': '',
+                    'categories': [{
+                        'categoryCode': 'storage_service_enterprise',
+                    }],
+                }],
+            }, {
+                'capacity': '100',
+                'prices': [{
+                    'id': 3,
+                    'locationGroupId': '',
+                    'categories': [{
+                        'categoryCode': 'performance_storage_space',
+                    }],
+                    'capacityRestrictionMinimum': '100',
+                    'capacityRestrictionMaximum': '100',
+                }],
+            }, {
+                'capacity': '100',
+                'attributes': [{
+                    'value': '100',
+                }],
+                'prices': [{
+                    'id': 4,
+                    'locationGroupId': '',
+                    'categories': [{
+                        'categoryCode': 'storage_tier_level',
+                    }],
+                }],
+            }, {
+                'capacity': '10',
+                'prices': [{
+                    'id': 5,
+                    'locationGroupId': '',
+                    'categories': [{
+                        'categoryCode': 'storage_snapshot_space',
+                    }],
+                    'capacityRestrictionMinimum': '100',
+                    'capacityRestrictionMaximum': '100',
+                }],
+            }],
+        }]
+
+        result = self.block.order_block_volume(
+            "storage_service_enterprise",
+            "dal05",
+            100,
+            "LINUX",
+            tier_level=0.25,
+            snapshot_size=10,
             )
 
         self.assertEqual(
@@ -431,3 +544,241 @@ def test_order_snapshot_space(self):
                     'setupFee': '1'}],
                 },
             )
+
+    def test_order_block_replicant_invalid_location(self):
+        self.assertRaises(
+            exceptions.SoftLayerError,
+            self.block.order_replicant_volume,
+            100,
+            'WEEKLY',
+            'moon_center',
+            os_type='LINUX',
+        )
+
+    def test_order_block_replicant_invalid_storage_type(self):
+        mock = self.set_mock('SoftLayer_Network_Storage', 'getObject')
+
+        mock.return_value = {
+            'capacityGb': 20,
+            'billingItem': {
+                'categoryCode': 'not_the_storage_you_are_looking_for'
+            }
+        }
+
+        self.assertRaises(
+            exceptions.SoftLayerError,
+            self.block.order_replicant_volume,
+            100,
+            'WEEKLY',
+            'dal05',
+            os_type='LINUX',
+        )
+
+    def test_order_block_replicant_no_snapshot_space(self):
+        mock = self.set_mock('SoftLayer_Network_Storage', 'getObject')
+
+        mock.return_value = {
+            'capacityGb': 20,
+            'billingItem': {
+                'categoryCode': 'storage_service_enterprise'
+            }
+        }
+
+        self.assertRaises(
+            exceptions.SoftLayerError,
+            self.block.order_replicant_volume,
+            100,
+            'WEEKLY',
+            'dal05',
+            os_type='LINUX',
+        )
+
+    def test_order_block_replicant_primary_volume_cancelled(self):
+        mock = self.set_mock('SoftLayer_Network_Storage', 'getObject')
+
+        mock.return_value = {
+            'capacityGb': 20,
+            'snapshotCapacityGb': '10',
+            'schedules': [{
+                'id': 7770,
+                'type': {'keyname': 'SNAPSHOT_WEEKLY'}
+            }],
+            'billingItem': {
+                'categoryCode': 'storage_service_enterprise',
+                'cancellationDate': '2016-09-04T22:00:00-07:00'
+            }
+        }
+
+        self.assertRaises(
+            exceptions.SoftLayerError,
+            self.block.order_replicant_volume,
+            100,
+            'WEEKLY',
+            'dal05',
+            os_type='LINUX',
+        )
+
+    def test_order_block_replicant_snapshot_space_cancelled(self):
+        mock = self.set_mock('SoftLayer_Network_Storage', 'getObject')
+
+        mock.return_value = {
+            'capacityGb': 20,
+            'snapshotCapacityGb': '10',
+            'schedules': [{
+                'id': 7770,
+                'type': {'keyname': 'SNAPSHOT_WEEKLY'}
+            }],
+            'billingItem': {
+                'categoryCode': 'storage_service_enterprise',
+                'cancellationDate': '',
+                'activeChildren': [{
+                    'categoryCode': 'storage_snapshot_space',
+                    'cancellationDate': '2016-09-04T22:00:00-07:00'
+                }]
+            }
+        }
+
+        self.assertRaises(
+            exceptions.SoftLayerError,
+            self.block.order_replicant_volume,
+            100,
+            'WEEKLY',
+            'dal05',
+            os_type='LINUX',
+        )
+
+    def test_order_block_replicant_os_type_not_found(self):
+        mock = self.set_mock('SoftLayer_Network_Storage', 'getObject')
+
+        mock.return_value = {}
+
+        self.assertRaises(
+            exceptions.SoftLayerError,
+            self.block.order_replicant_volume,
+            100,
+            'WEEKLY',
+            'dal05',
+            tier='2',
+        )
+
+    def test_order_block_replicant(self):
+        mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects')
+        mock.return_value = [{
+            'id': 240,
+            'name': 'Endurance',
+            'items': [{
+                'capacity': '0',
+                'attributes': [{
+                    'value': '42',
+                }],
+                'prices': [{
+                    'locationGroupId': '530',
+                }],
+            }, {
+                'capacity': '10',
+                'attributes': [{
+                    'value': '200',
+                }],
+                'prices': [{
+                    'locationGroupId': '530',
+                }, {
+                    'locationGroupId': '',
+                    'categories': [{
+                        'categoryCode': 'storage_service_enterprise',
+                    }],
+                }, {
+                    'locationGroupId': '',
+                    'categories': [{
+                        'categoryCode': 'storage_tier_level',
+                    }],
+                }, {
+                    'id': 46130,
+                    'locationGroupId': '',
+                    'categories': [{
+                        'categoryCode': 'storage_snapshot_space',
+                    }],
+                    'capacityRestrictionMinimum': '200',
+                    'capacityRestrictionMaximum': '200',
+                }],
+            }, {
+                'capacity': '20',
+                'prices': [{
+                    'locationGroupId': '530',
+                }, {
+                    'locationGroupId': '',
+                    'categories': [{
+                        'categoryCode': 'storage_block',
+                    }],
+                }, {
+                    'locationGroupId': '',
+                    'categories': [{
+                        'categoryCode': 'storage_service_enterprise',
+                    }],
+                }, {
+                    'locationGroupId': '',
+                    'categories': [{
+                        'categoryCode': 'performance_storage_space',
+                    }],
+                    'capacityRestrictionMinimum': '742',
+                }, {
+                    'locationGroupId': '',
+                    'categories': [{
+                        'categoryCode': 'performance_storage_space',
+                    }],
+                    'capacityRestrictionMinimum': '42',
+                    'capacityRestrictionMaximum': '42',
+                }, {
+                    'locationGroupId': '',
+                    'categories': [{
+                        'categoryCode': 'performance_storage_space',
+                    }],
+                    'capacityRestrictionMinimum': '200',
+                    'capacityRestrictionMaximum': '200',
+                }, {
+                    'locationGroupId': '',
+                    'categories': [{
+                        'categoryCode': 'performance_storage_replication',
+                    }],
+                    'capacityRestrictionMinimum': '742',
+                }, {
+                    'locationGroupId': '',
+                    'categories': [{
+                        'categoryCode': 'performance_storage_replication',
+                    }],
+                    'capacityRestrictionMinimum': '42',
+                    'capacityRestrictionMaximum': '42',
+                }, {
+                    'locationGroupId': '',
+                    'categories': [{
+                        'categoryCode': 'performance_storage_replication',
+                    }],
+                    'capacityRestrictionMinimum': '200',
+                    'capacityRestrictionMaximum': '200',
+                }],
+            }],
+        }]
+
+        result = self.block.order_replicant_volume(
+            100,
+            'WEEKLY',
+            'dal05',
+            )
+
+        self.assertEqual(
+            result,
+            {
+                'orderId': 1234,
+                'orderDate': '2013-08-01 15:23:45',
+                'prices': [{
+                    'hourlyRecurringFee': '2',
+                    'id': 1,
+                    'item': {'description': 'this is a thing', 'id': 1},
+                    'laborFee': '2',
+                    'oneTimeFee': '2',
+                    'oneTimeFeeTax': '.1',
+                    'quantity': 1,
+                    'recurringFee': '2',
+                    'recurringFeeTax': '.1',
+                    'setupFee': '1'}],
+                },
+            )
diff --git a/tests/managers/file_tests.py b/tests/managers/file_tests.py
index 7187a7d8..1f9c5617 100644
--- a/tests/managers/file_tests.py
+++ b/tests/managers/file_tests.py
@@ -6,6 +6,7 @@
 """
 
 import SoftLayer
+from SoftLayer import exceptions
 from SoftLayer import fixtures
 from SoftLayer import testing
 
@@ -141,6 +142,30 @@ def test_cancel_snapshot_immediately(self):
             identifier=123,
         )
 
+    def test_replicant_failover(self):
+        result = self.file.failover_to_replicant(1234, 5678, immediate=True)
+
+        self.assertEqual(
+            fixtures.SoftLayer_Network_Storage.failoverToReplicant, result)
+        self.assert_called_with(
+            'SoftLayer_Network_Storage',
+            'failoverToReplicant',
+            args=(5678, True),
+            identifier=1234,
+        )
+
+    def test_replicant_failback(self):
+        result = self.file.failback_from_replicant(1234, 5678)
+
+        self.assertEqual(
+            fixtures.SoftLayer_Network_Storage.failbackFromReplicant, result)
+        self.assert_called_with(
+            'SoftLayer_Network_Storage',
+            'failbackFromReplicant',
+            args=(5678,),
+            identifier=1234,
+        )
+
     def test_delete_snapshot(self):
         result = self.file.delete_snapshot(100)
 
@@ -222,7 +247,7 @@ def test_order_file_volume_performance(self):
             "dal05",
             100,
             "LINUX",
-            iops=100
+            iops=100,
             )
 
         self.assertEqual(
@@ -306,7 +331,95 @@ def test_order_file_volume_endurance(self):
             "dal05",
             100,
             "LINUX",
-            tier_level=0.25
+            tier_level=0.25,
+            )
+
+        self.assertEqual(
+            result,
+            {
+                'orderDate': '2013-08-01 15:23:45',
+                'orderId': 1234,
+                'prices': [{
+                    'hourlyRecurringFee': '2',
+                    'id': 1,
+                    'item': {'description': 'this is a thing', 'id': 1},
+                    'laborFee': '2',
+                    'oneTimeFee': '2',
+                    'oneTimeFeeTax': '.1',
+                    'quantity': 1,
+                    'recurringFee': '2',
+                    'recurringFeeTax': '.1',
+                    'setupFee': '1'}],
+                },
+            )
+
+    def test_order_file_volume_endurance_with_snapshot(self):
+        mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects')
+        mock.return_value = [{
+            'id': 1,
+            'name': 'Endurance',
+            'items': [{
+                'capacity': '1',
+                'prices': [{
+                    'id': 1,
+                    'locationGroupId': '',
+                    'categories': [{
+                        'categoryCode': 'storage_file',
+                    }],
+                }],
+            }, {
+                'capacity': '1',
+                'prices': [{
+                    'id': 2,
+                    'locationGroupId': '',
+                    'categories': [{
+                        'categoryCode': 'storage_service_enterprise',
+                    }],
+                }],
+            }, {
+                'capacity': '100',
+                'prices': [{
+                    'id': 3,
+                    'locationGroupId': '',
+                    'categories': [{
+                        'categoryCode': 'performance_storage_space',
+                    }],
+                    'capacityRestrictionMinimum': '100',
+                    'capacityRestrictionMaximum': '100',
+                }],
+            }, {
+                'capacity': '100',
+                'attributes': [{
+                    'value': '100',
+                }],
+                'prices': [{
+                    'id': 4,
+                    'locationGroupId': '',
+                    'categories': [{
+                        'categoryCode': 'storage_tier_level',
+                    }],
+                }],
+            }, {
+                'capacity': '10',
+                'prices': [{
+                    'id': 5,
+                    'locationGroupId': '',
+                    'categories': [{
+                        'categoryCode': 'storage_snapshot_space',
+                    }],
+                    'capacityRestrictionMinimum': '100',
+                    'capacityRestrictionMaximum': '100',
+                }],
+            }],
+        }]
+
+        result = self.file.order_file_volume(
+            "storage_service_enterprise",
+            "dal05",
+            100,
+            "LINUX",
+            tier_level=0.25,
+            snapshot_size=10,
             )
 
         self.assertEqual(
@@ -432,3 +545,222 @@ def test_order_snapshot_space(self):
                     'setupFee': '1'}],
                 },
             )
+
+    def test_order_file_replicant_invalid_location(self):
+        self.assertRaises(
+            exceptions.SoftLayerError,
+            self.file.order_replicant_volume,
+            100,
+            'WEEKLY',
+            'moon_center',
+        )
+
+    def test_order_file_replicant_invalid_storage_type(self):
+        mock = self.set_mock('SoftLayer_Network_Storage', 'getObject')
+
+        mock.return_value = {
+            'capacityGb': 20,
+            'billingItem': {
+                'categoryCode': 'not_the_storage_you_are_looking_for'
+            }
+        }
+
+        self.assertRaises(
+            exceptions.SoftLayerError,
+            self.file.order_replicant_volume,
+            100,
+            'WEEKLY',
+            'dal05',
+        )
+
+    def test_order_file_replicant_no_snapshot_space(self):
+        mock = self.set_mock('SoftLayer_Network_Storage', 'getObject')
+
+        mock.return_value = {
+            'capacityGb': 20,
+            'billingItem': {
+                'categoryCode': 'storage_service_enterprise'
+            }
+        }
+
+        self.assertRaises(
+            exceptions.SoftLayerError,
+            self.file.order_replicant_volume,
+            100,
+            'WEEKLY',
+            'dal05',
+        )
+
+    def test_order_file_replicant_primary_volume_cancelled(self):
+        mock = self.set_mock('SoftLayer_Network_Storage', 'getObject')
+
+        mock.return_value = {
+            'capacityGb': 20,
+            'snapshotCapacityGb': '10',
+            'schedules': [{
+                'id': 7770,
+                'type': {'keyname': 'SNAPSHOT_WEEKLY'}
+            }],
+            'billingItem': {
+                'categoryCode': 'storage_service_enterprise',
+                'cancellationDate': '2016-09-04T22:00:00-07:00'
+            }
+        }
+
+        self.assertRaises(
+            exceptions.SoftLayerError,
+            self.file.order_replicant_volume,
+            100,
+            'WEEKLY',
+            'dal05',
+        )
+
+    def test_order_file_replicant_snapshot_space_cancelled(self):
+        mock = self.set_mock('SoftLayer_Network_Storage', 'getObject')
+
+        mock.return_value = {
+            'capacityGb': 20,
+            'snapshotCapacityGb': '10',
+            'schedules': [{
+                'id': 7770,
+                'type': {'keyname': 'SNAPSHOT_WEEKLY'}
+            }],
+            'billingItem': {
+                'categoryCode': 'storage_service_enterprise',
+                'cancellationDate': '',
+                'activeChildren': [{
+                    'categoryCode': 'storage_snapshot_space',
+                    'cancellationDate': '2016-09-04T22:00:00-07:00'
+                }]
+            }
+        }
+
+        self.assertRaises(
+            exceptions.SoftLayerError,
+            self.file.order_replicant_volume,
+            100,
+            'WEEKLY',
+            'dal05',
+        )
+
+    def test_order_file_replicant(self):
+        mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects')
+        mock.return_value = [{
+            'id': 240,
+            'name': 'Endurance',
+            'items': [{
+                'capacity': '0',
+                'attributes': [{
+                    'value': '42',
+                }],
+                'prices': [{
+                    'locationGroupId': '530',
+                }],
+            }, {
+                'capacity': '10',
+                'attributes': [{
+                    'value': '200',
+                }],
+                'prices': [{
+                    'locationGroupId': '530',
+                }, {
+                    'locationGroupId': '',
+                    'categories': [{
+                        'categoryCode': 'storage_service_enterprise',
+                    }],
+                }, {
+                    'locationGroupId': '',
+                    'categories': [{
+                        'categoryCode': 'storage_tier_level',
+                    }],
+                }, {
+                    'id': 46130,
+                    'locationGroupId': '',
+                    'categories': [{
+                        'categoryCode': 'storage_snapshot_space',
+                    }],
+                    'capacityRestrictionMinimum': '200',
+                    'capacityRestrictionMaximum': '200',
+                }],
+            }, {
+                'capacity': '20',
+                'prices': [{
+                    'locationGroupId': '530',
+                }, {
+                    'locationGroupId': '',
+                    'categories': [{
+                        'categoryCode': 'storage_file',
+                    }],
+                }, {
+                    'locationGroupId': '',
+                    'categories': [{
+                        'categoryCode': 'storage_service_enterprise',
+                    }],
+                }, {
+                    'locationGroupId': '',
+                    'categories': [{
+                        'categoryCode': 'performance_storage_space',
+                    }],
+                    'capacityRestrictionMinimum': '742',
+                }, {
+                    'locationGroupId': '',
+                    'categories': [{
+                        'categoryCode': 'performance_storage_space',
+                    }],
+                    'capacityRestrictionMinimum': '42',
+                    'capacityRestrictionMaximum': '42',
+                }, {
+                    'locationGroupId': '',
+                    'categories': [{
+                        'categoryCode': 'performance_storage_space',
+                    }],
+                    'capacityRestrictionMinimum': '200',
+                    'capacityRestrictionMaximum': '200',
+                }, {
+                    'locationGroupId': '',
+                    'categories': [{
+                        'categoryCode': 'performance_storage_replication',
+                    }],
+                    'capacityRestrictionMinimum': '742',
+                }, {
+                    'locationGroupId': '',
+                    'categories': [{
+                        'categoryCode': 'performance_storage_replication',
+                    }],
+                    'capacityRestrictionMinimum': '42',
+                    'capacityRestrictionMaximum': '42',
+                }, {
+                    'locationGroupId': '',
+                    'categories': [{
+                        'categoryCode': 'performance_storage_replication',
+                    }],
+                    'capacityRestrictionMinimum': '200',
+                    'capacityRestrictionMaximum': '200',
+                }],
+            }],
+        }]
+
+        result = self.file.order_replicant_volume(
+            100,
+            'WEEKLY',
+            'dal05',
+            )
+
+        self.assertEqual(
+            result,
+            {
+                'orderId': 1234,
+                'orderDate': '2013-08-01 15:23:45',
+                'prices': [{
+                    'hourlyRecurringFee': '2',
+                    'id': 1,
+                    'item': {'description': 'this is a thing', 'id': 1},
+                    'laborFee': '2',
+                    'oneTimeFee': '2',
+                    'oneTimeFeeTax': '.1',
+                    'quantity': 1,
+                    'recurringFee': '2',
+                    'recurringFeeTax': '.1',
+                    'setupFee': '1'}],
+                },
+            )