Repository: softlayer/softlayer-python
Pull request #1177 (2019-10-08 20:24:26)

Autoscale
### New Commands

```
slcli autoscale -h

Commands:
  detail  Get details of an Autoscale groups.
  edit    Edits an Autoscale group.
  list    List AutoScale Groups.
  logs    Retreive logs for an autoscale group
  scale   Scales an Autoscale group.
  tag     Tags all guests in an autoscale group.
```


Patch:
diff --git a/README.rst b/README.rst
index 53f5c2b5..c543ce5a 100644
--- a/README.rst
+++ b/README.rst
@@ -94,7 +94,7 @@ For the CLI, just use the -vvv option. If you are using the REST endpoint, this
 
 If you are using the library directly in python, you can do something like this.
 
-.. code-bock:: python
+.. code-block:: python
 
   import SoftLayer
   import logging
diff --git a/SoftLayer/CLI/autoscale/__init__.py b/SoftLayer/CLI/autoscale/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/SoftLayer/CLI/autoscale/detail.py b/SoftLayer/CLI/autoscale/detail.py
new file mode 100644
index 00000000..337be43a
--- /dev/null
+++ b/SoftLayer/CLI/autoscale/detail.py
@@ -0,0 +1,92 @@
+"""Get details of an Autoscale groups."""
+# :license: MIT, see LICENSE for more details.
+
+import click
+
+import SoftLayer
+from SoftLayer.CLI import environment
+from SoftLayer.CLI import formatting
+from SoftLayer.managers.autoscale import AutoScaleManager
+from SoftLayer import utils
+
+
+@click.command()
+@click.argument('identifier')
+@environment.pass_env
+def cli(env, identifier):
+    """Get details of an Autoscale groups."""
+
+    autoscale = AutoScaleManager(env.client)
+    group = autoscale.details(identifier)
+
+    # Group Config Table
+    table = formatting.KeyValueTable(["Group", "Value"])
+    table.align['Group'] = 'l'
+    table.align['Value'] = 'l'
+
+    table.add_row(['Id', group.get('id')])
+    # Ideally we would use regionalGroup->preferredDatacenter, but that generates an error.
+    table.add_row(['Datacenter', group['regionalGroup']['locations'][0]['longName']])
+    table.add_row(['Termination', utils.lookup(group, 'terminationPolicy', 'name')])
+    table.add_row(['Minimum Members', group.get('minimumMemberCount')])
+    table.add_row(['Maximum Members', group.get('maximumMemberCount')])
+    table.add_row(['Current Members', group.get('virtualGuestMemberCount')])
+    table.add_row(['Cooldown', "{} seconds".format(group.get('cooldown'))])
+    table.add_row(['Last Action', utils.clean_time(group.get('lastActionDate'))])
+
+    for network in group.get('networkVlans', []):
+        network_type = utils.lookup(network, 'networkVlan', 'networkSpace')
+        router = utils.lookup(network, 'networkVlan', 'primaryRouter', 'hostname')
+        vlan_number = utils.lookup(network, 'networkVlan', 'vlanNumber')
+        vlan_name = "{}.{}".format(router, vlan_number)
+        table.add_row([network_type, vlan_name])
+
+    env.fout(table)
+
+    # Template Config Table
+    config_table = formatting.KeyValueTable(["Template", "Value"])
+    config_table.align['Template'] = 'l'
+    config_table.align['Value'] = 'l'
+
+    template = group.get('virtualGuestMemberTemplate')
+
+    config_table.add_row(['Hostname', template.get('hostname')])
+    config_table.add_row(['Domain', template.get('domain')])
+    config_table.add_row(['Core', template.get('startCpus')])
+    config_table.add_row(['Ram', template.get('maxMemory')])
+    network = template.get('networkComponents')
+    config_table.add_row(['Network', network[0]['maxSpeed'] if network else 'Default'])
+    ssh_keys = template.get('sshKeys', [])
+    ssh_manager = SoftLayer.SshKeyManager(env.client)
+    for key in ssh_keys:
+        # Label isn't included when retrieved from the AutoScale group...
+        ssh_key = ssh_manager.get_key(key.get('id'))
+        config_table.add_row(['SSH Key {}'.format(ssh_key.get('id')), ssh_key.get('label')])
+    disks = template.get('blockDevices', [])
+    disk_type = "Local" if template.get('localDiskFlag') else "SAN"
+
+    for disk in disks:
+        disk_image = disk.get('diskImage')
+        config_table.add_row(['{} Disk {}'.format(disk_type, disk.get('device')), disk_image.get('capacity')])
+    config_table.add_row(['OS', template.get('operatingSystemReferenceCode')])
+    config_table.add_row(['Post Install', template.get('postInstallScriptUri') or 'None'])
+
+    env.fout(config_table)
+
+    # Policy Config Table
+    policy_table = formatting.KeyValueTable(["Policy", "Cooldown"])
+    policies = group.get('policies', [])
+    for policy in policies:
+        policy_table.add_row([policy.get('name'), policy.get('cooldown') or group.get('cooldown')])
+
+    env.fout(policy_table)
+
+    # Active Guests
+    member_table = formatting.Table(['Id', 'Hostname', 'Created'], title="Active Guests")
+    guests = group.get('virtualGuestMembers', [])
+    for guest in guests:
+        real_guest = guest.get('virtualGuest')
+        member_table.add_row([
+            real_guest.get('id'), real_guest.get('hostname'), utils.clean_time(real_guest.get('provisionDate'))
+        ])
+    env.fout(member_table)
diff --git a/SoftLayer/CLI/autoscale/edit.py b/SoftLayer/CLI/autoscale/edit.py
new file mode 100644
index 00000000..c470aebb
--- /dev/null
+++ b/SoftLayer/CLI/autoscale/edit.py
@@ -0,0 +1,57 @@
+"""Edits an Autoscale group."""
+# :license: MIT, see LICENSE for more details.
+
+import click
+
+from SoftLayer.CLI import environment
+from SoftLayer.managers.autoscale import AutoScaleManager
+
+
+@click.command()
+@click.argument('identifier')
+@click.option('--name', help="Scale group's name.")
+@click.option('--min', 'minimum', type=click.INT, help="Set the minimum number of guests")
+@click.option('--max', 'maximum', type=click.INT, help="Set the maximum number of guests")
+@click.option('--userdata', help="User defined metadata string")
+@click.option('--userfile', '-F', help="Read userdata from a file",
+              type=click.Path(exists=True, readable=True, resolve_path=True))
+@click.option('--cpu', type=click.INT, help="Number of CPUs for new guests (existing not effected")
+@click.option('--memory', type=click.INT, help="RAM in MB or GB for new guests (existing not effected")
+@environment.pass_env
+def cli(env, identifier, name, minimum, maximum, userdata, userfile, cpu, memory):
+    """Edits an Autoscale group."""
+
+    template = {}
+    autoscale = AutoScaleManager(env.client)
+    group = autoscale.details(identifier)
+
+    template['name'] = name
+    template['minimumMemberCount'] = minimum
+    template['maximumMemberCount'] = maximum
+    virt_template = {}
+    if userdata:
+        virt_template['userData'] = [{"value": userdata}]
+    elif userfile:
+        with open(userfile, 'r') as userfile_obj:
+            virt_template['userData'] = [{"value": userfile_obj.read()}]
+    virt_template['startCpus'] = cpu
+    virt_template['maxMemory'] = memory
+
+    # Remove any entries that are `None` as the API will complain about them.
+    template['virtualGuestMemberTemplate'] = clean_dict(virt_template)
+    clean_template = clean_dict(template)
+
+    # If there are any values edited in the template, we need to get the OLD template values and replace them.
+    if template['virtualGuestMemberTemplate']:
+        # Update old template with new values
+        for key, value in clean_template['virtualGuestMemberTemplate'].items():
+            group['virtualGuestMemberTemplate'][key] = value
+        clean_template['virtualGuestMemberTemplate'] = group['virtualGuestMemberTemplate']
+
+    autoscale.edit(identifier, clean_template)
+    click.echo("Done")
+
+
+def clean_dict(dictionary):
+    """Removes any `None` entires from the dictionary"""
+    return {k: v for k, v in dictionary.items() if v}
diff --git a/SoftLayer/CLI/autoscale/list.py b/SoftLayer/CLI/autoscale/list.py
new file mode 100644
index 00000000..2d360714
--- /dev/null
+++ b/SoftLayer/CLI/autoscale/list.py
@@ -0,0 +1,29 @@
+"""List Autoscale groups."""
+# :license: MIT, see LICENSE for more details.
+
+import click
+
+from SoftLayer.CLI import environment
+from SoftLayer.CLI import formatting
+from SoftLayer.managers.autoscale import AutoScaleManager
+from SoftLayer import utils
+
+
+@click.command()
+@environment.pass_env
+def cli(env):
+    """List AutoScale Groups."""
+
+    autoscale = AutoScaleManager(env.client)
+    groups = autoscale.list()
+
+    table = formatting.Table(["Id", "Name", "Status", "Min/Max", "Running"])
+    table.align['Name'] = 'l'
+    for group in groups:
+        status = utils.lookup(group, 'status', 'name')
+        min_max = "{}/{}".format(group.get('minimumMemberCount'), group.get('maximumMemberCount'))
+        table.add_row([
+            group.get('id'), group.get('name'), status, min_max, group.get('virtualGuestMemberCount')
+        ])
+
+    env.fout(table)
diff --git a/SoftLayer/CLI/autoscale/logs.py b/SoftLayer/CLI/autoscale/logs.py
new file mode 100644
index 00000000..6c4c401b
--- /dev/null
+++ b/SoftLayer/CLI/autoscale/logs.py
@@ -0,0 +1,37 @@
+"""Retreive logs for an autoscale group"""
+# :license: MIT, see LICENSE for more details.
+
+import click
+
+from SoftLayer.CLI import environment
+from SoftLayer.CLI import formatting
+from SoftLayer.managers.autoscale import AutoScaleManager
+from SoftLayer import utils
+
+
+@click.command()
+@click.argument('identifier')
+@click.option('--date-min', '-d', 'date_min', type=click.DateTime(formats=["%Y-%m-%d", "%m/%d/%Y"]),
+              help='Earliest date to retreive logs for.')
+@environment.pass_env
+def cli(env, identifier, date_min):
+    """Retreive logs for an autoscale group"""
+
+    autoscale = AutoScaleManager(env.client)
+
+    mask = "mask[id,createDate,description]"
+    object_filter = {}
+    if date_min:
+        object_filter['logs'] = {
+            'createDate': {
+                'operation': 'greaterThanDate',
+                'options': [{'name': 'date', 'value': [date_min.strftime("%m/%d/%Y")]}]
+            }
+        }
+
+    logs = autoscale.get_logs(identifier, mask=mask, object_filter=object_filter)
+    table = formatting.Table(['Date', 'Entry'], title="Logs")
+    table.align['Entry'] = 'l'
+    for log in logs:
+        table.add_row([utils.clean_time(log.get('createDate')), log.get('description')])
+    env.fout(table)
diff --git a/SoftLayer/CLI/autoscale/scale.py b/SoftLayer/CLI/autoscale/scale.py
new file mode 100644
index 00000000..69fe1305
--- /dev/null
+++ b/SoftLayer/CLI/autoscale/scale.py
@@ -0,0 +1,55 @@
+"""Scales an Autoscale group"""
+# :license: MIT, see LICENSE for more details.
+
+import click
+
+from SoftLayer.CLI import environment
+from SoftLayer.CLI import formatting
+from SoftLayer.managers.autoscale import AutoScaleManager
+from SoftLayer import utils
+
+
+@click.command()
+@click.argument('identifier')
+@click.option('--up/--down', 'scale_up', is_flag=True, default=True,
+              help="'--up' adds guests, '--down' removes guests.")
+@click.option('--by/--to', 'scale_by', is_flag=True, required=True,
+              help="'--by' will add/remove the specified number of guests."
+              " '--to' will add/remove a number of guests to get the group's guest count to the specified number.")
+@click.option('--amount', required=True, type=click.INT, help="Number of guests for the scale action.")
+@environment.pass_env
+def cli(env, identifier, scale_up, scale_by, amount):
+    """Scales an Autoscale group. Bypasses a scale group's cooldown period."""
+
+    autoscale = AutoScaleManager(env.client)
+
+    # Scale By, and go down, need to use negative amount
+    if not scale_up and scale_by:
+        amount = amount * -1
+
+    result = []
+    if scale_by:
+        click.secho("Scaling group {} by {}".format(identifier, amount), fg='green')
+        result = autoscale.scale(identifier, amount)
+    else:
+        click.secho("Scaling group {} to {}".format(identifier, amount), fg='green')
+        result = autoscale.scale_to(identifier, amount)
+
+    try:
+        # Check if the first guest has a cancellation date, assume we are removing guests if it is.
+        cancel_date = result[0]['virtualGuest']['billingItem']['cancellationDate'] or False
+    except (IndexError, KeyError, TypeError):
+        cancel_date = False
+
+    if cancel_date:
+        member_table = formatting.Table(['Id', 'Hostname', 'Created'], title="Cancelled Guests")
+    else:
+        member_table = formatting.Table(['Id', 'Hostname', 'Created'], title="Added Guests")
+
+    for guest in result:
+        real_guest = guest.get('virtualGuest')
+        member_table.add_row([
+            guest.get('id'), real_guest.get('hostname'), utils.clean_time(real_guest.get('createDate'))
+        ])
+
+    env.fout(member_table)
diff --git a/SoftLayer/CLI/autoscale/tag.py b/SoftLayer/CLI/autoscale/tag.py
new file mode 100644
index 00000000..58e4101b
--- /dev/null
+++ b/SoftLayer/CLI/autoscale/tag.py
@@ -0,0 +1,33 @@
+"""Tags all guests in an autoscale group."""
+# :license: MIT, see LICENSE for more details.
+
+import click
+
+from SoftLayer.CLI import environment
+from SoftLayer.managers.autoscale import AutoScaleManager
+from SoftLayer.managers.vs import VSManager
+
+
+@click.command()
+@click.argument('identifier')
+@click.option('--tags', '-g', help="Tags to set for each guest in this group. Existing tags are overwritten. "
+                                   "An empty string will remove all tags")
+@environment.pass_env
+def cli(env, identifier, tags):
+    """Tags all guests in an autoscale group.
+
+    --tags "Use, quotes, if you, want whitespace"
+
+    --tags Otherwise,Just,commas
+    """
+
+    autoscale = AutoScaleManager(env.client)
+    vsmanager = VSManager(env.client)
+    mask = "mask[id,virtualGuestId,virtualGuest[tagReferences,id,hostname]]"
+    guests = autoscale.get_virtual_guests(identifier, mask=mask)
+    click.echo("New Tags: {}".format(tags))
+    for guest in guests:
+        real_guest = guest.get('virtualGuest')
+        click.echo("Setting tags for {}".format(real_guest.get('hostname')))
+        vsmanager.set_tags(tags, real_guest.get('id'),)
+    click.echo("Done")
diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py
index fb33ee9e..5c37541d 100644
--- a/SoftLayer/CLI/routes.py
+++ b/SoftLayer/CLI/routes.py
@@ -302,6 +302,14 @@
 
     ('report', 'SoftLayer.CLI.report'),
     ('report:bandwidth', 'SoftLayer.CLI.report.bandwidth:cli'),
