Repository: softlayer/softlayer-python
Pull request #1214 (2020-01-23 20:11:08)

issue#1210 file block storage - adding new feature to list, assign, a…
New Feature - support subnet ACL management through cli 


Patch:
diff --git a/.travis.yml b/.travis.yml
index e40cb2a5..d3cc13a7 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -10,8 +10,6 @@ matrix:
       env: TOX_ENV=py36
     - python: "3.7"
       env: TOX_ENV=py37
-    - python: "3.8"
-      env: TOX_ENV=py38
     - python: "pypy3.5"
       env: TOX_ENV=pypy3
     - python: "3.6"
diff --git a/README.rst b/README.rst
index 535b5459..cdc37675 100644
--- a/README.rst
+++ b/README.rst
@@ -124,7 +124,7 @@ If you are using the library directly in python, you can do something like this.
 
 System Requirements
 -------------------
-* Python 3.5, 3.6, 3.7, or 3.8.
+* Python 3.5, 3.6, or 3.7.
 * A valid SoftLayer API username and key.
 * A connection to SoftLayer's private network is required to use
   our private network API endpoints.
diff --git a/SoftLayer/CLI/block/limit.py b/SoftLayer/CLI/block/limit.py
deleted file mode 100644
index 13d22c8c..00000000
--- a/SoftLayer/CLI/block/limit.py
+++ /dev/null
@@ -1,31 +0,0 @@
-"""List number of block storage volumes limit per datacenter."""
-# :license: MIT, see LICENSE for more details.
-
-import click
-import SoftLayer
-from SoftLayer.CLI import environment
-from SoftLayer.CLI import formatting
-
-DEFAULT_COLUMNS = [
-    'Datacenter',
-    'MaximumAvailableCount',
-    'ProvisionedCount'
-]
-
-
-@click.command()
-@click.option('--sortby', help='Column to sort by', default='Datacenter')
-@environment.pass_env
-def cli(env, sortby):
-    """List number of block storage volumes limit per datacenter."""
-    block_manager = SoftLayer.BlockStorageManager(env.client)
-    block_volumes = block_manager.list_block_volume_limit()
-
-    table = formatting.KeyValueTable(DEFAULT_COLUMNS)
-    table.sortby = sortby
-    for volume in block_volumes:
-        datacenter_name = volume['datacenterName']
-        maximum_available_count = volume['maximumAvailableCount']
-        provisioned_count = volume['provisionedCount']
-        table.add_row([datacenter_name, maximum_available_count, provisioned_count])
-    env.fout(table)
diff --git a/SoftLayer/CLI/block/subnets/__init__.py b/SoftLayer/CLI/block/subnets/__init__.py
new file mode 100644
index 00000000..8824c427
--- /dev/null
+++ b/SoftLayer/CLI/block/subnets/__init__.py
@@ -0,0 +1,1 @@
+"""Block Storage Subnets Control."""
diff --git a/SoftLayer/CLI/block/subnets/assign.py b/SoftLayer/CLI/block/subnets/assign.py
new file mode 100644
index 00000000..3ff2ab75
--- /dev/null
+++ b/SoftLayer/CLI/block/subnets/assign.py
@@ -0,0 +1,37 @@
+"""Assign block storage subnets to the given host id."""
+# :license: MIT, see LICENSE for more details.
+
+import click
+import SoftLayer
+from SoftLayer.CLI import environment
+
+
+@click.command()
+@click.argument('access_id', type=int)
+@click.option('--subnet-id', multiple=True, type=int,
+              help="ID of the subnets to assign; e.g.: --subnet-id 1234")
+@environment.pass_env
+def cli(env, access_id, subnet_id):
+    """Assign block storage subnets to the given host id.
+
+    access_id is the host_id obtained by: slcli block access-list <volume_id>
+
+    SoftLayer_Account::iscsiisolationdisabled must be False to use this command
+    """
+    try:
+        subnet_ids = list(subnet_id)
+        block_manager = SoftLayer.BlockStorageManager(env.client)
+        assigned_subnets = block_manager.assign_subnets_to_acl(access_id, subnet_ids)
+
+        for subnet in assigned_subnets:
+            message = "Successfully assigned subnet id: {} to allowed host id: {}".format(subnet, access_id)
+            click.echo(message)
+
+        failed_to_assign_subnets = list(set(subnet_ids) - set(assigned_subnets))
+        for subnet in failed_to_assign_subnets:
+            message = "Failed to assign subnet id: {} to allowed host id: {}".format(subnet, access_id)
+            click.echo(message)
+
+    except SoftLayer.SoftLayerAPIError as ex:
+        message = "Unable to assign subnets.\nReason: {}".format(ex.faultString)
+        click.echo(message)
diff --git a/SoftLayer/CLI/block/subnets/list.py b/SoftLayer/CLI/block/subnets/list.py
new file mode 100644
index 00000000..e111de1b
--- /dev/null
+++ b/SoftLayer/CLI/block/subnets/list.py
@@ -0,0 +1,43 @@
+"""List block storage assigned subnets for the given host id."""
+# :license: MIT, see LICENSE for more details.
+
+import click
+import SoftLayer
+from SoftLayer.CLI import environment
+from SoftLayer.CLI import formatting
+
+
+COLUMNS = [
+    'id',
+    'createDate',
+    'networkIdentifier',
+    'cidr'
+]
+
+
+@click.command()
+@click.argument('access_id', type=int)
+@environment.pass_env
+def cli(env, access_id):
+    """List block storage assigned subnets for the given host id.
+
+    access_id is the host_id obtained by: slcli block access-list <volume_id>
+    """
+
+    try:
+        block_manager = SoftLayer.BlockStorageManager(env.client)
+        subnets = block_manager.get_subnets_in_acl(access_id)
+
+        table = formatting.Table(COLUMNS)
+        for subnet in subnets:
+            row = ["{0}".format(subnet['id']),
+                   "{0}".format(subnet['createDate']),
+                   "{0}".format(subnet['networkIdentifier']),
+                   "{0}".format(subnet['cidr'])]
+            table.add_row(row)
+
+        env.fout(table)
+
+    except SoftLayer.SoftLayerAPIError as ex:
+        message = "Unable to list assigned subnets for access-id: {}.\nReason: {}".format(access_id, ex.faultString)
+        click.echo(message)
diff --git a/SoftLayer/CLI/block/subnets/remove.py b/SoftLayer/CLI/block/subnets/remove.py
new file mode 100644
index 00000000..d700fb1d
--- /dev/null
+++ b/SoftLayer/CLI/block/subnets/remove.py
@@ -0,0 +1,37 @@
+"""Remove block storage subnets for the given host id."""
+# :license: MIT, see LICENSE for more details.
+
+import click
+import SoftLayer
+from SoftLayer.CLI import environment
+
+
+@click.command()
+@click.argument('access_id', type=int)
+@click.option('--subnet-id', multiple=True, type=int,
+              help="ID of the subnets to remove; e.g.: --subnet-id 1234")
+@environment.pass_env
+def cli(env, access_id, subnet_id):
+    """Remove block storage subnets for the given host id.
+
+    access_id is the host_id obtained by: slcli block access-list <volume_id>
+
+    SoftLayer_Account::iscsiisolationdisabled must be False to use this command
+    """
+    try:
+        subnet_ids = list(subnet_id)
+        block_manager = SoftLayer.BlockStorageManager(env.client)
+        removed_subnets = block_manager.remove_subnets_from_acl(access_id, subnet_ids)
+
+        for subnet in removed_subnets:
+            message = "Successfully removed subnet id: {} for allowed host id: {}".format(subnet, access_id)
+            click.echo(message)
+
+        failed_to_remove_subnets = list(set(subnet_ids) - set(removed_subnets))
+        for subnet in failed_to_remove_subnets:
+            message = "Failed to remove subnet id: {} for allowed host id: {}".format(subnet, access_id)
+            click.echo(message)
+
+    except SoftLayer.SoftLayerAPIError as ex:
+        message = "Unable to remove subnets.\nReason: {}".format(ex.faultString)
+        click.echo(message)
diff --git a/SoftLayer/CLI/file/limit.py b/SoftLayer/CLI/file/limit.py
deleted file mode 100644
index 34dca756..00000000
--- a/SoftLayer/CLI/file/limit.py
+++ /dev/null
@@ -1,31 +0,0 @@
-"""List number of file storage volumes limit per datacenter."""
-# :license: MIT, see LICENSE for more details.
-
-import click
-import SoftLayer
-from SoftLayer.CLI import environment
-from SoftLayer.CLI import formatting
-
-DEFAULT_COLUMNS = [
-    'Datacenter',
-    'MaximumAvailableCount',
-    'ProvisionedCount'
-]
-
-
-@click.command()
-@click.option('--sortby', help='Column to sort by', default='Datacenter')
-@environment.pass_env
-def cli(env, sortby):
-    """List number of block storage volumes limit per datacenter."""
-    file_manager = SoftLayer.FileStorageManager(env.client)
-    file_volumes = file_manager.list_file_volume_limit()
-
-    table = formatting.KeyValueTable(DEFAULT_COLUMNS)
-    table.sortby = sortby
-    for volume in file_volumes:
-        datacenter_name = volume['datacenterName']
-        maximum_available_count = volume['maximumAvailableCount']
-        provisioned_count = volume['provisionedCount']
-        table.add_row([datacenter_name, maximum_available_count, provisioned_count])
-    env.fout(table)
diff --git a/SoftLayer/CLI/hardware/dns.py b/SoftLayer/CLI/hardware/dns.py
deleted file mode 100644
index 3b745800..00000000
--- a/SoftLayer/CLI/hardware/dns.py
+++ /dev/null
@@ -1,64 +0,0 @@
-"""Sync DNS records."""
-# :license: MIT, see LICENSE for more details.
-# pylint: disable=duplicate-code
-
-import click
-
-import SoftLayer
-from SoftLayer.CLI import environment
-from SoftLayer.CLI import exceptions
-from SoftLayer.CLI import formatting
-from SoftLayer.CLI import helpers
-
-
-@click.command(epilog="""If you don't specify any
-arguments, it will attempt to update both the A and PTR records. If you don't
-want to update both records, you may use the -a or --ptr arguments to limit
-the records updated.""")
-@click.argument('identifier')
-@click.option('--a-record', '-a', is_flag=True, help="Sync the A record for the host")
-@click.option('--aaaa-record', is_flag=True, help="Sync the AAAA record for the host")
-@click.option('--ptr', is_flag=True, help="Sync the PTR record for the host")
-@click.option('--ttl', default=7200, show_default=True, type=click.INT,
-              help="Sets the TTL for the A and/or PTR records")
-@environment.pass_env
-def cli(env, identifier, a_record, aaaa_record, ptr, ttl):
-    """Sync DNS records."""
-
-    mask = """mask[id, globalIdentifier, fullyQualifiedDomainName, hostname, domain,
-              primaryBackendIpAddress,primaryIpAddress,
-              primaryNetworkComponent[id,primaryIpAddress,primaryVersion6IpAddressRecord[ipAddress]]]"""
-    dns = SoftLayer.DNSManager(env.client)
-    server = SoftLayer.HardwareManager(env.client)
-
-    server_id = helpers.resolve_id(server.resolve_ids, identifier, 'VS')
-    instance = server.get_hardware(server_id, mask=mask)
-    zone_id = helpers.resolve_id(dns.resolve_ids, instance['domain'], name='zone')
-
-    if not instance['primaryIpAddress']:
-        raise exceptions.CLIAbort('No primary IP address associated with this hardware')
-
-    go_for_it = env.skip_confirmations or formatting.confirm(
-        "Attempt to update DNS records for %s" % instance['fullyQualifiedDomainName'])
-
-    if not go_for_it:
-        raise exceptions.CLIAbort("Aborting DNS sync")
-
-    # both will be true only if no options are passed in, basically.
-    both = (not ptr) and (not a_record) and (not aaaa_record)
-
-    if both or a_record:
-        dns.sync_host_record(zone_id, instance['hostname'], instance['primaryIpAddress'], 'a', ttl)
-
-    if both or ptr:
-        # getReverseDomainRecords returns a list of 1 element, so just get the top.
-        ptr_domains = env.client['Hardware_Server'].getReverseDomainRecords(id=instance['id']).pop()
-        dns.sync_ptr_record(ptr_domains, instance['primaryIpAddress'], instance['fullyQualifiedDomainName'], ttl)
-
-    if aaaa_record:
-        try:
-            # done this way to stay within 80 character lines
-            ipv6 = instance['primaryNetworkComponent']['primaryVersion6IpAddressRecord']['ipAddress']
-            dns.sync_host_record(zone_id, instance['hostname'], ipv6, 'aaaa', ttl)
-        except KeyError:
-            raise exceptions.CLIAbort("%s does not have an ipv6 address" % instance['fullyQualifiedDomainName'])
diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py
index d020ee9b..76c9fb77 100644
--- a/SoftLayer/CLI/routes.py
+++ b/SoftLayer/CLI/routes.py
@@ -80,6 +80,9 @@
     ('block:access-list', 'SoftLayer.CLI.block.access.list:cli'),
     ('block:access-revoke', 'SoftLayer.CLI.block.access.revoke:cli'),
     ('block:access-password', 'SoftLayer.CLI.block.access.password:cli'),
