Skip to content

Commit 3052686

Browse files
committed
cancel command was refactored, list and cancel guests options were added and some unittests
1 parent 746dd22 commit 3052686

File tree

8 files changed

+346
-47
lines changed

8 files changed

+346
-47
lines changed

SoftLayer/CLI/dedicatedhost/cancel.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,9 @@
1212

1313
@click.command()
1414
@click.argument('identifier')
15-
@click.option('--immediate',
16-
is_flag=True,
17-
default=False,
18-
help="Cancels the dedicated host immediately (instead of on the billing anniversary)")
1915
@environment.pass_env
20-
def cli(env, identifier, immediate):
21-
"""Cancel a dedicated host server."""
16+
def cli(env, identifier):
17+
"""Cancel a dedicated host server immediately"""
2218

2319
mgr = SoftLayer.DedicatedHostManager(env.client)
2420

@@ -27,6 +23,6 @@ def cli(env, identifier, immediate):
2723
if not (env.skip_confirmations or formatting.no_going_back(host_id)):
2824
raise exceptions.CLIAbort('Aborted')
2925

30-
mgr.cancel_host(host_id, immediate)
26+
mgr.cancel_host(host_id)
3127

32-
click.secho('Dedicated Host %s was successfully cancelled' % host_id, fg='green')
28+
click.secho('Dedicated Host %s was cancelled' % host_id, fg='green')
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
"""Cancel a dedicated host."""
2+
# :license: MIT, see LICENSE for more details.
3+
4+
import click
5+
6+
import SoftLayer
7+
from SoftLayer.CLI import environment
8+
from SoftLayer.CLI import exceptions
9+
from SoftLayer.CLI import formatting
10+
from SoftLayer.CLI import helpers
11+
12+
13+
@click.command()
14+
@click.argument('identifier')
15+
@environment.pass_env
16+
def cli(env, identifier):
17+
"""Cancel all virtual guests of the dedicated host immediately"""
18+
19+
dh_mgr = SoftLayer.DedicatedHostManager(env.client)
20+
vs_mgr = SoftLayer.VSManager(env.client)
21+
22+
host_id = helpers.resolve_id(dh_mgr.resolve_ids, identifier, 'dedicated host')
23+
24+
guests = dh_mgr.list_guests(host_id)
25+
26+
if guests:
27+
msg = '%s guest(s) will be cancelled, ' \
28+
'do you want to continue?' % len(guests)
29+
30+
if not (env.skip_confirmations or formatting.confirm(msg)):
31+
raise exceptions.CLIAbort('Aborted')
32+
33+
for guest in guests:
34+
vs_mgr.cancel_instance(guest['id'])
35+
36+
click.secho('All guests into the dedicated host %s were cancelled' % host_id, fg='green')
37+
38+
else:
39+
click.secho('There is not any guest into the dedicated host %s' % host_id, fg='red')
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
"""List dedicated servers."""
2+
# :license: MIT, see LICENSE for more details.
3+
4+
import click
5+
6+
import SoftLayer
7+
from SoftLayer.CLI import columns as column_helper
8+
from SoftLayer.CLI import environment
9+
from SoftLayer.CLI import formatting
10+
from SoftLayer.CLI import helpers
11+
12+
COLUMNS = [
13+
column_helper.Column('guid', ('globalIdentifier',)),
14+
column_helper.Column('cpu', ('maxCpu',)),
15+
column_helper.Column('memory', ('maxMemory',)),
16+
column_helper.Column('datacenter', ('datacenter', 'name')),
17+
column_helper.Column('primary_ip', ('primaryIpAddress',)),
18+
column_helper.Column('backend_ip', ('primaryBackendIpAddress',)),
19+
column_helper.Column(
20+
'created_by',
21+
('billingItem', 'orderItem', 'order', 'userRecord', 'username')),
22+
column_helper.Column('power_state', ('powerState', 'name')),
23+
column_helper.Column(
24+
'tags',
25+
lambda server: formatting.tags(server.get('tagReferences')),
26+
mask="tagReferences.tag.name"),
27+
]
28+
29+
DEFAULT_COLUMNS = [
30+
'id',
31+
'hostname',
32+
'domain',
33+
'primary_ip',
34+
'backend_ip',
35+
'power_state'
36+
]
37+
38+
39+
@click.command()
40+
@click.argument('identifier')
41+
@click.option('--cpu', '-c', help='Number of CPU cores', type=click.INT)
42+
@click.option('--domain', '-D', help='Domain portion of the FQDN')
43+
@click.option('--hostname', '-H', help='Host portion of the FQDN')
44+
@click.option('--memory', '-m', help='Memory in mebibytes', type=click.INT)
45+
@helpers.multi_option('--tag', help='Filter by tags')
46+
@click.option('--sortby',
47+
help='Column to sort by',
48+
default='hostname',
49+
show_default=True)
50+
@click.option('--columns',
51+
callback=column_helper.get_formatter(COLUMNS),
52+
help='Columns to display. [options: %s]'
53+
% ', '.join(column.name for column in COLUMNS),
54+
default=','.join(DEFAULT_COLUMNS),
55+
show_default=True)
56+
@environment.pass_env
57+
def cli(env, identifier, sortby, cpu, domain, hostname, memory, tag, columns):
58+
"""List guests into the dedicated host."""
59+
mgr = SoftLayer.DedicatedHostManager(env.client)
60+
guests = mgr.list_guests(host_id=identifier,
61+
cpus=cpu,
62+
hostname=hostname,
63+
domain= domain,
64+
memory=memory,
65+
tags=tag,
66+
mask=columns.mask())
67+
68+
table = formatting.Table(columns.columns)
69+
table.sortby = sortby
70+
71+
for guest in guests:
72+
table.add_row([value or formatting.blank()
73+
for value in columns.row(guest)])
74+
75+
env.fout(table)

