Skip to content

Commit 5a3ad54

Browse files
Merge branch 'master' of github.com:allmightyspiff/softlayer-python into xport-enhancements
2 parents 7e4340b + f8f743a commit 5a3ad54

File tree

13 files changed

+177
-63
lines changed

13 files changed

+177
-63
lines changed

CHANGELOG.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
# Change Log
22

3+
## [5.4.3] - 2018-03-30
4+
- Changes: https://github.com/softlayer/softlayer-python/compare/v5.4.2...master
5+
6+
- Corrected to current create-options output
7+
- Allow ordering of account restricted presets
8+
- Added lookup function for datacenter names and ability to use `slcli order` with short DC names
9+
- Changed locatoinGroupId to check for None instead of empty string
10+
- Added a way to try to cancel montly bare metal immediately. THis is done by automatically updating the cancellation request. A human still needs to read the ticket and process it for the reclaim to complete.
11+
312
## [5.4.2] - 2018-02-22
4-
- Changes: https://github.com/softlayer/softlayer-python/compare/v5.4.1...master
13+
- Changes: https://github.com/softlayer/softlayer-python/compare/v5.4.1...v5.4.2
514

615
- add GPU to the virtual create-options table
716
- Remove 'virtual' from the hardware ready command.

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/consts.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
66
:license: MIT, see LICENSE for more details.
77
"""
8-
VERSION = 'v5.4.2'
8+
VERSION = 'v5.4.3'
99
API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/'
1010
API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/'
1111
API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/'

SoftLayer/fixtures/SoftLayer_Location.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@
99
"longName": "San Jose 1",
1010
"name": "sjc01"
1111
}]
12+
getDatacenters = [{'id': 1854895, 'name': 'dal13', 'regions': [{'keyname': 'DALLAS13'}]}]

SoftLayer/fixtures/SoftLayer_Product_Package.py

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -666,15 +666,27 @@
666666
]
667667
}
668668

669+
activePreset1 = {
670+
'description': 'Single Xeon 1270, 8GB Ram, 2x1TB SATA disks, Non-RAID',
671+
'id': 64,
672+
'isActive': '1',
673+
'keyName': 'S1270_8GB_2X1TBSATA_NORAID',
674+
'name': 'S1270 8GB 2X1TBSATA NORAID',
675+
'packageId': 200
676+
}
677+
678+
activePreset2 = {
679+
'description': 'Dual Xeon Gold, 384GB Ram, 4x960GB SSD, RAID 10',
680+
'id': 65,
681+
'isActive': '1',
682+
'keyName': 'DGOLD_6140_384GB_4X960GB_SSD_SED_RAID_10',
683+
'name': 'DGOLD 6140 384GB 4X960GB SSD SED RAID 10',
684+
'packageId': 200
685+
}
686+
669687
getAllObjects = [{
670-
'activePresets': [{
671-
'description': 'Single Xeon 1270, 8GB Ram, 2x1TB SATA disks, Non-RAID',
672-
'id': 64,
673-
'isActive': '1',
674-
'keyName': 'S1270_8GB_2X1TBSATA_NORAID',
675-
'name': 'S1270 8GB 2X1TBSATA NORAID',
676-
'packageId': 200
677-
}],
688+
'activePresets': [activePreset1],
689+
'accountRestrictedActivePresets': [activePreset2],
678690
'description': 'Bare Metal Server',
679691
'firstOrderStepId': 1,
680692
'id': 200,

SoftLayer/managers/hardware.py

Lines changed: 35 additions & 15 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,
@@ -359,7 +378,7 @@ def get_create_options(self):
359378

360379
# Sizes
361380
sizes = []
362-
for preset in package['activePresets']:
381+
for preset in package['activePresets'] + package['accountRestrictedActivePresets']:
363382
sizes.append({
364383
'name': preset['description'],
365384
'key': preset['keyName']
@@ -418,6 +437,7 @@ def _get_package(self):
418437
prices
419438
],
420439
activePresets,
440+
accountRestrictedActivePresets,
421441
regions[location[location[priceGroups]]]
422442
'''
423443

@@ -774,7 +794,7 @@ def _get_location(package, location):
774794

775795
def _get_preset_id(package, size):
776796
"""Get the preset id given the keyName of the preset."""
777-
for preset in package['activePresets']:
797+
for preset in package['activePresets'] + package['accountRestrictedActivePresets']:
778798
if preset['keyName'] == size or preset['id'] == size:
779799
return preset['id']
780800

SoftLayer/managers/ordering.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
"""
88
# pylint: disable=no-self-use
99