+    ('block:subnets-list', 'SoftLayer.CLI.block.subnets.list:cli'),
+    ('block:subnets-assign', 'SoftLayer.CLI.block.subnets.assign:cli'),
+    ('block:subnets-remove', 'SoftLayer.CLI.block.subnets.remove: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'),
@@ -102,7 +105,6 @@
     ('block:volume-modify', 'SoftLayer.CLI.block.modify:cli'),
     ('block:volume-order', 'SoftLayer.CLI.block.order:cli'),
     ('block:volume-set-lun-id', 'SoftLayer.CLI.block.lun:cli'),
-    ('block:volume-limits', 'SoftLayer.CLI.block.limit:cli'),
 
     ('event-log', 'SoftLayer.CLI.event_log'),
     ('event-log:get', 'SoftLayer.CLI.event_log.get:cli'),
@@ -133,7 +135,6 @@
     ('file:volume-list', 'SoftLayer.CLI.file.list:cli'),
     ('file:volume-modify', 'SoftLayer.CLI.file.modify:cli'),
     ('file:volume-order', 'SoftLayer.CLI.file.order:cli'),
-    ('file:volume-limits', 'SoftLayer.CLI.file.limit:cli'),
 
     ('firewall', 'SoftLayer.CLI.firewall'),
     ('firewall:add', 'SoftLayer.CLI.firewall.add:cli'),
