Repository: softlayer/softlayer-python
Pull request #747 (2016-08-03 19:07:01)

Detect rest transport

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

Coverage increased (+0.04%) to 79.671% when pulling **b5697bd18049e3bfcf3a90223e576c708aaeec6d on sudorandom:detect-rest-transport** into **4932cac9c059c5589b19d8420beb64395a8590e6 on softlayer:master**.

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

Coverage increased (+0.04%) to 79.671% when pulling **b5697bd18049e3bfcf3a90223e576c708aaeec6d on sudorandom:detect-rest-transport** into **4932cac9c059c5589b19d8420beb64395a8590e6 on softlayer:master**.


Patch:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 7a82eb15..460e436e 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -3,9 +3,9 @@ # Contributing to softlayer-python
 We are happy to accept contributions to softlayer-python.  Please follow the
 guidelines below.  
 
-* Sign our contributor agreement (CLA) You can find the [CLA here](./docs/cla-individual.md).
+* Sign our contributor agreement (CLA) You can find the [CLA here](./docs/dev/cla-individual.md).
 
-* If you're contributing on behalf of your employer we'll need a signed copy of our corporate contributor agreement (CCLA) as well.  You can find the [CCLA here](./docs/cla-corporate.md).
+* If you're contributing on behalf of your employer we'll need a signed copy of our corporate contributor agreement (CCLA) as well.  You can find the [CCLA here](./docs/dev/cla-corporate.md).
     
 * Fork the repo, make your changes, and open a pull request.
 
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/API.py b/SoftLayer/API.py
index 9b9fd518..322309f2 100644
--- a/SoftLayer/API.py
+++ b/SoftLayer/API.py
@@ -85,14 +85,24 @@ def create_client_from_env(username=None,
                                           proxy=proxy,
                                           config_file=config_file)
 
-    # Default the transport to use XMLRPC
     if transport is None:
-        transport = transports.XmlRpcTransport(
-            endpoint_url=settings.get('endpoint_url'),
-            proxy=settings.get('proxy'),
-            timeout=settings.get('timeout'),
-            user_agent=user_agent,
-        )
+        url = settings.get('endpoint_url')
+        if url is not None and '/rest' in url:
+            # If this looks like a rest endpoint, use the rest transport
+            transport = transports.RestTransport(
+                endpoint_url=settings.get('endpoint_url'),
+                proxy=settings.get('proxy'),
+                timeout=settings.get('timeout'),
+                user_agent=user_agent,
+            )
+        else:
+            # Default the transport to use XMLRPC
+            transport = transports.XmlRpcTransport(
+                endpoint_url=settings.get('endpoint_url'),
+                proxy=settings.get('proxy'),
+                timeout=settings.get('timeout'),
+                user_agent=user_agent,
+            )
 
     # If we have enough information to make an auth driver, let's do it
     if auth is None and settings.get('username') and settings.get('api_key'):
diff --git a/SoftLayer/CLI/block/access/authorize.py b/SoftLayer/CLI/block/access/authorize.py
index 4e9dc20f..df76b60e 100644
--- a/SoftLayer/CLI/block/access/authorize.py
+++ b/SoftLayer/CLI/block/access/authorize.py
@@ -1,4 +1,4 @@
-"""Create a block storage snapshot."""
+"""Authorizes hosts on a specific block volume."""
 # :license: MIT, see LICENSE for more details.
 
 import click
@@ -40,3 +40,6 @@ def cli(env, volume_id, hardware_id, virtual_id, ip_address_id, ip_address):
                                            hardware_id,
                                            virtual_id,
                                            ip_address_id_list)
+
+    # If no exception was raised, the command succeeded
+    click.echo('The specified hosts were authorized to access %s' % volume_id)
diff --git a/SoftLayer/CLI/block/access/list.py b/SoftLayer/CLI/block/access/list.py
new file mode 100644
index 00000000..b011e263
--- /dev/null
+++ b/SoftLayer/CLI/block/access/list.py
@@ -0,0 +1,38 @@
+"""List hosts with access to block volume."""
+# :license: MIT, see LICENSE for more details.
+
+import click
+import SoftLayer
+from SoftLayer.CLI import columns as column_helper
+from SoftLayer.CLI import environment
+from SoftLayer.CLI import formatting
+from SoftLayer.CLI import storage_utils
+
+
+@click.command()
+@click.argument('volume_id')
+@click.option('--sortby', help='Column to sort by', default='name')
+@click.option('--columns',
+              callback=column_helper.get_formatter(storage_utils.COLUMNS),
+              help='Columns to display. Options: {0}'.format(
+                  ', '.join(column.name for column in storage_utils.COLUMNS)),
+              default=','.join(storage_utils.DEFAULT_COLUMNS))
+@environment.pass_env
+def cli(env, columns, sortby, volume_id):
+    """List ACLs."""
+    block_manager = SoftLayer.BlockStorageManager(env.client)
+    access_list = block_manager.get_block_volume_access_list(
+        volume_id=volume_id)
+    table = formatting.Table(columns.columns)
+    table.sortby = sortby
+
+    for key, type_name in [('allowedVirtualGuests', 'VIRTUAL'),
+                           ('allowedHardware', 'HARDWARE'),
+                           ('allowedSubnets', 'SUBNET'),
+                           ('allowedIpAddresses', 'IP')]:
+        for obj in access_list.get(key, []):
+            obj['type'] = type_name
+            table.add_row([value or formatting.blank()
+                           for value in columns.row(obj)])
+
+    env.fout(table)
diff --git a/SoftLayer/CLI/block/access/revoke.py b/SoftLayer/CLI/block/access/revoke.py
index 1b0c93a4..c2284bef 100644
--- a/SoftLayer/CLI/block/access/revoke.py
+++ b/SoftLayer/CLI/block/access/revoke.py
@@ -1,4 +1,4 @@
-"""Create a block storage snapshot."""
+"""Revokes hosts' access on a specific block volume."""
 # :license: MIT, see LICENSE for more details.
 
 import click
@@ -14,7 +14,7 @@
 @click.option('--virtual-id', '-v', multiple=True,
               help='The id of one SoftLayer_Virtual_Guest'
               ' to revoke authorization')
-@click.option('--ip_address-id', '-i', multiple=True,
+@click.option('--ip-address-id', '-i', multiple=True,
               help='The id of one SoftLayer_Network_Subnet_IpAddress'
               ' to revoke authorization')
 @click.option('--ip-address', multiple=True,
@@ -36,3 +36,6 @@ def cli(env, volume_id, hardware_id, virtual_id, ip_address_id, ip_address):
                                              hardware_id,
                                              virtual_id,
                                              ip_address_id_list)
+
+    # If no exception was raised, the command succeeded
+    click.echo('Access to %s was revoked for the specified hosts' % volume_id)
diff --git a/SoftLayer/CLI/block/cancel.py b/SoftLayer/CLI/block/cancel.py
index a23cee00..03118451 100644
--- a/SoftLayer/CLI/block/cancel.py
+++ b/SoftLayer/CLI/block/cancel.py
@@ -25,4 +25,15 @@ def cli(env, volume_id, reason, immediate):
     if not (env.skip_confirmations or formatting.no_going_back(volume_id)):
         raise exceptions.CLIAbort('Aborted')
 
-    block_storage_manager.cancel_block_volume(volume_id, reason, immediate)
+    cancelled = block_storage_manager.cancel_block_volume(volume_id,
+                                                          reason, immediate)
+
+    if cancelled:
+        if immediate:
+            click.echo('Block volume with id %s has been marked'
+                       ' for immediate cancellation' % volume_id)
+        else:
+            click.echo('Block volume with id %s has been marked'
+                       ' for cancellation' % volume_id)
+    else:
+        click.echo('Unable to cancel block volume %s' % volume_id)
diff --git a/SoftLayer/CLI/block/detail.py b/SoftLayer/CLI/block/detail.py
index c98736ed..51a1fbd4 100644
--- a/SoftLayer/CLI/block/detail.py
+++ b/SoftLayer/CLI/block/detail.py
@@ -51,9 +51,52 @@ def cli(env, volume_id):
             'Snapshot Capacity (GB)',
             block_volume['snapshotCapacityGb'],
         ])
-        table.add_row([
-            'Snapshot Used (Bytes)',
-            block_volume['parentVolume']['snapshotSizeBytes'],
-        ])
+        if 'snapshotSizeBytes' in block_volume['parentVolume']:
+            table.add_row([
+                'Snapshot Used (Bytes)',
+                block_volume['parentVolume']['snapshotSizeBytes'],
+            ])
+
+    table.add_row(['# of Active Transactions', "%i"
+                   % block_volume['activeTransactionCount']])
+
+    if block_volume['activeTransactions']:
+        for trans in block_volume['activeTransactions']:
+            table.add_row([
+                '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 40edcb7c..41e09ca3 100644
--- a/SoftLayer/CLI/block/list.py
+++ b/SoftLayer/CLI/block/list.py
@@ -16,12 +16,21 @@
                          mask="serviceResource.datacenter.name"),
     column_helper.Column(
         'storage_type',
-        lambda b: b['storageType']['keyName'].split('_').pop(0),
+        lambda b: b['storageType']['keyName'].split('_').pop(0)
+        if 'storageType' in b and 'keyName' in b['storageType']
+        and isinstance(b['storageType']['keyName'], str)
+        else '-',
         mask="storageType.keyName"),
     column_helper.Column('capacity_gb', ('capacityGb',), mask="capacityGb"),
     column_helper.Column('bytes_used', ('bytesUsed',), mask="bytesUsed"),
     column_helper.Column('ip_addr', ('serviceResourceBackendIpAddress',),
                          mask="serviceResourceBackendIpAddress"),
+    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 = [
@@ -31,7 +40,9 @@
     'storage_type',
     'capacity_gb',
     'bytes_used',
-    'ip_addr'
+    'ip_addr',
+    'lunId',
+    'active_transactions'
 ]
 
 
diff --git a/SoftLayer/CLI/block/order.py b/SoftLayer/CLI/block/order.py
index f1840078..22430e42 100644
--- a/SoftLayer/CLI/block/order.py
+++ b/SoftLayer/CLI/block/order.py
@@ -7,17 +7,17 @@
 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)
 @click.option('--storage-type',
-              help='Type of storage volume',
+              help='Type of block storage volume',
               type=click.Choice(['performance', 'endurance']),
               required=True)
 @click.option('--size',
               type=int,
-              help='Size of storage volume in GB',
+              help='Size of block storage volume in GB',
               required=True)
 @click.option('--iops',
               type=int,
@@ -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/__init__.py b/SoftLayer/CLI/block/snapshot/__init__.py
new file mode 100644
index 00000000..eb4d41b0
--- /dev/null
+++ b/SoftLayer/CLI/block/snapshot/__init__.py
@@ -0,0 +1,1 @@
+"""Block Storage Snapshot Control."""
diff --git a/SoftLayer/CLI/block/snapshot/cancel.py b/SoftLayer/CLI/block/snapshot/cancel.py
new file mode 100644
index 00000000..7a6150bf
--- /dev/null
+++ b/SoftLayer/CLI/block/snapshot/cancel.py
@@ -0,0 +1,40 @@
+"""Cancel a snapshot space subscription."""
+# :license: MIT, see LICENSE for more details.
+
+import click
+
+import SoftLayer
+from SoftLayer.CLI import environment
+from SoftLayer.CLI import exceptions
+from SoftLayer.CLI import formatting
+
+
+@click.command()
+@click.argument('volume-id')
+@click.option('--reason', help="An optional reason for cancellation")
+@click.option('--immediate',
+              is_flag=True,
+              help="Cancels the snapshot space immediately instead "
+                   "of on the billing anniversary")
+@environment.pass_env
+def cli(env, volume_id, reason, immediate):
+    """Cancel existing snapshot space for a given volume."""
+
+    block_storage_manager = SoftLayer.BlockStorageManager(env.client)
+
+    if not (env.skip_confirmations or formatting.no_going_back(volume_id)):
+        raise exceptions.CLIAbort('Aborted')
+
+    cancelled = block_storage_manager.cancel_snapshot_space(
+        volume_id, reason, immediate)
+
+    if cancelled:
+        if immediate:
+            click.echo('Block volume with id %s has been marked'
+                       ' for immediate snapshot cancellation' % volume_id)
+        else:
+            click.echo('Block volume with id %s has been marked'
+                       ' for snapshot cancellation' % volume_id)
+    else:
+        click.echo('Unable to cancel snapshot space for block volume %s'
+                   % volume_id)
diff --git a/SoftLayer/CLI/block/snapshot/create.py b/SoftLayer/CLI/block/snapshot/create.py
new file mode 100644
index 00000000..68dc976f
--- /dev/null
+++ b/SoftLayer/CLI/block/snapshot/create.py
@@ -0,0 +1,24 @@
+"""Create a block storage snapshot."""
+# :license: MIT, see LICENSE for more details.
+
+import click
+import SoftLayer
+from SoftLayer.CLI import environment
+
+
+@click.command()
+@click.argument('volume_id')
+@click.option('--notes', '-n',
+              help='Notes to set on the new snapshot')
+@environment.pass_env
+def cli(env, volume_id, notes):
+    """Creates a snapshot on a given volume"""
+    block_manager = SoftLayer.BlockStorageManager(env.client)
+    snapshot = block_manager.create_snapshot(volume_id, notes=notes)
+
+    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/block/snapshot_delete.py b/SoftLayer/CLI/block/snapshot/delete.py
similarity index 65%
rename from SoftLayer/CLI/block/snapshot_delete.py
rename to SoftLayer/CLI/block/snapshot/delete.py
index 6daa3f4d..229a5d7e 100644
--- a/SoftLayer/CLI/block/snapshot_delete.py
+++ b/SoftLayer/CLI/block/snapshot/delete.py
@@ -1,4 +1,4 @@
-"""Create a block storage snapshot."""
+"""Delete a block storage snapshot."""
 # :license: MIT, see LICENSE for more details.
 
 import click
@@ -12,4 +12,7 @@
 def cli(env, snapshot_id):
     """Deletes a snapshot on a given volume"""
     block_manager = SoftLayer.BlockStorageManager(env.client)
-    block_manager.delete_snapshot(snapshot_id)
+    deleted = block_manager.delete_snapshot(snapshot_id)
+
+    if deleted:
+        click.echo('Snapshot %s deleted' % snapshot_id)
diff --git a/SoftLayer/CLI/block/snapshot/disable.py b/SoftLayer/CLI/block/snapshot/disable.py
new file mode 100644
index 00000000..f34d3483
--- /dev/null
+++ b/SoftLayer/CLI/block/snapshot/disable.py
@@ -0,0 +1,29 @@
+"""Disable scheduled snapshots of a specific volume"""
+# :license: MIT, see LICENSE for more details.
+
+import click
+import SoftLayer
+from SoftLayer.CLI import environment
+from SoftLayer.CLI import exceptions
+
+
+@click.command()
+@click.argument('volume_id')
+@click.option('--schedule-type',
+              help='Snapshot schedule [HOURLY|DAILY|WEEKLY]',
+              required=True)
+@environment.pass_env
+def cli(env, volume_id, schedule_type):
+    """Disables snapshots on the specified schedule for a given volume"""
+
+    if (schedule_type != 'HOURLY' and schedule_type != 'DAILY'
+            and schedule_type != 'WEEKLY'):
+        raise exceptions.CLIAbort(
+            '--schedule-type must be HOURLY, DAILY, or WEEKLY')
+
+    block_manager = SoftLayer.BlockStorageManager(env.client)
+    disabled = block_manager.disable_snapshots(volume_id, schedule_type)
+
+    if disabled:
+        click.echo('%s snapshots have been disabled for volume %s'
+                   % (schedule_type, volume_id))
diff --git a/SoftLayer/CLI/block/snapshot/enable.py b/SoftLayer/CLI/block/snapshot/enable.py
new file mode 100644
index 00000000..6ade9464
--- /dev/null
+++ b/SoftLayer/CLI/block/snapshot/enable.py
@@ -0,0 +1,58 @@
+# snapshot_enable.py
+"""Create a block storage snapshot [ENABLE]."""
+
+import click
+import SoftLayer
+from SoftLayer.CLI import environment
+from SoftLayer.CLI import exceptions
+
+
+@click.command()
+@click.argument('volume_id')
+@click.option('--schedule-type',
+              help='Snapshot schedule [HOURLY|DAILY|WEEKLY]',
+              required=True)
+@click.option('--retention-count',
+              help='Number of snapshots to retain',
+              required=True)
+@click.option('--minute',
+              help='Minute of the day when snapshots should be taken',
+              default=0)
+@click.option('--hour',
+              help='Hour of the day when snapshots should be taken',
+              default=0)
+@click.option('--day-of-week',
+              help='Day of the week when snapshots should be taken',
+              default='SUNDAY')
+@environment.pass_env
+def cli(env, volume_id, schedule_type, retention_count,
+        minute, hour, day_of_week):
+    """Enables snapshots for a given volume on the specified schedule"""
+    block_manager = SoftLayer.BlockStorageManager(env.client)
+
+    valid_schedule_types = {'HOURLY', 'DAILY', 'WEEKLY'}
+    valid_days = {'SUNDAY', 'MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY',
+                  'FRIDAY', 'SATURDAY'}
+
+    if schedule_type not in valid_schedule_types:
+        raise exceptions.CLIAbort(
+            '--schedule-type must be HOURLY, DAILY, or WEEKLY, not '
+            + schedule_type)
+
+    if minute < 0 or minute > 59:
+        raise exceptions.CLIAbort(
+            '--minute value must be between 0 and 59')
+    if hour < 0 or hour > 23:
+        raise exceptions.CLIAbort(
+            '--hour value must be between 0 and 23')
+    if day_of_week not in valid_days:
+        raise exceptions.CLIAbort(
+            '--day_of_week value must be a valid day (ex: SUNDAY)')
+
+    enabled = block_manager.enable_snapshots(volume_id, schedule_type,
+                                             retention_count, minute,
+                                             hour, day_of_week)
+
+    if enabled:
+        click.echo('%s snapshots have been enabled for volume %s'
+                   % (schedule_type, volume_id))
diff --git a/SoftLayer/CLI/block/snapshot_list.py b/SoftLayer/CLI/block/snapshot/list.py
similarity index 65%
rename from SoftLayer/CLI/block/snapshot_list.py
rename to SoftLayer/CLI/block/snapshot/list.py
index 7ffea3f5..b47f5949 100644
--- a/SoftLayer/CLI/block/snapshot_list.py
+++ b/SoftLayer/CLI/block/snapshot/list.py
@@ -9,17 +9,12 @@
 
 
 COLUMNS = [
-    column_helper.Column(
-        'id',
-        ('snapshots', 'id',),
-        mask='snapshots.id'),
-    column_helper.Column('name', ('snapshots', 'notes',),
-                         mask='snapshots.notes'),
-    column_helper.Column('created',
-                         ('snapshots', 'snapshotCreationTimestamp',),
-                         mask='snapshots.snapshotCreationTimestamp'),
-    column_helper.Column('size_bytes', ('snapshots', 'snapshotSizeBytes',),
-                         mask='snapshots.snapshotSizeBytes'),
+    column_helper.Column('id', ('id',), mask='id'),
+    column_helper.Column('name', ('notes',), mask='notes'),
+    column_helper.Column('created', ('snapshotCreationTimestamp',),
+                         mask='snapshotCreationTimestamp'),
+    column_helper.Column('size_bytes', ('snapshotSizeBytes',),
+                         mask='snapshotSizeBytes'),
 ]
 
 DEFAULT_COLUMNS = [
@@ -40,12 +35,12 @@
                   ', '.join(column.name for column in COLUMNS)),
               default=','.join(DEFAULT_COLUMNS))
 @environment.pass_env
-def cli(env, sortby, columns, volume_id):
+def cli(env, volume_id, sortby, columns):
     """List block storage snapshots."""
     block_manager = SoftLayer.BlockStorageManager(env.client)
     snapshots = block_manager.get_block_volume_snapshot_list(
-        volume_id=volume_id,
-        mask=columns.mask(),
+        volume_id,
+        mask=columns.mask()
     )
 
     table = formatting.Table(columns.columns)
