Skip to content

Commit 4ca237a

Browse files
added a way to try to cancel montly bare metal immediately
1 parent 4ec8756 commit 4ca237a

File tree

3 files changed

+80
-36
lines changed

3 files changed

+80
-36
lines changed

SoftLayer/CLI/hardware/cancel.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,11 @@
1515
@click.option('--immediate',
1616
is_flag=True,
1717
default=False,
18-
help="""Cancels the server immediately (instead of on the billing
19-
anniversary)""")
18+
help="Cancels the server immediately (instead of on the billing anniversary)")
2019
@click.option('--comment',
2120
help="An optional comment to add to the cancellation ticket")
2221
@click.option('--reason',
23-
help="""An optional cancellation reason. See cancel-reasons for
24-
a list of available options""")
22+
help="An optional cancellation reason. See cancel-reasons for a list of available options")
2523
@environment.pass_env
2624
def cli(env, identifier, immediate, comment, reason):
2725
"""Cancel a dedicated server."""

SoftLayer/managers/hardware.py

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import socket
1010
import time
1111

12+
1213
import SoftLayer
1314
from SoftLayer.decoration import retry
1415
from SoftLayer.managers import ordering
@@ -56,8 +57,7 @@ def __init__(self, client, ordering_manager=None):
5657
else:
5758
self.ordering_manager = ordering_manager
5859

59-
def cancel_hardware(self, hardware_id, reason='unneeded', comment='',
60-
immediate=False):
60+
def cancel_hardware(self, hardware_id, reason='unneeded', comment='', immediate=False):
6161
"""Cancels the specified dedicated server.
6262
6363
Example::
@@ -66,27 +66,46 @@ def cancel_hardware(self, hardware_id, reason='unneeded', comment='',
6666
result = mgr.cancel_hardware(hardware_id=1234)
6767
6868
:param int hardware_id: The ID of the hardware to be cancelled.
69-
:param string reason: The reason code for the cancellation. This should
70-
come from :func:`get_cancellation_reasons`.
71-
:param string comment: An optional comment to include with the
72-
cancellation.
69+
:param string reason: The reason code for the cancellation. This should come from
70+
:func:`get_cancellation_reasons`.
71+
:param string comment: An optional comment to include with the cancellation.
72+
:param bool immediate: If set to True, will automatically update the cancelation ticket to request
73+
the resource be reclaimed asap. This request still has to be reviewed by a human
74+
:returns: True on success or an exception
7375
"""
7476

7577
# Get cancel reason
7678
reasons = self.get_cancellation_reasons()
7779
cancel_reason = reasons.get(reason, reasons['unneeded'])
80+
ticket_mgr = SoftLayer.TicketManager(self.client)
81+
mask = 'mask[id, hourlyBillingFlag, billingItem[id], openCancellationTicket[id]]'
82+
hw_billing = self.get_hardware(hardware_id, mask=mask)
7883

79-
hw_billing = self.get_hardware(hardware_id,
80-
mask='mask[id, billingItem.id]')
8184
if 'billingItem' not in hw_billing:
82-
raise SoftLayer.SoftLayerError(
83-
"No billing item found for hardware")
85+
raise SoftLayer.SoftLayerError("Ticket #%s already exists for this server" %
86+
hw_billing['openCancellationTicket']['id'])
8487

8588
billing_id = hw_billing['billingItem']['id']
8689

87-
return self.client.call('Billing_Item', 'cancelItem',
88-
immediate, False, cancel_reason, comment,
89-
id=billing_id)
90+
if immediate and not hw_billing['hourlyBillingFlag']:
91+
LOGGER.warning("Immediate cancelation of montly servers is not guaranteed. " +
92+
"Please check the cancelation ticket for updates.")
93+
94+
result = self.client.call('Billing_Item', 'cancelItem',
95+
False, False, cancel_reason, comment, id=billing_id)
96+
hw_billing = self.get_hardware(hardware_id, mask=mask)
97+
ticket_number = hw_billing['openCancellationTicket']['id']
98+
cancel_message = "Please reclaim this server ASAP, it is no longer needed. Thankyou."
99+
ticket_mgr.update_ticket(ticket_number, cancel_message)
100+
LOGGER.info("Cancelation ticket #%s has been updated requesting immediate reclaim", ticket_number)
101+
else:
102+
result = self.client.call('Billing_Item', 'cancelItem',
103+
immediate, False, cancel_reason, comment, id=billing_id)
104+
hw_billing = self.get_hardware(hardware_id, mask=mask)
105+
ticket_number = hw_billing['openCancellationTicket']['id']
106+
LOGGER.info("Cancelation ticket #%s has been created", ticket_number)
107+
108+
return result
90109

