Repository: softlayer/softlayer-python
Pull request #1835 (2023-01-23 21:19:27)

New Command: `slcli virtual host-list`
Issue: https://github.com/softlayer/softlayer-python/issues/1829
Observations: virtual host-list command was added with its unit tests
```
slcli vs host-list --order 85857762 -n dedicatedhost01 -d dal13 --owner sl307608-dcabero
┌────────┬─────────────────┬────────────┬──────────────┬───────────────────────┬──────────────────────────┬────────────────────────┬────────┐
│   Id   │ Name            │ Datacenter │    Router    │ CPU (allocated/total) │ Memory (allocated/total) │ Disk (allocated/total) │ Guests │
├────────┼─────────────────┼────────────┼──────────────┼───────────────────────┼──────────────────────────┼────────────────────────┼────────┤
│ 656700 │ dedicatedhost01 │   dal13    │ bcr01a.dal13 │         0/56          │          0/242           │         0/1200         │   0    │
└────────┴─────────────────┴────────────┴──────────────┴───────────────────────┴──────────────────────────┴────────────────────────┴────────┘
```


Patch:
diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py
index 2f55a0db..bab2a9d4 100644
--- a/SoftLayer/CLI/core.py
+++ b/SoftLayer/CLI/core.py
@@ -52,11 +52,11 @@ def get_latest_version():
     return latest
 
 
-CONTEXT_SETTINGS = dict(
-    help_option_names=['--help', '-h'],
-    auto_envvar_prefix='SLCLI',
-    max_content_width=999
-)
+CONTEXT_SETTINGS = {
+    "help_option_names": ['--help', '-h'],
+    "auto_envvar_prefix": 'SLCLI',
+    "max_content_width": 999
+}
 
 
 def get_version_message(ctx, param, value):
diff --git a/SoftLayer/CLI/dedicatedhost/list.py b/SoftLayer/CLI/dedicatedhost/list.py
index f03930ae..071bdfb9 100644
--- a/SoftLayer/CLI/dedicatedhost/list.py
+++ b/SoftLayer/CLI/dedicatedhost/list.py
@@ -4,67 +4,54 @@
 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 helpers
 
-COLUMNS = [
-    column_helper.Column('datacenter', ('datacenter', 'name')),
-    column_helper.Column(
-        'created_by',
-        ('billingItem', 'orderItem', 'order', 'userRecord', 'username')),
-    column_helper.Column(
-        'tags',
-        lambda server: formatting.tags(server.get('tagReferences')),
-        mask="tagReferences.tag.name"),
-]
-
-DEFAULT_COLUMNS = [
-    'id',
-    'name',
-    'cpuCount',
-    'diskCapacity',
-    'memoryCapacity',
-    'datacenter',
-    'guestCount',
-]
-
 
 @click.command(cls=SoftLayer.CLI.command.SLCommand, )
-@click.option('--cpu', '-c', help='Number of CPU cores', type=click.INT)
 @helpers.multi_option('--tag', help='Filter by tags')
 @click.option('--sortby', help='Column to sort by',
-              default='name',
-              show_default=True)
-@click.option('--columns',
-              callback=column_helper.get_formatter(COLUMNS),
-              help='Columns to display. [options: %s]'
-              % ', '.join(column.name for column in COLUMNS),
-              default=','.join(DEFAULT_COLUMNS),
+              default='Name',
               show_default=True)
-@click.option('--datacenter', '-d', help='Datacenter shortname')
-@click.option('--name', '-H', help='Host portion of the FQDN')
-@click.option('--memory', '-m', help='Memory capacity in mebibytes',
-              type=click.INT)
-@click.option('--disk', '-D', help='Disk capacity')
+@click.option('--datacenter', '-d', help='Filter by datacenter shortname')
+@click.option('--name', '-H', help='Filter by host portion of the FQDN')
+@click.option('--order', help='Filter by ID of the order which purchased this dedicated host', type=click.INT)
+@click.option('--owner', help='Filter by owner of the dedicated host')
 @environment.pass_env
-def cli(env, sortby, cpu, columns, datacenter, name, memory, disk, tag):
+def cli(env, sortby, datacenter, name, tag, order, owner):
     """List dedicated host."""
     mgr = SoftLayer.DedicatedHostManager(env.client)