diff --git a/SoftLayer/CLI/block/snapshot/order.py b/SoftLayer/CLI/block/snapshot/order.py
new file mode 100644
index 00000000..119c4d86
--- /dev/null
+++ b/SoftLayer/CLI/block/snapshot/order.py
@@ -0,0 +1,52 @@
+"""Order snapshot space for a block storage volume."""
+# :license: MIT, see LICENSE for more details.
+
+import click
+import SoftLayer
+from SoftLayer.CLI import environment
+from SoftLayer.CLI import exceptions
+
+
+@click.command()
+@click.argument('volume_id')
+@click.option('--capacity',
+              type=int,
+              help='Size of snapshot space to create in GB',
+              required=True)
+@click.option('--tier',
+              help='Endurance Storage Tier (IOPS per GB) of the block'
+              ' volume for which space is ordered [optional]',
+              type=click.Choice(['0.25', '2', '4']))
+@click.option('--upgrade',
+              type=bool,
+              help='Flag to indicate that the order is an upgrade',
+              default=False,
+              is_flag=True)
+@environment.pass_env
+def cli(env, volume_id, capacity, tier, upgrade):
+    """Order snapshot space for a block storage volume."""
+    block_manager = SoftLayer.BlockStorageManager(env.client)
+
+    if tier is not None:
+        tier = float(tier)
+
+    try:
+        order = block_manager.order_snapshot_space(
+            volume_id,
+            capacity=capacity,
+            tier=tier,
+            upgrade=upgrade
+        )
+    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'])
+        if 'status' in order['placedOrder'].keys():
+            click.echo(" > Order status: %s" % order['placedOrder']['status'])
+    else:
+        click.echo("Order could not be placed! Please verify your options " +
+                   "and try again.")
diff --git a/SoftLayer/CLI/block/snapshot/restore.py b/SoftLayer/CLI/block/snapshot/restore.py
new file mode 100644
index 00000000..8cdab7b0
--- /dev/null
+++ b/SoftLayer/CLI/block/snapshot/restore.py
@@ -0,0 +1,22 @@
+"""Restore a block volume from a snapshot."""
+# :license: MIT, see LICENSE for more details.
+
+import click
+import SoftLayer
+from SoftLayer.CLI import environment
+
+
+@click.command()
+@click.argument('volume_id')
+@click.option('--snapshot-id', '-s',
+              help='The id of the snapshot which will be used'
+              ' to restore the block volume')
+@environment.pass_env
+def cli(env, volume_id, snapshot_id):
+    """Restore block volume using a given snapshot"""
+    block_manager = SoftLayer.BlockStorageManager(env.client)
+    success = block_manager.restore_from_snapshot(volume_id, snapshot_id)
+
+    if success:
+        click.echo('Block volume %s is being restored using snapshot %s'
+                   % (volume_id, snapshot_id))
diff --git a/SoftLayer/CLI/file/__init__.py b/SoftLayer/CLI/file/__init__.py
new file mode 100644
index 00000000..2b9101d2
--- /dev/null
+++ b/SoftLayer/CLI/file/__init__.py
@@ -0,0 +1,1 @@
+"""File Storage."""
diff --git a/SoftLayer/CLI/file/access/__init__.py b/SoftLayer/CLI/file/access/__init__.py
new file mode 100644
index 00000000..22c4cd66
--- /dev/null
+++ b/SoftLayer/CLI/file/access/__init__.py
@@ -0,0 +1,1 @@
+"""File Storage Access Control."""
diff --git a/SoftLayer/CLI/file/access/authorize.py b/SoftLayer/CLI/file/access/authorize.py
new file mode 100644
index 00000000..92fe0365
--- /dev/null
+++ b/SoftLayer/CLI/file/access/authorize.py
@@ -0,0 +1,49 @@
+"""Authorizes hosts on a specific file volume."""
+# :license: MIT, see LICENSE for more details.
+
+import click
+import SoftLayer
+from SoftLayer.CLI import environment
+from SoftLayer.CLI import exceptions
+
+
+@click.command()
+@click.argument('volume_id')
+@click.option('--hardware-id', '-h', multiple=True,
+              help='The id of one SoftLayer_Hardware to authorize')
+@click.option('--virtual-id', '-v', multiple=True,
+              help='The id of one SoftLayer_Virtual_Guest to authorize')
+@click.option('--ip-address-id', '-i', multiple=True,
+              help='The id of one SoftLayer_Network_Subnet_IpAddress'
+              ' to authorize')
+@click.option('--ip-address', multiple=True,
+              help='An IP address to authorize')
+@click.option('--subnet-id', '-s', multiple=True,
+              help='The id of one SoftLayer_Network_Subnet to authorize')
+@environment.pass_env
+def cli(env, volume_id, hardware_id, virtual_id, ip_address_id,
+        ip_address, subnet_id):
+    """Authorizes hosts to access a given volume"""
+    file_manager = SoftLayer.FileStorageManager(env.client)
+    ip_address_id_list = list(ip_address_id)
+
+    # Convert actual IP Addresses to their SoftLayer ids
+    if ip_address is not None:
+        network_manager = SoftLayer.NetworkManager(env.client)
+        for ip_address_value in ip_address:
+            ip_address_object = network_manager.ip_lookup(ip_address_value)
+            if ip_address_object == "":
+                click.echo("IP Address not found on your account.  " +
+                           "Please confirm IP and try again.")
+                raise exceptions.ArgumentError('Incorrect IP Address')
+            else:
+                ip_address_id_list.append(ip_address_object['id'])
+
+    file_manager.authorize_host_to_volume(volume_id,
+                                          hardware_id,
+                                          virtual_id,
+                                          ip_address_id_list,
+                                          subnet_id)
+
+    # If no exception was raised, the command succeeded
+    click.echo('The specified hosts were authorized to access %s' % volume_id)
diff --git a/SoftLayer/CLI/file/access/list.py b/SoftLayer/CLI/file/access/list.py
new file mode 100644
index 00000000..cc9997a0
--- /dev/null
+++ b/SoftLayer/CLI/file/access/list.py
@@ -0,0 +1,38 @@
+"""List hosts with access to file volume."""
+# :license: MIT, see LICENSE for more details.
+
+import click
+import SoftLayer
+from SoftLayer.CLI import columns as column_helper
+from SoftLayer.CLI import environment
+from SoftLayer.CLI import formatting
+from SoftLayer.CLI import storage_utils
+
+
+@click.command()
+@click.argument('volume_id')
+@click.option('--sortby', help='Column to sort by', default='name')
+@click.option('--columns',
+              callback=column_helper.get_formatter(storage_utils.COLUMNS),
+              help='Columns to display. Options: {0}'.format(
+                  ', '.join(column.name for column in storage_utils.COLUMNS)),
+              default=','.join(storage_utils.DEFAULT_COLUMNS))
+@environment.pass_env
+def cli(env, columns, sortby, volume_id):
+    """List ACLs."""
+    file_manager = SoftLayer.FileStorageManager(env.client)
+    access_list = file_manager.get_file_volume_access_list(
+        volume_id=volume_id)
+    table = formatting.Table(columns.columns)
+    table.sortby = sortby
+
+    for key, type_name in [('allowedVirtualGuests', 'VIRTUAL'),
+                           ('allowedHardware', 'HARDWARE'),
+                           ('allowedSubnets', 'SUBNET'),
+                           ('allowedIpAddresses', 'IP')]:
+        for obj in access_list.get(key, []):
+            obj['type'] = type_name
+            table.add_row([value or formatting.blank()
+                           for value in columns.row(obj)])
+
+    env.fout(table)
diff --git a/SoftLayer/CLI/file/access/revoke.py b/SoftLayer/CLI/file/access/revoke.py
new file mode 100644
index 00000000..e9119235
--- /dev/null
+++ b/SoftLayer/CLI/file/access/revoke.py
@@ -0,0 +1,46 @@
+"""Revokes hosts' access on a specific file 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('--hardware-id', '-h', multiple=True,
+              help='The id of one SoftLayer_Hardware'
+              ' to revoke authorization')
+@click.option('--virtual-id', '-v', multiple=True,
+              help='The id of one SoftLayer_Virtual_Guest'
+              ' to revoke authorization')
+@click.option('--ip-address-id', '-i', multiple=True,
+              help='The id of one SoftLayer_Network_Subnet_IpAddress'
+              ' to revoke authorization')
+@click.option('--ip-address', multiple=True,
+              help='An IP address to revoke authorization')
+@click.option('--subnet-id', '-s', multiple=True,
+              help='The id of one SoftLayer_Network_Subnet'
+              ' to revoke authorization')
+@environment.pass_env
+def cli(env, volume_id, hardware_id, virtual_id, ip_address_id,
+        ip_address, subnet_id):
+    """Revokes authorization for hosts accessing a given volume"""
+    file_manager = SoftLayer.FileStorageManager(env.client)
+    ip_address_id_list = list(ip_address_id)
+
+    # Convert actual IP Addresses to their SoftLayer ids
+    if ip_address is not None:
+        network_manager = SoftLayer.NetworkManager(env.client)
+        for ip_address_value in ip_address:
+            ip_address_object = network_manager.ip_lookup(ip_address_value)
+            ip_address_id_list.append(ip_address_object['id'])
+
+    file_manager.deauthorize_host_to_volume(volume_id,
+                                            hardware_id,
+                                            virtual_id,
+                                            ip_address_id_list,
+                                            subnet_id)
+
+    # If no exception was raised, the command succeeded
+    click.echo('Access to %s was revoked for the specified hosts' % volume_id)
diff --git a/SoftLayer/CLI/file/cancel.py b/SoftLayer/CLI/file/cancel.py
new file mode 100644
index 00000000..05229f3b
--- /dev/null
+++ b/SoftLayer/CLI/file/cancel.py
@@ -0,0 +1,39 @@
+"""Cancel an existing file storage volume."""
+# :license: MIT, see LICENSE for more details.
+
+import click
+
+import SoftLayer
+from SoftLayer.CLI import environment
+from SoftLayer.CLI import exceptions
+from SoftLayer.CLI import formatting
+
+
+@click.command()
+@click.argument('volume-id')
+@click.option('--reason', help="An optional reason for cancellation")
+@click.option('--immediate',
+              is_flag=True,
+              help="Cancels the file storage volume immediately instead "
+                   "of on the billing anniversary")
+@environment.pass_env
+def cli(env, volume_id, reason, immediate):
+    """Cancel an existing file storage volume."""
+
+    file_storage_manager = SoftLayer.FileStorageManager(env.client)
+
+    if not (env.skip_confirmations or formatting.no_going_back(volume_id)):
+        raise exceptions.CLIAbort('Aborted')
+
+    cancelled = file_storage_manager.cancel_file_volume(volume_id,
+                                                        reason, immediate)
+
+    if cancelled:
+        if immediate:
+            click.echo('File volume with id %s has been marked'
+                       ' for immediate cancellation' % volume_id)
+        else:
+            click.echo('File volume with id %s has been marked'
+                       ' for cancellation' % volume_id)
+    else:
+        click.echo('Unable to cancle file volume %s' % volume_id)
diff --git a/SoftLayer/CLI/file/detail.py b/SoftLayer/CLI/file/detail.py
new file mode 100644
index 00000000..b73ecc0d
--- /dev/null
+++ b/SoftLayer/CLI/file/detail.py
@@ -0,0 +1,111 @@
+"""Display details for a specified volume."""
+# :license: MIT, see LICENSE for more details.
+
+import click
+import SoftLayer
+from SoftLayer.CLI import environment
+from SoftLayer.CLI import formatting
+from SoftLayer import utils
+
+
+@click.command()
+@click.argument('volume_id')
+@environment.pass_env
+def cli(env, volume_id):
+    """Display details for a specified volume."""
+    file_manager = SoftLayer.FileStorageManager(env.client)
+    file_volume = file_manager.get_file_volume_details(volume_id)
+    file_volume = utils.NestedDict(file_volume)
+    used_space = int(file_volume['bytesUsed'])
+
+    table = formatting.KeyValueTable(['Name', 'Value'])
+    table.align['Name'] = 'r'
+    table.align['Value'] = 'l'
+
+    storage_type = file_volume['storageType']['keyName'].split('_').pop(0)
+    table.add_row(['ID', file_volume['id']])
+    table.add_row(['Username', file_volume['username']])
+    table.add_row(['Type', storage_type])
+    table.add_row(['Capacity (GB)', "%iGB" % file_volume['capacityGb']])
+
+    if used_space < (1 << 10):
+        table.add_row(['Used Space', "%dB" % used_space])
+    elif used_space < (1 << 20):
+        table.add_row(['Used Space', "%dKB" % (used_space / (1 << 10))])
+    elif used_space < (1 << 30):
+        table.add_row(['Used Space', "%dMB" % (used_space / (1 << 20))])
+    else:
+        table.add_row(['Used Space', "%dGB" % (used_space / (1 << 30))])
+
+    if file_volume.get('iops'):
+        table.add_row(['IOPs', file_volume['iops']])
+
+    if file_volume.get('storageTierLevel'):
+        table.add_row([
+            'Endurance Tier',
+            file_volume['storageTierLevel']['description'],
+        ])
+
+    table.add_row([
+        'Data Center',
+        file_volume['serviceResource']['datacenter']['name'],
+    ])
+    table.add_row([
+        'Target IP',
+        file_volume['serviceResourceBackendIpAddress'],
+    ])
+
+    if file_volume['snapshotCapacityGb']:
+        table.add_row([
+            'Snapshot Capacity (GB)',
+            file_volume['snapshotCapacityGb'],
+        ])
+        if 'snapshotSizeBytes' in file_volume['parentVolume']:
+            table.add_row([
+                'Snapshot Used (Bytes)',
+                file_volume['parentVolume']['snapshotSizeBytes'],
+            ])
+
+    table.add_row(['# of Active Transactions', "%i"
+                   % file_volume['activeTransactionCount']])
+
+    if file_volume['activeTransactions']:
+        for trans in file_volume['activeTransactions']:
+            table.add_row([
+                '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
new file mode 100644
index 00000000..a3266362
--- /dev/null
+++ b/SoftLayer/CLI/file/list.py
@@ -0,0 +1,75 @@
+"""List file storage volumes."""
+# :license: MIT, see LICENSE for more details.
+
+import click
+import SoftLayer
+from SoftLayer.CLI import columns as column_helper
+from SoftLayer.CLI import environment
+from SoftLayer.CLI import formatting
+
+
+COLUMNS = [
+    column_helper.Column('id', ('id',), mask="id"),
+    column_helper.Column('username', ('username',), mask="username"),
+    column_helper.Column('datacenter',
+                         ('serviceResource', 'datacenter', 'name'),
+                         mask="serviceResource.datacenter.name"),
+    column_helper.Column(
+        'storage_type',
+        lambda b: b['storageType']['keyName'].split('_').pop(0)
+        if 'storageType' in b and 'keyName' in b['storageType']
+        and isinstance(b['storageType']['keyName'], str)
+        else '-',
+        mask="storageType.keyName"),
+    column_helper.Column('capacity_gb', ('capacityGb',), mask="capacityGb"),
+    column_helper.Column('bytes_used', ('bytesUsed',), mask="bytesUsed"),
+    column_helper.Column('ip_addr', ('serviceResourceBackendIpAddress',),
+                         mask="serviceResourceBackendIpAddress"),
+    column_helper.Column('active_transactions', ('activeTransactionCount',),
+                         mask="activeTransactionCount"),
+    column_helper.Column(
+        'created_by',
+        ('billingItem', 'orderItem', 'order', 'userRecord', 'username')),
+]
+
+DEFAULT_COLUMNS = [
+    'id',
+    'username',
+    'datacenter',
+    'storage_type',
+    'capacity_gb',
+    'bytes_used',
+    'ip_addr',
+    'active_transactions'
+]
+
+
+@click.command()
+@click.option('--username', '-u', help='Volume username')
+@click.option('--datacenter', '-d', help='Datacenter shortname')
+@click.option('--storage-type',
+              help='Type of storage volume',
+              type=click.Choice(['performance', 'endurance']))
+@click.option('--sortby', help='Column to sort by', default='username')
+@click.option('--columns',
+              callback=column_helper.get_formatter(COLUMNS),
+              help='Columns to display. Options: {0}'.format(
+                  ', '.join(column.name for column in COLUMNS)),
+              default=','.join(DEFAULT_COLUMNS))
+@environment.pass_env
+def cli(env, sortby, columns, datacenter, username, storage_type):
+    """List file storage."""
+    file_manager = SoftLayer.FileStorageManager(env.client)
+    file_volumes = file_manager.list_file_volumes(datacenter=datacenter,
+                                                  username=username,
+                                                  storage_type=storage_type,
+                                                  mask=columns.mask())
+
+    table = formatting.Table(columns.columns)
+    table.sortby = sortby
+
+    for file_volume in file_volumes:
+        table.add_row([value or formatting.blank()
+                       for value in columns.row(file_volume)])
+
+    env.fout(table)
diff --git a/SoftLayer/CLI/file/order.py b/SoftLayer/CLI/file/order.py
new file mode 100644
index 00000000..0219efdb
--- /dev/null
+++ b/SoftLayer/CLI/file/order.py
@@ -0,0 +1,112 @@
+"""Order a file storage volume."""
+# :license: MIT, see LICENSE for more details.
+
+import click
+import SoftLayer
+from SoftLayer.CLI import environment
+from SoftLayer.CLI import exceptions
+
+
+CONTEXT_SETTINGS = {'token_normalize_func': lambda x: x.upper()}
+
+
+@click.command(context_settings=CONTEXT_SETTINGS)
+@click.option('--storage-type',
+              help='Type of file storage volume',
+              type=click.Choice(['performance', 'endurance']),
+              required=True)
+@click.option('--size',
+              type=int,
+              help='Size of file storage volume in GB',
+              required=True)
+@click.option('--iops',
+              type=int,
+              help='Performance Storage IOPs,'
+              ' between 100 and 6000 in multiples of 100'
+              '  [required for storage-type performance]')
+@click.option('--tier',
+              help='Endurance Storage Tier (IOP per GB)'
+              '  [required for storage-type endurance]',
+              type=click.Choice(['0.25', '2', '4']))
+@click.option('--os-type',
+              help='Operating System',
+              type=click.Choice([
+                  'HYPER_V',
+                  'LINUX',
+                  'VMWARE',
+                  'WINDOWS_2008',
+                  'WINDOWS_GPT',
+                  'WINDOWS',
+                  'XEN']),
+              required=True)
+@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, snapshot_size):
+    """Order a file storage volume."""
+    file_manager = SoftLayer.FileStorageManager(env.client)
+    storage_type = storage_type.lower()
+
+    if storage_type == 'performance':
+        if iops is None:
+            raise exceptions.CLIAbort(
+                'Option --iops required with Performance')
+
+        if iops < 100 or iops > 6000:
+            raise exceptions.CLIAbort(
+                'Option --iops must be between 100 and 6000, inclusive')
+
+        if iops % 100 != 0:
+            raise exceptions.CLIAbort(
+                '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',
+                location=location,
+                size=size,
+                iops=iops,
+                os_type=os_type
+            )
+        except ValueError as ex:
+            raise exceptions.ArgumentError(str(ex))
+
+    if storage_type == 'endurance':
+        if tier is None:
+            raise exceptions.CLIAbort(
+                'Option --tier required with Endurance in IOPS/GB [0.25,2,4]')
+
+        try:
+            order = file_manager.order_file_volume(
+                storage_type='storage_service_enterprise',
+                location=location,
+                size=size,
+                tier_level=float(tier),
+                os_type=os_type,
+                snapshot_size=snapshot_size
+            )
+        except ValueError as ex:
+            raise exceptions.ArgumentError(str(ex))
+
+    if 'placedOrder' in order.keys():
+        click.echo("Order #{0} placed successfully!".format(
+            order['placedOrder']['id']))
+        for item in order['placedOrder']['items']:
+            click.echo(" > %s" % item['description'])
+    else:
+        click.echo("Order could not be placed! Please verify your options " +
+                   "and try again.")
diff --git a/SoftLayer/CLI/file/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/__init__.py b/SoftLayer/CLI/file/snapshot/__init__.py
new file mode 100644
index 00000000..b9ec8544
--- /dev/null
+++ b/SoftLayer/CLI/file/snapshot/__init__.py
@@ -0,0 +1,1 @@
+"""File Storage Snapshot Control."""
diff --git a/SoftLayer/CLI/file/snapshot/cancel.py b/SoftLayer/CLI/file/snapshot/cancel.py
new file mode 100644
index 00000000..e0fc26f8
--- /dev/null
+++ b/SoftLayer/CLI/file/snapshot/cancel.py
@@ -0,0 +1,40 @@
+"""Cancel a snapshot space subscription."""
+# :license: MIT, see LICENSE for more details.
+
+import click
+
+import SoftLayer
+from SoftLayer.CLI import environment
+from SoftLayer.CLI import exceptions
+from SoftLayer.CLI import formatting
+
+
+@click.command()
+@click.argument('volume-id')
+@click.option('--reason', help="An optional reason for cancellation")
+@click.option('--immediate',
+              is_flag=True,
+              help="Cancels the snapshot space immediately instead "
+                   "of on the billing anniversary")
+@environment.pass_env
+def cli(env, volume_id, reason, immediate):
+    """Cancel existing snapshot space for a given volume."""
+
+    file_storage_manager = SoftLayer.FileStorageManager(env.client)
+
+    if not (env.skip_confirmations or formatting.no_going_back(volume_id)):
+        raise exceptions.CLIAbort('Aborted')
+
+    cancelled = file_storage_manager.cancel_snapshot_space(
+        volume_id, reason, immediate)
+
+    if cancelled:
+        if immediate:
+            click.echo('File volume with id %s has been marked'
+                       ' for immediate snapshot cancellation' % volume_id)
+        else:
+            click.echo('File volume with id %s has been marked'
+                       ' for snapshot cancellation' % volume_id)
+    else:
+        click.echo('Unable to cancel snapshot space for file volume %s'
+                   % volume_id)
diff --git a/SoftLayer/CLI/file/snapshot/create.py b/SoftLayer/CLI/file/snapshot/create.py
new file mode 100644
index 00000000..ec7ff1ff
--- /dev/null
+++ b/SoftLayer/CLI/file/snapshot/create.py
@@ -0,0 +1,24 @@
+"""Create a file storage snapshot."""
+# :license: MIT, see LICENSE for more details.
+
+import click
+import SoftLayer
+from SoftLayer.CLI import environment
+
+
+@click.command()
+@click.argument('volume_id')
+@click.option('--notes', '-n',
+              help='Notes to set on the new snapshot')
+@environment.pass_env
+def cli(env, volume_id, notes):
+    """Creates a snapshot on a given volume"""
+    file_storage_manager = SoftLayer.FileStorageManager(env.client)
+    snapshot = file_storage_manager.create_snapshot(volume_id, notes=notes)
+
+    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/snapshot/delete.py b/SoftLayer/CLI/file/snapshot/delete.py
new file mode 100644
index 00000000..8ff15313
--- /dev/null
+++ b/SoftLayer/CLI/file/snapshot/delete.py
@@ -0,0 +1,18 @@
+"""Delete a file storage snapshot."""
+# :license: MIT, see LICENSE for more details.
+
+import click
+import SoftLayer
+from SoftLayer.CLI import environment
+
+
+@click.command()
+@click.argument('snapshot_id')
+@environment.pass_env
+def cli(env, snapshot_id):
+    """Deletes a snapshot on a given volume"""
+    file_storage_manager = SoftLayer.FileStorageManager(env.client)
+    deleted = file_storage_manager.delete_snapshot(snapshot_id)
+
+    if deleted:
+        click.echo('Snapshot %s deleted' % snapshot_id)
diff --git a/SoftLayer/CLI/file/snapshot/disable.py b/SoftLayer/CLI/file/snapshot/disable.py
new file mode 100644
index 00000000..9ec4fde8
--- /dev/null
+++ b/SoftLayer/CLI/file/snapshot/disable.py
@@ -0,0 +1,29 @@
+"""Disable scheduled snapshots of a specific volume"""
+# :license: MIT, see LICENSE for more details.
+
+import click
+import SoftLayer
+from SoftLayer.CLI import environment
+from SoftLayer.CLI import exceptions
+
+
+@click.command()
+@click.argument('volume_id')
+@click.option('--schedule-type',
+              help='Snapshot schedule [HOURLY|DAILY|WEEKLY]',
+              required=True)
+@environment.pass_env
+def cli(env, volume_id, schedule_type):
+    """Disables snapshots on the specified schedule for a given volume"""
+
+    if (schedule_type != 'HOURLY' and schedule_type != 'DAILY'
+            and schedule_type != 'WEEKLY'):
+        raise exceptions.CLIAbort(
+            '--schedule_type must be HOURLY, DAILY, or WEEKLY')
+
+    file_manager = SoftLayer.FileStorageManager(env.client)
+    disabled = file_manager.disable_snapshots(volume_id, schedule_type)
+
+    if disabled:
+        click.echo('%s snapshots have been disabled for volume %s'
+                   % (schedule_type, volume_id))
diff --git a/SoftLayer/CLI/file/snapshot/enable.py b/SoftLayer/CLI/file/snapshot/enable.py
new file mode 100644
index 00000000..7e73feef
--- /dev/null
+++ b/SoftLayer/CLI/file/snapshot/enable.py
@@ -0,0 +1,58 @@
+# snapshot_enable.py
+"""Create a file storage snapshot [ENABLE]."""
+
+import click
+import SoftLayer
+from SoftLayer.CLI import environment
+from SoftLayer.CLI import exceptions
+
+
+@click.command()
+@click.argument('volume_id')
+@click.option('--schedule-type',
+              help='Snapshot schedule [HOURLY|DAILY|WEEKLY]',
+              required=True)
+@click.option('--retention-count',
+              help='Number of snapshots to retain',
+              required=True)
+@click.option('--minute',
+              help='Minute of the day when snapshots should be taken',
+              default=0)
+@click.option('--hour',
+              help='Hour of the day when snapshots should be taken',
+              default=0)
+@click.option('--day-of-week',
+              help='Day of the week when snapshots should be taken',
+              default='SUNDAY')
+@environment.pass_env
+def cli(env, volume_id, schedule_type, retention_count,
+        minute, hour, day_of_week):
+    """Enables snapshots for a given volume on the specified schedule"""
+    file_manager = SoftLayer.FileStorageManager(env.client)
+
+    valid_schedule_types = {'HOURLY', 'DAILY', 'WEEKLY'}
+    valid_days = {'SUNDAY', 'MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY',
+                  'FRIDAY', 'SATURDAY'}
+
+    if schedule_type not in valid_schedule_types:
+        raise exceptions.CLIAbort(
+            '--schedule-type must be HOURLY, DAILY, or WEEKLY, not '
+            + schedule_type)
+
+    if minute < 0 or minute > 59:
+        raise exceptions.CLIAbort(
+            '--minute value must be between 0 and 59')
+    if hour < 0 or hour > 23:
+        raise exceptions.CLIAbort(
+            '--hour value must be between 0 and 23')
+    if day_of_week not in valid_days:
+        raise exceptions.CLIAbort(
+            '--day_of_week value must be a valid day (ex: SUNDAY)')
+
+    enabled = file_manager.enable_snapshots(volume_id, schedule_type,
+                                            retention_count, minute,
+                                            hour, day_of_week)
+
+    if enabled:
+        click.echo('%s snapshots have been enabled for volume %s'
+                   % (schedule_type, volume_id))
diff --git a/SoftLayer/CLI/file/snapshot/list.py b/SoftLayer/CLI/file/snapshot/list.py
new file mode 100644
index 00000000..494900f0
--- /dev/null
+++ b/SoftLayer/CLI/file/snapshot/list.py
@@ -0,0 +1,53 @@
+"""List file storage snapshots."""
+# :license: MIT, see LICENSE for more details.
+
+import click
+import SoftLayer
+from SoftLayer.CLI import columns as column_helper
+from SoftLayer.CLI import environment
+from SoftLayer.CLI import formatting
+
+
+COLUMNS = [
+    column_helper.Column('id', ('id',), mask='id'),
+    column_helper.Column('name', ('notes',), mask='notes'),
+    column_helper.Column('created', ('snapshotCreationTimestamp',),
+                         mask='snapshotCreationTimestamp'),
+    column_helper.Column('size_bytes', ('snapshotSizeBytes',),
+                         mask='snapshotSizeBytes'),
+]
+
+DEFAULT_COLUMNS = [
+    'id',
+    'name',
+    'created',
+    'size_bytes'
+]
+
+
+@click.command()
+@click.argument('volume_id')
+@click.option('--sortby', help='Column to sort by',
+              default='created')
+@click.option('--columns',
+              callback=column_helper.get_formatter(COLUMNS),
+              help='Columns to display. Options: {0}'.format(
+                  ', '.join(column.name for column in COLUMNS)),
+              default=','.join(DEFAULT_COLUMNS))
+@environment.pass_env
+def cli(env, volume_id, sortby, columns):
+    """List file storage snapshots."""
+    file_manager = SoftLayer.FileStorageManager(env.client)
+    snapshots = file_manager.get_file_volume_snapshot_list(
+        volume_id,
+        mask=columns.mask()
+    )
+
+    table = formatting.Table(columns.columns)
+    table.sortby = sortby
+
+    for snapshot in snapshots:
+        table.add_row([value or formatting.blank()
+                       for value in columns.row(snapshot)])
+
+    env.fout(table)
diff --git a/SoftLayer/CLI/file/snapshot/order.py b/SoftLayer/CLI/file/snapshot/order.py
new file mode 100644
index 00000000..fbfff517
--- /dev/null
+++ b/SoftLayer/CLI/file/snapshot/order.py
@@ -0,0 +1,52 @@
+"""Order snapshot space for a file storage volume."""
+# :license: MIT, see LICENSE for more details.
+
+import click
+import SoftLayer
+from SoftLayer.CLI import environment
+from SoftLayer.CLI import exceptions
+
+
+@click.command()
+@click.argument('volume_id')
+@click.option('--capacity',
+              type=int,
+              help='Size of snapshot space to create in GB',
+              required=True)
+@click.option('--tier',
+              help='Endurance Storage Tier (IOPS per GB) of the file'
+              ' volume for which space is ordered [optional]',
+              type=click.Choice(['0.25', '2', '4']))
+@click.option('--upgrade',
+              type=bool,
+              help='Flag to indicate that the order is an upgrade',
+              default=False,
+              is_flag=True)
+@environment.pass_env
+def cli(env, volume_id, capacity, tier, upgrade):
+    """Order snapshot space for a file storage volume."""
+    file_manager = SoftLayer.FileStorageManager(env.client)
+
+    if tier is not None:
+        tier = float(tier)
+
+    try:
+        order = file_manager.order_snapshot_space(
+            volume_id,
+            capacity=capacity,
+            tier=tier,
+            upgrade=upgrade
+        )
+    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'])
+        if 'status' in order['placedOrder'].keys():
+            click.echo(" > Order status: %s" % order['placedOrder']['status'])
+    else:
+        click.echo("Order could not be placed! Please verify your options " +
+                   "and try again.")
diff --git a/SoftLayer/CLI/file/snapshot/restore.py b/SoftLayer/CLI/file/snapshot/restore.py
new file mode 100644
index 00000000..881a9338
--- /dev/null
+++ b/SoftLayer/CLI/file/snapshot/restore.py
@@ -0,0 +1,22 @@
+"""Restore a file volume from a snapshot."""
+# :license: MIT, see LICENSE for more details.
+
+import click
+import SoftLayer
+from SoftLayer.CLI import environment
+
+
+@click.command()
+@click.argument('volume_id')
+@click.option('--snapshot-id', '-s',
+              help='The id of the snapshot which will be used'
+              ' to restore the block volume')
+@environment.pass_env
+def cli(env, volume_id, snapshot_id):
+    """Restore file volume using a given snapshot"""
+    file_manager = SoftLayer.FileStorageManager(env.client)
+    success = file_manager.restore_from_snapshot(volume_id, snapshot_id)
+
+    if success:
+        click.echo('File volume %s is being restored using snapshot %s'
+                   % (volume_id, snapshot_id))
diff --git a/SoftLayer/CLI/hardware/detail.py b/SoftLayer/CLI/hardware/detail.py
index 012c5df5..bba31b2f 100644
--- a/SoftLayer/CLI/hardware/detail.py
+++ b/SoftLayer/CLI/hardware/detail.py
@@ -50,14 +50,13 @@ def cli(env, identifier, passwords, price):
                    result['primaryBackendIpAddress'] or formatting.blank()])
     table.add_row(['ipmi_ip',
                    result['networkManagementIpAddress'] or formatting.blank()])
-    table.add_row([
-        'os',
-        formatting.FormattedItem(
-            result['operatingSystem']['softwareLicense']
-            ['softwareDescription']['referenceCode'] or formatting.blank(),
-            result['operatingSystem']['softwareLicense']
-            ['softwareDescription']['name'] or formatting.blank()
-        )])
+    operating_system = utils.lookup(result,
+                                    'operatingSystem',
+                                    'softwareLicense',
+                                    'softwareDescription') or {}
+    table.add_row(['os', operating_system.get('name') or formatting.blank()])
+    table.add_row(['os_version',
+                   operating_system.get('version') or formatting.blank()])
 
     table.add_row(
         ['created', result['provisionDate'] or formatting.blank()])
diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py
index f7ff388f..03175c77 100644
--- a/SoftLayer/CLI/routes.py
+++ b/SoftLayer/CLI/routes.py
@@ -57,17 +57,44 @@
     ('dns:zone-print', 'SoftLayer.CLI.dns.zone_print:cli'),
 
     ('block', 'SoftLayer.CLI.block'),
-    ('block:volume-list', 'SoftLayer.CLI.block.list:cli'),
-    ('block:volume-detail', 'SoftLayer.CLI.block.detail:cli'),
+    ('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'),
+    ('block:snapshot-disable', 'SoftLayer.CLI.block.snapshot.disable:cli'),
+    ('block:snapshot-enable', 'SoftLayer.CLI.block.snapshot.enable:cli'),
+    ('block:snapshot-list', 'SoftLayer.CLI.block.snapshot.list:cli'),
+    ('block:snapshot-order', 'SoftLayer.CLI.block.snapshot.order:cli'),
+    ('block:snapshot-restore', 'SoftLayer.CLI.block.snapshot.restore:cli'),
     ('block:volume-cancel', 'SoftLayer.CLI.block.cancel:cli'),
+    ('block:volume-detail', 'SoftLayer.CLI.block.detail:cli'),
+    ('block:volume-list', 'SoftLayer.CLI.block.list:cli'),
     ('block:volume-order', 'SoftLayer.CLI.block.order:cli'),
 
-    ('block:snapshot-list', 'SoftLayer.CLI.block.snapshot_list:cli'),
-    ('block:snapshot-delete', 'SoftLayer.CLI.block.snapshot_delete:cli'),
-
-    ('block:access-list', 'SoftLayer.CLI.block.access_list:cli'),
-    ('block:access-authorize', 'SoftLayer.CLI.block.access.authorize:cli'),
-    ('block:access-revoke', 'SoftLayer.CLI.block.access.revoke:cli'),
+    ('file', 'SoftLayer.CLI.file'),
+    ('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'),
+    ('file:snapshot-disable', 'SoftLayer.CLI.file.snapshot.disable:cli'),
+    ('file:snapshot-enable', 'SoftLayer.CLI.file.snapshot.enable:cli'),
+    ('file:snapshot-list', 'SoftLayer.CLI.file.snapshot.list:cli'),
+    ('file:snapshot-order', 'SoftLayer.CLI.file.snapshot.order:cli'),
+    ('file:snapshot-restore', 'SoftLayer.CLI.file.snapshot.restore:cli'),
+    ('file:volume-cancel', 'SoftLayer.CLI.file.cancel:cli'),
+    ('file:volume-detail', 'SoftLayer.CLI.file.detail:cli'),
+    ('file:volume-list', 'SoftLayer.CLI.file.list:cli'),
+    ('file:volume-order', 'SoftLayer.CLI.file.order:cli'),
 
     ('firewall', 'SoftLayer.CLI.firewall'),
     ('firewall:add', 'SoftLayer.CLI.firewall.add:cli'),
diff --git a/SoftLayer/CLI/block/access_list.py b/SoftLayer/CLI/storage_utils.py
similarity index 65%
rename from SoftLayer/CLI/block/access_list.py
rename to SoftLayer/CLI/storage_utils.py
index 4b45a54b..dd00596b 100644
--- a/SoftLayer/CLI/block/access_list.py
+++ b/SoftLayer/CLI/storage_utils.py
@@ -1,11 +1,7 @@
-"""List hosts with access to volume."""
+"""Utility functions for use with File and Block commands."""
 # :license: MIT, see LICENSE for more details.
 
-import click
-import SoftLayer
 from SoftLayer.CLI import columns as column_helper
-from SoftLayer.CLI import environment
-from SoftLayer.CLI import formatting
 
 
 def _format_name(obj):
@@ -92,32 +88,3 @@ def _format_name(obj):
     'username',
     'password',
 ]
-
-
-@click.command()
-@click.argument('volume_id')
-@click.option('--sortby', help='Column to sort by', default='name')
-@click.option('--columns',
-              callback=column_helper.get_formatter(COLUMNS),
-              help='Columns to display. Options: {0}'.format(
-                  ', '.join(column.name for column in COLUMNS)),
-              default=','.join(DEFAULT_COLUMNS))
-@environment.pass_env
-def cli(env, columns, sortby, volume_id):
-    """List ACLs."""
-    block_manager = SoftLayer.BlockStorageManager(env.client)
-    access_list = block_manager.get_block_volume_access_list(
-        volume_id=volume_id)
-    table = formatting.Table(columns.columns)
-    table.sortby = sortby
-
-    for key, type_name in [('allowedVirtualGuests', 'VIRTUAL'),
-                           ('allowedHardware', 'HARDWARE'),
-                           ('allowedSubnets', 'SUBNET'),
-                           ('allowedIpAddresses', 'IP')]:
-        for obj in access_list.get(key, []):
-            obj['type'] = type_name
-            table.add_row([value or formatting.blank()
-                           for value in columns.row(obj)])
-
-    env.fout(table)
diff --git a/SoftLayer/CLI/virt/detail.py b/SoftLayer/CLI/virt/detail.py
index a4464e61..00921436 100644
--- a/SoftLayer/CLI/virt/detail.py
+++ b/SoftLayer/CLI/virt/detail.py
@@ -49,12 +49,7 @@ def cli(env, identifier, passwords=False, price=False):
                                     'operatingSystem',
                                     'softwareLicense',
                                     'softwareDescription') or {}
-    table.add_row([
-        'os',
-        formatting.FormattedItem(
-            operating_system.get('version') or formatting.blank(),
-            operating_system.get('name') or formatting.blank()
-        )])
+    table.add_row(['os', operating_system.get('name') or formatting.blank()])
     table.add_row(['os_version',
                    operating_system.get('version') or formatting.blank()])
     table.add_row(['cores', result['maxCpu']])
diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py
index 509945b3..e3649d31 100644
--- a/SoftLayer/fixtures/SoftLayer_Account.py
+++ b/SoftLayer/fixtures/SoftLayer_Account.py
@@ -472,6 +472,7 @@
     'username': 'user',
     'password': 'pass',
     'serviceResourceBackendIpAddress': '127.0.0.1',
+    'storageType': {'keyName': 'ENDURANCE_STORAGE'},
 }]
 
 getActiveQuotes = [{
diff --git a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py
index 60f0b154..6919dfef 100644
--- a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py
+++ b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py
@@ -31,8 +31,9 @@
     'operatingSystem': {
         'softwareLicense': {
             'softwareDescription': {
-                'referenceCode': 'Ubuntu',
-                'name': 'Ubuntu 12.04 LTS',
+                'referenceCode': 'UBUNTU_12_64',
+                'name': 'Ubuntu',
+                'version': 'Ubuntu 12.04 LTS',
             }
         },
         'passwords': [
diff --git a/SoftLayer/fixtures/SoftLayer_Network_Storage.py b/SoftLayer/fixtures/SoftLayer_Network_Storage.py
index 5a45f14f..52a178e3 100644
--- a/SoftLayer/fixtures/SoftLayer_Network_Storage.py
+++ b/SoftLayer/fixtures/SoftLayer_Network_Storage.py
@@ -1,6 +1,15 @@
 getObject = {
     'accountId': 1234,
-    'billingItem': {'id': 449},
+    'billingItem': {
+        'id': 449,
+        'cancellationDate': '',
+        'categoryCode': 'storage_service_enterprise',
+        'activeChildren': [{
+            'categoryCode': 'storage_snapshot_space',
+            'id': 123,
+            'cancellationDate': '',
+        }]
+    },
     'capacityGb': 20,
     'createDate': '2015:50:15-04:00',
     'guestId': '',
@@ -12,14 +21,22 @@
     'password': '',
     'serviceProviderId': 1,
     'iops': 1000,
-    'storageTierLevel': {'description': 'Tier 1'},
-    'snapshotCapacityGb': 10,
+    'storageTierLevel': {'description': '2 IOPS per GB'},
+    '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',
     'username': 'username',
     'storageType': {'keyName': 'ENDURANCE_STORAGE'},
+    'bytesUsed': 0,
+    'activeTransactions': None,
+    'activeTransactionCount': 0,
     'allowedVirtualGuests': [{
         'id': 1234,
         'hostname': 'test-server',
@@ -60,8 +77,40 @@
             '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 = []
+
+getSnapshots = [{
+    'id': 470,
+    'notes': 'unit_testing_note',
+    'snapshotCreationTimestamp': '2016-07-06T07:41:19-05:00',
+    'snapshotSizeBytes': '42',
+}]
+
 deleteObject = True
 allowAccessFromHostList = True
 removeAccessFromHostList = True
+failoverToReplicant = True
+failbackFromReplicant = True
+restoreFromSnapshot = True
+
+createSnapshot = {
+    'id': 449
+}
+
+enableSnapshots = True
+disableSnapshots = True
diff --git a/SoftLayer/managers/__init__.py b/SoftLayer/managers/__init__.py
index cab39fbb..b9a44a0d 100644
--- a/SoftLayer/managers/__init__.py
+++ b/SoftLayer/managers/__init__.py
@@ -10,6 +10,7 @@
 from SoftLayer.managers.block import BlockStorageManager
 from SoftLayer.managers.cdn import CDNManager
 from SoftLayer.managers.dns import DNSManager
+from SoftLayer.managers.file import FileStorageManager
 from SoftLayer.managers.firewall import FirewallManager
 from SoftLayer.managers.hardware import HardwareManager
 from SoftLayer.managers.image import ImageManager
@@ -29,6 +30,7 @@
     'BlockStorageManager',
     'CDNManager',
     'DNSManager',
+    'FileStorageManager',
     'FirewallManager',
     'HardwareManager',
     'ImageManager',
diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py
index 26040321..90f94cb4 100644
--- a/SoftLayer/managers/block.py
+++ b/SoftLayer/managers/block.py
@@ -6,18 +6,17 @@
     :license: MIT, see LICENSE for more details.
 """
 from SoftLayer import exceptions