SoftLayer/CLI/routes.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
('dedicatedhost:create-options', 'SoftLayer.CLI.dedicatedhost.create_options:cli'),
3939
('dedicatedhost:detail', 'SoftLayer.CLI.dedicatedhost.detail:cli'),
4040
('dedicatedhost:cancel', 'SoftLayer.CLI.dedicatedhost.cancel:cli'),
41+
('dedicatedhost:cancel-all-guests', 'SoftLayer.CLI.dedicatedhost.cancel_guests:cli'),
42+
('dedicatedhost:list-guests', 'SoftLayer.CLI.dedicatedhost.list_guests:cli'),
4143

4244
('cdn', 'SoftLayer.CLI.cdn'),
4345
('cdn:detail', 'SoftLayer.CLI.cdn.detail:cli'),

SoftLayer/fixtures/SoftLayer_Virtual_DedicatedHost.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,63 @@
7676
'id': 12345,
7777
'createDate': '2017-11-02T11:40:56-07:00'
7878
}
79+
80+
deleteObject = True
81+
82+
getGuests = [{
83+
'id': 100,
84+
'metricTrackingObjectId': 1,
85+
'hostname': 'vs-test1',
86+
'domain': 'test.sftlyr.ws',
87+
'fullyQualifiedDomainName': 'vs-test1.test.sftlyr.ws',
88+
'status': {'keyName': 'ACTIVE', 'name': 'Active'},
89+
'datacenter': {'id': 50, 'name': 'TEST00',
90+
'description': 'Test Data Center'},
91+
'powerState': {'keyName': 'RUNNING', 'name': 'Running'},
92+
'maxCpu': 2,
93+
'maxMemory': 1024,
94+
'primaryIpAddress': '172.16.240.2',
95+
'globalIdentifier': '1a2b3c-1701',
96+
'primaryBackendIpAddress': '10.45.19.37',
97+
'hourlyBillingFlag': False,
98+
99+
'billingItem': {
100+
'id': 6327,
101+
'recurringFee': 1.54,
102+
'orderItem': {
103+
'order': {
104+
'userRecord': {
105+
'username': 'chechu',
106+
}
107+
}
108+
}
109+
},
110+
}, {
111+
'id': 104,
112+
'metricTrackingObjectId': 2,
113+
'hostname': 'vs-test2',
114+
'domain': 'test.sftlyr.ws',
115+
'fullyQualifiedDomainName': 'vs-test2.test.sftlyr.ws',
116+
'status': {'keyName': 'ACTIVE', 'name': 'Active'},
117+
'datacenter': {'id': 50, 'name': 'TEST00',
118+
'description': 'Test Data Center'},
119+
'powerState': {'keyName': 'RUNNING', 'name': 'Running'},
120+
'maxCpu': 4,
121+
'maxMemory': 4096,
122+
'primaryIpAddress': '172.16.240.7',
123+
'globalIdentifier': '05a8ac-6abf0',
124+
'primaryBackendIpAddress': '10.45.19.35',
125+
'hourlyBillingFlag': True,
126+
'billingItem': {
127+
'id': 6327,
128+
'recurringFee': 1.54,
129+
'orderItem': {
130+
'order': {
131+
'userRecord': {
132+
'username': 'chechu',
133+
}
134+
}
135+
}
136+
},
137+
'virtualRack': {'id': 1, 'bandwidthAllotmentTypeId': 2},
138+
}]