-    hosts = mgr.list_instances(cpus=cpu,
-                               datacenter=datacenter,
-                               hostname=name,
-                               memory=memory,
-                               disk=disk,
-                               tags=tag,
-                               mask=columns.mask())
-
-    table = formatting.Table(columns.columns)
+    dedicated_hosts = mgr.list_instances(datacenter=datacenter,
+                                         hostname=name,
+                                         tags=tag,
+                                         order=order,
+                                         owner=owner)
+
+    table = formatting.Table(["Id", "Name", "Datacenter", "Router", "CPU (allocated/total)",
+                              "Memory (allocated/total)", "Disk (allocated/total)", "Guests"])
+    table.align['Name'] = 'l'
     table.sortby = sortby
 
-    for host in hosts:
-        table.add_row([value or formatting.blank()
-                       for value in columns.row(host)])
-
-    env.fout(table)
+    if len(dedicated_hosts) != 0:
+        for host in dedicated_hosts:
+            cpu_allocated = host.get('allocationStatus').get('cpuAllocated')
+            cpu_total = host.get('allocationStatus').get('cpuCount')
+            memory_allocated = host.get('allocationStatus').get('memoryAllocated')
+            memory_total = host.get('allocationStatus').get('memoryCapacity')
+            disk_allocated = host.get('allocationStatus').get('diskAllocated')
+            disk_total = host.get('allocationStatus').get('diskCapacity')
+            table.add_row([
+                host.get('id'),
+                host.get('name'),
+                host.get('datacenter').get('name'),
+                host.get('backendRouter').get('hostname'),
+                f"{cpu_allocated}/{cpu_total}",
+                f"{memory_allocated}/{memory_total}",
+                f"{disk_allocated}/{disk_total}",
+                host.get('guestCount')
+            ])
+
+        env.fout(table)
+    else:
+        click.secho("No dedicated hosts are found.")
diff --git a/SoftLayer/CLI/hardware/reload.py b/SoftLayer/CLI/hardware/reload.py
index 54813d96..a91951a2 100644
--- a/SoftLayer/CLI/hardware/reload.py
+++ b/SoftLayer/CLI/hardware/reload.py
@@ -13,7 +13,7 @@
 @click.command(cls=SoftLayer.CLI.command.SLCommand, )
 @click.argument('identifier')
 @click.option('--postinstall', '-i',
-              help=("Post-install script to download (Only HTTPS executes, HTTP leaves file in /root"))
+              help="Post-install script to download (Only HTTPS executes, HTTP leaves file in /root")
 @helpers.multi_option('--key', '-k', help="SSH keys to add to the root user")
 @click.option('--lvm', '-l', is_flag=True, default=False, show_default=True,
               help="A flag indicating that the provision should use LVM for all logical drives.")
diff --git a/SoftLayer/CLI/order/place.py b/SoftLayer/CLI/order/place.py
index 351b9678..8d04255e 100644
--- a/SoftLayer/CLI/order/place.py
+++ b/SoftLayer/CLI/order/place.py
@@ -28,7 +28,7 @@
 @click.option('--billing', type=click.Choice(['hourly', 'monthly']), default='hourly', show_default=True,
               help="Billing rate")
 @click.option('--complex-type',
-              help=("The complex type of the order. Starts with 'SoftLayer_Container_Product_Order'."))
+              help="The complex type of the order. Starts with 'SoftLayer_Container_Product_Order'.")
 @click.option('--extras',
               help="JSON string denoting extra data that needs to be sent with the order")
 @click.argument('order_items', nargs=-1)
diff --git a/SoftLayer/CLI/order/quote.py b/SoftLayer/CLI/order/quote.py
index 429a683e..246bbbd8 100644
--- a/SoftLayer/CLI/order/quote.py
+++ b/SoftLayer/CLI/order/quote.py
@@ -75,7 +75,7 @@ def _parse_create_args(client, args):
 @click.option('--quantity', type=int, default=None,
               help="The quantity of the item being ordered if different from quoted value")
 @click.option('--complex-type', default='SoftLayer_Container_Product_Order_Hardware_Server', show_default=True,
-              help=("The complex type of the order. Starts with 'SoftLayer_Container_Product_Order'."))
+              help="The complex type of the order. Starts with 'SoftLayer_Container_Product_Order'.")
 @click.option('--userdata', '-u', help="User defined metadata string")
 @click.option('--userfile', '-F', type=click.Path(exists=True, readable=True, resolve_path=True),
               help="Read userdata from file")
diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py
index bf41055c..ff754030 100644
--- a/SoftLayer/CLI/routes.py
+++ b/SoftLayer/CLI/routes.py
@@ -57,6 +57,7 @@
     ('virtual:notifications', 'SoftLayer.CLI.virt.notifications:cli'),
     ('virtual:notification-add', 'SoftLayer.CLI.virt.notification_add:cli'),
     ('virtual:notification-delete', 'SoftLayer.CLI.virt.notification_delete:cli'),
+    ('virtual:host-list', 'SoftLayer.CLI.dedicatedhost.list:cli'),
 
     ('dedicatedhost', 'SoftLayer.CLI.dedicatedhost'),
     ('dedicatedhost:list', 'SoftLayer.CLI.dedicatedhost.list:cli'),
diff --git a/SoftLayer/CLI/storage_utils.py b/SoftLayer/CLI/storage_utils.py
index 3d23e094..7dff6d04 100644
--- a/SoftLayer/CLI/storage_utils.py
+++ b/SoftLayer/CLI/storage_utils.py
@@ -65,7 +65,7 @@ def _format_name(obj):
 
         return name
     else:
-        raise Exception('Unknown type %s' % obj['type'])
+        raise ValueError('Unknown type %s' % obj['type'])
 
 
 COLUMNS = [
diff --git a/SoftLayer/CLI/virt/create.py b/SoftLayer/CLI/virt/create.py
index 3a581a5b..b75793c9 100644
--- a/SoftLayer/CLI/virt/create.py
+++ b/SoftLayer/CLI/virt/create.py
@@ -199,9 +199,9 @@ def _parse_create_args(client, args):
 @click.option('--router-private', type=click.INT,
               help="The ID of the private ROUTER on which you want the virtual server placed")
 @helpers.multi_option('--public-security-group', '-S',
-                      help=('Security group ID to associate with the public interface'))
+                      help='Security group ID to associate with the public interface')
 @helpers.multi_option('--private-security-group', '-s',
-                      help=('Security group ID to associate with the private interface'))
+                      help='Security group ID to associate with the private interface')
 @click.option('--wait', type=click.INT,
               help="Wait until VS is finished provisioning for up to X seconds before returning")
 @click.option('--placementgroup',
diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py
index 62ee7f06..6a5ade1f 100644
--- a/SoftLayer/fixtures/SoftLayer_Account.py
+++ b/SoftLayer/fixtures/SoftLayer_Account.py
@@ -704,17 +704,192 @@
     'metricTrackingObjectId': 10,
 }]
 