+from SoftLayer.managers import storage_utils
 from SoftLayer import utils
 
 
-ENDURANCE_TIERS = {
-    0.25: 100,
-    2: 200,
-    4: 300,
-}
+class BlockStorageManager(utils.IdentifierMixin, object):
+    """Manages SoftLayer Block Storage volumes.
 
+    See product information here: http://www.softlayer.com/block-storage
 
-class BlockStorageManager(utils.IdentifierMixin, object):
-    """Manages Block Storage volumes."""
+    :param SoftLayer.API.BaseClient client: the client instance
+    """
 
     def __init__(self, client):
         self.configuration = {}
@@ -37,10 +36,12 @@ def list_block_volumes(self, datacenter=None, username=None,
             items = [
                 'id',
                 'username',
+                'lunId',
                 'capacityGb',
                 'bytesUsed',
                 'serviceResource.datacenter[name]',
-                'serviceResourceBackendIpAddress'
+                'serviceResourceBackendIpAddress',
+                'activeTransactionCount'
             ]
             kwargs['mask'] = ','.join(items)
 
@@ -50,7 +51,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()))
@@ -88,6 +89,14 @@ def get_block_volume_details(self, volume_id, **kwargs):
                 'storageTierLevel',
                 'iops',
                 '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',
@@ -120,16 +129,17 @@ def get_block_volume_snapshot_list(self, volume_id, **kwargs):
         :return: Returns a list of snapshots for the specified volume.
         """
         if 'mask' not in kwargs:
-            items = '''snapshots[
-    id,
-    notes,
-    snapshotSizeBytes,
-    storageType[keyName],
-    snapshotCreationTimestamp
-    hourlySchedule,
-    dailySchedule,
-    weeklySchedule
-]'''
+            items = [
+                'id',
+                'notes',
+                'snapshotSizeBytes',
+                'storageType[keyName]',
+                'snapshotCreationTimestamp',
+                'hourlySchedule',
+                'dailySchedule',
+                'weeklySchedule'
+                ]
+
             kwargs['mask'] = ','.join(items)
 
         return self.client.call('Network_Storage', 'getSnapshots',
@@ -138,76 +148,89 @@ def get_block_volume_snapshot_list(self, volume_id, **kwargs):
     def authorize_host_to_volume(self, volume_id,
                                  hardware_ids=None,
                                  virtual_guest_ids=None,
-                                 ip_address_ids=None):
+                                 ip_address_ids=None,
+                                 **kwargs):
         """Authorizes hosts to Block Storage Volumes
 