@@ -236,7 +237,6 @@
     ('hardware:rescue', 'SoftLayer.CLI.hardware.power:rescue'),
     ('hardware:ready', 'SoftLayer.CLI.hardware.ready:cli'),
     ('hardware:toggle-ipmi', 'SoftLayer.CLI.hardware.toggle_ipmi:cli'),
-    ('hardware:dns-sync', 'SoftLayer.CLI.hardware.dns:cli'),
 
     ('securitygroup', 'SoftLayer.CLI.securitygroup'),
     ('securitygroup:list', 'SoftLayer.CLI.securitygroup.list:cli'),
diff --git a/SoftLayer/CLI/virt/detail.py b/SoftLayer/CLI/virt/detail.py
index 131117df..bf93a834 100644
--- a/SoftLayer/CLI/virt/detail.py
+++ b/SoftLayer/CLI/virt/detail.py
@@ -126,15 +126,14 @@ def cli(env, identifier, passwords=False, price=False):
 def _bw_table(bw_data):
     """Generates a bandwidth useage table"""
     table = formatting.Table(['Type', 'In GB', 'Out GB', 'Allotment'])
-    for bw_point in bw_data.get('usage'):
+    for bw_point in bw_data.get('useage'):
         bw_type = 'Private'
         allotment = 'N/A'
         if bw_point['type']['alias'] == 'PUBLIC_SERVER_BW':
             bw_type = 'Public'
-            if not bw_data.get('allotment'):
+            allotment = utils.lookup(bw_data, 'allotment', 'amount')
+            if allotment is None:
                 allotment = '-'
-            else:
-                allotment = utils.lookup(bw_data, 'allotment', 'amount')
 
         table.add_row([bw_type, bw_point['amountIn'], bw_point['amountOut'], allotment])
     return table
diff --git a/SoftLayer/CLI/virt/dns.py b/SoftLayer/CLI/virt/dns.py
index 26b4904c..ca600465 100644
--- a/SoftLayer/CLI/virt/dns.py
+++ b/SoftLayer/CLI/virt/dns.py
@@ -1,6 +1,5 @@
 """Sync DNS records."""
 # :license: MIT, see LICENSE for more details.
-# pylint: disable=duplicate-code
 
 import click
 