91110
@retry(logger=LOGGER)
92111
def list_hardware(self, tags=None, cpus=None, memory=None, hostname=None,

tests/managers/hardware_tests.py

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import mock
1010

11+
1112
import SoftLayer
1213
from SoftLayer import fixtures
1314
from SoftLayer import managers
@@ -240,51 +241,77 @@ def test_place_order(self, create_dict):
240241

241242
def test_cancel_hardware_without_reason(self):
242243
mock = self.set_mock('SoftLayer_Hardware_Server', 'getObject')
243-
mock.return_value = {'id': 987, 'billingItem': {'id': 1234}}
244+
mock.return_value = {'id': 987, 'billingItem': {'id': 1234},
245+
'openCancellationTicket': {'id': 1234}}
244246

245247
result = self.hardware.cancel_hardware(987)
246248

247249
self.assertEqual(result, True)
248250
reasons = self.hardware.get_cancellation_reasons()
249251
args = (False, False, reasons['unneeded'], '')
250-
self.assert_called_with('SoftLayer_Billing_Item', 'cancelItem',
251-
identifier=1234,
252-
args=args)
252+
self.assert_called_with('SoftLayer_Billing_Item', 'cancelItem', identifier=1234, args=args)
253253

254254
def test_cancel_hardware_with_reason_and_comment(self):
255255
mock = self.set_mock('SoftLayer_Hardware_Server', 'getObject')
256-
mock.return_value = {'id': 987, 'billingItem': {'id': 1234}}
256+
mock.return_value = {'id': 987, 'billingItem': {'id': 1234},
257+
'openCancellationTicket': {'id': 1234}}
257258

258-
result = self.hardware.cancel_hardware(6327,
259-
reason='sales',
260-
comment='Test Comment')
259+
result = self.hardware.cancel_hardware(6327, reason='sales', comment='Test Comment')
261260

262261
self.assertEqual(result, True)
263262
reasons = self.hardware.get_cancellation_reasons()
264263
args = (False, False, reasons['sales'], 'Test Comment')
265-
self.assert_called_with('SoftLayer_Billing_Item', 'cancelItem',
266-
identifier=1234,
267-
args=args)
264+
self.assert_called_with('SoftLayer_Billing_Item', 'cancelItem', identifier=1234, args=args)
268265

269266
def test_cancel_hardware(self):
270-
267+
mock = self.set_mock('SoftLayer_Hardware_Server', 'getObject')
268+
mock.return_value = {'id': 987, 'billingItem': {'id': 6327},
269+
'openCancellationTicket': {'id': 4567}}
271270
result = self.hardware.cancel_hardware(6327)
272271

273272
self.assertEqual(result, True)
274-
self.assert_called_with('SoftLayer_Billing_Item',
275-
'cancelItem',
276-
identifier=6327,
277-
args=(False, False, 'No longer needed', ''))
273+
self.assert_called_with('SoftLayer_Billing_Item', 'cancelItem',
274+
identifier=6327, args=(False, False, 'No longer needed', ''))
278275

279276
def test_cancel_hardware_no_billing_item(self):
280277
mock = self.set_mock('SoftLayer_Hardware_Server', 'getObject')
281-
mock.return_value = {'id': 987}
278+
mock.return_value = {'id': 987, 'openCancellationTicket': {'id': 1234},
279+
'openCancellationTicket': {'id': 1234}}
282280

283281
ex = self.assertRaises(SoftLayer.SoftLayerError,
284282
self.hardware.cancel_hardware,
285283
6327)
286-
self.assertEqual("No billing item found for hardware",
287-
str(ex))
284+
self.assertEqual("Ticket #1234 already exists for this server", str(ex))
285+
286+
def test_cancel_hardware_monthly_now(self):
287+
mock = self.set_mock('SoftLayer_Hardware_Server', 'getObject')
288+
mock.return_value = {'id': 987, 'billingItem': {'id': 1234},
289+
'openCancellationTicket': {'id': 4567},
290+
'hourlyBillingFlag': False}
291+
with self.assertLogs('SoftLayer.managers.hardware', level='INFO') as logs:
292+
result = self.hardware.cancel_hardware(987, immediate=True)
293+
# should be 2 infom essages here
294+
self.assertEqual(len(logs.records), 2)
295+
296+
self.assertEqual(result, True)
297+
self.assert_called_with('SoftLayer_Billing_Item', 'cancelItem',
298+
identifier=1234, args=(False, False, 'No longer needed', ''))
299+
cancel_message = "Please reclaim this server ASAP, it is no longer needed. Thankyou."
300+
self.assert_called_with('SoftLayer_Ticket', 'addUpdate',
301+
identifier=4567, args=({'entry': cancel_message},))
302+
303+
def test_cancel_hardware_monthly_whenever(self):
304+
mock = self.set_mock('SoftLayer_Hardware_Server', 'getObject')
305+
mock.return_value = {'id': 987, 'billingItem': {'id': 6327},
306+
'openCancellationTicket': {'id': 4567}}
307+
308+
with self.assertLogs('SoftLayer.managers.hardware', level='INFO') as logs:
309+
result = self.hardware.cancel_hardware(987, immediate=False)
310+
# should be 2 infom essages here
311+
self.assertEqual(len(logs.records), 1)
312+
self.assertEqual(result, True)
313+
self.assert_called_with('SoftLayer_Billing_Item', 'cancelItem',
314+
identifier=6327, args=(False, False, 'No longer needed', ''))
288315

289316
def test_change_port_speed_public(self):
290317
self.hardware.change_port_speed(2, True, 100)

0 commit comments

Comments
 (0)