-        :param volume_id: The Volume to authorize hosts to
+        :param volume_id: The Block volume to authorize hosts to
         :param hardware_ids: A List of SoftLayer_Hardware ids
         :param virtual_guest_ids: A List of SoftLayer_Virtual_Guest ids
         :param ip_address_ids: A List of SoftLayer_Network_Subnet_IpAddress ids
+        :return: Returns an array of
+                SoftLayer_Network_Storage_Allowed_Host objects
+                which now have access to the given Block volume
         """
         host_templates = []
-
-        if hardware_ids is not None:
-            for hardware_id in hardware_ids:
-                host_templates.append({
-                    'objectType': 'SoftLayer_Hardware',
-                    'id': hardware_id
-                })
-
-        if virtual_guest_ids is not None:
-            for virtual_guest_id in virtual_guest_ids:
-                host_templates.append({
-                    'objectType': 'SoftLayer_Virtual_Guest',
-                    'id': virtual_guest_id
-                })
-
-        if ip_address_ids is not None:
-            for ip_address_id in ip_address_ids:
-                host_templates.append({
-                    'objectType': 'SoftLayer_Network_Subnet_IpAddress',
-                    'id': ip_address_id
-                })
+        storage_utils.populate_host_templates(host_templates,
+                                              hardware_ids,
+                                              virtual_guest_ids,
+                                              ip_address_ids,
+                                              None)
 
         return self.client.call('Network_Storage', 'allowAccessFromHostList',
-                                host_templates, id=volume_id)
+                                host_templates, id=volume_id, **kwargs)
 
     def deauthorize_host_to_volume(self, volume_id,
                                    hardware_ids=None,
                                    virtual_guest_ids=None,
-                                   ip_address_ids=None):
+                                   ip_address_ids=None,
+                                   **kwargs):
         """Revokes authorization of hosts to Block Storage Volumes
 
-        :param volume_id: The Volume to deauthorize hosts to
+        :param volume_id: The Block volume to deauthorize hosts to
         :param hardware_ids: A List of SoftLayer_Hardware ids
         :param virtual_guest_ids: A List of SoftLayer_Virtual_Guest ids
         :param ip_address_ids: A List of SoftLayer_Network_Subnet_IpAddress ids
+        :return: Returns an array of
+                SoftLayer_Network_Storage_Allowed_Host objects
+                which have access to the given Block volume
         """
         host_templates = []
-
-        if hardware_ids is not None:
-            for hardware_id in hardware_ids:
-                host_templates.append({
-                    'objectType': 'SoftLayer_Hardware',
-                    'id': hardware_id
-                })
-
-        if virtual_guest_ids is not None:
-            for virtual_guest_id in virtual_guest_ids:
-                host_templates.append({
-                    'objectType': 'SoftLayer_Virtual_Guest',
-                    'id': virtual_guest_id
-                })
-
-        if ip_address_ids is not None:
-            for ip_address_id in ip_address_ids:
-                host_templates.append({
-                    'objectType': 'SoftLayer_Network_Subnet_IpAddress',
-                    'id': ip_address_id
-                })
+        storage_utils.populate_host_templates(host_templates,
+                                              hardware_ids,
+                                              virtual_guest_ids,
+                                              ip_address_ids,
+                                              None)
 
         return self.client.call('Network_Storage', 'removeAccessFromHostList',
-                                host_templates, id=volume_id)
+                                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.
@@ -218,7 +241,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)
@@ -228,35 +251,51 @@ 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:
-            location_id = self._get_location_id(location)
+            location_id = storage_utils.get_location_id(self, location)
         except ValueError:
             raise exceptions.SoftLayerError(
                 "Invalid datacenter name specified. "
                 "Please provide the lower case short name (e.g.: dal09)")
 
         base_type_name = 'SoftLayer_Container_Product_Order_Network_'
-        package = self._get_package(storage_type)
+        package = storage_utils.get_package(self, storage_type)
         if storage_type == 'performance_storage_iscsi':
             complex_type = base_type_name + 'PerformanceStorage_Iscsi'
             prices = [
-                _find_performance_block_price(package),
-                _find_performance_space_price(package, iops),
-                _find_performance_iops_price(package, size, iops),
+                storage_utils.find_performance_price(
+                    package,
+                    'performance_storage_iscsi'
+                    ),
+                storage_utils.find_performance_space_price(package, iops),
+                storage_utils.find_performance_iops_price(package, size, iops),
             ]
         elif storage_type == 'storage_service_enterprise':
             complex_type = base_type_name + 'Storage_Enterprise'
             prices = [
-                _find_endurance_block_price(package),
-                _find_endurance_price(package),
-                _find_endurance_space_price(package, size, tier_level),
-                _find_endurance_tier_price(package, tier_level),
+                storage_utils.find_endurance_price(package, 'storage_block'),
+                storage_utils.find_endurance_price(
+                    package,
+                    'storage_service_enterprise'
+                    ),
+                storage_utils.find_endurance_space_price(
+                    package,
+                    size,
+                    tier_level
+                    ),
+                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(
-                "storage_type must be either Performance or Endurance")
+                "Block volume storage_type must be either "
+                "Performance or Endurance")
 
         order = {
             'complexType': complex_type,
@@ -269,209 +308,184 @@ def order_block_volume(self, storage_type, location, size, os_type,
 
         return self.client.call('Product_Order', 'placeOrder', order)
 
-    def _get_package(self, category_code):
-        """Returns a product packaged based on type of storage.
+    def create_snapshot(self, volume_id, notes='', **kwargs):
+        """Creates a snapshot on the given block volume.
 
-        :param category_code: Category code of product package.
-        :return: Returns a packaged based on type of storage.
+        :param integer volume_id: The id of the volume
+        :param string notes: The notes or "name" to assign the snapshot
+        :return: Returns the id of the new snapshot
         """
 
-        _filter = utils.NestedDict({})
-        _filter['categories']['categoryCode'] = (
-            utils.query_filter(category_code))
-        _filter['statusCode'] = (utils.query_filter('ACTIVE'))
-
-        packages = self.client.call('Product_Package', 'getAllObjects',
-                                    filter=_filter.to_dict(),
-                                    mask="""
-id,
-name,
-items[prices[categories],attributes]
-""")
-        if len(packages) == 0:
-            raise ValueError('No packages were found for %s' % category_code)
-        if len(packages) > 1:
-            raise ValueError('More than one package was found for %s'
-                             % category_code)
-
-        return packages[0]
-
-    def _get_location_id(self, location):
-        """Returns location id
-
-        :param location: Datacenter short name
-        :return: Returns location id
-        """
-        loc_svc = self.client['Location_Datacenter']
-        datacenters = loc_svc.getDatacenters(mask='mask[longName,id,name]')
-        for datacenter in datacenters:
-            if datacenter['name'] == location:
-                location = datacenter['id']
-                return location
-        raise ValueError('Invalid datacenter name specified.')
+        return self.client.call('Network_Storage', 'createSnapshot',
+                                notes, id=volume_id, **kwargs)
 
-    def cancel_block_volume(self, volume_id,
-                            reason='No longer needed',
-                            immediate=False):
-        """Cancels the given block storage volume.
+    def order_snapshot_space(self, volume_id, capacity, tier,
+                             upgrade, **kwargs):
+        """Orders snapshot space for the given block volume.
 
-        :param integer volume_id: the volume ID
-        :param string reason: The reason for cancellation
-        :param boolean immediate_flag: Cancel immediately or
-        on anniversary date
+        :param integer volume_id: The id of the volume
+        :param integer capacity: The capacity to order, in GB
+        :param float tier: The tier level of the block volume, in IOPS per GB
+        :param boolean upgrade: Flag to indicate if this order is an upgrade
+        :return: Returns a SoftLayer_Container_Product_Order_Receipt
         """
-        block_volume = self.get_block_volume_details(
-            volume_id,
-            mask='mask[id,billingItem[id]]')
-        billing_item_id = block_volume['billingItem']['id']
-
-        self.client['Billing_Item'].cancelItem(
-            immediate,
-            True,
-            reason,
-            id=billing_item_id)
-
-
-def _find_endurance_block_price(package):
-    for item in package['items']:
-        for price in item['prices']:
-            if price['locationGroupId'] != '':
-                continue
-
-            if not _has_category(price['categories'], 'storage_block'):
-                continue
-
-            return price
-
-    raise ValueError("Could not find price for block storage")
-
-
-def _find_endurance_price(package):
-    for item in package['items']:
-        for price in item['prices']:
-            if price['locationGroupId'] != '':
-                continue
-
-            if not _has_category(price['categories'],
-                                 'storage_service_enterprise'):
-                continue
-
-            return price
-
-    raise ValueError("Could not find price for endurance block storage")
-
-
-def _find_endurance_space_price(package, size, tier_level):
-    for item in package['items']:
-        if int(item['capacity']) != size:
-            continue
-
-        for price in item['prices']:
-            # Only collect prices from valid location groups.
-            if price['locationGroupId'] != '':
-                continue
-
-            if not _has_category(price['categories'],
-                                 'performance_storage_space'):
-                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 disk space")
+        package = storage_utils.get_package(self, 'storage_service_enterprise')
+        block_mask = 'serviceResource.datacenter[id],'\
+            'storageTierLevel,billingItem'
+        block_volume = self.get_block_volume_details(volume_id,
+                                                     mask=block_mask,
+                                                     **kwargs)
+
+        storage_type = block_volume['billingItem']['categoryCode']
+        if storage_type != 'storage_service_enterprise':
+            raise exceptions.SoftLayerError(
+                "Block volume storage_type must be Endurance")
 
+        if tier is None:
+            tier = storage_utils.find_endurance_tier_iops_per_gb(block_volume)
+        prices = [storage_utils.find_snapshot_space_price(
+            package, capacity, tier)]
 
-def _find_endurance_tier_price(package, tier_level):
-    for item in package['items']:
-        for attribute in item.get('attributes', []):
-            if int(attribute['value']) == ENDURANCE_TIERS.get(tier_level):
-                break
+        if upgrade:
+            complex_type = 'SoftLayer_Container_Product_Order_'\
+                           'Network_Storage_Enterprise_SnapshotSpace_Upgrade'
         else:
-            continue
+            complex_type = 'SoftLayer_Container_Product_Order_'\
+                           'Network_Storage_Enterprise_SnapshotSpace'
 
-        for price in item['prices']:
-            # Only collect prices from valid location groups.
-            if price['locationGroupId'] != '':
-                continue
+        snapshot_space_order = {
+            'complexType': complex_type,
+            'packageId': package['id'],
+            'prices': prices,
+            'quantity': 1,
+            'location': block_volume['serviceResource']['datacenter']['id'],
+            'volumeId': volume_id,
+        }
 
-            if not _has_category(price['categories'], 'storage_tier_level'):
-                continue
+        return self.client.call('Product_Order', 'placeOrder',
+                                snapshot_space_order)
 
-            return price
+    def cancel_snapshot_space(self, volume_id,
+                              reason='No longer needed',
+                              immediate=False):
+        """Cancels snapshot space for a given volume.
 
-    raise ValueError("Could not find price for tier")
+        :param integer volume_id: The volume ID
+        :param string reason: The reason for cancellation
+        :param boolean immediate_flag: Cancel immediately or
+        on anniversary date
+        """
 
+        block_volume = self.get_block_volume_details(
+            volume_id,
+            mask='mask[id,billingItem[activeChildren]]')
 
-def _find_performance_block_price(package):
-    for item in package['items']:
-        for price in item['prices']:
-            # Only collect prices from valid location groups.
-            if price['locationGroupId'] != '':
-                continue
+        if 'activeChildren' not in block_volume['billingItem']:
+            raise exceptions.SoftLayerError(
+                'No snapshot space found to cancel')
 
-            if not _has_category(price['categories'],
-                                 'performance_storage_iscsi'):
-                continue
+        children_array = block_volume['billingItem']['activeChildren']
+        billing_item_id = None
 
-            return price
+        for child in children_array:
+            if child['categoryCode'] == 'storage_snapshot_space':
+                billing_item_id = child['id']
+                break
 
-    raise ValueError("Could not find price for performance storage")
+        if not billing_item_id:
+            raise exceptions.SoftLayerError(
+                'No snapshot space found to cancel')
 
+        return self.client['Billing_Item'].cancelItem(
+            immediate,
+            True,
+            reason,
+            id=billing_item_id)
 
-def _find_performance_space_price(package, size):
-    for item in package['items']:
-        if int(item['capacity']) != size:
-            continue
+    def enable_snapshots(self, volume_id, schedule_type, retention_count,
+                         minute, hour, day_of_week, **kwargs):
+        """Enables snapshots for a specific block volume at a given schedule
+
+        :param integer volume_id: The id of the volume
+        :param string schedule_type: 'HOURLY'|'DAILY'|'WEEKLY'
+        :param integer retention_count: Number of snapshots to be kept
+        :param integer minute: Minute when to take snapshot
+        :param integer hour: Hour when to take snapshot
+        :param string day_of_week: Day when to take snapshot
+        :return: Returns whether successfully scheduled or not
+        """
 
-        for price in item['prices']:
-            # Only collect prices from valid location groups.
-            if price['locationGroupId'] != '':
-                continue
+        return self.client.call('Network_Storage', 'enableSnapshots',
+                                schedule_type,
+                                retention_count,
+                                minute,
+                                hour,
+                                day_of_week,
+                                id=volume_id,
+                                **kwargs)
+
+    def disable_snapshots(self, volume_id, schedule_type):
+        """Disables snapshots for a specific block volume at a given schedule
+
+        :param integer volume_id: The id of the volume
+        :param string schedule_type: 'HOURLY'|'DAILY'|'WEEKLY'
+        :return: Returns whether successfully disabled or not
+        """
 
-            if not _has_category(price['categories'],
-                                 'performance_storage_space'):
-                continue
+        return self.client.call('Network_Storage', 'disableSnapshots',
+                                schedule_type, id=volume_id)
 
-            return price
+    def restore_from_snapshot(self, volume_id, snapshot_id):
+        """Restores a specific volume from a snapshot
 
-    raise ValueError("Could not find price for disk space")
+        :param integer volume_id: The id of the volume
+        :param integer snapshot_id: The id of the restore point
+        :return: Returns whether succesfully restored or not
+        """
 
+        return self.client.call('Network_Storage', 'restoreFromSnapshot',
+                                snapshot_id, id=volume_id)
 
-def _find_performance_iops_price(package, size, iops):
-    for item in package['items']:
-        if int(item['capacity']) != int(iops):
-            continue
+    def cancel_block_volume(self, volume_id,
+                            reason='No longer needed',
+                            immediate=False):
+        """Cancels the given block storage volume.
 
-        for price in item['prices']:
-            # Only collect prices from valid location groups.
-            if price['locationGroupId'] != '':
-                continue
+        :param integer volume_id: The volume ID
+        :param string reason: The reason for cancellation
+        :param boolean immediate_flag: Cancel immediately or
+        on anniversary date
+        """
+        block_volume = self.get_block_volume_details(
+            volume_id,
+            mask='mask[id,billingItem[id]]')
+        billing_item_id = block_volume['billingItem']['id']
 
-            if not _has_category(price['categories'],
-                                 'performance_storage_iops'):
-                continue
+        return self.client['Billing_Item'].cancelItem(
+            immediate,
+            True,
+            reason,
+            id=billing_item_id)
 
-            if size < int(price['capacityRestrictionMinimum']):
-                continue
+    def failover_to_replicant(self, volume_id, replicant_id, immediate=False):
+        """Failover to a volume replicant.
 
