"""
SoftLayer.vs_capacity
~~~~~~~~~~~~~~~~~~~~~~~
Reserved Capacity Manager and helpers
:license: MIT, see License for more details.
"""
import logging
from SoftLayer.exceptions import SoftLayerError
from SoftLayer.managers import ordering
from SoftLayer.managers.vs import VSManager
from SoftLayer import utils
# Invalid names are ignored due to long method names and short argument names
# pylint: disable=invalid-name
LOGGER = logging.getLogger(__name__)
[docs]
class CapacityManager(utils.IdentifierMixin, object):
"""Manages SoftLayer Reserved Capacity Groups.
Product Information
- https://cloud.ibm.com/docs/virtual-servers?topic=virtual-servers-about-reserved-virtual-servers
- https://softlayer.github.io/reference/services/SoftLayer_Virtual_ReservedCapacityGroup/
- https://softlayer.github.io/reference/services/SoftLayer_Virtual_ReservedCapacityGroup_Instance/
:param SoftLayer.API.BaseClient client: the client instance
:param SoftLayer.managers.OrderingManager ordering_manager: an optional manager to handle ordering.
If none is provided, one will be auto initialized.
"""
[docs]
def __init__(self, client, ordering_manager=None):
self.client = client
self.account = client['Account']
self.capacity_package = 'RESERVED_CAPACITY'
self.rcg_service = 'Virtual_ReservedCapacityGroup'
if ordering_manager is None:
self.ordering_manager = ordering.OrderingManager(client)
[docs]
def list(self):
"""List Reserved Capacities"""
mask = """mask[availableInstanceCount, occupiedInstanceCount,
instances[id, billingItem[description, hourlyRecurringFee]], instanceCount, backendRouter[datacenter]]"""
results = self.client.call('Account', 'getReservedCapacityGroups', mask=mask)
return results
[docs]
def get_object(self, identifier, mask=None):
"""Get a Reserved Capacity Group
:param int identifier: Id of the SoftLayer_Virtual_ReservedCapacityGroup
:param string mask: override default object Mask
"""
if mask is None:
mask = "mask[instances[billingItem[item[keyName],category], guest], backendRouter[datacenter]]"
result = self.client.call(self.rcg_service, 'getObject', id=identifier, mask=mask)
return result
[docs]
def get_create_options(self):
"""List available reserved capacity plans"""
mask = "mask[attributes,prices[pricingLocationGroup]]"
results = self.ordering_manager.list_items(self.capacity_package, mask=mask)
return results
[docs]
def get_available_routers(self, dc=None):
"""Pulls down all backendRouterIds that are available
:param string dc: A specific location to get routers for, like 'dal13'.
:returns list: A list of locations where RESERVED_CAPACITY can be ordered.
"""
mask = "mask[locations]"
# Step 1, get the package id
package = self.ordering_manager.get_package_by_key(self.capacity_package, mask="id")
# Step 2, get the regions this package is orderable in
regions = self.client.call('Product_Package', 'getRegions', id=package['id'], mask=mask, iter=True)
_filter = None
routers = {}
if dc is not None:
_filter = {'datacenterName': {'operation': dc}}
# Step 3, for each location in each region, get the pod details, which contains the router id
pods = self.client.call('Network_Pod', 'getAllObjects', filter=_filter, iter=True)
for region in regions:
routers[region['keyname']] = []
for location in region['locations']:
location['location']['pods'] = []
for pod in pods:
if pod['datacenterName'] == location['location']['name']:
location['location']['pods'].append(pod)
# Step 4, return the data.
return regions
[docs]
def create(self, name, backend_router_id, flavor, instances, test=False):
"""Orders a Virtual_ReservedCapacityGroup
:param string name: Name for the new reserved capacity
:param int backend_router_id: This selects the pod. See create_options for a list
:param string flavor: Capacity KeyName, see create_options for a list
:param int instances: Number of guest this capacity can support
:param bool test: If True, don't actually order, just test.
"""
# Since orderManger needs a DC id, just send in 0, the API will ignore it
args = (self.capacity_package, 0, [flavor])
extras = {"backendRouterId": backend_router_id, "name": name}
kwargs = {
'extras': extras,
'quantity': instances,
'complex_type': 'SoftLayer_Container_Product_Order_Virtual_ReservedCapacity',
'hourly': True
}
if test:
receipt = self.ordering_manager.verify_order(*args, **kwargs)
else:
receipt = self.ordering_manager.place_order(*args, **kwargs)
return receipt
[docs]
def create_guest(self, capacity_id, test, guest_object):
"""Turns an empty Reserve Capacity into a real Virtual Guest
:param int capacity_id: ID of the RESERVED_CAPACITY_GROUP to create this guest into
:param bool test: True will use verifyOrder, False will use placeOrder
:param dictionary guest_object: Below is the minimum info you need to send in
guest_object = {
'domain': 'test.com',
'hostname': 'A1538172419',
'os_code': 'UBUNTU_LATEST_64',
'primary_disk': '25',
}
"""
vs_manager = VSManager(self.client)
mask = "mask[instances[id, billingItem[id, item[id,keyName]]], backendRouter[id, datacenter[name]]]"
capacity = self.get_object(capacity_id, mask=mask)
try:
capacity_flavor = capacity['instances'][0]['billingItem']['item']['keyName']
flavor = _flavor_string(capacity_flavor, guest_object['primary_disk'])
except KeyError as ex:
raise SoftLayerError("Unable to find capacity Flavor.") from ex
guest_object['flavor'] = flavor
guest_object['datacenter'] = capacity['backendRouter']['datacenter']['name']
# Reserved capacity only supports SAN as of 20181008
guest_object['local_disk'] = False
# Reserved capacity only supports monthly ordering via Virtual_Guest::generateOrderTemplate
# Hourly ordering would require building out the order manually.
guest_object['hourly'] = False
template = vs_manager.verify_create_instance(**guest_object)
template['reservedCapacityId'] = capacity_id
if guest_object.get('ipv6'):
ipv6_price = self.ordering_manager.get_price_id_list('PUBLIC_CLOUD_SERVER', ['1_IPV6_ADDRESS'])
template['prices'].append({'id': ipv6_price[0]})
if test:
result = self.client.call('Product_Order', 'verifyOrder', template)
else:
result = self.client.call('Product_Order', 'placeOrder', template)
return result
def _flavor_string(capacity_key, primary_disk):
"""Removed the _X_YEAR_TERM from capacity_key and adds the primary disk size, creating the flavor keyName
This will work fine unless 10 year terms are invented... or flavor format changes...
"""
flavor = "%sX%s" % (capacity_key[:-12], primary_disk)
return flavor