Skip to content

Commit 6b54d42

Browse files
Merge pull request #1623 from caberos/issue1603
slcli autoscale create
2 parents 10c6c31 + d511e92 commit 6b54d42

File tree

10 files changed

+324
-1
lines changed

10 files changed

+324
-1
lines changed

SoftLayer/CLI/autoscale/create.py

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
"""Order/Create a scale group."""
2+
# :license: MIT, see LICENSE for more details.
3+
4+
import click
5+
from SoftLayer import utils
6+
7+
import SoftLayer
8+
from SoftLayer.CLI import environment
9+
from SoftLayer.CLI import exceptions
10+
from SoftLayer.CLI import formatting
11+
from SoftLayer.CLI import helpers
12+
from SoftLayer.managers.autoscale import AutoScaleManager
13+
14+
15+
@click.command()
16+
@click.option('--name', required=True, prompt=True, help="Scale group's name.")
17+
@click.option('--cooldown', required=True, prompt=True, type=click.INT,
18+
help="The number of seconds this group will wait after lastActionDate before performing another action.")
19+
@click.option('--min', 'minimum', required=True, prompt=True, type=click.INT, help="Set the minimum number of guests")
20+
@click.option('--max', 'maximum', required=True, prompt=True, type=click.INT, help="Set the maximum number of guests")
21+
@click.option('--regional', required=True, prompt=True, type=click.INT,
22+
help="The identifier of the regional group this scaling group is assigned to.")
23+
@click.option('--postinstall', '-i', help="Post-install script to download")
24+
@click.option('--os', '-o', required=True, prompt=True, help="OS install code. Tip: you can specify <OS>_LATEST")
25+
@click.option('--datacenter', '-d', required=True, prompt=True, help="Datacenter shortname")
26+
@click.option('--hostname', '-H', required=True, prompt=True, help="Host portion of the FQDN")
27+
@click.option('--domain', '-D', required=True, prompt=True, help="Domain portion of the FQDN")
28+
@click.option('--cpu', required=True, prompt=True, type=click.INT,
29+
help="Number of CPUs for new guests (existing not effected")
30+
@click.option('--memory', required=True, prompt=True, type=click.INT,
31+
help="RAM in MB or GB for new guests (existing not effected")
32+
@click.option('--policy-relative', required=True, prompt=True,
33+
help="The type of scale to perform(ABSOLUTE, PERCENT, RELATIVE).")
34+
@click.option('--termination-policy',
35+
help="The termination policy for the group(CLOSEST_TO_NEXT_CHARGE=1, NEWEST=2, OLDEST=3).")
36+
@click.option('--policy-name', help="Collection of policies for this group. This can be empty.")
37+
@click.option('--policy-amount', help="The number to scale by. This number has different meanings based on type.")
38+
@click.option('--userdata', help="User defined metadata string")
39+
@helpers.multi_option('--key', '-k', help="SSH keys to add to the root user")
40+
@helpers.multi_option('--disk', required=True, prompt=True, help="Disk sizes")
41+
@environment.pass_env
42+
def cli(env, **args):
43+
"""Order/Create a scale group.
44+
45+
E.g.
46+
47+
'slcli autoscale create --name test --cooldown 3600 --min 1 --max 2 -o CENTOS_7_64 --datacenter dal10
48+
--termination-policy 2 -H testvs -D test.com --cpu 2 --memory 1024 --policy-relative absolute
49+
--policy-name policytest --policy-amount 3 --regional 102 --disk 25 --disk 30 --disk 25'
50+
51+
"""
52+
scale = AutoScaleManager(env.client)
53+
network = SoftLayer.NetworkManager(env.client)
54+
55+
datacenter = network.get_datacenter(args.get('datacenter'))
56+
57+
ssh_keys = []
58+
for key in args.get('key'):
59+
resolver = SoftLayer.SshKeyManager(env.client).resolve_ids
60+
key_id = helpers.resolve_id(resolver, key, 'SshKey')
61+
ssh_keys.append(key_id)
62+
scale_actions = [
63+
{
64+
"amount": args['policy_amount'],
65+
"scaleType": args['policy_relative']
66+
}
67+
]
68+
policy_template = {
69+
'name': args['policy_name'],
70+
'policies': scale_actions
71+
72+
}
73+
policies = []
74+
75+
block = []
76+
number_disk = 0
77+
for guest_disk in args['disk']:
78+
if number_disk == 1:
79+
number_disk = 2
80+
disks = {'diskImage': {'capacity': guest_disk}, 'device': number_disk}
81+
block.append(disks)
82+
number_disk += 1
83+
84+
virt_template = {
85+
'localDiskFlag': False,
86+
'domain': args['domain'],
87+
'hostname': args['hostname'],
88+
'sshKeys': ssh_keys,
89+
'postInstallScriptUri': args.get('postinstall'),
90+
'operatingSystemReferenceCode': args['os'],
91+
'maxMemory': args.get('memory'),
92+
'datacenter': {'id': datacenter[0]['id']},
93+
'startCpus': args.get('cpu'),
94+
'blockDevices': block,
95+
'hourlyBillingFlag': True,
96+
'privateNetworkOnlyFlag': False,
97+
'networkComponents': [{'maxSpeed': 100}],
98+
'typeId': 1,
99+
'userData': [{
100+
'value': args.get('userdata')
101+
}],
102+
'networkVlans': [],
103+
104+
}
105+
106+
order = {
107+
'name': args['name'],
108+
'cooldown': args['cooldown'],
109+
'maximumMemberCount': args['maximum'],
110+
'minimumMemberCount': args['minimum'],
111+
'regionalGroupId': args['regional'],
112+
'suspendedFlag': False,
113+
'balancedTerminationFlag': False,
114+
'virtualGuestMemberTemplate': virt_template,
115+
'virtualGuestMemberCount': 0,
116+
'policies': policies.append(utils.clean_dict(policy_template)),
117+
'terminationPolicyId': args['termination_policy']
118+
}
119+
120+
if not (env.skip_confirmations or formatting.confirm(
121+
"This action will incur charges on your account. Continue?")):
122+
raise exceptions.CLIAbort('Aborting scale group order.')
123+
124+
result = scale.create(order)
125+
126+
table = formatting.KeyValueTable(['name', 'value'])
127+
vsi_table = formatting.KeyValueTable(['Id', 'Domain', 'Hostmane'])
128+
table.align['name'] = 'r'
129+
table.align['value'] = 'l'
130+
table.add_row(['Id', result['id']])
131+
table.add_row(['Created', result['createDate']])
132+
table.add_row(['Name', result['name']])
133+
for vsi in result['virtualGuestMembers']:
134+
vsi_table.add_row([vsi['virtualGuest']['id'], vsi['virtualGuest']['domain'], vsi['virtualGuest']['hostname']])
135+
136+
table.add_row(['VirtualGuests', vsi_table])
137+
output = table
138+
139+
env.fout(output)