@@ -16,49 +15,138 @@
 want to update both records, you may use the -a or --ptr arguments to limit
 the records updated.""")
 @click.argument('identifier')
-@click.option('--a-record', '-a', is_flag=True, help="Sync the A record for the host")
-@click.option('--aaaa-record', is_flag=True, help="Sync the AAAA record for the host")
+@click.option('--a-record', '-a',
+              is_flag=True,
+              help="Sync the A record for the host")
+@click.option('--aaaa-record',
+              is_flag=True,
+              help="Sync the AAAA record for the host")
 @click.option('--ptr', is_flag=True, help="Sync the PTR record for the host")
-@click.option('--ttl', default=7200, show_default=True, type=click.INT,
+@click.option('--ttl',
+              default=7200,
+              show_default=True,
+              type=click.INT,
               help="Sets the TTL for the A and/or PTR records")
 @environment.pass_env
 def cli(env, identifier, a_record, aaaa_record, ptr, ttl):
     """Sync DNS records."""
 
-    mask = """mask[id, globalIdentifier, fullyQualifiedDomainName, hostname, domain,
-              primaryBackendIpAddress,primaryIpAddress,
-              primaryNetworkComponent[id,primaryIpAddress,primaryVersion6IpAddressRecord[ipAddress]]]"""
+    items = ['id',
+             'globalIdentifier',
+             'fullyQualifiedDomainName',
+             'hostname',
+             'domain',
+             'primaryBackendIpAddress',
+             'primaryIpAddress',
+             '''primaryNetworkComponent[
+                id, primaryIpAddress,
+                primaryVersion6IpAddressRecord[ipAddress]
+             ]''']
+    mask = "mask[%s]" % ','.join(items)
     dns = SoftLayer.DNSManager(env.client)
-    server = SoftLayer.VSManager(env.client)
+    vsi = SoftLayer.VSManager(env.client)
 
-    server_id = helpers.resolve_id(server.resolve_ids, identifier, 'VS')
-    instance = server.get_instance(server_id, mask=mask)
-    zone_id = helpers.resolve_id(dns.resolve_ids, instance['domain'], name='zone')
+    vs_id = helpers.resolve_id(vsi.resolve_ids, identifier, 'VS')
+    instance = vsi.get_instance(vs_id, mask=mask)
+    zone_id = helpers.resolve_id(dns.resolve_ids,
+                                 instance['domain'],
+                                 name='zone')
+
+    def sync_a_record():
+        """Sync A record."""
+        records = dns.get_records(zone_id,
+                                  host=instance['hostname'],
+                                  record_type='a')
+        if not records:
+            # don't have a record, lets add one to the base zone
+            dns.create_record(zone['id'],
+                              instance['hostname'],
+                              'a',
+                              instance['primaryIpAddress'],
+                              ttl=ttl)
+        else:
+            if len(records) != 1:
+                raise exceptions.CLIAbort("Aborting A record sync, found "
+                                          "%d A record exists!" % len(records))
+            rec = records[0]
+            rec['data'] = instance['primaryIpAddress']
+            rec['ttl'] = ttl
+            dns.edit_record(rec)
+
+    def sync_aaaa_record():
+        """Sync AAAA record."""
+        records = dns.get_records(zone_id,
+                                  host=instance['hostname'],
+                                  record_type='aaaa')
+        try:
+            # done this way to stay within 80 character lines
+            component = instance['primaryNetworkComponent']
+            record = component['primaryVersion6IpAddressRecord']
+            ip_address = record['ipAddress']
+        except KeyError:
+            raise exceptions.CLIAbort("%s does not have an ipv6 address"
+                                      % instance['fullyQualifiedDomainName'])
+
+        if not records:
+            # don't have a record, lets add one to the base zone
+            dns.create_record(zone['id'],
+                              instance['hostname'],
+                              'aaaa',
+                              ip_address,
+                              ttl=ttl)
+        else:
+            if len(records) != 1:
+                raise exceptions.CLIAbort("Aborting A record sync, found "
+                                          "%d A record exists!" % len(records))
+            rec = records[0]
+            rec['data'] = ip_address
+            rec['ttl'] = ttl
+            dns.edit_record(rec)
+
+    def sync_ptr_record():
+        """Sync PTR record."""
+        host_rec = instance['primaryIpAddress'].split('.')[-1]
+        ptr_domains = (env.client['Virtual_Guest']
+                       .getReverseDomainRecords(id=instance['id'])[0])
+        edit_ptr = None
+        for ptr in ptr_domains['resourceRecords']:
+            if ptr['host'] == host_rec:
+                ptr['ttl'] = ttl
+                edit_ptr = ptr
+                break
+
+        if edit_ptr:
+            edit_ptr['data'] = instance['fullyQualifiedDomainName']
+            dns.edit_record(edit_ptr)
+        else:
+            dns.create_record(ptr_domains['id'],
+                              host_rec,
+                              'ptr',
+                              instance['fullyQualifiedDomainName'],
+                              ttl=ttl)
 
     if not instance['primaryIpAddress']:
-        raise exceptions.CLIAbort('No primary IP address associated with this VS')
+        raise exceptions.CLIAbort('No primary IP address associated with '
+                                  'this VS')
+
+    zone = dns.get_zone(zone_id)
 
     go_for_it = env.skip_confirmations or formatting.confirm(
-        "Attempt to update DNS records for %s" % instance['fullyQualifiedDomainName'])
+        "Attempt to update DNS records for %s"
+        % instance['fullyQualifiedDomainName'])
 
     if not go_for_it:
         raise exceptions.CLIAbort("Aborting DNS sync")
 
-    # both will be true only if no options are passed in, basically.
-    both = (not ptr) and (not a_record) and (not aaaa_record)
+    both = False
+    if not ptr and not a_record and not aaaa_record:
+        both = True
 
     if both or a_record:
-        dns.sync_host_record(zone_id, instance['hostname'], instance['primaryIpAddress'], 'a', ttl)
+        sync_a_record()
 
     if both or ptr:
-        # getReverseDomainRecords returns a list of 1 element, so just get the top.
-        ptr_domains = env.client['Virtual_Guest'].getReverseDomainRecords(id=instance['id']).pop()
-        dns.sync_ptr_record(ptr_domains, instance['primaryIpAddress'], instance['fullyQualifiedDomainName'], ttl)
+        sync_ptr_record()
 
     if aaaa_record:
-        try:
-            # done this way to stay within 80 character lines
-            ipv6 = instance['primaryNetworkComponent']['primaryVersion6IpAddressRecord']['ipAddress']
-            dns.sync_host_record(zone_id, instance['hostname'], ipv6, 'aaaa', ttl)
-        except KeyError:
-            raise exceptions.CLIAbort("%s does not have an ipv6 address" % instance['fullyQualifiedDomainName'])
+        sync_aaaa_record()
diff --git a/SoftLayer/fixtures/SoftLayer_Network_Storage.py b/SoftLayer/fixtures/SoftLayer_Network_Storage.py
index 3c8d335e..8a0c9772 100644
--- a/SoftLayer/fixtures/SoftLayer_Network_Storage.py
+++ b/SoftLayer/fixtures/SoftLayer_Network_Storage.py
@@ -226,9 +226,3 @@
 
 enableSnapshots = True
 disableSnapshots = True
-
-getVolumeCountLimits = {
-    'datacenterName': 'global',
-    'maximumAvailableCount': 300,
-    'provisionedCount': 100
-}
diff --git a/SoftLayer/fixtures/SoftLayer_Network_Storage_Allowed_Host.py b/SoftLayer/fixtures/SoftLayer_Network_Storage_Allowed_Host.py
index 0582e04b..5bf8c335 100644
--- a/SoftLayer/fixtures/SoftLayer_Network_Storage_Allowed_Host.py
+++ b/SoftLayer/fixtures/SoftLayer_Network_Storage_Allowed_Host.py
@@ -1,1 +1,61 @@
+TEST_ALLOWED_HOST = {
+    'id': 12345,
+    'name': 'Test Allowed Host',
+    'accountId': 1234,
+    'credentialId': None,
+    'createDate': '2020-01-01 00:00:01',
+    'iscsiAclCredentials': {
+        'id': 129,
+        'allowedHostId': 12345,
+        'subnetId': 12345678
+    },
+    'subnetsInAcl': [{
+        'id': 12345678,
+        'accountId': 1234,
+        'networkIdentifier': '10.11.12.13',
+        'cidr': '14',
+        'billingRecordId': None,
+        'parentId': None,
+        'networkVlanId': None,
+        'createDate': '2020-01-02 00:00:01',
+        'modifyDate': None,
+        'subnetType': 'SECONDARY_ON_VLAN',
+        'restrictAllocationFlag': 0,
+        'leafFlag': 1,
+        'ownerId': 1,
+        'ipAddressBegin': 129123,
+        'ipAddressEnd': 129145,
+        'purgeFlag': 0
+    }]
+}
+
+getObject = TEST_ALLOWED_HOST
+
+getSubnetsInAcl = [{
+        'id': 12345678,
+        'accountId': 1234,
+        'networkIdentifier': '10.11.12.13',
+        'cidr': '14',
+        'billingRecordId': None,
+        'parentId': None,
+        'networkVlanId': None,
+        'createDate': '2020-01-02 00:00:01',
+        'modifyDate': None,
+        'subnetType': 'SECONDARY_ON_VLAN',
+        'restrictAllocationFlag': 0,
+        'leafFlag': 1,
+        'ownerId': 1,
+        'ipAddressBegin': 129123,
+        'ipAddressEnd': 129145,
+        'purgeFlag': 0
+}]
+
+assignSubnetsToAcl = [
+    12345678
+]
+
+removeSubnetsFromAcl = [
+    12345678
+]
+
 setCredentialPassword = True
diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py
index fe1b203b..306e0f37 100644
--- a/SoftLayer/managers/block.py
+++ b/SoftLayer/managers/block.py
@@ -24,13 +24,6 @@ def __init__(self, client):
         self.configuration = {}
         self.client = client
 
-    def list_block_volume_limit(self):
-        """Returns a list of block volume count limit.
-
-        :return: Returns a list of block volume count limit.
-        """
-        return self.client.call('Network_Storage', 'getVolumeCountLimits')
-
     def list_block_volumes(self, datacenter=None, username=None,
                            storage_type=None, **kwargs):
         """Returns a list of block volumes.