+
+    ('autoscale', 'SoftLayer.CLI.autoscale'),
+    ('autoscale:list', 'SoftLayer.CLI.autoscale.list:cli'),
+    ('autoscale:detail', 'SoftLayer.CLI.autoscale.detail:cli'),
+    ('autoscale:scale', 'SoftLayer.CLI.autoscale.scale:cli'),
+    ('autoscale:logs', 'SoftLayer.CLI.autoscale.logs:cli'),
+    ('autoscale:tag', 'SoftLayer.CLI.autoscale.tag:cli'),
+    ('autoscale:edit', 'SoftLayer.CLI.autoscale.edit:cli')
 ]
 
 ALL_ALIASES = {
diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py
index a0c865e3..ddb2a435 100644
--- a/SoftLayer/fixtures/SoftLayer_Account.py
+++ b/SoftLayer/fixtures/SoftLayer_Account.py
@@ -769,3 +769,86 @@
         }
     }
 ]
+
+getScaleGroups = [
+    {
+        "accountId": 31111,
+        "cooldown": 1800,
+        "createDate": "2016-10-25T01:48:34+08:00",
+        "id": 12222222,
+        "lastActionDate": "2016-10-25T01:48:34+08:00",
+        "maximumMemberCount": 5,
+        "minimumMemberCount": 0,
+        "name": "tests",
+        "regionalGroupId": 663,
+        "virtualGuestMemberTemplate": {
+            "accountId": 31111,
+            "domain": "sodg.com",
+            "hostname": "testing",
+            "id": None,
+            "maxCpu": None,
+            "maxMemory": 32768,
+            "startCpus": 32,
+            "blockDevices": [
+                {
+                    "device": "0",
+                    "diskImage": {
+                        "capacity": 25,
+                    }
+                }
+            ],
+            "datacenter": {
+                "name": "sao01",
+            },
+            "hourlyBillingFlag": True,
+            "operatingSystemReferenceCode": "CENTOS_LATEST",
+        },
+        "virtualGuestMemberCount": 0,
+        "status": {
+            "id": 1,
+            "keyName": "ACTIVE",
+            "name": "Active"
+        },
+    },
+    {
+        "accountId": 31111,
+        "cooldown": 1800,
+        "createDate": "2018-04-24T04:22:00+08:00",
+        "id": 224533333,
+        "lastActionDate": "2019-01-19T04:53:18+08:00",
+        "maximumMemberCount": 10,
+        "minimumMemberCount": 0,
+        "modifyDate": "2019-01-19T04:53:21+08:00",
+        "name": "test-ajcb",
+        "regionalGroupId": 1025,
+        "virtualGuestMemberTemplate": {
+            "accountId": 31111,
+            "domain": "test.local",
+            "hostname": "autoscale-ajcb01",
+            "id": None,
+            "maxCpu": None,
+            "maxMemory": 1024,
+            "postInstallScriptUri": "http://test.com",
+            "startCpus": 1,
+            "blockDevices": [
+                {
+                    "device": "0",
+                    "diskImage": {
+                        "capacity": 25,
+                    }
+                }
+            ],
+            "datacenter": {
+                "name": "seo01",
+            },
+            "hourlyBillingFlag": True,
+            "operatingSystemReferenceCode": "CENTOS_7_64",
+        },
+        "virtualGuestMemberCount": 0,
+        "status": {
+            "id": 1,
+            "keyName": "ACTIVE",
+            "name": "Active"
+        }
+    }
+]
diff --git a/SoftLayer/fixtures/SoftLayer_Scale_Group.py b/SoftLayer/fixtures/SoftLayer_Scale_Group.py
new file mode 100644
index 00000000..f04d8f56
--- /dev/null
+++ b/SoftLayer/fixtures/SoftLayer_Scale_Group.py
@@ -0,0 +1,457 @@
+getObject = {
+    'accountId': 31111,
+    'balancedTerminationFlag': False,
+    'cooldown': 1800,
+    'createDate': '2018-04-30T15:07:40-04:00',
+    'desiredMemberCount': None,
+    'id': 12222222,
+    'lastActionDate': '2019-10-02T16:26:17-04:00',
+    'loadBalancers': [],
+    'maximumMemberCount': 6,
+    'minimumMemberCount': 2,
+    'modifyDate': '2019-10-03T17:16:47-04:00',
+    'name': 'tests',
+    'networkVlans': [
+        {
+            'networkVlan': {
+                'accountId': 31111,
+                'id': 2222222,
+                'modifyDate': '2019-07-16T13:09:47-04:00',
+                'networkSpace': 'PRIVATE',
+                'primaryRouter': {
+                    'hostname': 'bcr01a.sao01'
+                },
+                'primarySubnetId': 1172222,
+                'vlanNumber': 1111
+            },
+            'networkVlanId': 2281111
+        }
+    ],
+    'policies': [
+        {
+            'actions': [
+                {
+                    'amount': 1,
+                    'createDate': '2019-09-26T18:30:22-04:00',
+                    'deleteFlag': None,
+                    'id': 611111,
+                    'modifyDate': None,
+                    'scalePolicy': None,
+                    'scalePolicyId': 681111,
+                    'scaleType': 'RELATIVE',
+                    'typeId': 1
+                }
+            ],
+            'cooldown': None,
+            'createDate': '2019-09-26T18:30:14-04:00',
+            'id': 680000,
+            'name': 'prime-poly',
+            'scaleActions': [
+                {
+                    'amount': 1,
+                    'createDate': '2019-09-26T18:30:22-04:00',
+                    'deleteFlag': None,
+                    'id': 633333,
+                    'modifyDate': None,
+                    'scalePolicy': None,
+                    'scalePolicyId': 680123,
+                    'scaleType': 'RELATIVE',
+                    'typeId': 1
+                }
+            ],
+            'triggers': [
+                {
+                    'createDate': '2019-09-26T18:30:14-04:00',
+                    'deleteFlag': None,
+                    'id': 557111,
+                    'modifyDate': None,
+                    'scalePolicy': None,
+                    'scalePolicyId': 680000,
+                    'typeId': 3
+                }
+            ]
+        }
+    ],
+    'regionalGroup': {
+        'description': 'sa-bra-south-1',
+        'id': 663,
+        'locationGroupTypeId': 42,
+        'locations': [
+            {
+                'id': 983497,
+                'longName': 'Sao Paulo 1',
+                'name': 'sao01',
+                'statusId': 2
+            }
+        ],
+        'name': 'sa-bra-south-1',
+        'securityLevelId': None
+    },
+    'regionalGroupId': 663,
+    'status': {
+        'id': 1, 'keyName': 'ACTIVE', 'name': 'Active'
+    },
+    'suspendedFlag': False,
+    'terminationPolicy': {
+        'id': 2, 'keyName': 'NEWEST', 'name': 'Newest'
+    },
+    'terminationPolicyId': 2,
+    'virtualGuestAssets': [],
+    'virtualGuestMemberCount': 6,
+    'virtualGuestMemberTemplate': {
+        'accountId': 31111,
+        'blockDevices': [
+            {
+                'bootableFlag': None,
+                'createDate': None,
+                'device': '0',
+                'diskImage': {
+                    'capacity': 25,
+                    'createDate': None,
+                    'id': None,
+                    'modifyDate': None,
+                    'parentId': None,
+                    'storageRepositoryId': None,
+                    'typeId': None},
+                'diskImageId': None,
+                'guestId': None,
+                'hotPlugFlag': None,
+                'id': None,
+                'modifyDate': None,
+                'statusId': None
+            },
+            {
+                'bootableFlag': None,
+                'createDate': None,
+                'device': '2',
+                'diskImage': {
+                    'capacity': 10,
+                    'createDate': None,
+                    'id': None,
+                    'modifyDate': None,
+                    'parentId': None,
+                    'storageRepositoryId': None,
+                    'typeId': None},
+                'diskImageId': None,
+                'guestId': None,
+                'hotPlugFlag': None,
+                'id': None,
+                'modifyDate': None,
+                'statusId': None
+            }
+        ],
+        'createDate': None,
+        'datacenter': {
+            'id': None,
+            'name': 'sao01',
+            'statusId': None
+        },
+        'dedicatedAccountHostOnlyFlag': None,
+        'domain': 'tech-support.com',
+        'hostname': 'testing',
+        'hourlyBillingFlag': True,
+        'id': None,
+        'lastPowerStateId': None,
+        'lastVerifiedDate': None,
+        'localDiskFlag': False,
+        'maxCpu': None,
+        'maxMemory': 1024,
+        'metricPollDate': None,
+        'modifyDate': None,
+        'networkComponents': [
+            {
+                'createDate': None,
+                'guestId': None,
+                'id': None,
+                'maxSpeed': 100,
+                'modifyDate': None,
+                'networkId': None,
+                'port': None,
+                'speed': None
+            }
+        ],
+        'operatingSystemReferenceCode': 'CENTOS_LATEST',
+        'placementGroupId': None,
+        'postInstallScriptUri': 'https://test.com/',
+        'privateNetworkOnlyFlag': False,
+        'provisionDate': None,
+        'sshKeys': [
+            {
+                'createDate': None,
+                'id': 490279,
+                'modifyDate': None
+            }
+        ],
+        'startCpus': 1,
+        'statusId': None,
+        'typeId': None},
+    'virtualGuestMembers': [
+        {
+            'id': 3111111,
+            'virtualGuest': {
+
+                'domain': 'tech-support.com',
+                'hostname': 'test',
+                'provisionDate': '2019-09-27T14:29:53-04:00'
+            }
+        }
+    ]
+}
+
+getVirtualGuestMembers = getObject['virtualGuestMembers']
+
+scale = [
+    {
+        "accountId": 31111,
+        "cooldown": 1800,
+        "createDate": "2016-10-25T01:48:34+08:00",
+        "id": 12222222,
+        "maximumMemberCount": 5,
+        "minimumMemberCount": 0,
+        "name": "tests",
+        "virtualGuest": {
+            "accountId": 31111,
+            "createDate": "2019-10-02T15:24:54-06:00",
+            "billingItem": {
+                "cancellationDate": "2019-10-02T08:34:21-06:00"}
+        },
+        "virtualGuestMemberTemplate": {
+            "accountId": 31111,
+            "domain": "sodg.com",
+            "hostname": "testing",
+            "maxMemory": 32768,
+            "startCpus": 32,
+            "blockDevices": [
+                {
+                    "device": "0",
+                    "diskImage": {
+                        "capacity": 25,
+                    }
+                }
+            ],
+            "datacenter": {
+                "name": "sao01",
+            },
+            "hourlyBillingFlag": True,
+            "operatingSystemReferenceCode": "CENTOS_LATEST",
+            "privateNetworkOnlyFlag": True
+        },
+        "virtualGuestMemberCount": 0,
+        "status": {
+            "id": 1,
+            "keyName": "ACTIVE",
+            "name": "Active"
+        },
+        "virtualGuestAssets": [],
+        "virtualGuestMembers": []
+    },
+    {
+        "accountId": 31111,
+        "cooldown": 1800,
+        "createDate": "2018-04-24T04:22:00+08:00",
+        "id": 224533333,
+        "maximumMemberCount": 10,
+        "minimumMemberCount": 0,
+        "modifyDate": "2019-01-19T04:53:21+08:00",
+        "name": "test-ajcb",
+        "virtualGuest": {
+            "accountId": 31111,
+            "createDate": "2019-10-02T15:24:54-06:00",
+            "billingItem": {
+                "cancellationDate": "2019-10-02T08:34:21-06:00"}
+        },
+        "virtualGuestMemberTemplate": {
+            "accountId": 31111,
+            "domain": "test.local",
+            "hostname": "autoscale-ajcb01",
+            "id": None,
+            "maxCpu": None,
+            "maxMemory": 1024,
+            "postInstallScriptUri": "http://test.com",
+            "startCpus": 1,
+            "blockDevices": [
+                {
+                    "device": "0",
+                    "diskImage": {
+                        "capacity": 25,
+                    }
+                }
+            ],
+            "datacenter": {
+                "name": "seo01",
+            },
+            "hourlyBillingFlag": True,
+            "operatingSystemReferenceCode": "CENTOS_7_64",
+        },
+        "virtualGuestMemberCount": 0,
+        "status": {
+            "id": 1,
+            "keyName": "ACTIVE",
+            "name": "Active"
+        },
+        "virtualGuestAssets": [],
+        "virtualGuestMembers": []
+    }
+]
+
+scaleTo = [
+    {
+        "accountId": 31111,
+        "cooldown": 1800,
+        "createDate": "2016-10-25T01:48:34+08:00",
+        "id": 12222222,
+        "lastActionDate": "2016-10-25T01:48:34+08:00",
+        "maximumMemberCount": 5,
+        "minimumMemberCount": 0,
+        "name": "tests",
+        "regionalGroupId": 663,
+        "virtualGuest": {
+        },
+        "virtualGuestMemberTemplate": {
+            "accountId": 31111,
+            "domain": "sodg.com",
+            "hostname": "testing",
+            "id": None,
+            "maxCpu": None,
+            "maxMemory": 32768,
+            "startCpus": 32,
+            "datacenter": {
+                "name": "sao01",
+            },
+            "hourlyBillingFlag": True,
+            "operatingSystemReferenceCode": "CENTOS_LATEST",
+            "privateNetworkOnlyFlag": True
+        },
+        "virtualGuestMemberCount": 0,
+        "status": {
+            "id": 1,
+            "keyName": "ACTIVE",
+            "name": "Active"
+        },
+        "virtualGuestAssets": [],
+        "virtualGuestMembers": []
+    },
+    {
+        "accountId": 31111,
+        "cooldown": 1800,
+        "createDate": "2018-04-24T04:22:00+08:00",
+        "id": 224533333,
+        "lastActionDate": "2019-01-19T04:53:18+08:00",
+        "maximumMemberCount": 10,
+        "minimumMemberCount": 0,
+        "modifyDate": "2019-01-19T04:53:21+08:00",
+        "name": "test-ajcb",
+        "regionalGroupId": 1025,
+        "virtualGuest": {
+            "accountId": 31111,
+            "createDate": "2019-10-02T15:24:54-06:00",
+            "billingItem": {
+                "cancellationDate": "2019-10-02T08:34:21-06:00"}
+        },
+        "virtualGuestMemberTemplate": {
+            "accountId": 31111,
+            "domain": "test.local",
+            "hostname": "autoscale-ajcb01",
+            "id": None,
+            "maxCpu": None,
+            "maxMemory": 1024,
+            "postInstallScriptUri": "http://test.com",
+            "startCpus": 1,
+            "blockDevices": [
+                {
+                    "device": "0",
+                    "diskImage": {
+                        "capacity": 25,
+                    }
+                }
+            ],
+            "datacenter": {
+                "name": "seo01",
+            },
+            "hourlyBillingFlag": True,
+            "operatingSystemReferenceCode": "CENTOS_7_64",
+        },
+        "virtualGuestMemberCount": 0,
+        "status": {
+            "id": 1,
+            "keyName": "ACTIVE",
+            "name": "Active"
+        },
+        "virtualGuestAssets": [],
+        "virtualGuestMembers": []
+    },
+]
+
+getLogs = [
+    {
+        "createDate": "2019-10-03T04:26:11+08:00",
+        "description": "Scaling group to 6 member(s) by adding 3 member(s) as manually requested",
+        "id": 3821111,
+        "scaleGroupId": 2252222,
+        "scaleGroup": {
+            "accountId": 31111,
+            "cooldown": 1800,
+            "createDate": "2018-05-01T03:07:40+08:00",
+            "id": 2251111,
+            "lastActionDate": "2019-10-03T04:26:17+08:00",
+            "maximumMemberCount": 6,
+            "minimumMemberCount": 2,
+            "modifyDate": "2019-10-03T04:26:21+08:00",
+            "name": "ajcb-autoscale11",
+            "regionalGroupId": 663,
+            "terminationPolicyId": 2,
+            "virtualGuestMemberTemplate": {
+                "accountId": 31111,
+                "domain": "techsupport.com",
+                "hostname": "ajcb-autoscale22",
+                "maxMemory": 1024,
+                "postInstallScriptUri": "https://pastebin.com/raw/62wrEKuW",
+                "startCpus": 1,
+                "blockDevices": [
+                    {
+                        "device": "0",
+                        "diskImage": {
+                            "capacity": 25,
+                        }
+                    },
+                    {
+                        "device": "2",
+                        "diskImage": {
+                            "capacity": 10,
+                        }
+                    }
+                ],
+                "datacenter": {
+                    "name": "sao01",
+                },
+                "networkComponents": [
+                    {
+                        "maxSpeed": 100,
+                    }
+                ],
+                "operatingSystemReferenceCode": "CENTOS_LATEST",
+                "sshKeys": [
+                    {
+                        "id": 49111,
+                    }
+                ]
+            },
+            "logs": [
+                {
+                    "createDate": "2019-09-28T02:31:35+08:00",
+                    "description": "Scaling group to 3 member(s) by removing -1 member(s) as manually requested",
+                    "id": 3821111,
+                    "scaleGroupId": 2251111,
+                },
+                {
+                    "createDate": "2019-09-28T02:26:11+08:00",
+                    "description": "Scaling group to 4 member(s) by adding 2 member(s) as manually requested",
+                    "id": 38211111,
+                    "scaleGroupId": 2251111,
+                },
+            ]
+        }
+    },
+]
+
+editObject = True
diff --git a/SoftLayer/fixtures/SoftLayer_Scale_Policy.py b/SoftLayer/fixtures/SoftLayer_Scale_Policy.py
new file mode 100644
index 00000000..5586fec1
--- /dev/null
+++ b/SoftLayer/fixtures/SoftLayer_Scale_Policy.py
@@ -0,0 +1,58 @@
+getObject = {
+    "cooldown": None,
+    "createDate": "2019-09-27T06:30:14+08:00",
+    "id": 11111,
+    "name": "prime-poly",
+    "scaleGroupId": 2255111,
+    "scaleGroup": {
+        "accountId": 31111,
+        "cooldown": 1800,
+        "createDate": "2018-05-01T03:07:40+08:00",
+        "id": 2252222,
+        "lastActionDate": "2019-09-28T02:31:47+08:00",
+        "maximumMemberCount": 6,
+        "minimumMemberCount": 2,
+        "modifyDate": "2019-09-28T02:31:50+08:00",
+        "name": "ajcb-autoscale11",
+        "regionalGroupId": 663,
+        "terminationPolicyId": 2,
+        "virtualGuestMemberTemplate": {
+            "accountId": 31111,
+            "domain": "techsupport.com",
+            "hostname": "ajcb-autoscale22",
+            "id": None,
+            "maxMemory": 1024,
+            "postInstallScriptUri": "https://pastebin.com/raw/62wrEKuW",
+            "startCpus": 1,
+            "blockDevices": [
+                {
+                    "device": "0",
+                    "diskImage": {
+                        "capacity": 25,
+                    }
+                },
+                {
+                    "device": "2",
+                    "diskImage": {
+                        "capacity": 10,
+                    }
+                }
+            ],
+            "datacenter": {
+                "id": None,
+                "name": "sao01",
+            },
+            "networkComponents": [
+                {
+                    "maxSpeed": 100,
+                }
+            ],
+            "operatingSystemReferenceCode": "CENTOS_LATEST",
+            "sshKeys": [
+                {
+                    "id": 490279,
+                }
+            ]
+        }
+    }
+}
diff --git a/SoftLayer/managers/autoscale.py b/SoftLayer/managers/autoscale.py
new file mode 100644
index 00000000..40d7ebe8
--- /dev/null
+++ b/SoftLayer/managers/autoscale.py
@@ -0,0 +1,116 @@
+"""
+    SoftLayer.autoscale
+    ~~~~~~~~~~~~~~~~~~~
+    Autoscale manager
+
+    :license: MIT, see LICENSE for more details.
+"""
+
+
+class AutoScaleManager(object):
+    """Manager for interacting with Autoscale instances."""
+
+    def __init__(self, client):
+        self.client = client
+
+    def list(self, mask=None):
+        """Calls `SoftLayer_Account::getScaleGroups()`_
+
+        :param mask: optional SoftLayer_Scale_Group objectMask
+
+        .. _SoftLayer_Account::getScaleGroups():
+            https://sldn.softlayer.com/reference/services/SoftLayer_Account/getScaleGroups/
+        """
+        if not mask:
+            mask = "mask[status,virtualGuestMemberCount]"
+
+        return self.client.call('SoftLayer_Account', 'getScaleGroups', mask=mask, iter=True)
+
+    def details(self, identifier, mask=None):
+        """Calls `SoftLayer_Scale_Group::getObject()`_
+
+        :param identifier: SoftLayer_Scale_Group id
+        :param mask: optional SoftLayer_Scale_Group objectMask
+
+        .. _SoftLayer_Scale_Group::getObject():
+            https://sldn.softlayer.com/reference/services/SoftLayer_Scale_Group/getObject/
+        """
+        if not mask:
+            mask = """mask[virtualGuestMembers[id,virtualGuest[id,hostname,domain,provisionDate]], terminationPolicy,
+                   virtualGuestMemberCount, virtualGuestMemberTemplate[sshKeys],
+                   policies[id,name,createDate,cooldown,actions,triggers,scaleActions],
+                   networkVlans[networkVlanId,networkVlan[networkSpace,primaryRouter[hostname]]],
+                   loadBalancers, regionalGroup[locations]]"""
+        return self.client.call('SoftLayer_Scale_Group', 'getObject', id=identifier, mask=mask)
+
+    def get_policy(self, identifier, mask=None):
+        """Calls `SoftLayer_Scale_Policy::getObject()`_
+
+        :param identifier: SoftLayer_Scale_Policy id
+        :param mask: optional SoftLayer_Scale_Policy objectMask
+
+        .. _SoftLayer_Scale_Policy::getObject():
+            https://sldn.softlayer.com/reference/services/SoftLayer_Scale_Policy/getObject/
+        """
+        if not mask:
+            mask = """mask[cooldown, createDate, id, name, actions, triggers[type]
+
+            ]"""
+
+        return self.client.call('SoftLayer_Scale_Policy', 'getObject', id=identifier, mask=mask)
+
+    def scale(self, identifier, amount):
+        """Calls `SoftLayer_Scale_Group::scale()`_
+
+        :param identifier: SoftLayer_Scale_Group Id
+        :param amount: positive or negative number to scale the group by
+
+        .. _SoftLayer_Scale_Group::scale():
+            https://sldn.softlayer.com/reference/services/SoftLayer_Scale_Group/scale/
+        """
+        return self.client.call('SoftLayer_Scale_Group', 'scale', amount, id=identifier)
+
+    def scale_to(self, identifier, amount):
+        """Calls `SoftLayer_Scale_Group::scaleTo()`_
+
+        :param identifier: SoftLayer_Scale_Group Id
+        :param amount: number to scale the group to.
+
+        .. _SoftLayer_Scale_Group::scaleTo():
+            https://sldn.softlayer.com/reference/services/SoftLayer_Scale_Group/scaleTo/
+        """
+        return self.client.call('SoftLayer_Scale_Group', 'scaleTo', amount, id=identifier)
+
+    def get_logs(self, identifier, mask=None, object_filter=None):
+        """Calls `SoftLayer_Scale_Group::getLogs()`_
+
+        :param identifier: SoftLayer_Scale_Group Id
+        :param mask: optional SoftLayer_Scale_Group_Log objectMask
+        :param object_filter: optional SoftLayer_Scale_Group_Log objectFilter
+
+        .. _SoftLayer_Scale_Group::getLogs():
+            https://sldn.softlayer.com/reference/services/SoftLayer_Scale_Group/getLogs/
+        """
+        return self.client.call('SoftLayer_Scale_Group', 'getLogs', id=identifier, mask=mask, filter=object_filter,
+                                iter=True)
+
+    def get_virtual_guests(self, identifier, mask=None):
+        """Calls `SoftLayer_Scale_Group::getVirtualGuestMembers()`_
+
+        :param identifier: SoftLayer_Scale_Group Id
+        :param mask: optional SoftLayer_Scale_Member objectMask
+        .. _SoftLayer_Scale_Group::getVirtualGuestMembers():
+            https://sldn.softlayer.com/reference/services/SoftLayer_Scale_Group/getVirtualGuestMembers/
+        """
+        return self.client.call('SoftLayer_Scale_Group', 'getVirtualGuestMembers', id=identifier, mask=mask, iter=True)
+
+    def edit(self, identifier, template):
+        """Calls `SoftLayer_Scale_Group::editObject()`_
+
+        :param identifier: SoftLayer_Scale_Group id
+        :param template: `SoftLayer_Scale_Group`_
+        .. _SoftLayer_Scale_Group::editObject():
+            https://sldn.softlayer.com/reference/services/SoftLayer_Scale_Group/editObject/
+        .. _SoftLayer_Scale_Group: https://sldn.softlayer.com/reference/datatypes/SoftLayer_Scale_Group/
+        """
+        return self.client.call('SoftLayer_Scale_Group', 'editObject', template, id=identifier)
diff --git a/docs/api/managers/autoscale.rst b/docs/api/managers/autoscale.rst
new file mode 100644
index 00000000..3a617c24
--- /dev/null
+++ b/docs/api/managers/autoscale.rst
@@ -0,0 +1,5 @@
+.. _autoscale:
+
+.. automodule:: SoftLayer.managers.autoscale
+   :members:
+   :inherited-members:
\ No newline at end of file
diff --git a/docs/cli/autoscale.rst b/docs/cli/autoscale.rst
new file mode 100644
index 00000000..a3aa3146
--- /dev/null
+++ b/docs/cli/autoscale.rst
@@ -0,0 +1,38 @@
+.. _cli_autoscale:
+
+Autoscale Commands
+==================
+These commands were added in version `5.8.1 <https://github.com/softlayer/softlayer-python/releases/tag/v5.8.1>`_
+
+For making changes to the triggers or the autoscale group itself, see the `Autoscale Portal`_
+
+- `Autoscale Product <https://www.ibm.com/cloud/auto-scaling>`_
+- `Autoscale Documentation <https://cloud.ibm.com/docs/vsi?topic=virtual-servers-about-auto-scale>`_
+- `Autoscale Portal`_
+
+.. click:: SoftLayer.CLI.autoscale.list:cli
+    :prog: autoscale list
+    :show-nested:
+
+.. click:: SoftLayer.CLI.autoscale.detail:cli
+    :prog: autoscale detail
+    :show-nested:
+
+.. click:: SoftLayer.CLI.autoscale.scale:cli
+    :prog: autoscale scale
+    :show-nested:
+
+.. click:: SoftLayer.CLI.autoscale.logs:cli
+    :prog: autoscale logs
+    :show-nested:
+
+.. click:: SoftLayer.CLI.autoscale.tag:cli
+    :prog: autoscale tag
+    :show-nested:
+
+.. click:: SoftLayer.CLI.autoscale.edit:cli
+    :prog: autoscale edit
+    :show-nested:
+
+
+.. _Autoscale Portal: https://cloud.ibm.com/classic/autoscale
\ No newline at end of file