-            if size > int(price['capacityRestrictionMaximum']):
-                continue
+        :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 price
+        return self.client.call('Network_Storage', 'failoverToReplicant',
+                                replicant_id, immediate, id=volume_id)
 
-    raise ValueError("Could not find price for iops")
+    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
+        """
 
-def _has_category(categories, category_code):
-    return any(
-        True
-        for category
-        in categories
-        if category['categoryCode'] == category_code
-    )
+        return self.client.call('Network_Storage', 'failbackFromReplicant',
+                                replicant_id, id=volume_id)
diff --git a/SoftLayer/managers/cdn.py b/SoftLayer/managers/cdn.py
index d93f148a..994ab8fa 100644
--- a/SoftLayer/managers/cdn.py
+++ b/SoftLayer/managers/cdn.py
@@ -15,7 +15,13 @@
 
 
 class CDNManager(utils.IdentifierMixin, object):
-    """Manage CDN accounts."""
+    """Manage CDN accounts and content.
+
+    See product information here:
+    http://www.softlayer.com/content-delivery-network
+
+    :param SoftLayer.API.BaseClient client: the client instance
+    """
 
     def __init__(self, client):
         self.client = client
diff --git a/SoftLayer/managers/dns.py b/SoftLayer/managers/dns.py
index c2524800..c1b7b3b6 100644
--- a/SoftLayer/managers/dns.py
+++ b/SoftLayer/managers/dns.py
@@ -11,9 +11,11 @@
 
 
 class DNSManager(utils.IdentifierMixin, object):
-    """Domain Name System manager.
+    """Manage SoftLayer DNS.
 
-    :param SoftLayer.API.Client client: the client instance
+    See product information here: http://www.softlayer.com/DOMAIN-SERVICES
+
+    :param SoftLayer.API.BaseClient client: the client instance
 
     """
 
diff --git a/SoftLayer/managers/file.py b/SoftLayer/managers/file.py
new file mode 100644
index 00000000..ae179206
--- /dev/null
+++ b/SoftLayer/managers/file.py
@@ -0,0 +1,473 @@
+"""
+    SoftLayer.file
+    ~~~~~~~~~~~~~~~
+    File Storage Manager
+
+    :license: MIT, see LICENSE for more details.
+"""
+from SoftLayer import exceptions
+from SoftLayer.managers import storage_utils
+from SoftLayer import utils
+
+
+class FileStorageManager(utils.IdentifierMixin, object):
+    """Manages file Storage volumes."""
+
+    def __init__(self, client):
+        self.configuration = {}
+        self.client = client
+
+    def list_file_volumes(self, datacenter=None, username=None,
+                          storage_type=None, **kwargs):
+        """Returns a list of file volumes.
+
+        :param datacenter: Datacenter short name (e.g.: dal09)
+        :param username: Name of volume.
+        :param storage_type: Type of volume: Endurance or Performance
+        :param kwargs:
+        :return: Returns a list of file volumes.
+        """
+        if 'mask' not in kwargs:
+            items = [
+                'id',
+                'username',
+                'capacityGb',
+                'bytesUsed',
+                'serviceResource.datacenter[name]',
+                'serviceResourceBackendIpAddress',
+                'activeTransactionCount'
+            ]
+            kwargs['mask'] = ','.join(items)
+
+        _filter = utils.NestedDict(kwargs.get('filter') or {})
+
+        _filter['nasNetworkStorage']['serviceResource']['type']['type'] = \
+            (utils.query_filter('!~ NAS'))
+
+        _filter['nasNetworkStorage']['storageType']['keyName'] = (
+            utils.query_filter('*FILE_STORAGE*'))
+        if storage_type:
+            _filter['nasNetworkStorage']['storageType']['keyName'] = (
+                utils.query_filter('%s_FILE_STORAGE' % storage_type.upper()))
+
+        if datacenter:
+            _filter['nasNetworkStorage']['serviceResource']['datacenter'][
+                'name'] = (utils.query_filter(datacenter))
+
+        if username:
+            _filter['nasNetworkStorage']['username'] = \
+                (utils.query_filter(username))
+
+        kwargs['filter'] = _filter.to_dict()
+        return self.client.call('Account', 'getNasNetworkStorage', **kwargs)
+
+    def get_file_volume_details(self, volume_id, **kwargs):
+        """Returns details about the specified volume.
+
+        :param volume_id: ID of volume.
+        :param kwargs:
+        :return: Returns details about the specified volume.
+        """
+
+        if 'mask' not in kwargs:
+            items = [
+                'id',
+                'username',
+                'password',
+                'capacityGb',
+                'bytesUsed',
+                'snapshotCapacityGb',
+                'parentVolume.snapshotSizeBytes',
+                'storageType.keyName',
+                'serviceResource.datacenter[name]',
+                'serviceResourceBackendIpAddress',
+                'storageTierLevel',
+                'iops',
+                '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',
+                                id=volume_id, **kwargs)
+
+    def get_file_volume_access_list(self, volume_id, **kwargs):
+        """Returns a list of authorized hosts for a specified volume.
+
+        :param volume_id: ID of volume.
+        :param kwargs:
+        :return: Returns a list of authorized hosts for a specified volume.
+        """
+        if 'mask' not in kwargs:
+            items = [
+                'id',
+                'allowedVirtualGuests[allowedHost[credential]]',
+                'allowedHardware[allowedHost[credential]]',
+                'allowedSubnets[allowedHost[credential]]',
+                'allowedIpAddresses[allowedHost[credential]]',
+            ]
+            kwargs['mask'] = ','.join(items)
+        return self.client.call('Network_Storage', 'getObject',
+                                id=volume_id, **kwargs)
+
+    def get_file_volume_snapshot_list(self, volume_id, **kwargs):
+        """Returns a list of snapshots for the specified volume.
+
+        :param volume_id: ID of volume.
+        :param kwargs:
+        :return: Returns a list of snapshots for the specified volume.
+        """
+        if 'mask' not in kwargs:
+            items = [
+                'id',
+                'notes',
+                'snapshotSizeBytes',
+                'storageType[keyName]',
+                'snapshotCreationTimestamp',
+                'hourlySchedule',
+                'dailySchedule',
+                'weeklySchedule'
+                ]
+            kwargs['mask'] = ','.join(items)
+
+        return self.client.call('Network_Storage', 'getSnapshots',
+                                id=volume_id, **kwargs)
+
+    def authorize_host_to_volume(self, volume_id,
+                                 hardware_ids=None,
+                                 virtual_guest_ids=None,
+                                 ip_address_ids=None,
+                                 subnet_ids=None,
+                                 **kwargs):
+        """Authorizes hosts to File Storage Volumes
+
+        :param volume_id: The File volume to authorize hosts to
+        :param hardware_ids: A List of SoftLayer_Hardware ids
+        :param virtual_guest_ids: A List of SoftLayer_Virtual_Guest ids
+        :param ip_address_ids: A List of SoftLayer_Network_Subnet_IpAddress ids
+        :param subnet_ids: A List of SoftLayer_Network_Subnet ids
+        :return: Returns an array of
+                SoftLayer_Network_Storage_Allowed_Host objects
+                which now have access to the given File volume
+        """
+        host_templates = []
+        storage_utils.populate_host_templates(host_templates,
+                                              hardware_ids,
+                                              virtual_guest_ids,
+                                              ip_address_ids,
+                                              subnet_ids)
+
+        return self.client.call('Network_Storage', 'allowAccessFromHostList',
+                                host_templates, id=volume_id, **kwargs)
+
+    def deauthorize_host_to_volume(self, volume_id,
+                                   hardware_ids=None,
+                                   virtual_guest_ids=None,
+                                   ip_address_ids=None,
+                                   subnet_ids=None,
+                                   **kwargs):
+        """Revokes authorization of hosts to File Storage Volumes
+
+        :param volume_id: The File volume to deauthorize hosts to
+        :param hardware_ids: A List of SoftLayer_Hardware ids
+        :param virtual_guest_ids: A List of SoftLayer_Virtual_Guest ids
+        :param ip_address_ids: A List of SoftLayer_Network_Subnet_IpAddress ids
+        :param subnet_ids: A List of SoftLayer_Network_Subnet ids
+        :return: Returns an array of
+                SoftLayer_Network_Storage_Allowed_Host objects
+                which have access to the given File volume
+        """
+        host_templates = []
+        storage_utils.populate_host_templates(host_templates,
+                                              hardware_ids,
+                                              virtual_guest_ids,
+                                              ip_address_ids,
+                                              subnet_ids)
+
+        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.
+
+        :param snapshot_id: The ID of the snapshot object to delete.
+        """
+        return self.client.call('Network_Storage', 'deleteObject',
+                                id=snapshot_id)
+
+    def order_file_volume(self, storage_type, location, size, os_type,
+                          iops=None, tier_level=None, snapshot_size=None):
+        """Places an order for a file volume.
+
+        :param storage_type: "performance_storage_iscsi" (performance)
+                             or "storage_service_enterprise" (endurance)
+        :param location: Datacenter in which to order iSCSI volume
+        :param size: Size of the desired volume, in GB
+        :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:
+            location_id = storage_utils.get_location_id(self, location)
+        except ValueError:
+            raise exceptions.SoftLayerError(
+                "Invalid datacenter name specified. "
+                "Please provide the lower case short name (e.g.: dal09)")
+
+        base_type_name = 'SoftLayer_Container_Product_Order_Network_'
+        package = storage_utils.get_package(self, storage_type)
+        if storage_type == 'performance_storage_nfs':
+            complex_type = base_type_name + 'PerformanceStorage_Nfs'
+            prices = [
+                storage_utils.find_performance_price(
+                    package,
+                    'performance_storage_nfs'
+                    ),
+                storage_utils.find_performance_space_price(package, iops),
+                storage_utils.find_performance_iops_price(package, size, iops),
+            ]
+        elif storage_type == 'storage_service_enterprise':
+            complex_type = base_type_name + 'Storage_Enterprise'
+            prices = [
+                storage_utils.find_endurance_price(package, 'storage_file'),
+                storage_utils.find_endurance_price(
+                    package,
+                    'storage_service_enterprise'
+                    ),
+                storage_utils.find_endurance_space_price(
+                    package,
+                    size,
+                    tier_level
+                    ),
+                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 "
+                "Performance or Endurance")
+
+        order = {
+            'complexType': complex_type,
+            'packageId': package['id'],
+            'osFormatType': {'keyName': os_type},
+            'prices': prices,
+            'quantity': 1,
+            'location': location_id,
+        }
+
+        return self.client.call('Product_Order', 'placeOrder', order)
+
+    def create_snapshot(self, volume_id, notes='', **kwargs):
+        """Creates a snapshot on the given file volume.
+
+        :param integer volume_id: The id of the volume
+        :param string notes: The notes or "name" to assign the snapshot
+        :return: Returns the id of the new snapshot
+        """
+
+        return self.client.call('Network_Storage', 'createSnapshot',
+                                notes, id=volume_id, **kwargs)
+
+    def enable_snapshots(self, volume_id, schedule_type, retention_count,
+                         minute, hour, day_of_week, **kwargs):
+        """Enables snapshots for a specific file volume at a given schedule
+
+        :param integer volume_id: The id of the volume
+        :param string schedule_type: 'HOURLY'|'DAILY'|'WEEKLY'
+        :return: Returns whether successfully scheduled or not
+        """
+
+        return self.client.call('Network_Storage', 'enableSnapshots',
+                                schedule_type,
+                                retention_count,
+                                minute,
+                                hour,
+                                day_of_week,
+                                id=volume_id,
+                                **kwargs)
+
+    def disable_snapshots(self, volume_id, schedule_type):
+        """Disables snapshots for a specific file volume at a given schedule
+
+        :param integer volume_id: The id of the volume
+        :param string schedule_type: 'HOURLY'|'DAILY'|'WEEKLY'
+        :return: Returns whether successfully disabled or not
+        """
+
+        return self.client.call('Network_Storage', 'disableSnapshots',
+                                schedule_type, id=volume_id)
+
+    def order_snapshot_space(self, volume_id, capacity, tier,
+                             upgrade, **kwargs):
+        """Orders snapshot space for the given file volume.
+
+        :param integer volume_id: The id of the volume
+        :param integer capacity: The capacity to order, in GB
+        :param float tier: The tier level of the file volume, in IOPS per GB
+        :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'
+        file_volume = self.get_file_volume_details(volume_id,
+                                                   mask=file_mask,
+                                                   **kwargs)
+
+        storage_type = file_volume['billingItem']['categoryCode']
+        if storage_type != 'storage_service_enterprise':
+            raise exceptions.SoftLayerError(
+                "File volume storage_type must be Endurance")
+
+        if tier is None:
+            tier = storage_utils.find_endurance_tier_iops_per_gb(file_volume)
+        prices = [storage_utils.find_snapshot_space_price(
+            package, capacity, tier)]
+
+        if upgrade:
+            complex_type = 'SoftLayer_Container_Product_Order_'\
+                           'Network_Storage_Enterprise_SnapshotSpace_Upgrade'
+        else:
+            complex_type = 'SoftLayer_Container_Product_Order_'\
+                           'Network_Storage_Enterprise_SnapshotSpace'
+
+        snapshot_space_order = {
+            'complexType': complex_type,
+            'packageId': package['id'],
+            'prices': prices,
+            'quantity': 1,
+            'location': file_volume['serviceResource']['datacenter']['id'],
+            'volumeId': volume_id,
+        }
+
+        return self.client.call('Product_Order', 'placeOrder',
+                                snapshot_space_order)
+
+    def cancel_snapshot_space(self, volume_id,
+                              reason='No longer needed',
+                              immediate=False):
+        """Cancels snapshot space for a given volume.
+
+        :param integer volume_id: The volume ID
+        :param string reason: The reason for cancellation
+        :param boolean immediate_flag: Cancel immediately or
+        on anniversary date
+        """
+
+        file_volume = self.get_file_volume_details(
+            volume_id,
+            mask='mask[id,billingItem[activeChildren]]')
+        children_array = file_volume['billingItem']['activeChildren']
+        billing_item_id = None
+
+        if 'activeChildren' not in file_volume['billingItem']:
+            raise exceptions.SoftLayerError(
+                'No snapshot space found to cancel')
+
+        for child in children_array:
+            if child['categoryCode'] == 'storage_snapshot_space':
+                billing_item_id = child['id']
+                break
+
+        if not billing_item_id:
+            raise exceptions.SoftLayerError(
+                'No snapshot space found to cancel')
+
+        return self.client['Billing_Item'].cancelItem(
+            immediate,
+            True,
+            reason,
+            id=billing_item_id)
+
+    def restore_from_snapshot(self, volume_id, snapshot_id):
+        """Restores a specific volume from a snapshot
+
+        :param integer volume_id: The id of the volume
+        :param integer snapshot_id: The id of the restore point
+        :return: Returns whether succesfully restored or not
+        """
+
+        return self.client.call('Network_Storage', 'restoreFromSnapshot',
+                                snapshot_id, id=volume_id)
+
+    def cancel_file_volume(self, volume_id,
+                           reason='No longer needed',
+                           immediate=False):
+        """Cancels the given file storage volume.
+
+        :param integer volume_id: The volume ID
+        :param string reason: The reason for cancellation
+        :param boolean immediate_flag: Cancel immediately or
+        on anniversary date
+        """
+        file_volume = self.get_file_volume_details(
+            volume_id,
+            mask='mask[id,billingItem[id]]')
+        billing_item_id = file_volume['billingItem']['id']
+
+        return self.client['Billing_Item'].cancelItem(
+            immediate,
+            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/firewall.py b/SoftLayer/managers/firewall.py
index 4ed350b4..210d481c 100644
--- a/SoftLayer/managers/firewall.py
+++ b/SoftLayer/managers/firewall.py
@@ -10,7 +10,7 @@
 RULE_MASK = ('mask[orderValue,action,destinationIpAddress,'
              'destinationIpSubnetMask,protocol,destinationPortRangeStart,'
              'destinationPortRangeEnd,sourceIpAddress,sourceIpSubnetMask,'
-             'version]')
+             'version,notes]')
 
 
 def has_firewall(vlan):
@@ -29,9 +29,11 @@ def has_firewall(vlan):
 
 
 class FirewallManager(utils.IdentifierMixin, object):
-    """Manages firewalls.
+    """Manages SoftLayer firewalls
 
-    :param SoftLayer.API.Client client: the API client instance
+    See product information here: http://www.softlayer.com/firewalls
+
+    :param SoftLayer.API.BaseClient client: the client instance
 
     """
 
diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py
index 3a77d057..abfc3a63 100644
--- a/SoftLayer/managers/hardware.py
+++ b/SoftLayer/managers/hardware.py
@@ -19,7 +19,7 @@
 
 
 class HardwareManager(utils.IdentifierMixin, object):
-    """Manage hardware devices.
+    """Manage SoftLayer hardware servers.
 
     Example::
 
@@ -32,7 +32,9 @@ class HardwareManager(utils.IdentifierMixin, object):
        client = SoftLayer.Client()
        mgr = SoftLayer.HardwareManager(client)
 
-    :param SoftLayer.API.Client client: an API client instance
+    See product information here: http://www.softlayer.com/bare-metal-servers
+
+    :param SoftLayer.API.BaseClient client: the client instance
     :param SoftLayer.managers.OrderingManager ordering_manager: an optional
                                               manager to handle ordering.
                                               If none is provided, one will be
@@ -182,43 +184,42 @@ def get_hardware(self, hardware_id, **kwargs):
         """
 
         if 'mask' not in kwargs:
-            items = [
-                'id',
-                'globalIdentifier',
-                'fullyQualifiedDomainName',
-                'hostname',
-                'domain',
-                'provisionDate',
-                'hardwareStatus',
-                'processorPhysicalCoreAmount',
-                'memoryCapacity',
-                'notes',
-                'privateNetworkOnlyFlag',
-                'primaryBackendIpAddress',
-                'primaryIpAddress',
-                'networkManagementIpAddress',
-                'userData',
-                'datacenter',
+            kwargs['mask'] = (
+                'id,'
+                'globalIdentifier,'
+                'fullyQualifiedDomainName,'
+                'hostname,'
+                'domain,'
+                'provisionDate,'
+                'hardwareStatus,'
+                'processorPhysicalCoreAmount,'
+                'memoryCapacity,'
+                'notes,'
+                'privateNetworkOnlyFlag,'
+                'primaryBackendIpAddress,'
+                'primaryIpAddress,'
+                'networkManagementIpAddress,'
+                'userData,'
+                'datacenter,'
                 '''networkComponents[id, status, speed, maxSpeed, name,
                    ipmiMacAddress, ipmiIpAddress, macAddress, primaryIpAddress,
                    port, primarySubnet[id, netmask, broadcastAddress,
-                                       networkIdentifier, gateway]]''',
-                'hardwareChassis[id,name]',
-                'activeTransaction[id, transactionStatus[friendlyName,name]]',
+                                       networkIdentifier, gateway]],'''
+                'hardwareChassis[id,name],'
+                'activeTransaction[id, transactionStatus[friendlyName,name]],'
                 '''operatingSystem[
                     softwareLicense[softwareDescription[manufacturer,
                                                         name,
                                                         version,
                                                         referenceCode]],
-                    passwords[username,password]]''',
-                'billingItem.nextInvoiceTotalRecurringAmount',
-                'hourlyBillingFlag',
-                'tagReferences[id,tag[name,id]]',
-                'networkVlans[id,vlanNumber,networkSpace]',
-                'billingItem.orderItem.order.userRecord[username]',
-                'remoteManagementAccounts[username,password]',
-            ]
-            kwargs['mask'] = "mask[%s]" % ','.join(items)
+                    passwords[username,password]],'''
+                'billingItem.nextInvoiceTotalRecurringAmount,'
+                'hourlyBillingFlag,'
+                'tagReferences[id,tag[name,id]],'
+                'networkVlans[id,vlanNumber,networkSpace],'
+                'billingItem.orderItem.order.userRecord[username],'
+                'remoteManagementAccounts[username,password]'
+            )
 
         return self.hardware.getObject(id=hardware_id, **kwargs)
 
diff --git a/SoftLayer/managers/image.py b/SoftLayer/managers/image.py
index 57389bd8..81eee928 100644
--- a/SoftLayer/managers/image.py
+++ b/SoftLayer/managers/image.py
@@ -13,9 +13,12 @@
 
 
 class ImageManager(utils.IdentifierMixin, object):
-    """Manages server images.
+    """Manages SoftLayer server images.
 
-    :param SoftLayer.API.Client client: an API client instance
+    See product information here:
+    https://knowledgelayer.softlayer.com/topic/image-templates
+
+    :param SoftLayer.API.BaseClient client: the client instance
     """
 
     def __init__(self, client):