SoftLayer/managers/dedicated_host.py

Lines changed: 100 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -37,29 +37,111 @@ def __init__(self, client, ordering_manager=None):
3737
if ordering_manager is None:
3838
self.ordering_manager = ordering.OrderingManager(client)
3939

40-
def cancel_host(self, host_id, immediate=True):
41-
"""Cancels a dedicated host server.
42-
43-
Example::
44-
# Cancels dedicated host id 1234
45-
result = mgr.cancel_host(host_id=1234)
40+
def cancel_host(self, host_id):
41+
"""Cancel a dedicated host immediately, it fails if there are still guests in the host.
4642
4743
:param host_id: The ID of the dedicated host to be cancelled.
48-
:param immediate: If False the dedicated host will be reclaimed in the anniversary date.
49-
Default is True
5044
:return: True on success or an exception
45+
46+
Example::
47+
# Cancels dedicated host id 12345
48+
result = mgr.cancel_host(12345)
49+
5150
"""
52-
mask = 'mask[id,billingItem[id,hourlyFlag]]'
53-
host_billing = self.get_host(host_id, mask=mask)
54-
billing_id = host_billing['billingItem']['id']
55-
is_hourly = host_billing['billingItem']['hourlyFlag']
51+
return self.host.deleteObject(id=host_id)
5652

57-
if is_hourly and immediate is False:
58-
raise SoftLayer.SoftLayerError("Hourly Dedicated Hosts can only be cancelled immediately.")
59-
else:
60-
# Monthly dedicated host can be reclaimed immediately and no reasons are required
61-
result = self.client['Billing_Item'].cancelItem(immediate, False, id=billing_id)
62-
return result
53+
def list_guests(self, host_id, tags=None, cpus=None, memory=None, hostname=None,
54+
domain=None, local_disk=None, nic_speed=None, public_ip=None,
55+
private_ip=None, **kwargs):
56+
"""Retrieve a list of all virtual servers on the dedicated host.
57+
58+
Example::
59+
60+
# Print out a list of hourly instances in the host id 12345.
61+
62+
for vsi in mgr.list_guests(host_id=12345, hourly=True):
63+
print vsi['fullyQualifiedDomainName'], vsi['primaryIpAddress']
64+
65+
# Using a custom object-mask. Will get ONLY what is specified
66+
object_mask = "mask[hostname,monitoringRobot[robotStatus]]"
67+
for vsi in mgr.list_guests(mask=object_mask,hourly=True):
68+
print vsi
69+
70+
:param integer host_id: the identifier of dedicated host
71+
:param boolean hourly: include hourly instances
72+
:param boolean monthly: include monthly instances
73+
:param list tags: filter based on list of tags
74+
:param integer cpus: filter based on number of CPUS
75+
:param integer memory: filter based on amount of memory
76+
:param string hostname: filter based on hostname
77+
:param string domain: filter based on domain
78+
:param string local_disk: filter based on local_disk
79+
:param integer nic_speed: filter based on network speed (in MBPS)
80+
:param string public_ip: filter based on public ip address
81+
:param string private_ip: filter based on private ip address
82+
:param dict \\*\\*kwargs: response-level options (mask, limit, etc.)
83+
:returns: Returns a list of dictionaries representing the matching
84+
virtual servers
85+
"""
86+
if 'mask' not in kwargs:
87+
items = [
88+
'id',
89+
'globalIdentifier',
90+
'hostname',
91+
'domain',
92+
'fullyQualifiedDomainName',
93+
'primaryBackendIpAddress',
94+
'primaryIpAddress',
95+
'lastKnownPowerState.name',
96+
'hourlyBillingFlag',
97+
'powerState',
98+
'maxCpu',
99+
'maxMemory',
100+
'datacenter',
101+
'activeTransaction.transactionStatus[friendlyName,name]',
102+
'status',
103+
]
104+
kwargs['mask'] = "mask[%s]" % ','.join(items)
105+
106+
_filter = utils.NestedDict(kwargs.get('filter') or {})
107+
108+
if tags:
109+
_filter['guests']['tagReferences']['tag']['name'] = {
110+
'operation': 'in',
111+
'options': [{'name': 'data', 'value': tags}],
112+
}
113+
114+
if cpus:
115+
_filter['guests']['maxCpu'] = utils.query_filter(cpus)
116+
117+
if memory:
118+
_filter['guests']['maxMemory'] = utils.query_filter(memory)
119+
120+
if hostname:
121+
_filter['guests']['hostname'] = utils.query_filter(hostname)
122+
123+
if domain:
124+
_filter['guests']['domain'] = utils.query_filter(domain)
125+
126+
if local_disk is not None:
127+
_filter['guests']['localDiskFlag'] = (
128+
utils.query_filter(bool(local_disk)))
129+
130+
if nic_speed:
131+
_filter['guests']['networkComponents']['maxSpeed'] = (
132+
utils.query_filter(nic_speed))
133+
134+
if public_ip:
135+
_filter['guests']['primaryIpAddress'] = (
136+
utils.query_filter(public_ip))
137+
138+
if private_ip:
139+
_filter['guests']['primaryBackendIpAddress'] = (
140+
utils.query_filter(private_ip))
141+
142+
kwargs['filter'] = _filter.to_dict()
143+
kwargs['iter'] = True
144+
return self.host.getGuests(id=host_id, **kwargs)
63145

64146
def list_instances(self, tags=None, cpus=None, memory=None, hostname=None,
65147
disk=None, datacenter=None, **kwargs):

tests/CLI/modules/dedicatedhost_tests.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -342,10 +342,40 @@ def test_create_verify_no_price_or_more_than_one(self):
342342
def test_cancel_host(self, cancel_mock):
343343
result = self.run_command(['--really', 'dedicatedhost', 'cancel', '12345'])
344344
self.assert_no_fail(result)
345-
cancel_mock.assert_called_with(12345, False)
346-
self.assertEqual(str(result.output), 'Dedicated Host 12345 was successfully cancelled\n')
345+
cancel_mock.assert_called_with(12345)
346+
self.assertEqual(str(result.output), 'Dedicated Host 12345 was cancelled\n')
347347

348348
def test_cancel_host_abort(self):
349349
result = self.run_command(['dedicatedhost', 'cancel', '12345'])
350350
self.assertEqual(result.exit_code, 2)
351351
self.assertIsInstance(result.exception, exceptions.CLIAbort)
352+
353+
@mock.patch('SoftLayer.DedicatedHostManager.cancel_host')
354+
def test_cancel_all_guest(self, cancel_mock):
355+
result = self.run_command(['--really', 'dedicatedhost', 'cancel', '12345'])
356+
self.assert_no_fail(result)
357+
cancel_mock.assert_called_with(12345)
358+
self.assertEqual(str(result.output), 'Dedicated Host 12345 was cancelled\n')
359+
360+
def test_cancel_all_guest_empty_list(self):
361+
result = self.run_command(['dedicatedhost', 'cancel', '12345'])
362+
self.assertEqual(result.exit_code, 2)
363+
self.assertIsInstance(result.exception, exceptions.CLIAbort)
364+
365+
def test_list_guests(self):
366+
result = self.run_command(['dh', 'list-guests', '123', '--tag=tag'])
367+
368+
self.assert_no_fail(result)
369+
self.assertEqual(json.loads(result.output),
370+
[{'hostname': 'vs-test1',
371+
'domain': 'test.sftlyr.ws',
372+
'primary_ip': '172.16.240.2',
373+
'id': 100,
374+
'power_state': 'Running',
375+
'backend_ip': '10.45.19.37'},
376+
{'hostname': 'vs-test2',
377+
'domain': 'test.sftlyr.ws',
378+
'primary_ip': '172.16.240.7',
379+
'id': 104,
380+
'power_state': 'Running',
381+
'backend_ip': '10.45.19.35'}])

0 commit comments

Comments
 (0)