SoftLayer/CLI/routes.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,7 @@
373373

374374
('autoscale', 'SoftLayer.CLI.autoscale'),
375375
('autoscale:list', 'SoftLayer.CLI.autoscale.list:cli'),
376+
('autoscale:create', 'SoftLayer.CLI.autoscale.create:cli'),
376377
('autoscale:detail', 'SoftLayer.CLI.autoscale.detail:cli'),
377378
('autoscale:scale', 'SoftLayer.CLI.autoscale.scale:cli'),
378379
('autoscale:logs', 'SoftLayer.CLI.autoscale.logs:cli'),

SoftLayer/fixtures/SoftLayer_Scale_Group.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,3 +456,79 @@
456456

457457
editObject = True
458458
forceDeleteObject = True
459+
460+
createObject = {
461+
"accountId": 307608,
462+
"cooldown": 3600,
463+
"createDate": "2022-04-22T13:45:24-06:00",
464+
"id": 5446140,
465+
"lastActionDate": "2022-04-22T13:45:29-06:00",
466+
"maximumMemberCount": 5,
467+
"minimumMemberCount": 1,
468+
"name": "test22042022",
469+
"regionalGroupId": 4568,
470+
"suspendedFlag": False,
471+
"terminationPolicyId": 2,
472+
"virtualGuestMemberTemplate": {
473+
"accountId": 307608,
474+
"domain": "test.com",
475+
"hostname": "testvs",
476+
"maxMemory": 2048,
477+
"startCpus": 2,
478+
"blockDevices": [
479+
{
480+
"diskImage": {
481+
"capacity": 100,
482+
}
483+
}
484+
],
485+
"hourlyBillingFlag": True,
486+
"localDiskFlag": True,
487+
"networkComponents": [
488+
{
489+
"maxSpeed": 100,
490+
}
491+
],
492+
"operatingSystemReferenceCode": "CENTOS_7_64",
493+
"userData": [
494+
{
495+
"value": "the userData"
496+
}
497+
]
498+
},
499+
"virtualGuestMemberCount": 0,
500+
"networkVlans": [],
501+
"policies": [],
502+
"status": {
503+
"id": 1,
504+
"keyName": "ACTIVE",
505+
"name": "Active"
506+
},
507+
"virtualGuestAssets": [],
508+
"virtualGuestMembers": [
509+
{
510+
"createDate": "2022-04-22T13:45:29-06:00",
511+
"id": 123456,
512+
"scaleGroupId": 5446140,
513+
"virtualGuest": {
514+
"createDate": "2022-04-22T13:45:28-06:00",
515+
"deviceStatusId": 3,
516+
"domain": "test.com",
517+
"fullyQualifiedDomainName": "testvs-97e7.test.com",
518+
"hostname": "testvs-97e7",
519+
"id": 129911702,
520+
"maxCpu": 2,
521+
"maxCpuUnits": "CORE",
522+
"maxMemory": 2048,
523+
"startCpus": 2,
524+
"statusId": 1001,
525+
"typeId": 1,
526+
"uuid": "46e55f99-b412-4287-95b5-b8182b2fc924",
527+
"status": {
528+
"keyName": "ACTIVE",
529+
"name": "Active"
530+
}
531+
}
532+
}
533+
]
534+
}