diff --git a/SoftLayer/managers/iscsi.py b/SoftLayer/managers/iscsi.py
index 9c07a15d..85df7d40 100644
--- a/SoftLayer/managers/iscsi.py
+++ b/SoftLayer/managers/iscsi.py
@@ -10,7 +10,14 @@
 
 
 class ISCSIManager(utils.IdentifierMixin, object):
-    """Manages iSCSI storages."""
+    """Manages SoftLayer iSCSI storage accounts.
+
+    See product information here:
+    https://knowledgelayer.softlayer.com/topic/iscsi
+
+    :param SoftLayer.API.BaseClient client: the client instance
+
+    """
 
     def __init__(self, client):
         self.configuration = {}
diff --git a/SoftLayer/managers/load_balancer.py b/SoftLayer/managers/load_balancer.py
index 47fa362d..854c8c62 100644
--- a/SoftLayer/managers/load_balancer.py
+++ b/SoftLayer/managers/load_balancer.py
@@ -9,9 +9,12 @@
 
 
 class LoadBalancerManager(utils.IdentifierMixin, object):
-    """Manages load balancers.
+    """Manages SoftLayer load balancers.
+
+    See product information here: http://www.softlayer.com/load-balancing
+
+    :param SoftLayer.API.BaseClient client: the client instance
 
-    :param SoftLayer.API.Client client: the API client instance
     """
 
     def __init__(self, client):
diff --git a/SoftLayer/managers/messaging.py b/SoftLayer/managers/messaging.py
index 8c273b6b..8c0a0519 100644
--- a/SoftLayer/managers/messaging.py
+++ b/SoftLayer/managers/messaging.py
@@ -23,7 +23,7 @@
 
 
 class QueueAuth(requests.auth.AuthBase):
-    """Message Queue authentication for requests.
+    """SoftLayer Message Queue authentication for requests.
 
     :param endpoint: endpoint URL
     :param username: SoftLayer username
@@ -72,7 +72,13 @@ def __call__(self, resp):
 
 
 class MessagingManager(object):
-    """Manage SoftLayer Message Queue."""
+    """Manage SoftLayer Message Queue accounts.
+
+    See product information here: http://www.softlayer.com/message-queue
+
+    :param SoftLayer.API.BaseClient client: the client instance
+
+    """
     def __init__(self, client):
         self.client = client
 
diff --git a/SoftLayer/managers/metadata.py b/SoftLayer/managers/metadata.py
index 38aa4db5..0246d7f7 100644
--- a/SoftLayer/managers/metadata.py
+++ b/SoftLayer/managers/metadata.py
@@ -35,7 +35,10 @@
 
 
 class MetadataManager(object):
-    """Provides an interface for the metadata service.
+    """Provides an interface for the SoftLayer metadata service.
+
+    See product information here:
+    http://sldn.softlayer.com/reference/services/SoftLayer_Resource_Metadata
 
     This provides metadata about the resourse it is called from.
     See `METADATA_ATTRIBUTES` for full list of attributes.
@@ -51,6 +54,8 @@ class MetadataManager(object):
             >>> meta.get('fqdn')
             'test.example.com'
 
+    :param SoftLayer.API.BaseClient client: the client instance
+
     """
 
     attribs = METADATA_MAPPING
diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py
index 904b91f9..690da99b 100644
--- a/SoftLayer/managers/network.py
+++ b/SoftLayer/managers/network.py
@@ -36,7 +36,13 @@
 
 
 class NetworkManager(object):
-    """Manage Networks."""
+    """Manage SoftLayer network objects: VLANs, subnets, IPs and rwhois
+
+    See product information here: http://www.softlayer.com/networking
+
+    :param SoftLayer.API.BaseClient client: the client instance
+
+    """
     def __init__(self, client):
         self.client = client
         self.account = client['Account']
diff --git a/SoftLayer/managers/object_storage.py b/SoftLayer/managers/object_storage.py
index 71da8d22..395c6e2f 100644
--- a/SoftLayer/managers/object_storage.py
+++ b/SoftLayer/managers/object_storage.py
@@ -16,7 +16,13 @@
 
 
 class ObjectStorageManager(object):
-    """Manager for Object Storage."""
+    """Manager for SoftLayer Object Storage accounts.
+
+    See product information here: http://www.softlayer.com/object-storage
+
+    :param SoftLayer.API.BaseClient client: the client instance
+
+    """
 
     def __init__(self, client):
         self.client = client
diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py
index f7026b9e..798a8464 100644
--- a/SoftLayer/managers/ordering.py
+++ b/SoftLayer/managers/ordering.py
@@ -8,9 +8,9 @@
 
 
 class OrderingManager(object):
-    """Manages hardware devices.
+    """Manager to help ordering via the SoftLayer API.
 
-    :param SoftLayer.API.Client client: an API client instance
+    :param SoftLayer.API.BaseClient client: the client instance
     """
 
     def __init__(self, client):
diff --git a/SoftLayer/managers/sshkey.py b/SoftLayer/managers/sshkey.py
index e7f424f3..a294d525 100644
--- a/SoftLayer/managers/sshkey.py
+++ b/SoftLayer/managers/sshkey.py
@@ -10,9 +10,12 @@
 
 
 class SshKeyManager(utils.IdentifierMixin, object):
-    """Manages account SSH keys.
+    """Manages account SSH keys in SoftLayer.
 
-    :param SoftLayer.API.Client client: an API client instance
+    See product information here:
+    https://knowledgelayer.softlayer.com/procedure/ssh-keys
+
+    :param SoftLayer.API.BaseClient client: the client instance
     """
 
     def __init__(self, client):
diff --git a/SoftLayer/managers/ssl.py b/SoftLayer/managers/ssl.py
index ef407def..f0d75dc3 100644
--- a/SoftLayer/managers/ssl.py
+++ b/SoftLayer/managers/ssl.py
@@ -8,9 +8,9 @@
 
 
 class SSLManager(object):
-    """Manages SSL certificates.
+    """Manages SSL certificates in SoftLayer.
 
-    :param SoftLayer.API.Client client: an API client instance
+    See product information here: http://www.softlayer.com/ssl-certificates
 
     Example::
 
@@ -23,6 +23,8 @@ class SSLManager(object):
        client = SoftLayer.Client()
        mgr = SoftLayer.SSLManager(client)
 
+    :param SoftLayer.API.BaseClient client: the client instance
+
     """
 
     def __init__(self, client):
diff --git a/SoftLayer/managers/storage_utils.py b/SoftLayer/managers/storage_utils.py
new file mode 100644
index 00000000..0ee01ef9
--- /dev/null
+++ b/SoftLayer/managers/storage_utils.py
@@ -0,0 +1,454 @@
+"""
+    SoftLayer.storage_utils
+    ~~~~~~~~~~~~~~~
+    Utility functions used by File and Block Storage Managers
+
+    :license: MIT, see LICENSE for more details.
+"""
+from SoftLayer import exceptions
+from SoftLayer import utils
+
+
+ENDURANCE_TIERS = {
+    0.25: 100,
+    2: 200,
+    4: 300,
+}
+
+
+def populate_host_templates(host_templates,
+                            hardware_ids=None,
+                            virtual_guest_ids=None,
+                            ip_address_ids=None,
+                            subnet_ids=None):
+    """Populate the given host_templates array with the IDs provided
+
+    :param host_templates: The array to which host templates will be added
+    :param hardware_ids: A List of SoftLayer_Hardware ids
+    :param virtual_guest_ids: A List of SoftLayer_Virtual_Guest ids
+    :param ip_address_ids: A List of SoftLayer_Network_Subnet_IpAddress ids
+    :param subnet_ids: A List of SoftLayer_Network_Subnet ids
+    """
+    if hardware_ids is not None:
+        for hardware_id in hardware_ids:
+            host_templates.append({
+                'objectType': 'SoftLayer_Hardware',
+                'id': hardware_id
+            })
+
+    if virtual_guest_ids is not None:
+        for virtual_guest_id in virtual_guest_ids:
+            host_templates.append({
+                'objectType': 'SoftLayer_Virtual_Guest',
+                'id': virtual_guest_id
+            })
+
+    if ip_address_ids is not None:
+        for ip_address_id in ip_address_ids:
+            host_templates.append({
+                'objectType': 'SoftLayer_Network_Subnet_IpAddress',
+                'id': ip_address_id
+            })
+
+    if subnet_ids is not None:
+        for subnet_id in subnet_ids:
+            host_templates.append({
+                'objectType': 'SoftLayer_Network_Subnet',
+                'id': subnet_id
+            })
+
+
+def get_package(manager, category_code):
+    """Returns a product packaged based on type of storage.
+
+    :param manager: The storage manager which calls this function.
+    :param category_code: Category code of product package.
+    :return: Returns a packaged based on type of storage.
+    """
+
+    _filter = utils.NestedDict({})
+    _filter['categories']['categoryCode'] = (
+        utils.query_filter(category_code))
+    _filter['statusCode'] = (utils.query_filter('ACTIVE'))
+
+    packages = manager.client.call(
+        'Product_Package', 'getAllObjects',
+        filter=_filter.to_dict(),
+        mask='id,name,items[prices[categories],attributes]'
+    )
+    if len(packages) == 0:
+        raise ValueError('No packages were found for %s' % category_code)
+    if len(packages) > 1:
+        raise ValueError('More than one package was found for %s'
+                         % category_code)
+
+    return packages[0]
+
+
+def get_location_id(manager, location):
+    """Returns location id
+
+    :param manager: The storage manager which calls this function.
+    :param location: Datacenter short name
+    :return: Returns location id
+    """
+    loc_svc = manager.client['Location_Datacenter']
+    datacenters = loc_svc.getDatacenters(mask='mask[longName,id,name]')
+    for datacenter in datacenters:
+        if datacenter['name'] == location:
+            location = datacenter['id']
+            return location
+    raise ValueError('Invalid datacenter name specified.')
+
+
+def find_endurance_price(package, price_category):
+    """Find the price in the given package that has the specified category
+
+    :param package: The product package of the endurance storage type
+    :param price_category: The price category to search for
+    :return: Returns the price for the given category, or an error if not found
+    """
+    for item in package['items']:
+        for price in item['prices']:
+            # Only collect prices from valid location groups.
+            if price['locationGroupId'] != '':
+                continue
+
+            if not _has_category(price['categories'], price_category):
+                continue
+
+            return price
+
+    raise ValueError("Could not find price for endurance storage")
+
+
+def find_endurance_space_price(package, size, tier_level):
+    """Find the price in the given package with the specified size and tier
+
+    :param package: The product package of the endurance storage type
+    :param size: The size for which a price is desired
+    :param tier_level: The endurance tier for which a price is desired
+    :return: Returns the price for the size and tier, or an error if not found
+    """
+    for item in package['items']:
+        if int(item['capacity']) != size:
+            continue
+
+        for price in item['prices']:
+            # Only collect prices from valid location groups.
+            if price['locationGroupId'] != '':
+                continue
+
+            if not _has_category(price['categories'],
+                                 'performance_storage_space'):
+                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 disk space")
+
+
+def find_endurance_tier_price(package, tier_level):
+    """Find the price in the given package with the specified tier level
+
+    :param package: The product package of the endurance storage type
+    :param tier_level: The endurance tier for which a price is desired
+    :return: Returns the price for the given tier, or an error if not found
+    """
+    for item in package['items']:
+        for attribute in item.get('attributes', []):
+            if int(attribute['value']) == ENDURANCE_TIERS.get(tier_level):
+                break
+        else:
+            continue
+
+        for price in item['prices']:
+            # Only collect prices from valid location groups.
+            if price['locationGroupId'] != '':
+                continue
+
+            if not _has_category(price['categories'], 'storage_tier_level'):
+                continue
+
+            return price
+
+    raise ValueError("Could not find price for tier")
+
+
+def find_endurance_tier_iops_per_gb(volume):
+    """Find the tier for the given endurance volume (IOPS per GB)
+
+    :param volume: The volume for which the tier level is desired
+    :return: Returns a float value indicating the IOPS per GB for the volume
+    """
+    tier_description_split = volume['storageTierLevel']['description'].split()
+
+    if tier_description_split != []:
+        iops_per_gb = tier_description_split[0]
+
+        if iops_per_gb == '0.25':
+            return 0.25
+
+        if iops_per_gb == '2':
+            return 2.0
+
+        if iops_per_gb == '4':
+            return 4.0
+
+    raise ValueError("Could not find tier IOPS per GB for this volume")
+
+
+def find_performance_price(package, price_category):
+    """Find the price in the given package that has the specified category
+
+    :param package: The product package of the performance storage type
+    :param price_category: The price category to search for
+    :return: Returns the price for the given category, or an error if not found
+    """
+    for item in package['items']:
+        for price in item['prices']:
+            # Only collect prices from valid location groups.
+            if price['locationGroupId'] != '':
+                continue
+
+            if not _has_category(price['categories'], price_category):
+                continue
+
+            return price
+
+    raise ValueError("Could not find price for performance storage")
+
+
+def find_performance_space_price(package, size):
+    """Find the price in the given package with the specified size
+
+    :param package: The product package of the performance storage type
+    :param size: The size for which a price is desired
+    :return: Returns the price for the given size, or an error if not found
+    """
+    for item in package['items']:
+        if int(item['capacity']) != size:
+            continue
+
+        for price in item['prices']:
+            # Only collect prices from valid location groups.
+            if price['locationGroupId'] != '':
+                continue
+
+            if not _has_category(price['categories'],
+                                 'performance_storage_space'):
+                continue
+
+            return price
+
+    raise ValueError("Could not find disk space price for the given volume")
+
+
+def find_performance_iops_price(package, size, iops):
+    """Find the price in the given package with the specified size and iops
+
+    :param package: The product package of the performance storage type
+    :param size: The size for which a price is desired
+    :param iops: The number of IOPS for which a price is desired
+    :return: Returns the price for the size and IOPS, or an error if not found
+    """
+    for item in package['items']:
+        if int(item['capacity']) != int(iops):
+            continue
+
+        for price in item['prices']:
+            # Only collect prices from valid location groups.
+            if price['locationGroupId'] != '':
+                continue
+
+            if not _has_category(price['categories'],
+                                 'performance_storage_iops'):
+                continue
+
+            if size < int(price['capacityRestrictionMinimum']):
+                continue
+
+            if size > int(price['capacityRestrictionMaximum']):
+                continue
+
+            return price
+
+    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
+
+    :param package: The product package of the endurance storage type
+    :param size: The snapshot space size for which a price is desired
+    :param tier_level: The tier of the volume for which space is being ordered
+    :return: Returns the price for the given size, or an error if not found
+    """
+    for item in package['items']:
+        if int(item['capacity']) != size:
+            continue
+
+        for price in item['prices']:
+            # Only collect prices from valid location groups.
+            if price['locationGroupId'] != '':
+                continue
+
+            if not _has_category(price['categories'],
+                                 'storage_snapshot_space'):
+                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 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
+        for category
+        in categories
+        if category['categoryCode'] == category_code
+    )
diff --git a/SoftLayer/managers/ticket.py b/SoftLayer/managers/ticket.py
index d95f5c83..553f3260 100644
--- a/SoftLayer/managers/ticket.py
+++ b/SoftLayer/managers/ticket.py
@@ -10,9 +10,12 @@
 
 
 class TicketManager(utils.IdentifierMixin, object):
-    """Manages support Tickets.
+    """Manages SoftLayer support tickets.
+
+    See product information here: http://www.softlayer.com/support
+
+    :param SoftLayer.API.BaseClient client: the client instance
 
-    :param SoftLayer.API.Client client: an API client instance
     """
 
     def __init__(self, client):
@@ -46,8 +49,7 @@ def get_ticket(self, ticket_id):
         """Get details about a ticket.
 
         :param integer ticket_id: the ticket ID
-        :returns: A dictionary containing a large amount of information about
-                  the specified ticket.
+        :returns: dict -- information about the specified ticket
 
         """
         mask = ('id, title, assignedUser[firstName, lastName],status,'
@@ -91,7 +93,8 @@ def upload_attachment(self, ticket_id=None, file_path=None,
         :param string file_name:
                                   The name of the attachment shown
                                   in the ticket
-        :returns The uploaded attachment
+
+        :returns: dict -- The uploaded attachment
         """
         file_content = None
         with open(file_path, 'rb') as attached_file:
@@ -109,7 +112,8 @@ def attach_hardware(self, ticket_id=None, hardware_id=None):
 
         :param integer ticket_id: the id of the ticket to attach to
         :param integer hardware_id: the id of the hardware to attach
-        :returns The new ticket attachment
+
+        :returns: dict -- The new ticket attachment
         """
         return self.ticket.addAttachedHardware(hardware_id, id=ticket_id)
 
@@ -118,7 +122,8 @@ def attach_virtual_server(self, ticket_id=None, virtual_id=None):
 
         :param integer ticket_id: the id of the ticket to attach to
         :param integer virtual_id: the id of the virtual server to attach
-        :returns The new ticket attachment
+
+        :returns: dict -- The new ticket attachment
         """
         return self.ticket.addAttachedVirtualGuest(virtual_id, id=ticket_id)
 
@@ -127,7 +132,8 @@ def detach_hardware(self, ticket_id=None, hardware_id=None):
 
         :param ticket_id: the id of the ticket to detach from
         :param hardware_id: the id of the hardware to detach
-        :return: Whether the detachment was successful
+
+        :returns: bool -- Whether the detachment was successful
         """
         return self.ticket.removeAttachedHardware(hardware_id, id=ticket_id)
 
@@ -136,6 +142,7 @@ def detach_virtual_server(self, ticket_id=None, virtual_id=None):
 
         :param ticket_id: the id of the ticket to detach from
         :param virtual_id: the id of the virtual server to detach
-        :return: Whether the detachment was successful
+
+        :returns: bool -- Whether the detachment was successful
         """
         return self.ticket.removeAttachedVirtualGuest(virtual_id, id=ticket_id)
diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py
index 25090e07..a724b709 100644
--- a/SoftLayer/managers/vs.py
+++ b/SoftLayer/managers/vs.py
@@ -17,13 +17,9 @@
 
 
 class VSManager(utils.IdentifierMixin, object):
-    """Manages Virtual Servers.
+    """Manages SoftLayer Virtual Servers.
 
-    :param SoftLayer.API.Client client: an API client instance
-    :param SoftLayer.managers.OrderingManager ordering_manager: an optional
-                                              manager to handle ordering.
-                                              If none is provided, one will be
-                                              auto initialized.
+    See product information here: http://www.softlayer.com/virtual-servers
 
     Example::
 
@@ -36,6 +32,13 @@ class VSManager(utils.IdentifierMixin, object):
            client = SoftLayer.Client()
            mgr = SoftLayer.VSManager(client)
 
+
+    :param SoftLayer.API.BaseClient client: the client instance
+    :param SoftLayer.managers.OrderingManager ordering_manager: an optional
+                                              manager to handle ordering.
+                                              If none is provided, one will be
+                                              auto initialized.
+
     """
 
     def __init__(self, client, ordering_manager=None):
@@ -172,49 +175,50 @@ def get_instance(self, instance_id, **kwargs):
         """
 
         if 'mask' not in kwargs:
-            items = [
-                'id',
-                'globalIdentifier',
-                'fullyQualifiedDomainName',
-                'hostname',
-                'domain',
-                'createDate',
-                'modifyDate',
-                'provisionDate',
-                'notes',
-                'dedicatedAccountHostOnlyFlag',
-                'privateNetworkOnlyFlag',
-                'primaryBackendIpAddress',
-                'primaryIpAddress',
+            kwargs['mask'] = (
+                'id,'
+                'globalIdentifier,'
+                'fullyQualifiedDomainName,'
+                'hostname,'
+                'domain,'
+                'createDate,'
+                'modifyDate,'
+                'provisionDate,'
+                'notes,'
+                'dedicatedAccountHostOnlyFlag,'
+                'privateNetworkOnlyFlag,'
+                'primaryBackendIpAddress,'
+                'primaryIpAddress,'
                 '''networkComponents[id, status, speed, maxSpeed, name,
                                      macAddress, primaryIpAddress, port,
-                                     primarySubnet]''',
-                'lastKnownPowerState.name',
-                'powerState',
-                'status',
-                'maxCpu',
-                'maxMemory',
-                'datacenter',
-                'activeTransaction[id, transactionStatus[friendlyName,name]]',
-                'lastOperatingSystemReload.id',
-                'blockDevices',
-                'blockDeviceTemplateGroup[id, name, globalIdentifier]',
-                'postInstallScriptUri',
-                '''softwareComponents[
-                    passwords[username,password,notes],
-                    softwareLicense[softwareDescription[name]]]''',
+                                     primarySubnet],'''
+                'lastKnownPowerState.name,'
+                'powerState,'
+                'status,'
+                'maxCpu,'
+                'maxMemory,'
+                'datacenter,'
+                'activeTransaction[id, transactionStatus[friendlyName,name]],'
+                'lastOperatingSystemReload.id,'
+                'blockDevices,'
+                'blockDeviceTemplateGroup[id, name, globalIdentifier],'
+                'postInstallScriptUri,'
                 '''operatingSystem[passwords[username,password],
                                    softwareLicense.softwareDescription[
                                        manufacturer,name,version,
-                                       referenceCode]]''',
-                'hourlyBillingFlag',
-                'userData',
-                'billingItem.recurringFee',
-                'tagReferences[id,tag[name,id]]',
-                'networkVlans[id,vlanNumber,networkSpace]',
+                                       referenceCode]],'''
+                '''softwareComponents[
+                    passwords[username,password,notes],
+                    softwareLicense[softwareDescription[
+                                        manufacturer,name,version,
+                                        referenceCode]]],'''
+                'hourlyBillingFlag,'
+                'userData,'
+                'billingItem.recurringFee,'
+                'tagReferences[id,tag[name,id]],'
+                'networkVlans[id,vlanNumber,networkSpace],'
                 'billingItem.orderItem.order.userRecord[username]'
-            ]
-            kwargs['mask'] = "mask[%s]" % ','.join(items)
+            )
 
         return self.guest.getObject(id=instance_id, **kwargs)
 
diff --git a/docs/cla-corporate.md b/docs/dev/cla-corporate.md
similarity index 100%
rename from docs/cla-corporate.md
rename to docs/dev/cla-corporate.md
diff --git a/docs/cla-individual.md b/docs/dev/cla-individual.md
similarity index 100%
rename from docs/cla-individual.md
rename to docs/dev/cla-individual.md
diff --git a/docs/dev/index.rst b/docs/dev/index.rst
index 4814920b..21bb0d40 100644
--- a/docs/dev/index.rst
+++ b/docs/dev/index.rst
@@ -20,7 +20,7 @@ Code Organization
 Setting Up A Dev Environment
 ----------------------------
 Before working with the SoftLayer Python API client source, we strongly
-recommend that you know how to use Python's virtualization environment,
+recommend that you know how to use Python's virtual environment,
 `virtualenv <https://pypi.python.org/pypi/virtualenv>`_. Virtualenv allows you
 to create isolated Python environments that are individually tailored to
 particular development projects. Each environment can have its own set of
@@ -62,9 +62,13 @@ the root of the source tree:
   tox
 
 In addition to testing different versions of Python, tox checks for common
-mistakes in the code using `Flake8 <https://pypi.python.org/pypi/flake8/2.0>`_.
-You should eliminate the simple errors reported by Flake8 before submitting
-your code.
+mistakes in the code using `Flake8 <https://pypi.python.org/pypi/flake8/2.0>`_ and `pylint <https://www.pylint.org/>`_.
+You should eliminate the linting errors that are reported before submitting
+your code. You can run only the linting checks by using this command:
+
+::
+
+  tox -eanalysis
 
 The project's configuration instructs tox to test against many different
 versions of Python. A tox test will use as many of those as it can find on your
@@ -73,7 +77,7 @@ you point the `Travis <https://travis-ci.org>`_ continuous integration tool at
 your GitHub fork. Travis will run the test against the full suite of Python
 versions every time you push new code.
 
-Using tox to running the tests in multiple environments can be very time
+Using tox to run tests in multiple environments can be very time
 consuming. If you wish to quickly run the tests in your own environment, you
 may do so using `py.test <http://pytest.org/>`_.  The command to do that
 is:
@@ -109,7 +113,7 @@ The primary docs are built at
 
 Style
 -----
-This project follows :pep:`8` and most of the style suggestions that pyflakes
+This project tries to follow :pep:`8` and most of the style suggestions that pyflakes
 recommends. Run `Flake8 <https://pypi.python.org/pypi/flake8/2.0>`_ regularly.
 Flake8, with project-specific exceptions, can be run by using tox:
 
@@ -120,12 +124,15 @@ Flake8, with project-specific exceptions, can be run by using tox:
 
 Contributing
 ------------
-Contributing to the Python API bindings follows the fork-pull-request model on
+Contributing to the Python API bindings follows the `fork-pull-request model <https://guides.github.com/introduction/flow/>`_ on
 `GitHub <http://github.com>`_. The project uses GitHub's
 `issue tracker <https://github.com/softlayer/softlayer-python/issues>`_ and
 `pull requests <https://github.com/softlayer/softlayer-python/pulls>`_ to
 manage source control, bug fixes and new feature development regarding the API
-bindings and the CLI.
+bindings and the CLI. In order to contribute, we require that you sign a contributer agreemenet:
+
+* Sign our contributor agreement (CLA) You can find the :download:`CLA here <cla-individual.md>`.
+* If you're contributing on behalf of your employer we'll need a signed copy of our corporate contributor agreement (CCLA) as well.  You can find the :download:`CCLA here <cla-corporate.md>`.
 
 
 Developer Resources


Test patch:
diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py
index 98f13f44..fbb225a0 100644
--- a/tests/CLI/modules/block_tests.py
+++ b/tests/CLI/modules/block_tests.py
@@ -7,6 +7,7 @@
 from SoftLayer import testing
 
 import json
+import mock
 
 
 class BlockTests(testing.TestCase):
@@ -59,26 +60,41 @@ def test_volume_cancel(self):
             '--really', 'block', 'volume-cancel', '1234'])
 
         self.assert_no_fail(result)
-        self.assertEqual("", result.output)
+        self.assertEqual('Block volume with id 1234 has been marked'
+                         ' for cancellation\n', result.output)
         self.assert_called_with('SoftLayer_Billing_Item', 'cancelItem',
                                 args=(False, True, None))
 
     def test_volume_detail(self):
         result = self.run_command(['block', 'volume-detail', '1234'])
-
         self.assert_no_fail(result)
         self.assertEqual({
             'Username': 'username',
             'LUN Id': '2',
-            'Endurance Tier': 'Tier 1',
+            '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',
+            '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):
@@ -92,7 +108,285 @@ def test_volume_list(self):
                 'datacenter': None,
                 'id': 100,
                 'ip_addr': '10.1.2.3',
+                'lunId': None,
                 'storage_type': 'ENDURANCE',
-                'username': 'username'
+                'username': 'username',
+                'active_transactions': None
             }],
             json.loads(result.output))
+
+    def test_volume_order_performance_iops_not_given(self):
+        result = self.run_command(['block', 'volume-order',
+                                   '--storage-type=performance', '--size=20',
+                                   '--os-type=linux', '--location=dal05'])
+
+        self.assertEqual(2, result.exit_code)
+
+    def test_volume_order_performance_iops_out_of_range(self):
+        result = self.run_command(['block', 'volume-order',
+                                   '--storage-type=performance', '--size=20',
+                                   '--iops=80000', '--os-type=linux',
+                                   '--location=dal05'])
+
+        self.assertEqual(2, result.exit_code)
+
+    def test_volume_order_performance_iops_not_multiple_of_100(self):
+        result = self.run_command(['block', 'volume-order',
+                                   '--storage-type=performance', '--size=20',
+                                   '--iops=122', '--os-type=linux',
+                                   '--location=dal05'])
+
+        self.assertEqual(2, result.exit_code)
+
+    def test_volume_order_performance_snapshot_error(self):
+        result = self.run_command(['block', 'volume-order',
+                                   '--storage-type=performance', '--size=20',
+                                   '--iops=100', '--os-type=linux',
+                                   '--location=dal05', '--snapshot-size=10'])
+
+        self.assertEqual(2, result.exit_code)
+
+    @mock.patch('SoftLayer.BlockStorageManager.order_block_volume')
+    def test_volume_order_performance(self, order_mock):
+        order_mock.return_value = {
+            'placedOrder': {
+                'id': 478,
+                'items': [
+                    {'description': 'Performance Storage'},
+                    {'description': 'Block Storage'},
+                    {'description': '0.25 IOPS per GB'},
+                    {'description': '20 GB Storage Space'}]
+            }
+        }
+
+        result = self.run_command(['block', 'volume-order',
+                                   '--storage-type=performance', '--size=20',
+                                   '--iops=100', '--os-type=linux',
+                                   '--location=dal05'])
+
+        self.assert_no_fail(result)
+        self.assertEqual(result.output,
+                         'Order #478 placed successfully!\n'
+                         ' > Performance Storage\n > Block Storage\n'
+                         ' > 0.25 IOPS per GB\n > 20 GB Storage Space\n')
+
+    def test_volume_order_endurance_tier_not_given(self):
+        result = self.run_command(['block', 'volume-order',
+                                   '--storage-type=endurance', '--size=20',
+                                   '--os-type=linux', '--location=dal05'])
+
+        self.assertEqual(2, result.exit_code)
+
+    @mock.patch('SoftLayer.BlockStorageManager.order_block_volume')
+    def test_volume_order_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', '--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'
+                         ' > 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',
+                                   '--schedule-type=HOURLY', '--minute=10',
+                                   '--retention-count=5'])
+
+        self.assert_no_fail(result)
+
+    def test_disable_snapshots(self):
+        result = self.run_command(['block', 'snapshot-disable', '12345678',
+                                   '--schedule-type=HOURLY'])
+        self.assert_no_fail(result)
+
+    def test_create_snapshot(self):
+        result = self.run_command(['block', 'snapshot-create', '12345678'])
+
+        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'])
+
+        self.assert_no_fail(result)
+        self.assertEqual([
+            {
+                'id': 470,
+                'name': 'unit_testing_note',
+                'created': '2016-07-06T07:41:19-05:00',
+                'size_bytes': '42',
+            }],
+            json.loads(result.output))
+
+    def test_snapshot_cancel(self):
+        result = self.run_command(['--really',
+                                   'block', 'snapshot-cancel', '1234'])
+
+        self.assert_no_fail(result)
+        self.assertEqual('Block volume with id 1234 has been marked'
+                         ' for snapshot cancellation\n', result.output)
+        self.assert_called_with('SoftLayer_Billing_Item', 'cancelItem',
+                                args=(False, True, None))
+
+    def test_snapshot_restore(self):
+        result = self.run_command(['block', 'snapshot-restore', '12345678',
+                                   '--snapshot-id=87654321'])
+
+        self.assert_no_fail(result)
+        self.assertEqual(result.output, 'Block volume 12345678 is being'
+                         ' restored using snapshot 87654321\n')
+
+    @mock.patch('SoftLayer.BlockStorageManager.order_snapshot_space')
+    def test_snapshot_order(self, order_mock):
+        order_mock.return_value = {
+            'placedOrder': {
+                'id': 8702,
+                'items': [{'description':
+                           '10 GB Storage Space (Snapshot Space)'}],
+                'status': 'PENDING_APPROVAL',
+                }
+        }
+
+        result = self.run_command(['block', 'snapshot-order', '1234',
+                                   '--capacity=10', '--tier=0.25'])
+
+        self.assert_no_fail(result)
+        self.assertEqual(result.output,
+                         'Order #8702 placed successfully!\n'
+                         ' > 10 GB Storage Space (Snapshot Space)\n'
+                         ' > Order status: PENDING_APPROVAL\n')
+
+    def test_authorize_host_to_volume(self):
+        result = self.run_command(['block', 'access-authorize', '12345678',
+                                   '--hardware-id=100', '--virtual-id=10',
+                                   '--ip-address-id=192',
+                                   '--ip-address=192.3.2.1'])
+
+        self.assert_no_fail(result)
+
+    def test_deauthorize_host_to_volume(self):
+        result = self.run_command(['block', 'access-revoke', '12345678',
+                                   '--hardware-id=100', '--virtual-id=10',
+                                   '--ip-address-id=192',
+                                   '--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
new file mode 100644
index 00000000..3cbce526
--- /dev/null
+++ b/tests/CLI/modules/file_tests.py
@@ -0,0 +1,400 @@
+"""
+    SoftLayer.tests.CLI.modules.file_tests
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    :license: MIT, see LICENSE for more details.
+"""
+from SoftLayer import testing
+
+import json
+import mock
+
+
+class FileTests(testing.TestCase):
+
+    def test_access_list(self):
+        result = self.run_command(['file', 'access-list', '1234'])
+
+        self.assert_no_fail(result)
+        self.assertEqual([
+            {
+                'username': 'joe',
+                'name': 'test-server.example.com',
+                'type': 'VIRTUAL',
+                'host_iqn': 'test-server',
+                'password': '12345',
+                'private_ip_address': '10.0.0.1',
+                'id': 1234,
+            },
+            {
+                'username': 'joe',
+                'name': 'test-server.example.com',
+                'type': 'HARDWARE',
+                'host_iqn': 'test-server',
+                'password': '12345',
+                'private_ip_address': '10.0.0.2',
+                'id': 1234,
+            },
+            {
+                'username': 'joe',
+                'name': '10.0.0.1/24 (backend subnet)',
+                'type': 'SUBNET',
+                'host_iqn': 'test-server',
+                'password': '12345',
+                'private_ip_address': None,
+                'id': 1234,
+            },
+            {
+                'username': 'joe',
+                'name': '10.0.0.1 (backend ip)',
+                'type': 'IP',
+                'host_iqn': 'test-server',
+                'password': '12345',
+                'private_ip_address': None,
+                'id': 1234,
+            }],
+            json.loads(result.output),)
+
+    def test_authorize_host_to_volume(self):
+        result = self.run_command(['file', 'access-authorize', '12345678',
+                                   '--hardware-id=100', '--virtual-id=10',
+                                   '--ip-address-id=192',
+                                   '--ip-address=192.3.2.1',
+                                   '--subnet-id=200'])
+
+        self.assert_no_fail(result)
+
+    def test_deauthorize_host_to_volume(self):
+        result = self.run_command(['file', 'access-revoke', '12345678',
+                                   '--hardware-id=100', '--virtual-id=10',
+                                   '--ip-address-id=192',
+                                   '--ip-address=192.3.2.1',
+                                   '--subnet-id=200'])
+
+        self.assert_no_fail(result)
+
+    def test_volume_list(self):
+        result = self.run_command(['file', 'volume-list'])
+
+        self.assert_no_fail(result)
+        self.assertEqual([
+            {
+                'bytes_used': None,
+                'capacity_gb': 10,
+                'datacenter': 'Dallas',
+                'id': 1,
+                'ip_addr': '127.0.0.1',
+                'storage_type': 'ENDURANCE',
+                'username': 'user',
+                'active_transactions': None
+            }],
+            json.loads(result.output))
+
+    def test_snapshot_list(self):
+        result = self.run_command(['file', 'snapshot-list', '1234'])
+
+        self.assert_no_fail(result)
+        self.assertEqual([
+            {
+                'id': 470,
+                'name': 'unit_testing_note',
+                'created': '2016-07-06T07:41:19-05:00',
+                'size_bytes': '42',
+            }],
+            json.loads(result.output))
+
+    def test_volume_cancel(self):
+        result = self.run_command([
+            '--really', 'file', 'volume-cancel', '1234'])
+
+        self.assert_no_fail(result)
+        self.assertEqual('File volume with id 1234 has been marked'
+                         ' for cancellation\n', result.output)
+        self.assert_called_with('SoftLayer_Billing_Item', 'cancelItem',
+                                args=(False, True, None))
+
+    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',
+            '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_performance(self, order_mock):
+        order_mock.return_value = {
+            'placedOrder': {
+                'id': 478,
+                'items': [
+                    {'description': 'Performance Storage'},
+                    {'description': 'File Storage'},
+                    {'description': '0.25 IOPS per GB'},
+                    {'description': '20 GB Storage Space'}]
+            }
+        }
+
+        result = self.run_command(['file', 'volume-order',
+                                   '--storage-type=performance', '--size=20',
+                                   '--iops=100', '--os-type=linux',
+                                   '--location=dal05'])
+
+        self.assert_no_fail(result)
+        self.assertEqual(result.output,
+                         'Order #478 placed successfully!\n'
+                         ' > Performance Storage\n > File Storage\n'
+                         ' > 0.25 IOPS per GB\n > 20 GB Storage Space\n')
+
+    def test_volume_order_endurance_tier_not_given(self):
+        result = self.run_command(['file', 'volume-order',
+                                   '--storage-type=endurance', '--size=20',
+                                   '--os-type=linux', '--location=dal05'])
+
+        self.assertEqual(2, result.exit_code)
+
+    @mock.patch('SoftLayer.FileStorageManager.order_file_volume')
+    def test_volume_order_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', '--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'
+                         ' > 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',
+                                   '--schedule-type=HOURLY', '--minute=10',
+                                   '--retention-count=5'])
+
+        self.assert_no_fail(result)
+
+    def test_disable_snapshots(self):
+        result = self.run_command(['file', 'snapshot-disable', '12345678',
+                                   '--schedule-type=HOURLY'])
+        self.assert_no_fail(result)
+
+    def test_create_snapshot(self):
+        result = self.run_command(['file', 'snapshot-create', '12345678'])
+
+        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'])
+
+        self.assert_no_fail(result)
+        self.assertEqual(result.output, 'File volume 12345678 is being'
+                         ' restored using snapshot 87654321\n')
+
+    def test_delete_snapshot(self):
+        result = self.run_command(['file', 'snapshot-delete', '12345678'])
+
+        self.assert_no_fail(result)
+
+    @mock.patch('SoftLayer.FileStorageManager.order_snapshot_space')
+    def test_snapshot_order(self, order_mock):
+        order_mock.return_value = {
+            'placedOrder': {
+                'id': 8702,
+                'items': [{'description':
+                           '10 GB Storage Space (Snapshot Space)'}],
+                'status': 'PENDING_APPROVAL',
+                }
+        }
+
+        result = self.run_command(['file', 'snapshot-order', '1234',
+                                   '--capacity=10', '--tier=0.25'])
+
+        self.assert_no_fail(result)
+        self.assertEqual(result.output,
+                         'Order #8702 placed successfully!\n'
+                         ' > 10 GB Storage Space (Snapshot Space)\n'
+                         ' > Order status: PENDING_APPROVAL\n')
+
+    def test_snapshot_cancel(self):
+        result = self.run_command(['--really',
+                                   'file', 'snapshot-cancel', '1234'])
+
+        self.assert_no_fail(result)
+        self.assertEqual('File volume with id 1234 has been marked'
+                         ' 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/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py
index 63fcab7e..851e4e0c 100644
--- a/tests/CLI/modules/server_tests.py
+++ b/tests/CLI/modules/server_tests.py
@@ -41,6 +41,7 @@ def test_server_details(self):
             'memory': 2048,
             'notes': 'These are test notes.',
             'os': 'Ubuntu',
+            'os_version': 'Ubuntu 12.04 LTS',
             'owner': 'chechu',
             'price rate': 16.08,
             'private_ip': '10.1.0.2',
@@ -55,7 +56,7 @@ def test_server_details(self):
         }
 
         self.assert_no_fail(result)
-        self.assertEqual(json.loads(result.output), expected)
+        self.assertEqual(expected, json.loads(result.output))
 
     def test_detail_vs_empty_tag(self):
         mock = self.set_mock('SoftLayer_Hardware_Server', 'getObject')
diff --git a/tests/CLI/modules/vs_tests.py b/tests/CLI/modules/vs_tests.py
index 53bad7c3..9818d7d3 100644
--- a/tests/CLI/modules/vs_tests.py
+++ b/tests/CLI/modules/vs_tests.py
@@ -49,7 +49,7 @@ def test_detail_vs(self):
                           'guid': '1a2b3c-1701',
                           'memory': 1024,
                           'modified': {},