@@ -208,6 +201,46 @@ def deauthorize_host_to_volume(self, volume_id,
         return self.client.call('Network_Storage', 'removeAccessFromHostList',
                                 host_templates, id=volume_id, **kwargs)
 
+    def assign_subnets_to_acl(self, access_id, subnet_ids):
+        """Assigns subnet records to ACL for the access host.
+
+        access_id is the host_id obtained by: slcli block access-list <volume_id>
+
+        :param integer access_id: id of the access host
+        :param list subnet_ids: The ids of the subnets to be assigned
+        :return: Returns int array of assigned subnet ids
+        """
+        return self.client.call('Network_Storage_Allowed_Host',
+                                'assignSubnetsToAcl',
+                                subnet_ids,
+                                id=access_id)
+
+    def remove_subnets_from_acl(self, access_id, subnet_ids):
+        """Removes subnet records from ACL for the access host.
+
+        access_id is the host_id obtained by: slcli block access-list <volume_id>
+
+        :param integer access_id: id of the access host
+        :param list subnet_ids: The ids of the subnets to be removed
+        :return: Returns int array of removed subnet ids
+        """
+        return self.client.call('Network_Storage_Allowed_Host',
+                                'removeSubnetsFromAcl',
+                                subnet_ids,
+                                id=access_id)
+
+    def get_subnets_in_acl(self, access_id):
+        """Returns a list of subnet records for the access host.
+
+         access_id is the host_id obtained by: slcli block access-list <volume_id>
+
+        :param integer access_id: id of the access host
+        :return: Returns an array of SoftLayer_Network_Subnet objects
+        """
+        return self.client.call('Network_Storage_Allowed_Host',
+                                'getSubnetsInAcl',
+                                id=access_id)
+
     def get_replication_partners(self, volume_id):
         """Acquires list of replicant volumes pertaining to the given volume.
 
diff --git a/SoftLayer/managers/dns.py b/SoftLayer/managers/dns.py
index 3a2ea914..a3fc322a 100644
--- a/SoftLayer/managers/dns.py
+++ b/SoftLayer/managers/dns.py
@@ -7,7 +7,6 @@
 """
 import time
 
-from SoftLayer import exceptions
 from SoftLayer import utils
 
 
@@ -206,11 +205,13 @@ def get_records(self, zone_id, ttl=None, data=None, host=None,
             _filter['resourceRecords']['data'] = utils.query_filter(data)
 
         if record_type:
-            _filter['resourceRecords']['type'] = utils.query_filter(record_type.lower())
+            _filter['resourceRecords']['type'] = utils.query_filter(
+                record_type.lower())
 
         results = self.service.getResourceRecords(
             id=zone_id,
-            mask='id,expire,domainId,host,minimum,refresh,retry,mxPriority,ttl,type,data,responsiblePerson',
+            mask='id,expire,domainId,host,minimum,refresh,retry,'
+            'mxPriority,ttl,type,data,responsiblePerson',
             filter=_filter.to_dict(),
         )
 
@@ -225,7 +226,6 @@ def edit_record(self, record):
         :param dict record: the record to update
 
         """
-        record.pop('isGatewayAddress', None)
         self.record.editObject(record, id=record['id'])
 
     def dump_zone(self, zone_id):
@@ -235,47 +235,3 @@ def dump_zone(self, zone_id):
 
         """
         return self.service.getZoneFileContents(id=zone_id)
-
-    def sync_host_record(self, zone_id, hostname, ip_address, record_type='a', ttl=7200):
-        """For a given zone_id, will set hostname's A record to ip_address
-
-        :param integer zone_id: The zone id for the domain
-        :param string hostname: host part of the record
-        :param string ip_address: data part of the record
-        :param integer ttl: TTL for the record
-        :param string record_type: 'a' or 'aaaa'
-        """
-        records = self.get_records(zone_id, host=hostname, record_type=record_type)
-        if not records:
-            # don't have a record, lets add one to the base zone
-            self.create_record(zone_id, hostname, record_type, ip_address, ttl=ttl)
-        else:
-            if len(records) != 1:
-                raise exceptions.SoftLayerError("Aborting record sync, found %d records!" % len(records))
-            rec = records[0]
-            rec['data'] = ip_address
-            rec['ttl'] = ttl
-            self.edit_record(rec)
-
-    def sync_ptr_record(self, ptr_domains, ip_address, fqdn, ttl=7200):
-        """Sync PTR record.
-
-        :param dict ptr_domains: result from SoftLayer_Virtual_Guest.getReverseDomainRecords or
-                                 SoftLayer_Hardware_Server.getReverseDomainRecords
-        :param string ip_address: ip address to sync with
-        :param string fqdn: Fully Qualified Domain Name
-        :param integer ttl: TTL for the record
-        """
-        host_rec = ip_address.split('.')[-1]
-        edit_ptr = None
-        for ptr in ptr_domains['resourceRecords']:
-            if ptr.get('host', '') == host_rec:
-                ptr['ttl'] = ttl
-                edit_ptr = ptr
-                break
-
-        if edit_ptr:
-            edit_ptr['data'] = fqdn
-            self.edit_record(edit_ptr)
-        else:
-            self.create_record(ptr_domains['id'], host_rec, 'ptr', fqdn, ttl=ttl)
diff --git a/SoftLayer/managers/file.py b/SoftLayer/managers/file.py
index 95cf85c8..c0d8fcae 100644
--- a/SoftLayer/managers/file.py
+++ b/SoftLayer/managers/file.py
@@ -19,13 +19,6 @@ def __init__(self, client):
         self.configuration = {}
         self.client = client
 
-    def list_file_volume_limit(self):
-        """Returns a list of file volume count limit.
-
-        :return: Returns a list of file volume count limit.
-        """
-        return self.client.call('Network_Storage', 'getVolumeCountLimits')
-
     def list_file_volumes(self, datacenter=None, username=None,
                           storage_type=None, **kwargs):
         """Returns a list of file volumes.
diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py
index b1391bc3..24a264b7 100644
--- a/SoftLayer/managers/vs.py
+++ b/SoftLayer/managers/vs.py
@@ -1074,10 +1074,8 @@ def get_bandwidth_allocation(self, instance_id):
         a_mask = "mask[allocation[amount]]"
         allotment = self.client.call('Virtual_Guest', 'getBandwidthAllotmentDetail', id=instance_id, mask=a_mask)
         u_mask = "mask[amountIn,amountOut,type]"
-        usage = self.client.call('Virtual_Guest', 'getBillingCycleBandwidthUsage', id=instance_id, mask=u_mask)
-        if allotment:
-            return {'allotment': allotment.get('allocation'), 'usage': usage}
-        return {'allotment': allotment, 'usage': usage}
+        useage = self.client.call('Virtual_Guest', 'getBillingCycleBandwidthUsage', id=instance_id, mask=u_mask)
+        return {'allotment': allotment.get('allocation'), 'useage': useage}
 
     # pylint: disable=inconsistent-return-statements
     def _get_price_id_for_upgrade(self, package_items, option, value, public=True):
diff --git a/docs/cli/hardware.rst b/docs/cli/hardware.rst
index 08fc273d..3e7eeaf4 100644
--- a/docs/cli/hardware.rst
+++ b/docs/cli/hardware.rst
@@ -94,6 +94,3 @@ This function updates the firmware of a server. If already at the latest version
    :prog: hw ready
    :show-nested:
 
-.. click:: SoftLayer.CLI.hardware.dns-sync:cli
-   :prog: hw dns-sync
-   :show-nested:
diff --git a/setup.py b/setup.py
index 65b1d5c4..e254b25c 100644
--- a/setup.py
+++ b/setup.py
@@ -51,7 +51,6 @@
         'Programming Language :: Python :: 3.5',
         'Programming Language :: Python :: 3.6',
         'Programming Language :: Python :: 3.7',
-        'Programming Language :: Python :: 3.8',
         'Programming Language :: Python :: Implementation :: CPython',
         'Programming Language :: Python :: Implementation :: PyPy',
     ],
diff --git a/tox.ini b/tox.ini
index 22af5722..b0d521ac 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
 [tox]
-envlist = py35,py36,py37,py38,pypy3,analysis,coverage
+envlist = py35,py36,py37,pypy3,analysis,coverage
 
 
 [flake8]


Test patch:
diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py
index 47ffda2b..ed2916c5 100644
--- a/tests/CLI/modules/block_tests.py
+++ b/tests/CLI/modules/block_tests.py
@@ -438,6 +438,23 @@ def test_deauthorize_host_to_volume(self):
 
         self.assert_no_fail(result)
 
+    def test_assign_subnets_to_acl(self):
+        result = self.run_command(['block', 'subnets-assign', '12345',
+                                   '--subnet-id=12345678'])
+
+        self.assert_no_fail(result)
+
+    def test_remove_subnets_from_acl(self):
+        result = self.run_command(['block', 'subnets-remove', '12345',
+                                   '--subnet-id=12345678'])
+
+        self.assert_no_fail(result)
+
+    def test_get_subnets_in_acl(self):
+        result = self.run_command(['block', 'subnets-list', '12345'])
+
+        self.assert_no_fail(result)
+
     def test_replicant_failover(self):
         result = self.run_command(['block', 'replica-failover', '12345678',
                                    '--replicant-id=5678'])
@@ -665,15 +682,3 @@ def test_modify_order(self, order_mock):
     def test_set_password(self):
         result = self.run_command(['block', 'access-password', '1234', '--password=AAAAA'])
         self.assert_no_fail(result)
-
-    @mock.patch('SoftLayer.BlockStorageManager.list_block_volume_limit')
-    def test_volume_limit(self, list_mock):
-        list_mock.return_value = [
-            {
-                "datacenterName": "global",
-                "maximumAvailableCount": 300,
-                "provisionedCount": 100
-            }]
-
-        result = self.run_command(['block', 'volume-limits'])
-        self.assert_no_fail(result)
diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py
index 0fc4ffc0..f64b1962 100644
--- a/tests/CLI/modules/file_tests.py
+++ b/tests/CLI/modules/file_tests.py
@@ -665,14 +665,3 @@ def test_modify_order(self, order_mock):
         self.assert_no_fail(result)
         self.assertEqual('Order #24602 placed successfully!\n > Storage as a Service\n > 1000 GBs\n > 4 IOPS per GB\n',
                          result.output)
-
-    @mock.patch('SoftLayer.FileStorageManager.list_file_volume_limit')
-    def test_volume_limit(self, list_mock):
-        list_mock.return_value = [
-            {
-                'datacenterName': 'global',
-                'maximumAvailableCount': 300,
-                'provisionedCount': 100
-            }]
-        result = self.run_command(['file', 'volume-limits'])
-        self.assert_no_fail(result)
diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py
index 14f8e920..29ec65d4 100644
--- a/tests/CLI/modules/server_tests.py
+++ b/tests/CLI/modules/server_tests.py
@@ -12,7 +12,6 @@
 import sys
 
 from SoftLayer.CLI import exceptions
-from SoftLayer import SoftLayerError
 from SoftLayer import testing
 
 import json
@@ -639,190 +638,3 @@ def test_bandwidth_hw_quite(self):
         self.assertEqual(output_summary[1]['Max Date'], date)
         self.assertEqual(output_summary[2]['Max GB'], 0.1172)
         self.assertEqual(output_summary[3]['Sum GB'], 0.0009)
-
-    @mock.patch('SoftLayer.CLI.formatting.confirm')
-    def test_dns_sync_both(self, confirm_mock):
-        confirm_mock.return_value = True
-        getReverseDomainRecords = self.set_mock('SoftLayer_Hardware_Server',
-                                                'getReverseDomainRecords')
-        getReverseDomainRecords.return_value = [{
-            'networkAddress': '172.16.1.100',
-            'name': '2.240.16.172.in-addr.arpa',
-            'resourceRecords': [{'data': 'test.softlayer.com.',
-                                 'id': 100,
-                                 'host': '12'}],
-            'updateDate': '2013-09-11T14:36:57-07:00',
-            'serial': 1234665663,
-            'id': 123456,
-        }]
-        getResourceRecords = self.set_mock('SoftLayer_Dns_Domain',
-                                           'getResourceRecords')
-        getResourceRecords.return_value = []
-        createAargs = ({
-            'type': 'a',
-            'host': 'hardware-test1',
-            'domainId': 12345,  # from SoftLayer_Account::getDomains
-            'data': '172.16.1.100',
-            'ttl': 7200
-        },)
-        createPTRargs = ({
-            'type': 'ptr',
-            'host': '100',
-            'domainId': 123456,
-            'data': 'hardware-test1.test.sftlyr.ws',
-            'ttl': 7200
-        },)
-
-        result = self.run_command(['hw', 'dns-sync', '1000'])
-
-        self.assert_no_fail(result)
-        self.assert_called_with('SoftLayer_Dns_Domain', 'getResourceRecords')
-        self.assert_called_with('SoftLayer_Hardware_Server',
-                                'getReverseDomainRecords')
-        self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord',
-                                'createObject',
-                                args=createAargs)
-        self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord',
-                                'createObject',
-                                args=createPTRargs)
-
-    @mock.patch('SoftLayer.CLI.formatting.confirm')
-    def test_dns_sync_v6(self, confirm_mock):
-        confirm_mock.return_value = True
-        getResourceRecords = self.set_mock('SoftLayer_Dns_Domain',
-                                           'getResourceRecords')
-        getResourceRecords.return_value = []
-        server = self.set_mock('SoftLayer_Hardware_Server', 'getObject')
-        test_server = {
-            'id': 1000,
-            'hostname': 'hardware-test1',
-            'domain': 'sftlyr.ws',
-            'primaryIpAddress': '172.16.1.100',
-            'fullyQualifiedDomainName': 'hw-test1.sftlyr.ws',
-            "primaryNetworkComponent": {}
-        }
-        server.return_value = test_server
-
-        result = self.run_command(['hw', 'dns-sync', '--aaaa-record', '1000'])
-
-        self.assertEqual(result.exit_code, 2)
-        self.assertIsInstance(result.exception, exceptions.CLIAbort)
-
-        test_server['primaryNetworkComponent'] = {
-            'primaryVersion6IpAddressRecord': {
-                'ipAddress': '2607:f0d0:1b01:0023:0000:0000:0000:0004'
-            }
-        }
-        createV6args = ({
-            'type': 'aaaa',
-            'host': 'hardware-test1',
-            'domainId': 12345,  # from SoftLayer_Account::getDomains
-            'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004',
-            'ttl': 7200
-        },)
-        server.return_value = test_server
-        result = self.run_command(['hw', 'dns-sync', '--aaaa-record', '1000'])
-        self.assert_no_fail(result)
-        self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord',
-                                'createObject',
-                                args=createV6args)
-
-        v6Record = {
-            'id': 1,
-            'ttl': 7200,
-            'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004',
-            'host': 'hardware-test1',
-            'type': 'aaaa'
-        }
-
-        getResourceRecords = self.set_mock('SoftLayer_Dns_Domain',
-                                           'getResourceRecords')
-        getResourceRecords.return_value = [v6Record]
-        editArgs = (v6Record,)
-        result = self.run_command(['hw', 'dns-sync', '--aaaa-record', '1000'])
-        self.assert_no_fail(result)
-        self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord',
-                                'editObject',
-                                args=editArgs)
-
-        getResourceRecords = self.set_mock('SoftLayer_Dns_Domain',
-                                           'getResourceRecords')
-        getResourceRecords.return_value = [v6Record, v6Record]
-        result = self.run_command(['hw', 'dns-sync', '--aaaa-record', '1000'])
-        self.assertEqual(result.exit_code, 1)
-        self.assertIsInstance(result.exception, SoftLayerError)
-
-    @mock.patch('SoftLayer.CLI.formatting.confirm')
-    def test_dns_sync_edit_a(self, confirm_mock):
-        confirm_mock.return_value = True
-        getResourceRecords = self.set_mock('SoftLayer_Dns_Domain',
-                                           'getResourceRecords')
-        getResourceRecords.return_value = [
-            {'id': 1, 'ttl': 7200, 'data': '1.1.1.1',
-             'host': 'hardware-test1', 'type': 'a'}
-        ]
-        editArgs = (
-            {'type': 'a', 'host': 'hardware-test1', 'data': '172.16.1.100',
-             'id': 1, 'ttl': 7200},
-        )
-        result = self.run_command(['hw', 'dns-sync', '-a', '1000'])
-        self.assert_no_fail(result)
-        self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord',
-                                'editObject',
-                                args=editArgs)
-
-        getResourceRecords = self.set_mock('SoftLayer_Dns_Domain',
-                                           'getResourceRecords')
-        getResourceRecords.return_value = [
-            {'id': 1, 'ttl': 7200, 'data': '1.1.1.1',
-             'host': 'hardware-test1', 'type': 'a'},
-            {'id': 2, 'ttl': 7200, 'data': '1.1.1.1',
-             'host': 'hardware-test1', 'type': 'a'}
-        ]
-        result = self.run_command(['hw', 'dns-sync', '-a', '1000'])
-        self.assertEqual(result.exit_code, 1)
-        self.assertIsInstance(result.exception, SoftLayerError)
-
-    @mock.patch('SoftLayer.CLI.formatting.confirm')
-    def test_dns_sync_edit_ptr(self, confirm_mock):
-        confirm_mock.return_value = True
-        getReverseDomainRecords = self.set_mock('SoftLayer_Hardware_Server',
-                                                'getReverseDomainRecords')
-        getReverseDomainRecords.return_value = [{
-            'networkAddress': '172.16.1.100',
-            'name': '100.1.16.172.in-addr.arpa',
-            'resourceRecords': [{'data': 'test.softlayer.com.',
-                                 'id': 123,
-                                 'host': '100'}],
-            'updateDate': '2013-09-11T14:36:57-07:00',
-            'serial': 1234665663,
-            'id': 123456,
-        }]
-        editArgs = ({'host': '100', 'data': 'hardware-test1.test.sftlyr.ws',
-                     'id': 123, 'ttl': 7200},)
-        result = self.run_command(['hw', 'dns-sync', '--ptr', '1000'])
-        self.assert_no_fail(result)
-        self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord',
-                                'editObject',
-                                args=editArgs)
-
-    @mock.patch('SoftLayer.CLI.formatting.confirm')
-    def test_dns_sync_misc_exception(self, confirm_mock):
-        confirm_mock.return_value = False
-        result = self.run_command(['hw', 'dns-sync', '-a', '1000'])
-        self.assertEqual(result.exit_code, 2)
-        self.assertIsInstance(result.exception, exceptions.CLIAbort)
-
-        guest = self.set_mock('SoftLayer_Hardware_Server', 'getObject')
-        test_guest = {
-            'id': 1000,
-            'primaryIpAddress': '',
-            'hostname': 'hardware-test1',
-            'domain': 'sftlyr.ws',
-            'fullyQualifiedDomainName': 'hardware-test1.sftlyr.ws',
-            "primaryNetworkComponent": {}
-        }
-        guest.return_value = test_guest
-        result = self.run_command(['hw', 'dns-sync', '-a', '1000'])
-        self.assertEqual(result.exit_code, 2)
-        self.assertIsInstance(result.exception, exceptions.CLIAbort)
diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py
index 2c79f42b..20323091 100644
--- a/tests/CLI/modules/vs/vs_tests.py
+++ b/tests/CLI/modules/vs/vs_tests.py
@@ -12,7 +12,6 @@
 from SoftLayer.CLI import exceptions
 from SoftLayer.fixtures import SoftLayer_Virtual_Guest as SoftLayer_Virtual_Guest
 from SoftLayer import SoftLayerAPIError
-from SoftLayer import SoftLayerError
 from SoftLayer import testing
 
 
@@ -205,17 +204,6 @@ def test_detail_vs_empty_tag(self):
             ['example-tag'],
         )
 
-    def test_detail_vs_empty_allotment(self):
-        mock = self.set_mock('SoftLayer_Virtual_Guest', 'getBandwidthAllotmentDetail')
-        mock.return_value = None
-        result = self.run_command(['vs', 'detail', '100'])
-
-        self.assert_no_fail(result)
-        self.assertEqual(
-            json.loads(result.output)['Bandwidth'][0]['Allotment'],
-            '-',
-        )
-
     def test_detail_vs_dedicated_host_not_found(self):
         ex = SoftLayerAPIError('SoftLayer_Exception', 'Not found')
         mock = self.set_mock('SoftLayer_Virtual_DedicatedHost', 'getObject')
@@ -322,7 +310,7 @@ def test_dns_sync_both(self, confirm_mock):
         createAargs = ({
             'type': 'a',
             'host': 'vs-test1',
-            'domainId': 12345,  # from SoftLayer_Account::getDomains
+            'domainId': 98765,
             'data': '172.16.240.2',
             'ttl': 7200
         },)
@@ -377,7 +365,7 @@ def test_dns_sync_v6(self, confirm_mock):
         createV6args = ({
             'type': 'aaaa',
             'host': 'vs-test1',
-            'domainId': 12345,
+            'domainId': 98765,
             'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004',
             'ttl': 7200
         },)
@@ -410,8 +398,8 @@ def test_dns_sync_v6(self, confirm_mock):
                                            'getResourceRecords')
         getResourceRecords.return_value = [v6Record, v6Record]
         result = self.run_command(['vs', 'dns-sync', '--aaaa-record', '100'])
-        self.assertEqual(result.exit_code, 1)
-        self.assertIsInstance(result.exception, SoftLayerError)
+        self.assertEqual(result.exit_code, 2)
+        self.assertIsInstance(result.exception, exceptions.CLIAbort)
 
     @mock.patch('SoftLayer.CLI.formatting.confirm')
     def test_dns_sync_edit_a(self, confirm_mock):
@@ -441,8 +429,8 @@ def test_dns_sync_edit_a(self, confirm_mock):
              'host': 'vs-test1', 'type': 'a'}
         ]
         result = self.run_command(['vs', 'dns-sync', '-a', '100'])
-        self.assertEqual(result.exit_code, 1)
-        self.assertIsInstance(result.exception, SoftLayerError)
+        self.assertEqual(result.exit_code, 2)
+        self.assertIsInstance(result.exception, exceptions.CLIAbort)
 
     @mock.patch('SoftLayer.CLI.formatting.confirm')
     def test_dns_sync_edit_ptr(self, confirm_mock):
diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py
index 280460c2..06d4d6ce 100644
--- a/tests/managers/block_tests.py
+++ b/tests/managers/block_tests.py
@@ -486,6 +486,43 @@ def test_deauthorize_host_to_volume(self):
             'removeAccessFromHostList',
             identifier=50)
 
+    def test_assign_subnets_to_acl(self):
+        result = self.block.assign_subnets_to_acl(
+            12345,
+            subnet_ids=[12345678])
+
+        self.assertEqual(fixtures.SoftLayer_Network_Storage_Allowed_Host.
+                         assignSubnetsToAcl, result)
+
+        self.assert_called_with(
+            'SoftLayer_Network_Storage_Allowed_Host',
+            'assignSubnetsToAcl',
+            identifier=12345)
+
+    def test_remove_subnets_from_acl(self):
+        result = self.block.remove_subnets_from_acl(
+            12345,
+            subnet_ids=[12345678])
+
+        self.assertEqual(fixtures.SoftLayer_Network_Storage_Allowed_Host.
+                         removeSubnetsFromAcl, result)
+
+        self.assert_called_with(
+            'SoftLayer_Network_Storage_Allowed_Host',
+            'removeSubnetsFromAcl',
+            identifier=12345)
+
+    def test_get_subnets_in_acl(self):
+        result = self.block.get_subnets_in_acl(12345)
+
+        self.assertEqual(fixtures.SoftLayer_Network_Storage_Allowed_Host.
+                         getSubnetsInAcl, result)
+
+        self.assert_called_with(
+            'SoftLayer_Network_Storage_Allowed_Host',
+            'getSubnetsInAcl',
+            identifier=12345)
+
     def test_create_snapshot(self):
         result = self.block.create_snapshot(123, 'hello world')
 
@@ -936,7 +973,3 @@ def test_setCredentialPassword(self):
         result = self.block.set_credential_password(access_id=102, password='AAAaaa')
         self.assertEqual(True, result)
         self.assert_called_with('SoftLayer_Network_Storage_Allowed_Host', 'setCredentialPassword')
-
-    def test_list_block_volume_limit(self):
-        result = self.block.list_block_volume_limit()
-        self.assertEqual(fixtures.SoftLayer_Network_Storage.getVolumeCountLimits, result)
diff --git a/tests/managers/cdn_tests.py b/tests/managers/cdn_tests.py
index 32f9ea9e..31c3c4fa 100644
--- a/tests/managers/cdn_tests.py
+++ b/tests/managers/cdn_tests.py
@@ -49,16 +49,16 @@ def test_add_origin(self):
                                    cache_query="include all")
 
         args = ({
-            'uniqueId': "12345",
-            'origin': '10.10.10.1',
-            'path': '/example/videos',
+                    'uniqueId': "12345",
+                    'origin': '10.10.10.1',
+                    'path': '/example/videos',
                     'originType': 'HOST_SERVER',
                     'header': 'test.example.com',
                     'httpPort': 80,
                     'protocol': 'HTTP',
                     'performanceConfiguration': 'General web delivery',
                     'cacheKeyQueryRule': "include all"
-        },)
+                },)
         self.assert_called_with('SoftLayer_Network_CdnMarketplace_Configuration_Mapping_Path',
                                 'createOriginPath',
                                 args=args)
@@ -69,9 +69,9 @@ def test_add_origin_with_bucket_and_file_extension(self):
                                    protocol='http', optimize_for="web", cache_query="include all")
 
         args = ({
-            'uniqueId': "12345",
-            'origin': '10.10.10.1',
-            'path': '/example/videos',
+                    'uniqueId': "12345",
+                    'origin': '10.10.10.1',
+                    'path': '/example/videos',
                     'originType': 'OBJECT_STORAGE',
                     'header': 'test.example.com',
                     'httpPort': 80,
@@ -80,7 +80,7 @@ def test_add_origin_with_bucket_and_file_extension(self):
                     'fileExtension': 'jpg',
                     'performanceConfiguration': 'General web delivery',
                     'cacheKeyQueryRule': "include all"
-        },)
+                },)
         self.assert_called_with('SoftLayer_Network_CdnMarketplace_Configuration_Mapping_Path',
                                 'createOriginPath',
                                 args=args)
diff --git a/tests/managers/file_tests.py b/tests/managers/file_tests.py
index d6ca66c6..9e69d7fc 100644
--- a/tests/managers/file_tests.py
+++ b/tests/managers/file_tests.py
@@ -668,6 +668,7 @@ def test_order_file_duplicate_performance_no_duplicate_snapshot(self):
     def test_order_file_duplicate_performance(self):
         mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects')
         mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE]
+
         mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME)
         mock_volume['storageType']['keyName'] = 'PERFORMANCE_FILE_STORAGE'
         mock = self.set_mock('SoftLayer_Network_Storage', 'getObject')
@@ -828,7 +829,3 @@ def test_order_file_modified_endurance(self):
                    'volume': {'id': 102},
                    'volumeSize': 1000},)
         )
-
-    def test_list_file_volume_limit(self):
-        result = self.file.list_file_volume_limit()
-        self.assertEqual(fixtures.SoftLayer_Network_Storage.getVolumeCountLimits, result)
diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py
index 045517cd..35d5b819 100644
--- a/tests/managers/ordering_tests.py
+++ b/tests/managers/ordering_tests.py
@@ -722,7 +722,7 @@ def test_get_item_capacity_core(self):
                 "capacity": "1",
                 "id": 10201,
                 "keyName": "GUEST_CORE_1_DEDICATED",
-        }]
+            }]
 
         item_capacity = self.ordering.get_item_capacity(items, ['GUEST_CORE_1_DEDICATED', 'OS_RHEL_7_X_LAMP_64_BIT'])
 
@@ -739,7 +739,7 @@ def test_get_item_capacity_storage(self):
                 "capacity": "1",
                 "id": 10201,
                 "keyName": "READHEAVY_TIER",
-        }]
+            }]
 
         item_capacity = self.ordering.get_item_capacity(items, ['READHEAVY_TIER', 'STORAGE_SPACE_FOR_2_IOPS_PER_GB'])
 
diff --git a/tests/managers/vs/vs_tests.py b/tests/managers/vs/vs_tests.py
index 47fcaf20..14d41966 100644
--- a/tests/managers/vs/vs_tests.py
+++ b/tests/managers/vs/vs_tests.py
@@ -902,26 +902,12 @@ def test_get_bandwidth_allocation(self):
         self.assert_called_with('SoftLayer_Virtual_Guest', 'getBandwidthAllotmentDetail', identifier=1234)
         self.assert_called_with('SoftLayer_Virtual_Guest', 'getBillingCycleBandwidthUsage', identifier=1234)
         self.assertEqual(result['allotment']['amount'], '250')
-        self.assertEqual(result['usage'][0]['amountIn'], '.448')
+        self.assertEqual(result['useage'][0]['amountIn'], '.448')
 
     def test_get_bandwidth_allocation_no_allotment(self):
         mock = self.set_mock('SoftLayer_Virtual_Guest', 'getBandwidthAllotmentDetail')
-        mock.return_value = None
+        mock.return_value = {}
 
         result = self.vs.get_bandwidth_allocation(1234)
 
         self.assertEqual(None, result['allotment'])
-
-    def test_get_bandwidth_allocation_with_allotment(self):
-        mock = self.set_mock('SoftLayer_Virtual_Guest', 'getBandwidthAllotmentDetail')
-        mock.return_value = {
-            "allocationId": 11111,
-            "id": 22222,
-            "allocation": {
-                "amount": "2000"
-            }
-        }
-
-        result = self.vs.get_bandwidth_allocation(1234)
-
-        self.assertEqual(2000, int(result['allotment']['amount']))