SoftLayer/managers/autoscale.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,17 @@ def edit(self, identifier, template):
117117
"""
118118
return self.client.call('SoftLayer_Scale_Group', 'editObject', template, id=identifier)
119119

120+
def create(self, template):
121+
"""Calls `SoftLayer_Scale_Group::createObject()`_
122+
123+
:param template: `SoftLayer_Scale_Group`_
124+
125+
.. _SoftLayer_Scale_Group::createObject():
126+
https://sldn.softlayer.com/reference/services/SoftLayer_Scale_Group/createObject/
127+
.. _SoftLayer_Scale_Group: https://sldn.softlayer.com/reference/datatypes/SoftLayer_Scale_Group/
128+
"""
129+
return self.client.call('SoftLayer_Scale_Group', 'createObject', template)
130+
120131
def delete(self, identifier):
121132
"""Calls `SoftLayer_Scale_Group::forceDeleteObject()`_
122133

SoftLayer/managers/network.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -839,3 +839,13 @@ def route(self, subnet_id, type_serv, target):
839839
"""
840840
return self.client.call('SoftLayer_Network_Subnet', 'route',
841841
type_serv, target, id=subnet_id, )
842+
843+
def get_datacenter(self, _filter=None, datacenter=None):
844+
"""Calls SoftLayer_Location::getDatacenters()
845+
846+
returns datacenter list.
847+
"""
848+
if datacenter:
849+
_filter = {"name": {"operation": datacenter}}
850+
851+
return self.client.call('SoftLayer_Location', 'getDatacenters', filter=_filter, limit=1)

SoftLayer/utils.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,3 +427,8 @@ def format_comment(comment, max_line_length=60):
427427

428428
comment_length = len(word) + 1
429429
return formatted_comment
430+
431+
432+
def clean_dict(dictionary):
433+
"""Removes any `None` entires from the dictionary"""
434+
return {k: v for k, v in dictionary.items() if v}

docs/cli/autoscale.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,5 +38,9 @@ For making changes to the triggers or the autoscale group itself, see the `Autos
3838
:prog: autoscale delete
3939
:show-nested:
4040

41+
.. click:: SoftLayer.CLI.autoscale.create:cli
42+
:prog: autoscale create
43+
:show-nested:
44+
4145

4246
.. _Autoscale Portal: https://cloud.ibm.com/classic/autoscale

tests/CLI/modules/autoscale_tests.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def test_autoscale_edit_userdata(self, manager):
7777
@mock.patch('SoftLayer.managers.autoscale.AutoScaleManager.edit')
7878
def test_autoscale_edit_userfile(self, manager):
7979
# On windows, python cannot edit a NamedTemporaryFile.
80-
if(sys.platform.startswith("win")):
80+
if (sys.platform.startswith("win")):
8181
self.skipTest("Test doesn't work in Windows")
8282
group = fixtures.SoftLayer_Scale_Group.getObject
8383
template = {
@@ -90,6 +90,28 @@ def test_autoscale_edit_userfile(self, manager):
9090
self.assert_no_fail(result)
9191
manager.assert_called_with('12345', template)
9292

93+
@mock.patch('SoftLayer.CLI.formatting.confirm')
94+
def test_autoscale_create(self, confirm_mock):
95+
confirm_mock.return_value = True
96+
result = self.run_command(['autoscale', 'create',
97+
'--name=test',
98+
'--cooldown=3600',
99+
'--min=1',
100+
'--max=3',
101+
'-o=CENTOS_7_64',
102+
'--datacenter=ams01',
103+
'--termination-policy=2',
104+
'-H=testvs',
105+
'-D=test.com',
106+
'--cpu=2',
107+
'--memory=1024',
108+
'--policy-relative=absolute',
109+
'--policy-amount=3',
110+
'--regional=102',
111+
'--disk=25'])
112+
self.assert_no_fail(result)
113+
self.assertEqual(result.exit_code, 0)
114+
93115
def test_autoscale_delete(self):
94116
result = self.run_command(['autoscale', 'delete', '12345'])
95117
self.assert_no_fail(result)

tests/managers/autoscale_tests.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,57 @@ def test_edit_object(self):
124124
args=(template,),
125125
identifier=12345)
126126

127+
def test_create_object(self):
128+
template = {
129+
'name': 'test',
130+
'cooldown': 3600,
131+
'maximumMemberCount': 5,
132+
'minimumMemberCount': 1,
133+
'regionalGroupId': 4568,
134+
'suspendedFlag': False,
135+
'balancedTerminationFlag': False,
136+
'virtualGuestMemberTemplate': {
137+
'domain': 'test.com',
138+
'hostname': 'testvs',
139+
'operatingSystemReferenceCode': 'CENTOS_7_64',
140+
'maxMemory': 2048,
141+
'datacenter': {
142+
'id': 265592
143+
},
144+
'startCpus': 2,
145+
'blockDevices': [
146+
{
147+
'diskImage': {
148+
'capacity': '100'
149+
},
150+
'device': 0
151+
}
152+
],
153+
'hourlyBillingFlag': True,
154+
'networkComponents': [
155+
{
156+
'maxSpeed': 100
157+
}
158+
],
159+
'localDiskFlag': True,
160+
'typeId': 1,
161+
'userData': [
162+
{
163+
'value': 'the userData'
164+
}
165+
]
166+
},
167+
'virtualGuestMemberCount': 0,
168+
169+
'terminationPolicyId': '2',
170+
}
171+
172+
self.autoscale.create(template)
173+
self.assert_called_with(
174+
'SoftLayer_Scale_Group',
175+
'createObject',
176+
args=(template,))
177+
127178
def test_delete_object(self):
128179
self.autoscale.delete(12345)
129180
self.assert_called_with('SoftLayer_Scale_Group', 'forceDeleteObject')

tests/managers/network_tests.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -633,3 +633,7 @@ def test_get_all_pods(self):
633633
def test_route(self):
634634
self.network.route('SoftLayer_Hardware_Server', 123456, 100)
635635
self.assert_called_with('SoftLayer_Network_Subnet', 'route')
636+
637+
def test_get_all_datacenter(self):
638+
self.network.get_datacenter()
639+
self.assert_called_with('SoftLayer_Location', 'getDatacenters')

0 commit comments

Comments
 (0)