10+
from re import match
11+
1012
from SoftLayer import exceptions
1113

1214
CATEGORY_MASK = '''id,
@@ -341,7 +343,7 @@ def get_price_id_list(self, package_keyname, item_keynames):
341343
# can take that ID and create the proper price for us in the location
342344
# in which the order is made
343345
price_id = [p['id'] for p in matching_item['prices']
344-
if p['locationGroupId'] == ''][0]
346+
if p['locationGroupId'] is None][0]
345347
prices.append(price_id)
346348

347349
return prices
@@ -443,7 +445,7 @@ def generate_order(self, package_keyname, location, item_keynames, complex_type=
443445
# 'domain': 'softlayer.com'}]}
444446
order.update(extras)
445447
order['packageId'] = package['id']
446-
order['location'] = location
448+
order['location'] = self.get_location_id(location)
447449
order['quantity'] = quantity
448450
order['useHourlyPricing'] = hourly
449451

@@ -471,3 +473,23 @@ def package_locations(self, package_keyname):
471473

472474
regions = self.package_svc.getRegions(id=package['id'], mask=mask)
473475
return regions
476+
477+
def get_location_id(self, location):
478+
"""Finds the location ID of a given datacenter
479+
480+
This is mostly used so either a dc name, or regions keyname can be used when ordering
481+
:param str location: Region Keyname (DALLAS13) or datacenter name (dal13)
482+
:returns: integer id of the datacenter
483+
"""
484+
485+
if isinstance(location, int):
486+
return location
487+
mask = "mask[id,name,regions[keyname]]"
488+
if match(r'[a-zA-Z]{3}[0-9]{2}', location) is not None:
489+
search = {'name': {'operation': location}}
490+
else:
491+
search = {'regions': {'keyname': {'operation': location}}}
492+
datacenter = self.client.call('SoftLayer_Location', 'getDatacenters', mask=mask, filter=search)
493+
if len(datacenter) != 1:
494+
raise exceptions.SoftLayerError("Unable to find location: %s" % location)
495+
return datacenter[0]['id']

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
setup(
1616
name='SoftLayer',
17-
version='5.4.2',
17+
version='5.4.3',
1818
description=DESCRIPTION,
1919
long_description=LONG_DESCRIPTION,
2020
author='SoftLayer Technologies, Inc.',

snap/snapcraft.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: slcli # check to see if it's available
2-
version: '5.4.2.0+git' # check versioning
2+
version: '5.4.3.0+git' # check versioning
33
summary: Python based SoftLayer API Tool. # 79 char long summary
44
description: |
55
A command-line interface is also included and can be used to manage various SoftLayer products and services.

tests/CLI/modules/order_tests.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,9 +210,9 @@ def test_location_list(self):
210210

211211
def _get_order_items(self):
212212
item1 = {'keyName': 'ITEM1', 'description': 'description1',
213-
'prices': [{'id': 1111, 'locationGroupId': ''}]}
213+
'prices': [{'id': 1111, 'locationGroupId': None}]}
214214
item2 = {'keyName': 'ITEM2', 'description': 'description2',
215-
'prices': [{'id': 2222, 'locationGroupId': ''}]}
215+
'prices': [{'id': 2222, 'locationGroupId': None}]}
216216

217217
return [item1, item2]
218218

0 commit comments

Comments
 (0)