Test patch:
diff --git a/tests/CLI/modules/autoscale_tests.py b/tests/CLI/modules/autoscale_tests.py
new file mode 100644
index 00000000..9ea7ebe4
--- /dev/null
+++ b/tests/CLI/modules/autoscale_tests.py
@@ -0,0 +1,86 @@
+"""
+    SoftLayer.tests.CLI.modules.autoscale_tests
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    :license: MIT, see LICENSE for more details.
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Tests for the autoscale cli command
+"""
+import mock
+
+from SoftLayer import fixtures
+from SoftLayer import testing
+
+import tempfile
+
+
+class AutoscaleTests(testing.TestCase):
+
+    def test_logs_dates(self):
+        result = self.run_command(['autoscale', 'logs', '123456', '-d', '2019-02-02'])
+        print(result)
+        self.assert_no_fail(result)
+
+    def test_scale_down(self):
+        result = self.run_command(['autoscale', 'scale', '123456', '--down', '--amount', '2'])
+        self.assert_no_fail(result)
+
+    def test_scale_up(self):
+        result = self.run_command(['autoscale', 'scale', '123456', '--up', '--amount', '2'])
+        self.assert_no_fail(result)
+
+    def test_scale_to(self):
+        result = self.run_command(['autoscale', 'scale', '789654123', '--down', '--amount', '2'])
+        self.assert_no_fail(result)
+
+    def test_scale_by_up(self):
+        result = self.run_command(['autoscale', 'scale', '789654123', '--by', '--down', '--amount', '-1'])
+        self.assert_no_fail(result)
+
+    def test_scale_cancel(self):
+        result = self.run_command(['autoscale', 'scale', '789654123', '--by', '--down', '--amount', '1'])
+        self.assert_no_fail(result)
+
+    def test_autoscale_list(self):
+        result = self.run_command(['autoscale', 'list'])
+        self.assert_no_fail(result)
+
+    def test_autoscale_detail(self):
+        result = self.run_command(['autoscale', 'detail', '12222222'])
+        self.assert_no_fail(result)
+
+    def test_autoscale_tag(self):
+        result = self.run_command(['autoscale', 'tag', '12345'])
+        self.assert_no_fail(result)
+
+    @mock.patch('SoftLayer.managers.autoscale.AutoScaleManager.edit')
+    def test_autoscale_edit(self, manager):
+        result = self.run_command(['autoscale', 'edit', '12345', '--name', 'test'])
+        self.assert_no_fail(result)
+        manager.assert_called_with('12345', {'name': 'test'})
+
+    @mock.patch('SoftLayer.managers.autoscale.AutoScaleManager.edit')
+    def test_autoscale_edit_userdata(self, manager):
+        group = fixtures.SoftLayer_Scale_Group.getObject
+        template = {
+            'virtualGuestMemberTemplate': group['virtualGuestMemberTemplate']
+        }
+        template['virtualGuestMemberTemplate']['userData'] = [{'value': 'test'}]
+
+        result = self.run_command(['autoscale', 'edit', '12345', '--userdata', 'test'])
+        self.assert_no_fail(result)
+        manager.assert_called_with('12345', template)
+
+    @mock.patch('SoftLayer.managers.autoscale.AutoScaleManager.edit')
+    def test_autoscale_edit_userfile(self, manager):
+        group = fixtures.SoftLayer_Scale_Group.getObject
+        template = {
+            'virtualGuestMemberTemplate': group['virtualGuestMemberTemplate']
+        }
+        template['virtualGuestMemberTemplate']['userData'] = [{'value': ''}]
+
+        with tempfile.NamedTemporaryFile() as userfile:
+            result = self.run_command(['autoscale', 'edit', '12345', '--userfile', userfile.name])
+        self.assert_no_fail(result)
+        manager.assert_called_with('12345', template)
diff --git a/tests/managers/autoscale_tests.py b/tests/managers/autoscale_tests.py
new file mode 100644
index 00000000..6da50540
--- /dev/null
+++ b/tests/managers/autoscale_tests.py
@@ -0,0 +1,125 @@
+"""
+    SoftLayer.tests.managers.autoscale_tests
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    :license: MIT, see LICENSE for more details.
+"""
+
+from SoftLayer.managers.autoscale import AutoScaleManager
+from SoftLayer import testing
+
+
+class AutoScaleTests(testing.TestCase):
+
+    def set_up(self):
+        self.autoscale = AutoScaleManager(self.client)
+
+    def test_autoscale_list(self):
+        self.autoscale.list()
+
+        self.assert_called_with(
+            'SoftLayer_Account',
+            'getScaleGroups'
+        )
+
+    def test_autoscale_list_with_mask(self):
+        self.autoscale.list(mask='mask[status,virtualGuestMemberCount]')
+
+        self.assert_called_with(
+            'SoftLayer_Account',
+            'getScaleGroups'
+        )
+
+    def test_autoscale_details(self):
+        self.autoscale.details(11111)
+
+        self.assert_called_with(
+            'SoftLayer_Scale_Group',
+            'getObject',
+            identifier=11111
+        )
+
+    def test_autoscale_details_with_mask(self):
+        self.autoscale.details(11111, mask='mask[virtualGuestMembers[id,virtualGuest[hostname,domain,provisionDate]], '
+                                           'terminationPolicy,virtualGuestMemberCount]')
+
+        self.assert_called_with(
+            'SoftLayer_Scale_Group',
+            'getObject',
+            identifier=11111
+        )
+
+    def test_autoscale_policy(self):
+        self.autoscale.get_policy(11111)
+
+        self.assert_called_with(
+            'SoftLayer_Scale_Policy',
+            'getObject',
+            identifier=11111
+        )
+
+    def test_autoscale_policy_with_mask(self):
+        self.autoscale.get_policy(11111, mask='mask[cooldown, createDate, id, name, actions, triggers[type]]')
+
+        self.assert_called_with(
+            'SoftLayer_Scale_Policy',
+            'getObject',
+            identifier=11111
+        )
+
+    def test_autoscale_scale(self):
+        self.autoscale.scale(11111, 3)
+
+        self.assert_called_with(
+            'SoftLayer_Scale_Group',
+            'scale',
+            identifier=11111
+        )
+
+    def test_autoscale_scaleTo(self):
+        self.autoscale.scale_to(11111, 3)
+
+        self.assert_called_with(
+            'SoftLayer_Scale_Group',
+            'scaleTo',
+            identifier=11111
+        )
+
+    def test_autoscale_getLogs(self):
+        self.autoscale.get_logs(11111)
+
+        self.assert_called_with(
+            'SoftLayer_Scale_Group',
+            'getLogs',
+            identifier=11111
+        )
+
+    def test_autoscale_get_virtual_guests(self):
+        self.autoscale.get_virtual_guests(11111)
+
+        self.assert_called_with(
+            'SoftLayer_Scale_Group',
+            'getVirtualGuestMembers',
+            identifier=11111,
+            mask=None
+        )
+
+    def test_autoscale_get_virtual_guests_mask(self):
+        test_mask = "mask[id]"
+        self.autoscale.get_virtual_guests(11111, mask=test_mask)
+
+        self.assert_called_with(
+            'SoftLayer_Scale_Group',
+            'getVirtualGuestMembers',
+            identifier=11111,
+            mask=test_mask
+        )
+
+    def test_edit_object(self):
+        template = {'name': 'test'}
+        self.autoscale.edit(12345, template)
+        self.assert_called_with(
+            'SoftLayer_Scale_Group',
+            'editObject',
+            args=(template,),
+            identifier=12345)