-                          'os': '12.04-64 Minimal for VSI',
+                          'os': 'Ubuntu',
                           'os_version': '12.04-64 Minimal for VSI',
                           'notes': 'notes',
                           'price rate': 1.54,
diff --git a/tests/api_tests.py b/tests/api_tests.py
index 8e89d27c..ff5fce60 100644
--- a/tests/api_tests.py
+++ b/tests/api_tests.py
@@ -25,6 +25,21 @@ def test_init(self):
         self.assertIsInstance(client.transport, transports.XmlRpcTransport)
         self.assertEqual(client.transport.timeout, 10)
 
+    def test_init_with_rest_url(self):
+        client = SoftLayer.Client(username='doesnotexist',
+                                  api_key='issurelywrong',
+                                  timeout=10,
+                                  endpoint_url='http://example.com/v3/rest/')
+
+        self.assertIsInstance(client.auth, SoftLayer.BasicHTTPAuthentication)
+        self.assertEqual(client.auth.username, 'doesnotexist')
+        self.assertEqual(client.auth.api_key, 'issurelywrong')
+        self.assertIsNotNone(client.transport)
+        self.assertIsInstance(client.transport, transports.RestTransport)
+        self.assertEqual(client.transport.endpoint_url,
+                         'http://example.com/v3/rest')
+        self.assertEqual(client.transport.timeout, 10)
+
     @mock.patch('SoftLayer.config.get_client_settings')
     def test_env(self, get_client_settings):
         auth = mock.Mock()
diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py
index e278cf32..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
 
@@ -17,48 +18,63 @@ def set_up(self):
     def test_cancel_block_volume_immediately(self):
         self.block.cancel_block_volume(123, immediate=True)
 
-        self.assert_called_with('SoftLayer_Billing_Item', 'cancelItem',
-                                args=(True, True, 'No longer needed'),
-                                identifier=449)
+        self.assert_called_with(
+            'SoftLayer_Billing_Item',
+            'cancelItem',
+            args=(True, True, 'No longer needed'),
+            identifier=449,
+        )
 
     def test_get_block_volume_details(self):
         result = self.block.get_block_volume_details(100)
 
-        self.assertEqual(fixtures.SoftLayer_Network_Storage.getObject,
-                         result)
-        self.assert_called_with('SoftLayer_Network_Storage', 'getObject',
-                                identifier=100)
+        self.assertEqual(fixtures.SoftLayer_Network_Storage.getObject, result)
+
+        self.assert_called_with(
+            'SoftLayer_Network_Storage',
+            'getObject',
+            identifier=100
+            )
 
     def test_list_block_volumes(self):
         result = self.block.list_block_volumes()
 
         self.assertEqual(fixtures.SoftLayer_Account.getIscsiNetworkStorage,
                          result)
+
         self.assert_called_with('SoftLayer_Account', 'getIscsiNetworkStorage')
 
     def test_get_block_volume_access_list(self):
         result = self.block.get_block_volume_access_list(100)
 
-        self.assertEqual(fixtures.SoftLayer_Network_Storage.getObject,
-                         result)
-        self.assert_called_with('SoftLayer_Network_Storage', 'getObject',
-                                identifier=100)
+        self.assertEqual(fixtures.SoftLayer_Network_Storage.getObject, result)
+
+        self.assert_called_with(
+            'SoftLayer_Network_Storage',
+            'getObject',
+            identifier=100)
 
     def test_get_block_volume_snapshot_list(self):
         result = self.block.get_block_volume_snapshot_list(100)
 
         self.assertEqual(fixtures.SoftLayer_Network_Storage.getSnapshots,
                          result)
-        self.assert_called_with('SoftLayer_Network_Storage', 'getSnapshots',
-                                identifier=100)
+
+        self.assert_called_with(
+            'SoftLayer_Network_Storage',
+            'getSnapshots',
+            identifier=100)
 
     def test_delete_snapshot(self):
         result = self.block.delete_snapshot(100)
 
         self.assertEqual(fixtures.SoftLayer_Network_Storage.deleteObject,
                          result)
-        self.assert_called_with('SoftLayer_Network_Storage', 'deleteObject',
-                                identifier=100)
+
+        self.assert_called_with(
+            'SoftLayer_Network_Storage',
+            'deleteObject',
+            identifier=100)
 
     def test_order_block_volume_no_package(self):
         mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects')
@@ -67,7 +83,11 @@ def test_order_block_volume_no_package(self):
         self.assertRaises(
             ValueError,
             self.block.order_block_volume,
-            "performance_storage_iscsi", "dal05", 100, "LINUX", iops=100,
+            "performance_storage_iscsi",
+            "dal05",
+            100,
+            "LINUX",
+            iops=100,
         )
 
     def test_order_block_volume_too_many_packages(self):
@@ -77,7 +97,45 @@ def test_order_block_volume_too_many_packages(self):
         self.assertRaises(
             ValueError,
             self.block.order_block_volume,
-            "performance_storage_iscsi", "dal05", 100, "LINUX", iops=100,
+            "performance_storage_iscsi",
+            "dal05",
+            100,
+            "LINUX",
+            iops=100,
+        )
+
+    def test_cancel_snapshot_immediately(self):
+        self.block.cancel_snapshot_space(1234, immediate=True)
+
+        self.assert_called_with(
+            'SoftLayer_Billing_Item',
+            'cancelItem',
+            args=(True, True, 'No longer needed'),
+            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):
@@ -118,7 +176,12 @@ def test_order_block_volume_performance(self):
         }]
 
         result = self.block.order_block_volume(
-            "performance_storage_iscsi", "dal05", 100, "LINUX", iops=100)
+            "performance_storage_iscsi",
+            "dal05",
+            100,
+            "LINUX",
+            iops=100,
+            )
 
         self.assertEqual(
             result,
@@ -189,8 +252,100 @@ def test_order_block_volume_endurance(self):
         }]
 
         result = self.block.order_block_volume(
-            "storage_service_enterprise", "dal05", 100, "LINUX",
-            tier_level=0.25)
+            "storage_service_enterprise",
+            "dal05",
+            100,
+            "LINUX",
+            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(
             result,
@@ -212,17 +367,418 @@ def test_order_block_volume_endurance(self):
             )
 
     def test_authorize_host_to_volume(self):
-        result = self.block.authorize_host_to_volume(50,
-                                                     hardware_ids=[100],
-                                                     virtual_guest_ids=[200],
-                                                     ip_address_ids=[300])
+        result = self.block.authorize_host_to_volume(
+            50,
+            hardware_ids=[100],
+            virtual_guest_ids=[200],
+            ip_address_ids=[300])
+
         self.assertEqual(fixtures.SoftLayer_Network_Storage.
                          allowAccessFromHostList, result)
 
+        self.assert_called_with(
+            'SoftLayer_Network_Storage',
+            'allowAccessFromHostList',
+            identifier=50)
+
     def test_deauthorize_host_to_volume(self):
-        result = self.block.authorize_host_to_volume(50,
-                                                     hardware_ids=[100],
-                                                     virtual_guest_ids=[200],
-                                                     ip_address_ids=[300])
+        result = self.block.deauthorize_host_to_volume(
+            50,
+            hardware_ids=[100],
+            virtual_guest_ids=[200],
+            ip_address_ids=[300])
+
         self.assertEqual(fixtures.SoftLayer_Network_Storage.
                          removeAccessFromHostList, result)
+
+        self.assert_called_with(
+            'SoftLayer_Network_Storage',
+            'removeAccessFromHostList',
+            identifier=50)
+
+    def test_create_snapshot(self):
+        result = self.block.create_snapshot(123, 'hello world')
+
+        self.assertEqual(fixtures.SoftLayer_Network_Storage.createSnapshot,
+                         result)
+
+        self.assert_called_with(
+            'SoftLayer_Network_Storage',
+            'createSnapshot',
+            identifier=123)
+
+    def test_snapshot_restore(self):
+        result = self.block.restore_from_snapshot(12345678, 87654321)
+
+        self.assertEqual(
+            fixtures.SoftLayer_Network_Storage.restoreFromSnapshot,
+            result)
+        self.assert_called_with(
+            'SoftLayer_Network_Storage',
+            'restoreFromSnapshot',
+            identifier=12345678)
+
+    def test_enable_snapshots(self):
+        result = self.block.enable_snapshots(12345678, 'WEEKLY', 10,
+                                             47, 16, 'FRIDAY')
+
+        self.assertEqual(fixtures.SoftLayer_Network_Storage.enableSnapshots,
+                         result)
+
+        self.assert_called_with(
+            'SoftLayer_Network_Storage',
+            'enableSnapshots',
+            identifier=12345678)
+
+    def test_disable_snapshots(self):
+        result = self.block.disable_snapshots(12345678, 'HOURLY')
+
+        self.assertEqual(fixtures.SoftLayer_Network_Storage.disableSnapshots,
+                         result)
+        self.assert_called_with(
+            'SoftLayer_Network_Storage',
+            'disableSnapshots',
+            identifier=12345678)
+
+    def test_order_snapshot_space_no_package(self):
+        mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects')
+        mock.return_value = []
+
+        self.assertRaises(
+            ValueError,
+            self.block.order_snapshot_space,
+            100,
+            5,
+            None,
+            False,
+        )
+
+    def test_order_snapshot_space_too_many_packages(self):
+        mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects')
+        mock.return_value = [{}, {}]
+
+        self.assertRaises(
+            ValueError,
+            self.block.order_snapshot_space,
+            100,
+            5,
+            None,
+            False,
+        )
+
+    def test_order_snapshot_space(self):
+        mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects')
+        mock.return_value = [{
+            'id': 240,
+            'name': 'Endurance',
+            'items': [{
+                'capacity': '0',
+            }, {
+                'capacity': '5',
+                'prices': [{
+                    'locationGroupId': '530',
+                    }],
+            }, {
+                'capacity': '5',
+                'prices': [{
+                    'locationGroupId': '',
+                    'categories': [{
+                        'categoryCode': 'storage_block',
+                    }],
+                }],
+            }, {
+                'capacity': '5',
+                'prices': [{
+                    'locationGroupId': '',
+                    'categories': [{
+                        'categoryCode': 'storage_snapshot_space',
+                    }],
+                    'capacityRestrictionMinimum': '300',
+                }],
+            }, {
+                'capacity': '5',
+                'prices': [{
+                    'locationGroupId': '',
+                    'categories': [{
+                        'categoryCode': 'storage_snapshot_space',
+                    }],
+                    'capacityRestrictionMinimum': '100',
+                    'capacityRestrictionMaximum': '100',
+                }],
+            }, {
+                'capacity': '5',
+                'prices': [{
+                    'id': 46130,
+                    'locationGroupId': '',
+                    'categories': [{
+                        'categoryCode': 'storage_snapshot_space',
+                    }],
+                    'capacityRestrictionMinimum': '200',
+                    'capacityRestrictionMaximum': '200',
+                }],
+            }],
+        }]
+
+        result = self.block.order_snapshot_space(
+            100,
+            5,
+            None,
+            False,
+            )
+
+        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'}],
+                },
+            )
+
+    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
new file mode 100644
index 00000000..1f9c5617
--- /dev/null
+++ b/tests/managers/file_tests.py
@@ -0,0 +1,766 @@
+"""
+    SoftLayer.tests.managers.file_tests
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    :license: MIT, see LICENSE for more details.
+"""
+
+import SoftLayer
+from SoftLayer import exceptions
+from SoftLayer import fixtures
+from SoftLayer import testing
+
+
+class FileTests(testing.TestCase):
+    def set_up(self):
+        self.file = SoftLayer.FileStorageManager(self.client)
+
+    def test_cancel_file_volume_immediately(self):
+        self.file.cancel_file_volume(123, immediate=True)
+
+        self.assert_called_with(
+            'SoftLayer_Billing_Item',
+            'cancelItem',
+            args=(True, True, 'No longer needed'),
+            identifier=449,
+        )
+
+    def test_authorize_host_to_volume(self):
+        result = self.file.authorize_host_to_volume(
+            50,
+            hardware_ids=[100],
+            virtual_guest_ids=[200],
+            ip_address_ids=[300],
+            subnet_ids=[400])
+
+        self.assertEqual(fixtures.SoftLayer_Network_Storage.
+                         allowAccessFromHostList, result)
+
+        self.assert_called_with(
+            'SoftLayer_Network_Storage',
+            'allowAccessFromHostList',
+            identifier=50)
+
+    def test_deauthorize_host_to_volume(self):
+        result = self.file.deauthorize_host_to_volume(
+            50,
+            hardware_ids=[100],
+            virtual_guest_ids=[200],
+            ip_address_ids=[300],
+            subnet_ids=[400])
+
+        self.assertEqual(fixtures.SoftLayer_Network_Storage.
+                         removeAccessFromHostList, result)
+
+        self.assert_called_with(
+            'SoftLayer_Network_Storage',
+            'removeAccessFromHostList',
+            identifier=50)
+
+    def test_get_file_volume_access_list(self):
+        result = self.file.get_file_volume_access_list(100)
+
+        self.assertEqual(fixtures.SoftLayer_Network_Storage.getObject, result)
+
+        self.assert_called_with(
+            'SoftLayer_Network_Storage',
+            'getObject',
+            identifier=100)
+
+    def test_enable_snapshots(self):
+        result = self.file.enable_snapshots(12345678, 'WEEKLY', 10,
+                                            47, 16, 'FRIDAY')
+
+        self.assertEqual(fixtures.SoftLayer_Network_Storage.enableSnapshots,
+                         result)
+
+        self.assert_called_with(
+            'SoftLayer_Network_Storage',
+            'enableSnapshots',
+            identifier=12345678)
+
+    def test_disable_snapshots(self):
+        result = self.file.disable_snapshots(12345678, 'HOURLY')
+
+        self.assertEqual(fixtures.SoftLayer_Network_Storage.disableSnapshots,
+                         result)
+        self.assert_called_with(
+            'SoftLayer_Network_Storage',
+            'disableSnapshots',
+            identifier=12345678)
+
+    def test_snapshot_restore(self):
+        result = self.file.restore_from_snapshot(12345678, 87654321)
+
+        self.assertEqual(
+            fixtures.SoftLayer_Network_Storage.restoreFromSnapshot,
+            result)
+        self.assert_called_with(
+            'SoftLayer_Network_Storage',
+            'restoreFromSnapshot',
+            identifier=12345678)
+
+    def test_get_file_volume_details(self):
+        result = self.file.get_file_volume_details(100)
+
+        self.assertEqual(fixtures.SoftLayer_Network_Storage.getObject, result)
+
+        self.assert_called_with(
+            'SoftLayer_Network_Storage',
+            'getObject',
+            identifier=100)
+
+    def test_get_file_volume_snapshot_list(self):
+        result = self.file.get_file_volume_snapshot_list(100)
+
+        self.assertEqual(fixtures.SoftLayer_Network_Storage.getSnapshots,
+                         result)
+
+        self.assert_called_with(
+            'SoftLayer_Network_Storage',
+            'getSnapshots',
+            identifier=100)
+
+    def test_create_snapshot(self):
+        result = self.file.create_snapshot(123, 'hello world')
+
+        self.assertEqual(fixtures.SoftLayer_Network_Storage.createSnapshot,
+                         result)
+
+        self.assert_called_with(
+            'SoftLayer_Network_Storage',
+            'createSnapshot',
+            identifier=123)
+
+    def test_cancel_snapshot_immediately(self):
+        self.file.cancel_snapshot_space(1234, immediate=True)
+
+        self.assert_called_with(
+            'SoftLayer_Billing_Item',
+            'cancelItem',
+            args=(True, True, 'No longer needed'),
+            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)
+
+        self.assertEqual(fixtures.SoftLayer_Network_Storage.deleteObject,
+                         result)
+
+        self.assert_called_with(
+            'SoftLayer_Network_Storage',
+            'deleteObject',
+            identifier=100)
+
+    def test_order_file_volume_no_package(self):
+        mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects')
+        mock.return_value = []
+
+        self.assertRaises(
+            ValueError,
+            self.file.order_file_volume,
+            "performance_storage_nfs",
+            "dal05",
+            40,
+            "LINUX",
+            iops=100,
+        )
+
+    def test_order_file_volume_too_many_packages(self):
+        mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects')
+        mock.return_value = [{}, {}]
+
+        self.assertRaises(
+            ValueError,
+            self.file.order_file_volume,
+            "performance_storage_nfs",
+            "dal05",
+            40,
+            "LINUX",
+            iops=100,
+        )
+
+    def test_order_file_volume_performance(self):
+        mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects')
+        mock.return_value = [{
+            'id': 1,
+            'name': 'Performance',
+            'items': [{
+                'capacity': '1',
+                'prices': [{
+                    'id': 1,
+                    'locationGroupId': '',
+                    'categories': [{
+                        'categoryCode': 'performance_storage_nfs',
+                    }],
+                }],
+            }, {
+                'capacity': '100',
+                'prices': [{
+                    'id': 2,
+                    'locationGroupId': '',
+                    'categories': [{
+                        'categoryCode': 'performance_storage_space',
+                    }],
+                }],
+            }, {
+                'capacity': '100',
+                'prices': [{
+                    'id': 3,
+                    'locationGroupId': '',
+                    'categories': [{
+                        'categoryCode': 'performance_storage_iops',
+                    }],
+                    'capacityRestrictionMinimum': '100',
+                    'capacityRestrictionMaximum': '100',
+                }],
+            }],
+        }]
+
+        result = self.file.order_file_volume(
+            "performance_storage_nfs",
+            "dal05",
+            100,
+            "LINUX",
+            iops=100,
+            )
+
+        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_list_file_volumes(self):
+        result = self.file.list_file_volumes()
+
+        self.assertEqual(fixtures.SoftLayer_Account.getNasNetworkStorage,
+                         result)
+
+        self.assert_called_with('SoftLayer_Account', 'getNasNetworkStorage')
+
+    def test_order_file_volume_endurance(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',
+                    }],
+                }],
+            }],
+        }]
+
+        result = self.file.order_file_volume(
+            "storage_service_enterprise",
+            "dal05",
+            100,
+            "LINUX",
+            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(
+            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_snapshot_space_no_package(self):
+        mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects')
+        mock.return_value = []
+
+        self.assertRaises(
+            ValueError,
+            self.file.order_snapshot_space,
+            100,
+            5,
+            None,
+            False,
+        )
+
+    def test_order_snapshot_space_too_many_packages(self):
+        mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects')
+        mock.return_value = [{}, {}]
+
+        self.assertRaises(
+            ValueError,
+            self.file.order_snapshot_space,
+            100,
+            5,
+            None,
+            False,
+        )
+
+    def test_order_snapshot_space(self):
+        mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects')
+        mock.return_value = [{
+            'id': 240,
+            'name': 'Endurance',
+            'items': [{
+                'capacity': '0',
+            }, {
+                'capacity': '5',
+                'prices': [{
+                    'locationGroupId': '530',
+                    }],
+            }, {
+                'capacity': '5',
+                'prices': [{
+                    'locationGroupId': '',
+                    'categories': [{
+                        'categoryCode': 'storage_file',
+                    }],
+                }],
+            }, {
+                'capacity': '5',
+                'prices': [{
+                    'locationGroupId': '',
+                    'categories': [{
+                        'categoryCode': 'storage_snapshot_space',
+                    }],
+                    'capacityRestrictionMinimum': '300',
+                }],
+            }, {
+                'capacity': '5',
+                'prices': [{
+                    'locationGroupId': '',
+                    'categories': [{
+                        'categoryCode': 'storage_snapshot_space',
+                    }],
+                    'capacityRestrictionMinimum': '100',
+                    'capacityRestrictionMaximum': '100',
+                }],
+            }, {
+                'capacity': '5',
+                'prices': [{
+                    'id': 46130,
+                    'locationGroupId': '',
+                    'categories': [{
+                        'categoryCode': 'storage_snapshot_space',
+                    }],
+                    'capacityRestrictionMinimum': '200',
+                    'capacityRestrictionMaximum': '200',
+                }],
+            }],
+        }]
+
+        result = self.file.order_snapshot_space(
+            100,
+            5,
+            None,
+            False,
+            )
+
+        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'}],
+                },
+            )
+
+    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'}],
+                },
+            )