From 746dd2222ddeeb950de9fa15ebb76a35a5ab4212 Mon Sep 17 00:00:00 2001 From: Albert Camacho Date: Tue, 23 Oct 2018 15:18:31 -0700 Subject: [PATCH 1/7] Cancel dedicated hosts option and unittests --- SoftLayer/CLI/dedicatedhost/cancel.py | 32 ++++++++++++++++++++++++ SoftLayer/CLI/routes.py | 1 + SoftLayer/managers/dedicated_host.py | 24 ++++++++++++++++++ tests/CLI/modules/dedicatedhost_tests.py | 12 +++++++++ tests/managers/dedicated_host_tests.py | 22 ++++++++++++++++ 5 files changed, 91 insertions(+) create mode 100644 SoftLayer/CLI/dedicatedhost/cancel.py diff --git a/SoftLayer/CLI/dedicatedhost/cancel.py b/SoftLayer/CLI/dedicatedhost/cancel.py new file mode 100644 index 000000000..54d6e4ac8 --- /dev/null +++ b/SoftLayer/CLI/dedicatedhost/cancel.py @@ -0,0 +1,32 @@ +"""Cancel a dedicated host.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers + + +@click.command() +@click.argument('identifier') +@click.option('--immediate', + is_flag=True, + default=False, + help="Cancels the dedicated host immediately (instead of on the billing anniversary)") +@environment.pass_env +def cli(env, identifier, immediate): + """Cancel a dedicated host server.""" + + mgr = SoftLayer.DedicatedHostManager(env.client) + + host_id = helpers.resolve_id(mgr.resolve_ids, identifier, 'dedicated host') + + if not (env.skip_confirmations or formatting.no_going_back(host_id)): + raise exceptions.CLIAbort('Aborted') + + mgr.cancel_host(host_id, immediate) + + click.secho('Dedicated Host %s was successfully cancelled' % host_id, fg='green') diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index fa46f36ac..00b19cf70 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -37,6 +37,7 @@ ('dedicatedhost:create', 'SoftLayer.CLI.dedicatedhost.create:cli'), ('dedicatedhost:create-options', 'SoftLayer.CLI.dedicatedhost.create_options:cli'), ('dedicatedhost:detail', 'SoftLayer.CLI.dedicatedhost.detail:cli'), + ('dedicatedhost:cancel', 'SoftLayer.CLI.dedicatedhost.cancel:cli'), ('cdn', 'SoftLayer.CLI.cdn'), ('cdn:detail', 'SoftLayer.CLI.cdn.detail:cli'), diff --git a/SoftLayer/managers/dedicated_host.py b/SoftLayer/managers/dedicated_host.py index e041e8d74..3de42da0c 100644 --- a/SoftLayer/managers/dedicated_host.py +++ b/SoftLayer/managers/dedicated_host.py @@ -37,6 +37,30 @@ def __init__(self, client, ordering_manager=None): if ordering_manager is None: self.ordering_manager = ordering.OrderingManager(client) + def cancel_host(self, host_id, immediate=True): + """Cancels a dedicated host server. + + Example:: + # Cancels dedicated host id 1234 + result = mgr.cancel_host(host_id=1234) + + :param host_id: The ID of the dedicated host to be cancelled. + :param immediate: If False the dedicated host will be reclaimed in the anniversary date. + Default is True + :return: True on success or an exception + """ + mask = 'mask[id,billingItem[id,hourlyFlag]]' + host_billing = self.get_host(host_id, mask=mask) + billing_id = host_billing['billingItem']['id'] + is_hourly = host_billing['billingItem']['hourlyFlag'] + + if is_hourly and immediate is False: + raise SoftLayer.SoftLayerError("Hourly Dedicated Hosts can only be cancelled immediately.") + else: + # Monthly dedicated host can be reclaimed immediately and no reasons are required + result = self.client['Billing_Item'].cancelItem(immediate, False, id=billing_id) + return result + def list_instances(self, tags=None, cpus=None, memory=None, hostname=None, disk=None, datacenter=None, **kwargs): """Retrieve a list of all dedicated hosts on the account diff --git a/tests/CLI/modules/dedicatedhost_tests.py b/tests/CLI/modules/dedicatedhost_tests.py index 1769d8cbf..e26139666 100644 --- a/tests/CLI/modules/dedicatedhost_tests.py +++ b/tests/CLI/modules/dedicatedhost_tests.py @@ -337,3 +337,15 @@ def test_create_verify_no_price_or_more_than_one(self): 'quantity': 1},) self.assert_called_with('SoftLayer_Product_Order', 'verifyOrder', args=args) + + @mock.patch('SoftLayer.DedicatedHostManager.cancel_host') + def test_cancel_host(self, cancel_mock): + result = self.run_command(['--really', 'dedicatedhost', 'cancel', '12345']) + self.assert_no_fail(result) + cancel_mock.assert_called_with(12345, False) + self.assertEqual(str(result.output), 'Dedicated Host 12345 was successfully cancelled\n') + + def test_cancel_host_abort(self): + result = self.run_command(['dedicatedhost', 'cancel', '12345']) + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.CLIAbort) diff --git a/tests/managers/dedicated_host_tests.py b/tests/managers/dedicated_host_tests.py index bdfa6d3f6..b28a7431d 100644 --- a/tests/managers/dedicated_host_tests.py +++ b/tests/managers/dedicated_host_tests.py @@ -540,6 +540,28 @@ def test_get_default_router_no_router_found(self): self.assertRaises(exceptions.SoftLayerError, self.dedicated_host._get_default_router, routers, 'notFound') + def test_cancel_host(self): + self.dedicated_host.host = mock.Mock() + self.dedicated_host.host.getObject.return_value = {'id': 987, 'billingItem': { + 'id': 1234, 'hourlyFlag': False}} + # Immediate cancellation + result = self.dedicated_host.cancel_host(987) + self.assertEqual(True, result) + + # Cancellation on anniversary + result = self.dedicated_host.cancel_host(987, immediate=False) + self.assertEqual(True, result) + + def test_cancel_host_billing_hourly_no_immediate(self): + self.dedicated_host.host = mock.Mock() + self.dedicated_host.host.getObject.return_value = {'id': 987, 'billingItem': { + 'id': 1234, 'hourlyFlag': True}} + + ex = self.assertRaises(SoftLayer.SoftLayerError, + self.dedicated_host.cancel_host, + 987, immediate=False) + self.assertEqual("Hourly Dedicated Hosts can only be cancelled immediately.", str(ex)) + def _get_routers_sample(self): routers = [ { From 3052686345cc884811166ded06269e5cbe98c934 Mon Sep 17 00:00:00 2001 From: acamacho Date: Mon, 12 Nov 2018 19:01:16 -0400 Subject: [PATCH 2/7] cancel command was refactored, list and cancel guests options were added and some unittests --- SoftLayer/CLI/dedicatedhost/cancel.py | 12 +- SoftLayer/CLI/dedicatedhost/cancel_guests.py | 39 ++++++ SoftLayer/CLI/dedicatedhost/list_guests.py | 75 +++++++++++ SoftLayer/CLI/routes.py | 2 + .../SoftLayer_Virtual_DedicatedHost.py | 60 +++++++++ SoftLayer/managers/dedicated_host.py | 118 +++++++++++++++--- tests/CLI/modules/dedicatedhost_tests.py | 34 ++++- tests/managers/dedicated_host_tests.py | 53 +++++--- 8 files changed, 346 insertions(+), 47 deletions(-) create mode 100644 SoftLayer/CLI/dedicatedhost/cancel_guests.py create mode 100644 SoftLayer/CLI/dedicatedhost/list_guests.py diff --git a/SoftLayer/CLI/dedicatedhost/cancel.py b/SoftLayer/CLI/dedicatedhost/cancel.py index 54d6e4ac8..58ed85d30 100644 --- a/SoftLayer/CLI/dedicatedhost/cancel.py +++ b/SoftLayer/CLI/dedicatedhost/cancel.py @@ -12,13 +12,9 @@ @click.command() @click.argument('identifier') -@click.option('--immediate', - is_flag=True, - default=False, - help="Cancels the dedicated host immediately (instead of on the billing anniversary)") @environment.pass_env -def cli(env, identifier, immediate): - """Cancel a dedicated host server.""" +def cli(env, identifier): + """Cancel a dedicated host server immediately""" mgr = SoftLayer.DedicatedHostManager(env.client) @@ -27,6 +23,6 @@ def cli(env, identifier, immediate): if not (env.skip_confirmations or formatting.no_going_back(host_id)): raise exceptions.CLIAbort('Aborted') - mgr.cancel_host(host_id, immediate) + mgr.cancel_host(host_id) - click.secho('Dedicated Host %s was successfully cancelled' % host_id, fg='green') + click.secho('Dedicated Host %s was cancelled' % host_id, fg='green') diff --git a/SoftLayer/CLI/dedicatedhost/cancel_guests.py b/SoftLayer/CLI/dedicatedhost/cancel_guests.py new file mode 100644 index 000000000..106266203 --- /dev/null +++ b/SoftLayer/CLI/dedicatedhost/cancel_guests.py @@ -0,0 +1,39 @@ +"""Cancel a dedicated host.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Cancel all virtual guests of the dedicated host immediately""" + + dh_mgr = SoftLayer.DedicatedHostManager(env.client) + vs_mgr = SoftLayer.VSManager(env.client) + + host_id = helpers.resolve_id(dh_mgr.resolve_ids, identifier, 'dedicated host') + + guests = dh_mgr.list_guests(host_id) + + if guests: + msg = '%s guest(s) will be cancelled, ' \ + 'do you want to continue?' % len(guests) + + if not (env.skip_confirmations or formatting.confirm(msg)): + raise exceptions.CLIAbort('Aborted') + + for guest in guests: + vs_mgr.cancel_instance(guest['id']) + + click.secho('All guests into the dedicated host %s were cancelled' % host_id, fg='green') + + else: + click.secho('There is not any guest into the dedicated host %s' % host_id, fg='red') diff --git a/SoftLayer/CLI/dedicatedhost/list_guests.py b/SoftLayer/CLI/dedicatedhost/list_guests.py new file mode 100644 index 000000000..b84cd4896 --- /dev/null +++ b/SoftLayer/CLI/dedicatedhost/list_guests.py @@ -0,0 +1,75 @@ +"""List dedicated servers.""" +# :license: MIT, see LICENSE for more details. + +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('guid', ('globalIdentifier',)), + column_helper.Column('cpu', ('maxCpu',)), + column_helper.Column('memory', ('maxMemory',)), + column_helper.Column('datacenter', ('datacenter', 'name')), + column_helper.Column('primary_ip', ('primaryIpAddress',)), + column_helper.Column('backend_ip', ('primaryBackendIpAddress',)), + column_helper.Column( + 'created_by', + ('billingItem', 'orderItem', 'order', 'userRecord', 'username')), + column_helper.Column('power_state', ('powerState', 'name')), + column_helper.Column( + 'tags', + lambda server: formatting.tags(server.get('tagReferences')), + mask="tagReferences.tag.name"), +] + +DEFAULT_COLUMNS = [ + 'id', + 'hostname', + 'domain', + 'primary_ip', + 'backend_ip', + 'power_state' +] + + +@click.command() +@click.argument('identifier') +@click.option('--cpu', '-c', help='Number of CPU cores', type=click.INT) +@click.option('--domain', '-D', help='Domain portion of the FQDN') +@click.option('--hostname', '-H', help='Host portion of the FQDN') +@click.option('--memory', '-m', help='Memory in mebibytes', type=click.INT) +@helpers.multi_option('--tag', help='Filter by tags') +@click.option('--sortby', + help='Column to sort by', + default='hostname', + 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), + show_default=True) +@environment.pass_env +def cli(env, identifier, sortby, cpu, domain, hostname, memory, tag, columns): + """List guests into the dedicated host.""" + mgr = SoftLayer.DedicatedHostManager(env.client) + guests = mgr.list_guests(host_id=identifier, + cpus=cpu, + hostname=hostname, + domain= domain, + memory=memory, + tags=tag, + mask=columns.mask()) + + table = formatting.Table(columns.columns) + table.sortby = sortby + + for guest in guests: + table.add_row([value or formatting.blank() + for value in columns.row(guest)]) + + env.fout(table) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 00b19cf70..e89c98e90 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -38,6 +38,8 @@ ('dedicatedhost:create-options', 'SoftLayer.CLI.dedicatedhost.create_options:cli'), ('dedicatedhost:detail', 'SoftLayer.CLI.dedicatedhost.detail:cli'), ('dedicatedhost:cancel', 'SoftLayer.CLI.dedicatedhost.cancel:cli'), + ('dedicatedhost:cancel-all-guests', 'SoftLayer.CLI.dedicatedhost.cancel_guests:cli'), + ('dedicatedhost:list-guests', 'SoftLayer.CLI.dedicatedhost.list_guests:cli'), ('cdn', 'SoftLayer.CLI.cdn'), ('cdn:detail', 'SoftLayer.CLI.cdn.detail:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_DedicatedHost.py b/SoftLayer/fixtures/SoftLayer_Virtual_DedicatedHost.py index 94c8e5cc4..e7be9e1db 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_DedicatedHost.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_DedicatedHost.py @@ -76,3 +76,63 @@ 'id': 12345, 'createDate': '2017-11-02T11:40:56-07:00' } + +deleteObject = True + +getGuests = [{ + 'id': 100, + 'metricTrackingObjectId': 1, + 'hostname': 'vs-test1', + 'domain': 'test.sftlyr.ws', + 'fullyQualifiedDomainName': 'vs-test1.test.sftlyr.ws', + 'status': {'keyName': 'ACTIVE', 'name': 'Active'}, + 'datacenter': {'id': 50, 'name': 'TEST00', + 'description': 'Test Data Center'}, + 'powerState': {'keyName': 'RUNNING', 'name': 'Running'}, + 'maxCpu': 2, + 'maxMemory': 1024, + 'primaryIpAddress': '172.16.240.2', + 'globalIdentifier': '1a2b3c-1701', + 'primaryBackendIpAddress': '10.45.19.37', + 'hourlyBillingFlag': False, + + 'billingItem': { + 'id': 6327, + 'recurringFee': 1.54, + 'orderItem': { + 'order': { + 'userRecord': { + 'username': 'chechu', + } + } + } + }, +}, { + 'id': 104, + 'metricTrackingObjectId': 2, + 'hostname': 'vs-test2', + 'domain': 'test.sftlyr.ws', + 'fullyQualifiedDomainName': 'vs-test2.test.sftlyr.ws', + 'status': {'keyName': 'ACTIVE', 'name': 'Active'}, + 'datacenter': {'id': 50, 'name': 'TEST00', + 'description': 'Test Data Center'}, + 'powerState': {'keyName': 'RUNNING', 'name': 'Running'}, + 'maxCpu': 4, + 'maxMemory': 4096, + 'primaryIpAddress': '172.16.240.7', + 'globalIdentifier': '05a8ac-6abf0', + 'primaryBackendIpAddress': '10.45.19.35', + 'hourlyBillingFlag': True, + 'billingItem': { + 'id': 6327, + 'recurringFee': 1.54, + 'orderItem': { + 'order': { + 'userRecord': { + 'username': 'chechu', + } + } + } + }, + 'virtualRack': {'id': 1, 'bandwidthAllotmentTypeId': 2}, +}] diff --git a/SoftLayer/managers/dedicated_host.py b/SoftLayer/managers/dedicated_host.py index 3de42da0c..89195c171 100644 --- a/SoftLayer/managers/dedicated_host.py +++ b/SoftLayer/managers/dedicated_host.py @@ -37,29 +37,111 @@ def __init__(self, client, ordering_manager=None): if ordering_manager is None: self.ordering_manager = ordering.OrderingManager(client) - def cancel_host(self, host_id, immediate=True): - """Cancels a dedicated host server. - - Example:: - # Cancels dedicated host id 1234 - result = mgr.cancel_host(host_id=1234) + def cancel_host(self, host_id): + """Cancel a dedicated host immediately, it fails if there are still guests in the host. :param host_id: The ID of the dedicated host to be cancelled. - :param immediate: If False the dedicated host will be reclaimed in the anniversary date. - Default is True :return: True on success or an exception + + Example:: + # Cancels dedicated host id 12345 + result = mgr.cancel_host(12345) + """ - mask = 'mask[id,billingItem[id,hourlyFlag]]' - host_billing = self.get_host(host_id, mask=mask) - billing_id = host_billing['billingItem']['id'] - is_hourly = host_billing['billingItem']['hourlyFlag'] + return self.host.deleteObject(id=host_id) - if is_hourly and immediate is False: - raise SoftLayer.SoftLayerError("Hourly Dedicated Hosts can only be cancelled immediately.") - else: - # Monthly dedicated host can be reclaimed immediately and no reasons are required - result = self.client['Billing_Item'].cancelItem(immediate, False, id=billing_id) - return result + def list_guests(self, host_id, tags=None, cpus=None, memory=None, hostname=None, + domain=None, local_disk=None, nic_speed=None, public_ip=None, + private_ip=None, **kwargs): + """Retrieve a list of all virtual servers on the dedicated host. + + Example:: + + # Print out a list of hourly instances in the host id 12345. + + for vsi in mgr.list_guests(host_id=12345, hourly=True): + print vsi['fullyQualifiedDomainName'], vsi['primaryIpAddress'] + + # Using a custom object-mask. Will get ONLY what is specified + object_mask = "mask[hostname,monitoringRobot[robotStatus]]" + for vsi in mgr.list_guests(mask=object_mask,hourly=True): + print vsi + + :param integer host_id: the identifier of dedicated host + :param boolean hourly: include hourly instances + :param boolean monthly: include monthly instances + :param list tags: filter based on list of tags + :param integer cpus: filter based on number of CPUS + :param integer memory: filter based on amount of memory + :param string hostname: filter based on hostname + :param string domain: filter based on domain + :param string local_disk: filter based on local_disk + :param integer nic_speed: filter based on network speed (in MBPS) + :param string public_ip: filter based on public ip address + :param string private_ip: filter based on private ip address + :param dict \\*\\*kwargs: response-level options (mask, limit, etc.) + :returns: Returns a list of dictionaries representing the matching + virtual servers + """ + if 'mask' not in kwargs: + items = [ + 'id', + 'globalIdentifier', + 'hostname', + 'domain', + 'fullyQualifiedDomainName', + 'primaryBackendIpAddress', + 'primaryIpAddress', + 'lastKnownPowerState.name', + 'hourlyBillingFlag', + 'powerState', + 'maxCpu', + 'maxMemory', + 'datacenter', + 'activeTransaction.transactionStatus[friendlyName,name]', + 'status', + ] + kwargs['mask'] = "mask[%s]" % ','.join(items) + + _filter = utils.NestedDict(kwargs.get('filter') or {}) + + if tags: + _filter['guests']['tagReferences']['tag']['name'] = { + 'operation': 'in', + 'options': [{'name': 'data', 'value': tags}], + } + + if cpus: + _filter['guests']['maxCpu'] = utils.query_filter(cpus) + + if memory: + _filter['guests']['maxMemory'] = utils.query_filter(memory) + + if hostname: + _filter['guests']['hostname'] = utils.query_filter(hostname) + + if domain: + _filter['guests']['domain'] = utils.query_filter(domain) + + if local_disk is not None: + _filter['guests']['localDiskFlag'] = ( + utils.query_filter(bool(local_disk))) + + if nic_speed: + _filter['guests']['networkComponents']['maxSpeed'] = ( + utils.query_filter(nic_speed)) + + if public_ip: + _filter['guests']['primaryIpAddress'] = ( + utils.query_filter(public_ip)) + + if private_ip: + _filter['guests']['primaryBackendIpAddress'] = ( + utils.query_filter(private_ip)) + + kwargs['filter'] = _filter.to_dict() + 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): diff --git a/tests/CLI/modules/dedicatedhost_tests.py b/tests/CLI/modules/dedicatedhost_tests.py index e26139666..50034f01c 100644 --- a/tests/CLI/modules/dedicatedhost_tests.py +++ b/tests/CLI/modules/dedicatedhost_tests.py @@ -342,10 +342,40 @@ def test_create_verify_no_price_or_more_than_one(self): def test_cancel_host(self, cancel_mock): result = self.run_command(['--really', 'dedicatedhost', 'cancel', '12345']) self.assert_no_fail(result) - cancel_mock.assert_called_with(12345, False) - self.assertEqual(str(result.output), 'Dedicated Host 12345 was successfully cancelled\n') + cancel_mock.assert_called_with(12345) + self.assertEqual(str(result.output), 'Dedicated Host 12345 was cancelled\n') def test_cancel_host_abort(self): result = self.run_command(['dedicatedhost', 'cancel', '12345']) self.assertEqual(result.exit_code, 2) self.assertIsInstance(result.exception, exceptions.CLIAbort) + + @mock.patch('SoftLayer.DedicatedHostManager.cancel_host') + def test_cancel_all_guest(self, cancel_mock): + result = self.run_command(['--really', 'dedicatedhost', 'cancel', '12345']) + self.assert_no_fail(result) + cancel_mock.assert_called_with(12345) + self.assertEqual(str(result.output), 'Dedicated Host 12345 was cancelled\n') + + def test_cancel_all_guest_empty_list(self): + result = self.run_command(['dedicatedhost', 'cancel', '12345']) + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.CLIAbort) + + def test_list_guests(self): + result = self.run_command(['dh', 'list-guests', '123', '--tag=tag']) + + self.assert_no_fail(result) + self.assertEqual(json.loads(result.output), + [{'hostname': 'vs-test1', + 'domain': 'test.sftlyr.ws', + 'primary_ip': '172.16.240.2', + 'id': 100, + 'power_state': 'Running', + 'backend_ip': '10.45.19.37'}, + {'hostname': 'vs-test2', + 'domain': 'test.sftlyr.ws', + 'primary_ip': '172.16.240.7', + 'id': 104, + 'power_state': 'Running', + 'backend_ip': '10.45.19.35'}]) diff --git a/tests/managers/dedicated_host_tests.py b/tests/managers/dedicated_host_tests.py index b28a7431d..7d51fa7f8 100644 --- a/tests/managers/dedicated_host_tests.py +++ b/tests/managers/dedicated_host_tests.py @@ -541,26 +541,10 @@ def test_get_default_router_no_router_found(self): self.dedicated_host._get_default_router, routers, 'notFound') def test_cancel_host(self): - self.dedicated_host.host = mock.Mock() - self.dedicated_host.host.getObject.return_value = {'id': 987, 'billingItem': { - 'id': 1234, 'hourlyFlag': False}} - # Immediate cancellation - result = self.dedicated_host.cancel_host(987) - self.assertEqual(True, result) - - # Cancellation on anniversary - result = self.dedicated_host.cancel_host(987, immediate=False) - self.assertEqual(True, result) - - def test_cancel_host_billing_hourly_no_immediate(self): - self.dedicated_host.host = mock.Mock() - self.dedicated_host.host.getObject.return_value = {'id': 987, 'billingItem': { - 'id': 1234, 'hourlyFlag': True}} + result = self.dedicated_host.cancel_host(789) - ex = self.assertRaises(SoftLayer.SoftLayerError, - self.dedicated_host.cancel_host, - 987, immediate=False) - self.assertEqual("Hourly Dedicated Hosts can only be cancelled immediately.", str(ex)) + self.assertEqual(result, True) + self.assert_called_with('SoftLayer_Virtual_DedicatedHost', 'deleteObject', identifier=789) def _get_routers_sample(self): routers = [ @@ -669,3 +653,34 @@ def _get_package(self): } return package + + def test_list_guests(self): + results = self.dedicated_host.list_guests(12345) + + for result in results: + self.assertIn(result['id'], [100, 104]) + self.assert_called_with('SoftLayer_Virtual_DedicatedHost', 'getGuests', identifier=12345) + + def test_list_guests_with_filters(self): + self.dedicated_host.list_guests(12345, tags=['tag1', 'tag2'], cpus=2, memory=1024, + hostname='hostname', domain='example.com', nic_speed=100, + public_ip='1.2.3.4', private_ip='4.3.2.1') + + _filter = { + 'guests': { + 'domain': {'operation': '_= example.com'}, + 'tagReferences': { + 'tag': {'name': { + 'operation': 'in', + 'options': [{ + 'name': 'data', 'value': ['tag1', 'tag2']}]}}}, + 'maxCpu': {'operation': 2}, + 'maxMemory': {'operation': 1024}, + 'hostname': {'operation': '_= hostname'}, + 'networkComponents': {'maxSpeed': {'operation': 100}}, + 'primaryIpAddress': {'operation': '_= 1.2.3.4'}, + 'primaryBackendIpAddress': {'operation': '_= 4.3.2.1'} + } + } + self.assert_called_with('SoftLayer_Virtual_DedicatedHost', 'getGuests', + identifier=12345, filter=_filter) \ No newline at end of file From 2ef45d8ec2569d747bdd1c5fe2361f9d6f4b2821 Mon Sep 17 00:00:00 2001 From: Albert Camacho Date: Thu, 15 Nov 2018 19:38:03 -0400 Subject: [PATCH 3/7] cancel-all-guests command was refactored and unittests were added on managers and cli --- SoftLayer/CLI/dedicatedhost/cancel_guests.py | 21 +++++------- SoftLayer/CLI/dedicatedhost/list_guests.py | 7 ++-- .../SoftLayer_Virtual_DedicatedHost.py | 10 ++---- SoftLayer/managers/dedicated_host.py | 32 ++++++++++++++++--- tests/CLI/modules/dedicatedhost_tests.py | 30 ++++++++++++----- tests/managers/dedicated_host_tests.py | 20 ++++++++++-- 6 files changed, 82 insertions(+), 38 deletions(-) diff --git a/SoftLayer/CLI/dedicatedhost/cancel_guests.py b/SoftLayer/CLI/dedicatedhost/cancel_guests.py index 106266203..2ae96d3b1 100644 --- a/SoftLayer/CLI/dedicatedhost/cancel_guests.py +++ b/SoftLayer/CLI/dedicatedhost/cancel_guests.py @@ -14,26 +14,21 @@ @click.argument('identifier') @environment.pass_env def cli(env, identifier): - """Cancel all virtual guests of the dedicated host immediately""" + """Cancel all virtual guests of the dedicated host immediately. + + Use the 'slcli vs cancel' command to cancel an specific guest + """ dh_mgr = SoftLayer.DedicatedHostManager(env.client) - vs_mgr = SoftLayer.VSManager(env.client) host_id = helpers.resolve_id(dh_mgr.resolve_ids, identifier, 'dedicated host') - guests = dh_mgr.list_guests(host_id) - - if guests: - msg = '%s guest(s) will be cancelled, ' \ - 'do you want to continue?' % len(guests) + if not (env.skip_confirmations or formatting.no_going_back(host_id)): + raise exceptions.CLIAbort('Aborted') - if not (env.skip_confirmations or formatting.confirm(msg)): - raise exceptions.CLIAbort('Aborted') - - for guest in guests: - vs_mgr.cancel_instance(guest['id']) + result = dh_mgr.cancel_guests(host_id) + if result is True: click.secho('All guests into the dedicated host %s were cancelled' % host_id, fg='green') - else: click.secho('There is not any guest into the dedicated host %s' % host_id, fg='red') diff --git a/SoftLayer/CLI/dedicatedhost/list_guests.py b/SoftLayer/CLI/dedicatedhost/list_guests.py index b84cd4896..bec37a89f 100644 --- a/SoftLayer/CLI/dedicatedhost/list_guests.py +++ b/SoftLayer/CLI/dedicatedhost/list_guests.py @@ -1,4 +1,4 @@ -"""List dedicated servers.""" +"""List guests which are in a dedicated host server.""" # :license: MIT, see LICENSE for more details. import click @@ -55,12 +55,13 @@ show_default=True) @environment.pass_env def cli(env, identifier, sortby, cpu, domain, hostname, memory, tag, columns): - """List guests into the dedicated host.""" + """List guests which are in a dedicated host server.""" + mgr = SoftLayer.DedicatedHostManager(env.client) guests = mgr.list_guests(host_id=identifier, cpus=cpu, hostname=hostname, - domain= domain, + domain=domain, memory=memory, tags=tag, mask=columns.mask()) diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_DedicatedHost.py b/SoftLayer/fixtures/SoftLayer_Virtual_DedicatedHost.py index e7be9e1db..43ab0ec34 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_DedicatedHost.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_DedicatedHost.py @@ -80,8 +80,7 @@ deleteObject = True getGuests = [{ - 'id': 100, - 'metricTrackingObjectId': 1, + 'id': 200, 'hostname': 'vs-test1', 'domain': 'test.sftlyr.ws', 'fullyQualifiedDomainName': 'vs-test1.test.sftlyr.ws', @@ -95,7 +94,6 @@ 'globalIdentifier': '1a2b3c-1701', 'primaryBackendIpAddress': '10.45.19.37', 'hourlyBillingFlag': False, - 'billingItem': { 'id': 6327, 'recurringFee': 1.54, @@ -108,8 +106,7 @@ } }, }, { - 'id': 104, - 'metricTrackingObjectId': 2, + 'id': 202, 'hostname': 'vs-test2', 'domain': 'test.sftlyr.ws', 'fullyQualifiedDomainName': 'vs-test2.test.sftlyr.ws', @@ -133,6 +130,5 @@ } } } - }, - 'virtualRack': {'id': 1, 'bandwidthAllotmentTypeId': 2}, + } }] diff --git a/SoftLayer/managers/dedicated_host.py b/SoftLayer/managers/dedicated_host.py index 89195c171..ec6cfe75d 100644 --- a/SoftLayer/managers/dedicated_host.py +++ b/SoftLayer/managers/dedicated_host.py @@ -33,6 +33,7 @@ def __init__(self, client, ordering_manager=None): self.client = client self.account = client['Account'] self.host = client['Virtual_DedicatedHost'] + self.guest = client['Virtual_Guest'] if ordering_manager is None: self.ordering_manager = ordering.OrderingManager(client) @@ -50,6 +51,29 @@ def cancel_host(self, host_id): """ return self.host.deleteObject(id=host_id) + def cancel_guests(self, host_id): + """Cancel all guests into the dedicated host immediately. + + To cancel an specified guest use the method VSManager.cancel_instance() + + :param host_id: The ID of the dedicated host. + :return: True on success, False if there isn't any guest or + an exception from the API + + Example:: + # Cancel guests of dedicated host id 12345 + result = mgr.cancel_guests(12345) + """ + result = False + + guest_list = self.host.getGuests(id=host_id, mask="id") + + if guest_list: + for virtual_guest in guest_list: + result = self.guest.deleteObject(virtual_guest['id']) + + return result + def list_guests(self, host_id, tags=None, cpus=None, memory=None, hostname=None, domain=None, local_disk=None, nic_speed=None, public_ip=None, private_ip=None, **kwargs): @@ -57,19 +81,17 @@ def list_guests(self, host_id, tags=None, cpus=None, memory=None, hostname=None, Example:: - # Print out a list of hourly instances in the host id 12345. + # Print out a list of instances with 4 cpu cores in the host id 12345. - for vsi in mgr.list_guests(host_id=12345, hourly=True): + for vsi in mgr.list_guests(host_id=12345, cpus=4): print vsi['fullyQualifiedDomainName'], vsi['primaryIpAddress'] # Using a custom object-mask. Will get ONLY what is specified object_mask = "mask[hostname,monitoringRobot[robotStatus]]" - for vsi in mgr.list_guests(mask=object_mask,hourly=True): + for vsi in mgr.list_guests(mask=object_mask,cpus=4): print vsi :param integer host_id: the identifier of dedicated host - :param boolean hourly: include hourly instances - :param boolean monthly: include monthly instances :param list tags: filter based on list of tags :param integer cpus: filter based on number of CPUS :param integer memory: filter based on amount of memory diff --git a/tests/CLI/modules/dedicatedhost_tests.py b/tests/CLI/modules/dedicatedhost_tests.py index 50034f01c..17793b215 100644 --- a/tests/CLI/modules/dedicatedhost_tests.py +++ b/tests/CLI/modules/dedicatedhost_tests.py @@ -341,8 +341,10 @@ def test_create_verify_no_price_or_more_than_one(self): @mock.patch('SoftLayer.DedicatedHostManager.cancel_host') def test_cancel_host(self, cancel_mock): result = self.run_command(['--really', 'dedicatedhost', 'cancel', '12345']) + self.assert_no_fail(result) cancel_mock.assert_called_with(12345) + self.assertEqual(str(result.output), 'Dedicated Host 12345 was cancelled\n') def test_cancel_host_abort(self): @@ -350,16 +352,28 @@ def test_cancel_host_abort(self): self.assertEqual(result.exit_code, 2) self.assertIsInstance(result.exception, exceptions.CLIAbort) - @mock.patch('SoftLayer.DedicatedHostManager.cancel_host') - def test_cancel_all_guest(self, cancel_mock): - result = self.run_command(['--really', 'dedicatedhost', 'cancel', '12345']) + def test_cancel_all_guests(self): + guests = self.set_mock('SoftLayer_Virtual_DedicatedHost', 'getGuests') + guests.return_value = [{'id': 987}, {'id': 654}] + + result = self.run_command(['--really', 'dedicatedhost', 'cancel-all-guests', '12345']) self.assert_no_fail(result) - cancel_mock.assert_called_with(12345) - self.assertEqual(str(result.output), 'Dedicated Host 12345 was cancelled\n') - def test_cancel_all_guest_empty_list(self): + self.assertEqual(str(result.output), 'All guests into the dedicated host 12345 were cancelled\n') + + def test_cancel_all_guests_empty_list(self): + guests = self.set_mock('SoftLayer_Virtual_DedicatedHost', 'getGuests') + guests.return_value = [] + + result = self.run_command(['--really', 'dedicatedhost', 'cancel-all-guests', '12345']) + self.assert_no_fail(result) + + self.assertEqual(str(result.output), 'There is not any guest into the dedicated host 12345\n') + + def test_cancel_all_guests_abort(self): result = self.run_command(['dedicatedhost', 'cancel', '12345']) self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.CLIAbort) def test_list_guests(self): @@ -370,12 +384,12 @@ def test_list_guests(self): [{'hostname': 'vs-test1', 'domain': 'test.sftlyr.ws', 'primary_ip': '172.16.240.2', - 'id': 100, + 'id': 200, 'power_state': 'Running', 'backend_ip': '10.45.19.37'}, {'hostname': 'vs-test2', 'domain': 'test.sftlyr.ws', 'primary_ip': '172.16.240.7', - 'id': 104, + 'id': 202, 'power_state': 'Running', 'backend_ip': '10.45.19.35'}]) diff --git a/tests/managers/dedicated_host_tests.py b/tests/managers/dedicated_host_tests.py index 7d51fa7f8..2f8183131 100644 --- a/tests/managers/dedicated_host_tests.py +++ b/tests/managers/dedicated_host_tests.py @@ -546,6 +546,22 @@ def test_cancel_host(self): self.assertEqual(result, True) self.assert_called_with('SoftLayer_Virtual_DedicatedHost', 'deleteObject', identifier=789) + def test_cancel_guests(self): + self.dedicated_host.host = mock.Mock() + self.dedicated_host.host.getGuests.return_value = [{'id': 987}, {'id': 654}] + + result = self.dedicated_host.cancel_guests(789) + + self.assertEqual(result, True) + + def test_cancel_guests_empty_list(self): + self.dedicated_host.host = mock.Mock() + self.dedicated_host.host.getGuests.return_value = [] + + result = self.dedicated_host.cancel_guests(789) + + self.assertEqual(result, False) + def _get_routers_sample(self): routers = [ { @@ -658,7 +674,7 @@ def test_list_guests(self): results = self.dedicated_host.list_guests(12345) for result in results: - self.assertIn(result['id'], [100, 104]) + self.assertIn(result['id'], [200, 202]) self.assert_called_with('SoftLayer_Virtual_DedicatedHost', 'getGuests', identifier=12345) def test_list_guests_with_filters(self): @@ -683,4 +699,4 @@ def test_list_guests_with_filters(self): } } self.assert_called_with('SoftLayer_Virtual_DedicatedHost', 'getGuests', - identifier=12345, filter=_filter) \ No newline at end of file + identifier=12345, filter=_filter) From 1cfac08ee8cc364c055da29d9c0c7a1098435171 Mon Sep 17 00:00:00 2001 From: Albert Camacho Date: Thu, 15 Nov 2018 19:51:42 -0400 Subject: [PATCH 4/7] fixed the issue in the cancel_guest method and I renamed some variables --- SoftLayer/managers/dedicated_host.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SoftLayer/managers/dedicated_host.py b/SoftLayer/managers/dedicated_host.py index ec6cfe75d..154943ea7 100644 --- a/SoftLayer/managers/dedicated_host.py +++ b/SoftLayer/managers/dedicated_host.py @@ -66,11 +66,11 @@ def cancel_guests(self, host_id): """ result = False - guest_list = self.host.getGuests(id=host_id, mask="id") + guests = self.host.getGuests(id=host_id, mask='id') - if guest_list: - for virtual_guest in guest_list: - result = self.guest.deleteObject(virtual_guest['id']) + if guests: + for vs in guests: + result = self.guest.deleteObject(id=vs['id']) return result From 72d0843cc6a9c0889d03e6684870818d9736c16a Mon Sep 17 00:00:00 2001 From: Albert Camacho Date: Thu, 15 Nov 2018 19:58:52 -0400 Subject: [PATCH 5/7] rename cancel-all-guest by cancel-guests --- SoftLayer/CLI/routes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index e89c98e90..e80a0b50a 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -38,7 +38,7 @@ ('dedicatedhost:create-options', 'SoftLayer.CLI.dedicatedhost.create_options:cli'), ('dedicatedhost:detail', 'SoftLayer.CLI.dedicatedhost.detail:cli'), ('dedicatedhost:cancel', 'SoftLayer.CLI.dedicatedhost.cancel:cli'), - ('dedicatedhost:cancel-all-guests', 'SoftLayer.CLI.dedicatedhost.cancel_guests:cli'), + ('dedicatedhost:cancel-guests', 'SoftLayer.CLI.dedicatedhost.cancel_guests:cli'), ('dedicatedhost:list-guests', 'SoftLayer.CLI.dedicatedhost.list_guests:cli'), ('cdn', 'SoftLayer.CLI.cdn'), From 4a82445f15fdf4cba5ec0462cb5856625d83ce5f Mon Sep 17 00:00:00 2001 From: Albert Camacho Date: Fri, 16 Nov 2018 14:35:51 -0400 Subject: [PATCH 6/7] fix unittests for cancel-guest --- tests/CLI/modules/dedicatedhost_tests.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/CLI/modules/dedicatedhost_tests.py b/tests/CLI/modules/dedicatedhost_tests.py index 17793b215..28e50d592 100644 --- a/tests/CLI/modules/dedicatedhost_tests.py +++ b/tests/CLI/modules/dedicatedhost_tests.py @@ -352,26 +352,26 @@ def test_cancel_host_abort(self): self.assertEqual(result.exit_code, 2) self.assertIsInstance(result.exception, exceptions.CLIAbort) - def test_cancel_all_guests(self): + def test_cancel_guests(self): guests = self.set_mock('SoftLayer_Virtual_DedicatedHost', 'getGuests') guests.return_value = [{'id': 987}, {'id': 654}] - result = self.run_command(['--really', 'dedicatedhost', 'cancel-all-guests', '12345']) + result = self.run_command(['--really', 'dedicatedhost', 'cancel-guests', '12345']) self.assert_no_fail(result) self.assertEqual(str(result.output), 'All guests into the dedicated host 12345 were cancelled\n') - def test_cancel_all_guests_empty_list(self): + def test_cancel_guests_empty_list(self): guests = self.set_mock('SoftLayer_Virtual_DedicatedHost', 'getGuests') guests.return_value = [] - result = self.run_command(['--really', 'dedicatedhost', 'cancel-all-guests', '12345']) + result = self.run_command(['--really', 'dedicatedhost', 'cancel-guests', '12345']) self.assert_no_fail(result) self.assertEqual(str(result.output), 'There is not any guest into the dedicated host 12345\n') - def test_cancel_all_guests_abort(self): - result = self.run_command(['dedicatedhost', 'cancel', '12345']) + def test_cancel_guests_abort(self): + result = self.run_command(['dedicatedhost', 'cancel-guests', '12345']) self.assertEqual(result.exit_code, 2) self.assertIsInstance(result.exception, exceptions.CLIAbort) From 2987fd9ef95202c102b1257ffb0fd022a721dba6 Mon Sep 17 00:00:00 2001 From: Albert Camacho Date: Fri, 16 Nov 2018 18:51:42 -0400 Subject: [PATCH 7/7] cancel-guests returns a dictionary about delete status --- SoftLayer/CLI/dedicatedhost/cancel_guests.py | 13 ++++++++-- SoftLayer/managers/dedicated_host.py | 26 ++++++++++++++++---- tests/CLI/modules/dedicatedhost_tests.py | 15 +++++++++-- tests/managers/dedicated_host_tests.py | 25 ++++++++++++++++--- 4 files changed, 67 insertions(+), 12 deletions(-) diff --git a/SoftLayer/CLI/dedicatedhost/cancel_guests.py b/SoftLayer/CLI/dedicatedhost/cancel_guests.py index 2ae96d3b1..537828de1 100644 --- a/SoftLayer/CLI/dedicatedhost/cancel_guests.py +++ b/SoftLayer/CLI/dedicatedhost/cancel_guests.py @@ -26,9 +26,18 @@ def cli(env, identifier): if not (env.skip_confirmations or formatting.no_going_back(host_id)): raise exceptions.CLIAbort('Aborted') + table = formatting.Table(['id', 'server name', 'status']) + result = dh_mgr.cancel_guests(host_id) - if result is True: - click.secho('All guests into the dedicated host %s were cancelled' % host_id, fg='green') + if result: + for status in result: + table.add_row([ + status['id'], + status['fqdn'], + status['status'] + ]) + + env.fout(table) else: click.secho('There is not any guest into the dedicated host %s' % host_id, fg='red') diff --git a/SoftLayer/managers/dedicated_host.py b/SoftLayer/managers/dedicated_host.py index 154943ea7..86258416b 100644 --- a/SoftLayer/managers/dedicated_host.py +++ b/SoftLayer/managers/dedicated_host.py @@ -57,20 +57,26 @@ def cancel_guests(self, host_id): To cancel an specified guest use the method VSManager.cancel_instance() :param host_id: The ID of the dedicated host. - :return: True on success, False if there isn't any guest or - an exception from the API + :return: The id, fqdn and status of all guests into a dictionary. The status + could be 'Cancelled' or an exception message, The dictionary is empty + if there isn't any guest in the dedicated host. Example:: # Cancel guests of dedicated host id 12345 result = mgr.cancel_guests(12345) """ - result = False + result = [] - guests = self.host.getGuests(id=host_id, mask='id') + guests = self.host.getGuests(id=host_id, mask='id,fullyQualifiedDomainName') if guests: for vs in guests: - result = self.guest.deleteObject(id=vs['id']) + status_info = { + 'id': vs['id'], + 'fqdn': vs['fullyQualifiedDomainName'], + 'status': self._delete_guest(vs['id']) + } + result.append(status_info) return result @@ -512,3 +518,13 @@ def get_router_options(self, datacenter=None, flavor=None): item = self._get_item(package, flavor) return self._get_backend_router(location['location']['locationPackageDetails'], item) + + def _delete_guest(self, guest_id): + """Deletes a guest and returns 'Cancelled' or and Exception message""" + msg = 'Cancelled' + try: + self.guest.deleteObject(id=guest_id) + except SoftLayer.SoftLayerAPIError as e: + msg = 'Exception: ' + e.faultString + + return msg diff --git a/tests/CLI/modules/dedicatedhost_tests.py b/tests/CLI/modules/dedicatedhost_tests.py index 28e50d592..077c3f033 100644 --- a/tests/CLI/modules/dedicatedhost_tests.py +++ b/tests/CLI/modules/dedicatedhost_tests.py @@ -353,13 +353,19 @@ def test_cancel_host_abort(self): self.assertIsInstance(result.exception, exceptions.CLIAbort) def test_cancel_guests(self): + vs1 = {'id': 987, 'fullyQualifiedDomainName': 'foobar.example.com'} + vs2 = {'id': 654, 'fullyQualifiedDomainName': 'wombat.example.com'} guests = self.set_mock('SoftLayer_Virtual_DedicatedHost', 'getGuests') - guests.return_value = [{'id': 987}, {'id': 654}] + guests.return_value = [vs1, vs2] + + vs_status1 = {'id': 987, 'server name': 'foobar.example.com', 'status': 'Cancelled'} + vs_status2 = {'id': 654, 'server name': 'wombat.example.com', 'status': 'Cancelled'} + expected_result = [vs_status1, vs_status2] result = self.run_command(['--really', 'dedicatedhost', 'cancel-guests', '12345']) self.assert_no_fail(result) - self.assertEqual(str(result.output), 'All guests into the dedicated host 12345 were cancelled\n') + self.assertEqual(expected_result, json.loads(result.output)) def test_cancel_guests_empty_list(self): guests = self.set_mock('SoftLayer_Virtual_DedicatedHost', 'getGuests') @@ -393,3 +399,8 @@ def test_list_guests(self): 'id': 202, 'power_state': 'Running', 'backend_ip': '10.45.19.35'}]) + + def _get_cancel_guests_return(self): + vs_status1 = {'id': 123, 'fqdn': 'foobar.example.com', 'status': 'Cancelled'} + vs_status2 = {'id': 456, 'fqdn': 'wombat.example.com', 'status': 'Cancelled'} + return [vs_status1, vs_status2] diff --git a/tests/managers/dedicated_host_tests.py b/tests/managers/dedicated_host_tests.py index 2f8183131..6888db3ce 100644 --- a/tests/managers/dedicated_host_tests.py +++ b/tests/managers/dedicated_host_tests.py @@ -547,12 +547,19 @@ def test_cancel_host(self): self.assert_called_with('SoftLayer_Virtual_DedicatedHost', 'deleteObject', identifier=789) def test_cancel_guests(self): + vs1 = {'id': 987, 'fullyQualifiedDomainName': 'foobar.example.com'} + vs2 = {'id': 654, 'fullyQualifiedDomainName': 'wombat.example.com'} self.dedicated_host.host = mock.Mock() - self.dedicated_host.host.getGuests.return_value = [{'id': 987}, {'id': 654}] + self.dedicated_host.host.getGuests.return_value = [vs1, vs2] + + # Expected result + vs_status1 = {'id': 987, 'fqdn': 'foobar.example.com', 'status': 'Cancelled'} + vs_status2 = {'id': 654, 'fqdn': 'wombat.example.com', 'status': 'Cancelled'} + delete_status = [vs_status1, vs_status2] result = self.dedicated_host.cancel_guests(789) - self.assertEqual(result, True) + self.assertEqual(result, delete_status) def test_cancel_guests_empty_list(self): self.dedicated_host.host = mock.Mock() @@ -560,7 +567,19 @@ def test_cancel_guests_empty_list(self): result = self.dedicated_host.cancel_guests(789) - self.assertEqual(result, False) + self.assertEqual(result, []) + + def test_delete_guest(self): + result = self.dedicated_host._delete_guest(123) + self.assertEqual(result, 'Cancelled') + + # delete_guest should return the exception message in case it fails + error_raised = SoftLayer.SoftLayerAPIError('SL Exception', 'SL message') + self.dedicated_host.guest = mock.Mock() + self.dedicated_host.guest.deleteObject.side_effect = error_raised + + result = self.dedicated_host._delete_guest(369) + self.assertEqual(result, 'Exception: SL message') def _get_routers_sample(self): routers = [