-getDedicatedHosts = [{
-    'datacenter': {
-        'name': 'dal05'
-    },
-    'memoryCapacity': 242,
-    'name': 'test-dedicated',
-    'diskCapacity': 1200,
-    'guestCount': 1,
-    'cpuCount': 56,
-    'id': 12345
-}]
+getDedicatedHosts = [
+    {
+        "cpuCount": 56,
+        "createDate": "2021-11-18T15:13:57-06:00",
+        "diskCapacity": 1200,
+        "id": 656700,
+        "memoryCapacity": 242,
+        "name": "dedicatedhost01",
+        "guestCount": 0,
+        "allocationStatus": {
+            "cpuAllocated": 0,
+            "cpuAvailable": 56,
+            "cpuCount": 56,
+            "diskAllocated": 0,
+            "diskAvailable": 1200,
+            "diskCapacity": 1200,
+            "guestCount": 0,
+            "memoryAllocated": 0,
+            "memoryAvailable": 242,
+            "memoryCapacity": 242
+        },
+        "backendRouter": {
+            "accountId": 1,
+            "bareMetalInstanceFlag": 0,
+            "domain": "softlayer.com",
+            "fullyQualifiedDomainName": "bcr01a.dal13.softlayer.com",
+            "hardwareStatusId": 5,
+            "hostname": "bcr01a.dal13",
+            "id": 1883692,
+            "notes": "",
+            "provisionDate": None,
+            "serviceProviderId": 1,
+            "serviceProviderResourceId": None
+        },
+        "billingItem": {
+            "allowCancellationFlag": 1,
+            "cancellationDate": None,
+            "categoryCode": "dedicated_virtual_hosts",
+            "createDate": "2021-11-18T15:13:59-06:00",
+            "currentHourlyCharge": "0",
+            "cycleStartDate": "2023-01-04T00:07:36-06:00",
+            "description": "56 Cores X 242 RAM X 1.2 TB",
+            "hostName": "dedicatedhost01",
+            "hourlyRecurringFee": "0",
+            "hoursUsed": "465",
+            "id": 910104016,
+            "laborFee": "0",
+            "laborFeeTaxRate": "0",
+            "lastBillDate": "2023-01-04T00:07:36-06:00",
+            "modifyDate": "2023-01-04T00:07:36-06:00",
+            "nextBillDate": "2023-02-04T00:00:00-06:00",
+            "oneTimeFee": "0",
+            "oneTimeFeeTaxRate": "0",
+            "orderItemId": 874370304,
+            "parentId": None,
+            "recurringFee": "0",
+            "recurringFeeTaxRate": "0",
+            "recurringMonths": 1,
+            "serviceProviderId": 1,
+            "setupFee": "0",
+            "setupFeeTaxRate": "0",
+            "orderItem": {
+                "categoryCode": "dedicated_virtual_hosts",
+                "description": "56 Cores X 242 RAM X 1.2 TB",
+                "hostName": "dedicatedhost01",
+                "hourlyRecurringFee": "0",
+                "id": 874370304,
+                "itemId": 10195,
+                "itemPriceId": "200269",
+                "laborAfterTaxAmount": "0",
+                "laborFee": "0",
+                "laborFeeTaxRate": "0",
+                "laborTaxAmount": "0",
+                "oneTimeAfterTaxAmount": "0",
+                "oneTimeFee": "0",
+                "oneTimeFeeTaxRate": "0",
+                "oneTimeTaxAmount": "0",
+                "parentId": None,
+                "presetId": None,
+                "promoCodeId": None,
+                "quantity": None,
+                "recurringAfterTaxAmount": "0",
+                "recurringFee": "0",
+                "recurringTaxAmount": "0",
+                "setupAfterTaxAmount": "0",
+                "setupFee": "0",
+                "setupFeeDeferralMonths": None,
+                "setupFeeTaxRate": "0",
+                "setupTaxAmount": "0",
+                "order": {
+                    "accountId": 307608,
+                    "createDate": "2021-11-18T15:13:57-06:00",
+                    "id": 85857762,
+                    "impersonatingUserRecordId": None,
+                    "modifyDate": "2021-11-18T15:14:00-06:00",
+                    "orderQuoteId": None,
+                    "orderTypeId": 11,
+                    "presaleEventId": None,
+                    "privateCloudOrderFlag": False,
+                    "status": "APPROVED",
+                    "userRecordId": 7650493,
+                    "userRecord": {
+                        "accountId": 307608,
+                        "address1": "4849 Alpha Rd",
+                        "city": "Dallas",
+                        "companyName": "SoftLayer Internal - Development Community",
+                        "country": "US",
+                        "createDate": "2019-06-10T15:21:33-07:00",
+                        "daylightSavingsTimeFlag": False,
+                        "denyAllResourceAccessOnCreateFlag": False,
+                        "displayName": "danielc",
+                        "email": "daniel.cabero@jalasoft.com",
+                        "firstName": "daniel",
+                        "forumPasswordHash": "unused",
+                        "iamAuthorizationStatus": 1,
+                        "iamId": "IBMid-550003CUGT",
+                        "id": 7650493,
+                        "isMasterUserFlag": False,
+                        "lastName": "cabero",
+                        "localeId": 1,
+                        "managedByFederationFlag": False,
+                        "managedByOpenIdConnectFlag": True,
+                        "minimumPasswordLifeHours": 0,
+                        "modifyDate": "2019-12-13T07:29:07-06:00",
+                        "openIdConnectUserName": "daniel.cabero@jalasoft.com",
+                        "parentId": 167758,
+                        "passwordExpireDate": None,
+                        "permissionCheckLikeMasterUserFlag": 0,
+                        "postalCode": "75244-4608",
+                        "pptpVpnAllowedFlag": False,
+                        "preventPreviousPasswords": 0,
+                        "savedId": "7650493",
+                        "secondaryLoginManagementFlag": True,
+                        "secondaryLoginRequiredFlag": None,
+                        "secondaryPasswordModifyDate": "2019-06-10T15:21:33-07:00",
+                        "secondaryPasswordTimeoutDays": 0,
+                        "sslVpnAllowedFlag": False,
+                        "state": "TX",
+                        "statusDate": None,
+                        "timezoneId": 113,
+                        "userStatusId": 1001,
+                        "username": "sl307608-dcabero",
+                        "vpnManualConfig": False,
+                        "ibmIdLink": {
+                            "createDate": "2019-06-10T15:21:34-07:00",
+                            "defaultFlag": 1,
+                            "destinationUserAlphanumericId": "daniel%2ecabero@jalasoft%2ecom",
+                            "destinationUserId": None,
+                            "iamIdVerificationFlag": 2,
+                            "id": 1051135,
+                            "realm": "IBMid",
+                            "serviceProviderId": 348,
+                            "uniqueIdentifier": "IBMid-550003CUGT",
+                            "userId": 7650493
+                        },
+                        "locale": {
+                            "friendlyName": "English",
+                            "id": 1,
+                            "languageTag": "en-US",
+                            "name": "English"
+                        },
+                        "timezone": {
+                            "id": 113,
+                            "longName": "(GMT-06:00) America/Chicago - CST",
+                            "name": "America/Chicago",
+                            "offset": "-0600",
+                            "shortName": "CST"
+                        },
+                        "userStatus": {
+                            "id": 1001,
+                            "keyName": "ACTIVE",
+                            "name": "Active"
+                        }
+                    }
+                }
+            },
+            "resourceTableId": 656700
+        },
+        "datacenter": {
+            "id": 1854895,
+            "longName": "Dallas 13",
+            "name": "dal13",
+            "statusId": 2
+        }
+    }
+]
 
 getUsers = [
     {'displayName': 'ChristopherG',
diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py
index 1910107d..f7d0f1f1 100644
--- a/SoftLayer/managers/block.py
+++ b/SoftLayer/managers/block.py
@@ -52,8 +52,7 @@ def list_block_volumes(self, datacenter=None, username=None, storage_type=None,
 
         _filter = utils.NestedDict(kwargs.get('filter') or {})
 
-        _filter['iscsiNetworkStorage']['serviceResource']['type']['type'] = \
-            (utils.query_filter('!~ ISCSI'))
+        _filter['iscsiNetworkStorage']['serviceResource']['type']['type'] = utils.query_filter('!~ ISCSI')
 
         _filter['iscsiNetworkStorage']['storageType']['keyName'] = (
             utils.query_filter('*BLOCK_STORAGE*'))
@@ -63,15 +62,14 @@ def list_block_volumes(self, datacenter=None, username=None, storage_type=None,
 
         if datacenter:
             _filter['iscsiNetworkStorage']['serviceResource']['datacenter'][
-                'name'] = (utils.query_filter(datacenter))
+                'name'] = utils.query_filter(datacenter)
 
         if username:
-            _filter['iscsiNetworkStorage']['username'] = \
-                (utils.query_filter(username))
+            _filter['iscsiNetworkStorage']['username'] = utils.query_filter(username)
 
         if order:
             _filter['iscsiNetworkStorage']['billingItem']['orderItem'][
-                'order']['id'] = (utils.query_filter(order))
+                'order']['id'] = utils.query_filter(order)
 
         kwargs['filter'] = _filter.to_dict()
         return self.client.call('Account', 'getIscsiNetworkStorage', iter=True, **kwargs)
diff --git a/SoftLayer/managers/dedicated_host.py b/SoftLayer/managers/dedicated_host.py
index c4396589..f9eda032 100644
--- a/SoftLayer/managers/dedicated_host.py
+++ b/SoftLayer/managers/dedicated_host.py
@@ -171,8 +171,8 @@ def list_guests(self, host_id, tags=None, cpus=None, memory=None, hostname=None,
         kwargs['iter'] = True
         return self.host.getGuests(id=host_id, **kwargs)
 
-    def list_instances(self, tags=None, cpus=None, memory=None, hostname=None,
-                       disk=None, datacenter=None, **kwargs):
+    def list_instances(self, tags=None, hostname=None,
+                       datacenter=None, order=None, owner=None):
         """Retrieve a list of all dedicated hosts on the account
 
         :param list tags: filter based on list of tags
@@ -185,19 +185,7 @@ def list_instances(self, tags=None, cpus=None, memory=None, hostname=None,
         :returns: Returns a list of dictionaries representing the matching dedicated host.
 
         """
-        if 'mask' not in kwargs:
-            items = [
-                'id',
-                'name',
-                'cpuCount',
-                'diskCapacity',
-                'memoryCapacity',
-                'datacenter',
-                'guestCount',
-            ]
-            kwargs['mask'] = "mask[%s]" % ','.join(items)
-
-        _filter = utils.NestedDict(kwargs.get('filter') or {})
+        _filter = utils.NestedDict({})
         if tags:
             _filter['dedicatedHosts']['tagReferences']['tag']['name'] = {
                 'operation': 'in',
@@ -209,23 +197,22 @@ def list_instances(self, tags=None, cpus=None, memory=None, hostname=None,
                 utils.query_filter(hostname)
             )
 
-        if cpus:
-            _filter['dedicatedHosts']['cpuCount'] = utils.query_filter(cpus)
-
-        if disk:
-            _filter['dedicatedHosts']['diskCapacity'] = (
-                utils.query_filter(disk))
-
-        if memory:
-            _filter['dedicatedHosts']['memoryCapacity'] = (
-                utils.query_filter(memory))
-
         if datacenter:
             _filter['dedicatedHosts']['datacenter']['name'] = (
                 utils.query_filter(datacenter))
 
-        kwargs['filter'] = _filter.to_dict()
-        return self.account.getDedicatedHosts(**kwargs)
+        if order:
+            _filter['dedicatedHosts']['billingItem']['orderItem']['order']['id'] = (
+                utils.query_filter(order))
+
+        if owner:
+            _filter['dedicatedHosts']['billingItem']['orderItem']['order']['userRecord']['username'] = (
+                utils.query_filter(owner))
+
+        object_filter = _filter.to_dict()
+        object_mask = "mask[id,name,createDate,cpuCount,diskCapacity,memoryCapacity,guestCount," \
+                      "datacenter,backendRouter,allocationStatus,billingItem[orderItem[order[userRecord]]]]"
+        return self.account.getDedicatedHosts(mask=object_mask, filter=object_filter)
 
     def get_host(self, host_id, **kwargs):
         """Get details about a dedicated host.
@@ -246,7 +233,7 @@ def get_host(self, host_id, **kwargs):
 
         """
         if 'mask' not in kwargs:
-            kwargs['mask'] = ('''
+            kwargs['mask'] = '''
                 id,
                 name,
                 cpuCount,
@@ -285,7 +272,7 @@ def get_host(self, host_id, **kwargs):
                     uuid
                 ],
                 guestCount
-            ''')
+            '''
 
         return self.host.getObject(id=host_id, **kwargs)
 
diff --git a/SoftLayer/managers/file.py b/SoftLayer/managers/file.py
index 734e5408..d7e3871b 100644
--- a/SoftLayer/managers/file.py
+++ b/SoftLayer/managers/file.py
@@ -48,8 +48,7 @@ def list_file_volumes(self, datacenter=None, username=None, storage_type=None, o
 
         _filter = utils.NestedDict(kwargs.get('filter') or {})
 
-        _filter['nasNetworkStorage']['serviceResource']['type']['type'] = \
-            (utils.query_filter('!~ NAS'))
+        _filter['nasNetworkStorage']['serviceResource']['type']['type'] = utils.query_filter('!~ NAS')
 
         _filter['nasNetworkStorage']['storageType']['keyName'] = (
             utils.query_filter('*FILE_STORAGE*'))
@@ -59,15 +58,14 @@ def list_file_volumes(self, datacenter=None, username=None, storage_type=None, o
 
         if datacenter:
             _filter['nasNetworkStorage']['serviceResource']['datacenter'][
-                'name'] = (utils.query_filter(datacenter))
+                'name'] = utils.query_filter(datacenter)
 
         if username:
-            _filter['nasNetworkStorage']['username'] = \
-                (utils.query_filter(username))
+            _filter['nasNetworkStorage']['username'] = utils.query_filter(username)
 
         if order:
             _filter['nasNetworkStorage']['billingItem']['orderItem'][
-                'order']['id'] = (utils.query_filter(order))
+                'order']['id'] = utils.query_filter(order)
 
         kwargs['filter'] = _filter.to_dict()
         return self.client.call('Account', 'getNasNetworkStorage', iter=True, **kwargs)
diff --git a/SoftLayer/managers/firewall.py b/SoftLayer/managers/firewall.py
index d5b6136c..8fe1f8b5 100644
--- a/SoftLayer/managers/firewall.py
+++ b/SoftLayer/managers/firewall.py
@@ -173,12 +173,12 @@ def _get_fwl_port_speed(self, server_id, is_virt=True):
 
         fwl_port_speed = 0
         if is_virt:
-            mask = ('primaryNetworkComponent[maxSpeed]')
+            mask = 'primaryNetworkComponent[maxSpeed]'
             svc = self.client['Virtual_Guest']
             primary = svc.getObject(mask=mask, id=server_id)
             fwl_port_speed = primary['primaryNetworkComponent']['maxSpeed']
         else:
-            mask = ('id,maxSpeed,networkComponentGroup.networkComponents')
+            mask = 'id,maxSpeed,networkComponentGroup.networkComponents'
             svc = self.client['Hardware_Server']
             network_components = svc.getFrontendNetworkComponents(
                 mask=mask, id=server_id)
diff --git a/SoftLayer/managers/storage_utils.py b/SoftLayer/managers/storage_utils.py
index 4ce33e6c..bd6dcd13 100644
--- a/SoftLayer/managers/storage_utils.py
+++ b/SoftLayer/managers/storage_utils.py
@@ -73,7 +73,7 @@ def get_package(manager, category_code):
     _filter = utils.NestedDict({})
     _filter['categories']['categoryCode'] = (
         utils.query_filter(category_code))
-    _filter['statusCode'] = (utils.query_filter('ACTIVE'))
+    _filter['statusCode'] = utils.query_filter('ACTIVE')
 
     packages = manager.client.call(
         'Product_Package', 'getAllObjects',
diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py
index ed3d091d..d0d5a03e 100644
--- a/SoftLayer/managers/vs.py
+++ b/SoftLayer/managers/vs.py
@@ -1362,7 +1362,7 @@ def _get_price_id_for_upgrade(self, package_items, option, value, public=True):
         }
         category_code = option_category[option]
         for item in package_items:
-            is_private = (item.get('units') == 'PRIVATE_CORE')
+            is_private = item.get('units') == 'PRIVATE_CORE'
             for price in item['prices']:
                 if 'locationGroupId' in price and price['locationGroupId']:
                     # Skip location based prices
diff --git a/SoftLayer/transports/xmlrpc.py b/SoftLayer/transports/xmlrpc.py
index 4830e376..99fbe1dd 100644
--- a/SoftLayer/transports/xmlrpc.py
+++ b/SoftLayer/transports/xmlrpc.py
@@ -164,7 +164,7 @@ def print_reproduceable(self, request):
         safe_payload = re.sub(r'<string>[a-z0-9]{64}</string>', r'<string>API_KEY_GOES_HERE</string>', request.payload)
         safe_payload = re.sub(r'(\s+)', r' ', safe_payload)
         safe_payload = safe_payload.encode()
-        substitutions = dict(url=request.url, payload=safe_payload, transport_headers=request.transport_headers,
-                             timeout=self.timeout, verify=request.verify, cert=request.cert,
-                             proxy=_proxies_dict(self.proxy))
+        substitutions = {"url": request.url, "payload": safe_payload, "transport_headers": request.transport_headers,
+                         "timeout": self.timeout, "verify": request.verify, "cert": request.cert,
+                         "proxy": _proxies_dict(self.proxy)}
         return output.substitute(substitutions)
diff --git a/docs/cli/dedicated.rst b/docs/cli/dedicated.rst
index ba11fb53..f6781c39 100644
--- a/docs/cli/dedicated.rst
+++ b/docs/cli/dedicated.rst
@@ -31,3 +31,7 @@ Dedicated Host Commands
 .. click:: SoftLayer.CLI.dedicatedhost.list_guests:cli
     :prog: dedicatedhost list-guests
     :show-nested:
+
+.. click:: SoftLayer.CLI.dedicatedhost.list:cli
+    :prog: virtual host-list
+    :show-nested:
diff --git a/docs/cli/vs.rst b/docs/cli/vs.rst
index d9fb3b2b..00163336 100644
--- a/docs/cli/vs.rst
+++ b/docs/cli/vs.rst
@@ -291,6 +291,11 @@ If no timezone is specified, IMS local time (CST) will be assumed, which might n
    :prog: virtual notification-delete
    :show-nested:
 
+.. click:: SoftLayer.CLI.virt.host_list:cli
+    This command is an alias for `slcli dedicatedhost list`
+    :prog: virtual host-list
+    :show-nested:
+
 Manages the migration of virutal guests. Supports migrating virtual guests on Dedicated Hosts as well.
 
 Reserved Capacity


Test patch:
diff --git a/tests/CLI/modules/dedicatedhost_tests.py b/tests/CLI/modules/dedicatedhost_tests.py
index a015fa70..6c12a17b 100644
--- a/tests/CLI/modules/dedicatedhost_tests.py
+++ b/tests/CLI/modules/dedicatedhost_tests.py
@@ -22,17 +22,24 @@ def test_list_dedicated_hosts(self):
         result = self.run_command(['dedicatedhost', 'list'])
 
         self.assert_no_fail(result)
-        self.assertEqual(json.loads(result.output),
-                         [{
-                             'cpuCount': 56,
-                             'datacenter': 'dal05',
-                             'diskCapacity': 1200,
-                             'guestCount': 1,
-                             'id': 12345,
-                             'memoryCapacity': 242,
-                             'name': 'test-dedicated'
-                         }]
-                         )
+        expected = [{
+            "Id": 656700,
+            "Name": "dedicatedhost01",
+            "Datacenter": "dal13",
+            "Router": "bcr01a.dal13",
+            "CPU (allocated/total)": "0/56",
+            "Memory (allocated/total)": "0/242",
+            "Disk (allocated/total)": "0/1200",
+            "Guests": 0
+        }]
+        self.assertEqual(json.loads(result.output), expected)
+
+    def test_empty_host_list(self):
+        mock = self.set_mock('SoftLayer_Account', 'getDedicatedHosts')
+        mock.return_value = {}
+        result = self.run_command(['vs', 'host-list'])
+        self.assert_no_fail(result)
+        self.assertIn("No dedicated hosts are found.", result.output)
 
     def test_details(self):
         mock = self.set_mock('SoftLayer_Virtual_DedicatedHost', 'getObject')
diff --git a/tests/managers/dedicated_host_tests.py b/tests/managers/dedicated_host_tests.py
index afe3df3a..3c129372 100644
--- a/tests/managers/dedicated_host_tests.py
+++ b/tests/managers/dedicated_host_tests.py
@@ -25,11 +25,10 @@ def test_list_instances(self):
     def test_list_instances_with_filters(self):
         results = self.dedicated_host.list_instances(
             tags=['tag1', 'tag2'],
-            cpus=2,
-            memory=1,
-            hostname='hostname',
-            datacenter='dal05',
-            disk=1
+            hostname='dedicatedhost01',
+            datacenter='dal13',
+            order=85857762,
+            owner='sl307608-dcabero'
         )
         self.assertEqual(results, fixtures.SoftLayer_Account.getDedicatedHosts)