From 42ba6f53da239667beccd356891816b8dc793b86 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Mon, 17 Sep 2018 17:43:01 -0500 Subject: [PATCH 01/18] #1026 groundwork for capacity commands --- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/virt/capacity/__init__.py | 43 +++++++++++++++++++++++++ SoftLayer/CLI/virt/capacity/list.py | 16 +++++++++ 3 files changed, 60 insertions(+) create mode 100644 SoftLayer/CLI/virt/capacity/__init__.py create mode 100644 SoftLayer/CLI/virt/capacity/list.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 196616a8e..b486f0e67 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -30,6 +30,7 @@ ('virtual:reload', 'SoftLayer.CLI.virt.reload:cli'), ('virtual:upgrade', 'SoftLayer.CLI.virt.upgrade:cli'), ('virtual:credentials', 'SoftLayer.CLI.virt.credentials:cli'), + ('virtual:capacity', 'SoftLayer.CLI.virt.capacity:cli'), ('dedicatedhost', 'SoftLayer.CLI.dedicatedhost'), ('dedicatedhost:list', 'SoftLayer.CLI.dedicatedhost.list:cli'), diff --git a/SoftLayer/CLI/virt/capacity/__init__.py b/SoftLayer/CLI/virt/capacity/__init__.py new file mode 100644 index 000000000..8172594ae --- /dev/null +++ b/SoftLayer/CLI/virt/capacity/__init__.py @@ -0,0 +1,43 @@ +"""Manages Reserved Capacity.""" +# :license: MIT, see LICENSE for more details. +import importlib +import click +import types +import SoftLayer +import os +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + +from pprint import pprint as pp +class capacityCommands(click.MultiCommand): + """Loads module for capacity related commands.""" + + def __init__(self, *path, **attrs): + click.MultiCommand.__init__(self, **attrs) + self.path = os.path.dirname(__file__) + + def list_commands(self, ctx): + """List all sub-commands.""" + + rv = [] + for filename in os.listdir(self.path): + if filename == '__init__.py': + continue + if filename.endswith('.py'): + rv.append(filename[:-3]) + rv.sort() + pp(rv) + return rv + + def get_command(self, ctx, name): + """Get command for click.""" + path = "%s.%s" % (__name__, name) + module = importlib.import_module(path) + return getattr(module, 'cli') + +@click.group(cls=capacityCommands, + help="Manages virtual server reserved capacity") +@environment.pass_env +def cli(env): + """Manages Capacity""" + pass diff --git a/SoftLayer/CLI/virt/capacity/list.py b/SoftLayer/CLI/virt/capacity/list.py new file mode 100644 index 000000000..401f922f3 --- /dev/null +++ b/SoftLayer/CLI/virt/capacity/list.py @@ -0,0 +1,16 @@ +"""Manages Reserved Capacity.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + + + +@click.command() +@environment.pass_env +def cli(env): + """Manages Capacity""" + print("LIaaaaST") From cd3d417763aba91ad6b0c0bdc1b4ab481b7f8306 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Wed, 19 Sep 2018 17:22:01 -0500 Subject: [PATCH 02/18] #1026 functions for create-options --- SoftLayer/CLI/virt/capacity/__init__.py | 1 - SoftLayer/CLI/virt/capacity/create-options.py | 40 ++++++++++++++++ SoftLayer/CLI/virt/capacity/create.py | 19 ++++++++ SoftLayer/CLI/virt/capacity/list.py | 10 ++-- SoftLayer/managers/vs_capacity.py | 47 +++++++++++++++++++ 5 files changed, 113 insertions(+), 4 deletions(-) create mode 100644 SoftLayer/CLI/virt/capacity/create-options.py create mode 100644 SoftLayer/CLI/virt/capacity/create.py create mode 100644 SoftLayer/managers/vs_capacity.py diff --git a/SoftLayer/CLI/virt/capacity/__init__.py b/SoftLayer/CLI/virt/capacity/__init__.py index 8172594ae..baf47c453 100644 --- a/SoftLayer/CLI/virt/capacity/__init__.py +++ b/SoftLayer/CLI/virt/capacity/__init__.py @@ -26,7 +26,6 @@ def list_commands(self, ctx): if filename.endswith('.py'): rv.append(filename[:-3]) rv.sort() - pp(rv) return rv def get_command(self, ctx, name): diff --git a/SoftLayer/CLI/virt/capacity/create-options.py b/SoftLayer/CLI/virt/capacity/create-options.py new file mode 100644 index 000000000..e1f254e53 --- /dev/null +++ b/SoftLayer/CLI/virt/capacity/create-options.py @@ -0,0 +1,40 @@ +"""List options for creating Reserved Capacity""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.managers.vs_capacity import CapacityManager as CapacityManager + + +from pprint import pprint as pp + +@click.command() +@environment.pass_env +def cli(env): + """List options for creating Reserved Capacity""" + manager = CapacityManager(env.client) + items = manager.get_create_options() + items.sort(key=lambda term: int(term['capacity'])) + table = formatting.Table(["KeyName", "Description", "Term", "Hourly Price"], title="Reserved Capacity Options") + table.align["Hourly Price"] = "l" + table.align["Description"] = "l" + table.align["KeyName"] = "l" + for item in items: + table.add_row([ + item['keyName'], item['description'], item['capacity'], get_price(item) + ]) + # pp(items) + env.fout(table) + + +def get_price(item): + the_price = "No Default Pricing" + for price in item.get('prices',[]): + if price.get('locationGroupId') == '': + the_price = "%0.4f" % float(price['hourlyRecurringFee']) + return the_price + + diff --git a/SoftLayer/CLI/virt/capacity/create.py b/SoftLayer/CLI/virt/capacity/create.py new file mode 100644 index 000000000..e60d46bab --- /dev/null +++ b/SoftLayer/CLI/virt/capacity/create.py @@ -0,0 +1,19 @@ +"""Create a Reserved Capacity instance""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.managers.vs_capacity import CapacityManager as CapacityManager + + +from pprint import pprint as pp + +@click.command() +@environment.pass_env +def cli(env): + """Create a Reserved Capacity instance""" + manager = CapacityManager(env.client) + diff --git a/SoftLayer/CLI/virt/capacity/list.py b/SoftLayer/CLI/virt/capacity/list.py index 401f922f3..3d6811c8e 100644 --- a/SoftLayer/CLI/virt/capacity/list.py +++ b/SoftLayer/CLI/virt/capacity/list.py @@ -1,4 +1,4 @@ -"""Manages Reserved Capacity.""" +"""List Reserved Capacity""" # :license: MIT, see LICENSE for more details. import click @@ -6,11 +6,15 @@ import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer.managers.vs_capacity import CapacityManager as CapacityManager +from pprint import pprint as pp @click.command() @environment.pass_env def cli(env): - """Manages Capacity""" - print("LIaaaaST") + """List Reserved Capacity""" + manager = CapacityManager(env.client) + result = manager.list() + pp(result) diff --git a/SoftLayer/managers/vs_capacity.py b/SoftLayer/managers/vs_capacity.py new file mode 100644 index 000000000..12060df25 --- /dev/null +++ b/SoftLayer/managers/vs_capacity.py @@ -0,0 +1,47 @@ +""" + SoftLayer.vs_capacity + ~~~~~~~~~~~~~~~~~~~~~~~ + Reserved Capacity Manager and helpers + + :license: MIT, see License for more details. +""" + +import logging +import SoftLayer + +from SoftLayer.managers import ordering +from SoftLayer import utils + +# Invalid names are ignored due to long method names and short argument names +# pylint: disable=invalid-name, no-self-use + +LOGGER = logging.getLogger(__name__) + +class CapacityManager(utils.IdentifierMixin, object): + """Manages SoftLayer Dedicated Hosts. + + See product information here https://www.ibm.com/cloud/dedicated + + + :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. + """ + + def __init__(self, client, ordering_manager=None): + self.client = client + self.account = client['Account'] + self.capacity_package = 'RESERVED_CAPACITY' + + if ordering_manager is None: + self.ordering_manager = ordering.OrderingManager(client) + + def list(self): + results = self.client.call('Account', 'getReservedCapacityGroups') + return results + + def get_create_options(self): + mask = "mask[attributes,prices[pricingLocationGroup]]" + # mask = "mask[id, description, capacity, units]" + results = self.ordering_manager.list_items(self.capacity_package, mask=mask) + return results \ No newline at end of file From 475b1eb4539a9ce9a3511727d5942f9374d0c6e5 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 20 Sep 2018 17:51:27 -0500 Subject: [PATCH 03/18] got capacity create working --- SoftLayer/CLI/virt/capacity/create-options.py | 12 +- SoftLayer/CLI/virt/capacity/create.py | 60 +- SoftLayer/managers/vs_capacity.py | 841 +++++++++++++++++- 3 files changed, 908 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/virt/capacity/create-options.py b/SoftLayer/CLI/virt/capacity/create-options.py index e1f254e53..7edefdb73 100644 --- a/SoftLayer/CLI/virt/capacity/create-options.py +++ b/SoftLayer/CLI/virt/capacity/create-options.py @@ -26,9 +26,16 @@ def cli(env): table.add_row([ item['keyName'], item['description'], item['capacity'], get_price(item) ]) - # pp(items) env.fout(table) + regions = manager.get_available_routers() + location_table = formatting.Table(['Location', 'POD', 'BackendRouterId'], 'Orderable Locations') + for region in regions: + for location in region['locations']: + for pod in location['location']['pods']: + location_table.add_row([region['keyname'], pod['backendRouterName'], pod['backendRouterId']]) + env.fout(location_table) + def get_price(item): the_price = "No Default Pricing" @@ -37,4 +44,5 @@ def get_price(item): the_price = "%0.4f" % float(price['hourlyRecurringFee']) return the_price - +def get_router_ids(): + pass diff --git a/SoftLayer/CLI/virt/capacity/create.py b/SoftLayer/CLI/virt/capacity/create.py index e60d46bab..58e03dd7f 100644 --- a/SoftLayer/CLI/virt/capacity/create.py +++ b/SoftLayer/CLI/virt/capacity/create.py @@ -11,9 +11,65 @@ from pprint import pprint as pp -@click.command() +@click.command(epilog="See 'slcli vs capacity create-options' for valid options") +@click.option('--name', '-n', required=True, prompt=True, + help="Name for your new reserved capacity") +@click.option('--datacenter', '-d', required=True, prompt=True, + help="Datacenter shortname") +@click.option('--backend_router_id', '-b', required=True, prompt=True, + help="backendRouterId, create-options has a list of valid ids to use.") +@click.option('--capacity', '-c', required=True, prompt=True, + help="Capacity keyname (C1_2X2_1_YEAR_TERM for example).") +@click.option('--quantity', '-q', required=True, prompt=True, + help="Number of VSI instances this capacity reservation can support.") +@click.option('--test', is_flag=True, + help="Do not actually create the virtual server") @environment.pass_env -def cli(env): +def cli(env, name, datacenter, backend_router_id, capacity, quantity, test=False): """Create a Reserved Capacity instance""" manager = CapacityManager(env.client) + result = manager.create( + name=name, + datacenter=datacenter, + backend_router_id=backend_router_id, + capacity=capacity, + quantity=quantity, + test=test) + pp(result) + if test: + table = formating.Table(['Name', 'Value'], "Test Order") + container = result['orderContainers'][0] + table.add_row(['Name', container['name']]) + table.add_row(['Location'], container['locationObject']['longName']) + for price in container['prices']: + table.add_row([price['item']['keyName'], price['item']['description']]) + table.add_row(['Total', result['postTaxRecurring']]) + else: + table = formatting.Table(['Name', 'Value'], "Reciept") + table.add_row(['Order Date', result['orderDate']]) + table.add_row(['Order ID', result['orderId']]) + table.add_row(['status', result['placedOrder']['status']]) + for item in result['placedOrder']['items']: + table.add_row([item['categoryCode'], item['description']]) + table.add_row(['Total', result['orderDetails']['postTaxRecurring']]) + env.fout(table) + + +""" +Calling: SoftLayer_Product_Order::placeOrder( +id=None, +mask='', +filter='None', +args=( + {'orderContainers': [ + {'backendRouterId': 1079095, + 'name': 'cgallo-test-capacity', + 'quantity': 1, + 'packageId': 1059, + 'location': 1854895, + 'useHourlyPricing': True, + 'complexType': 'SoftLayer_Container_Product_Order_Virtual_ReservedCapacity', + 'prices': [{'id': 217633}]}]},), limit=None, offset=None)) +Resetting dropped connection: r237377.application.qadal0501.softlayer.local +""" \ No newline at end of file diff --git a/SoftLayer/managers/vs_capacity.py b/SoftLayer/managers/vs_capacity.py index 12060df25..23acf4457 100644 --- a/SoftLayer/managers/vs_capacity.py +++ b/SoftLayer/managers/vs_capacity.py @@ -12,6 +12,7 @@ from SoftLayer.managers import ordering from SoftLayer import utils +from pprint import pprint as pp # Invalid names are ignored due to long method names and short argument names # pylint: disable=invalid-name, no-self-use @@ -44,4 +45,842 @@ def get_create_options(self): mask = "mask[attributes,prices[pricingLocationGroup]]" # mask = "mask[id, description, capacity, units]" results = self.ordering_manager.list_items(self.capacity_package, mask=mask) - return results \ No newline at end of file + return results + + def get_available_routers(self): + """Pulls down all backendRouterIds that are available""" + 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) + _filter = {'datacenterName': {'operation': ''}} + routers = {} + + # Step 3, for each location in each region, get the pod details, which contains the router id + for region in regions: + routers[region['keyname']] = [] + for location in region['locations']: + location['location']['pods'] = list() + _filter['datacenterName']['operation'] = location['location']['name'] + location['location']['pods'] = self.client.call('Network_Pod', 'getAllObjects', filter=_filter) + + # Step 4, return the data. + return regions + + def create(self, name, datacenter, backend_router_id, capacity, quantity, test=False): + """Orders a Virtual_ReservedCapacityGroup""" + args = (self.capacity_package, datacenter, [capacity]) + extras = {"backendRouterId": backend_router_id, "name": name} + kwargs = { + 'extras': extras, + 'quantity': quantity, + '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 + + + +""" +{'orderDate': '2018-09-20T16:48:32-06:00', + 'orderDetails': {'bigDataOrderFlag': False, + 'billingInformation': {'billingAddressLine1': 'Addr1 307608', + 'billingAddressLine2': 'Addr2 307608', + 'billingCity': 'Dallas', + 'billingCountryCode': 'US', + 'billingEmail': 'noreply@softlayer.com', + 'billingNameCompany': 'Customer ' + '307608', + 'billingNameFirst': 'FName THREE ' + 'HUNDRED SEVEN ' + 'THOU', + 'billingNameLast': 'LName THREE ' + 'HUNDRED SEVEN ' + 'THOU', + 'billingPhoneVoice': '0000307608', + 'billingPostalCode': '75244-4608', + 'billingState': 'TX', + 'cardExpirationMonth': '', + 'cardExpirationYear': '', + 'euSupported': '', + 'taxExempt': 0}, + 'billingOrderItemId': '', + 'containerSplHash': '0000000065689e3f00007f66446132d6', + 'currencyShortName': 'USD', + 'extendedHardwareTesting': '', + 'gdprConsentFlag': '', + 'imageTemplateId': '', + 'isManagedOrder': '', + 'message': '', + 'orderContainers': [{'backendRouterId': 1079095, + 'bigDataOrderFlag': False, + 'billingOrderItemId': '', + 'containerSplHash': '0000000065689e3400007f66446132d6', + 'currencyShortName': 'USD', + 'extendedHardwareTesting': '', + 'gdprConsentFlag': '', + 'imageTemplateId': '', + 'isManagedOrder': '', + 'itemCategoryQuestionAnswers': [], + 'location': '1854895', + 'locationObject': {'id': 1854895, + 'longName': 'Dallas ' + '13', + 'name': 'dal13'}, + 'message': '', + 'name': 'cgallo-test-01', + 'packageId': 1059, + 'paymentType': '', + 'postTaxRecurring': '6.17', + 'postTaxRecurringHourly': '6.17', + 'postTaxRecurringMonthly': '0', + 'postTaxSetup': '0', + 'preTaxRecurring': '6.17', + 'preTaxRecurringHourly': '6.17', + 'preTaxRecurringMonthly': '0', + 'preTaxSetup': '0', + 'presetId': '', + 'prices': [{'categories': [{'categoryCode': 'reserved_capacity', + 'id': 2060, + 'name': 'Reserved ' + 'Capacity'}], + 'hourlyRecurringFee': '.617', + 'id': 217633, + 'item': {'bundle': [{'bundleItem': {'capacity': '12', + 'description': 'B1.16x64 ' + '(1 ' + 'Year ' + 'Term)', + 'id': 12309, + 'keyName': 'B1_16X64_1_YEAR_TERM', + 'units': 'MONTHS'}, + 'bundleItemId': 12309, + 'category': {'categoryCode': 'guest_core', + 'id': 80, + 'name': 'Computing ' + 'Instance'}, + 'id': 44720, + 'itemPrice': {'hourlyRecurringFee': '0', + 'id': 210185, + 'itemId': 1194, + 'laborFee': '0', + 'oneTimeFee': '0', + 'recurringFee': '0', + 'setupFee': '0'}, + 'itemPriceId': 210185}, + {'bundleItem': {'capacity': '12', + 'description': 'B1.16x64 ' + '(1 ' + 'Year ' + 'Term)', + 'id': 12309, + 'keyName': 'B1_16X64_1_YEAR_TERM', + 'units': 'MONTHS'}, + 'bundleItemId': 12309, + 'category': {'categoryCode': 'ram', + 'id': 3, + 'name': 'RAM'}, + 'id': 44726, + 'itemPrice': {'hourlyRecurringFee': '0', + 'id': 216607, + 'itemId': 10575, + 'laborFee': '0', + 'oneTimeFee': '0', + 'recurringFee': '0', + 'setupFee': '0'}, + 'itemPriceId': 216607}], + 'capacity': '12', + 'description': 'B1.16x64 ' + '(1 ' + 'Year ' + 'Term)', + 'id': 12309, + 'keyName': 'B1_16X64_1_YEAR_TERM', + 'thirdPartyPolicyAssignments': [], + 'units': 'MONTHS'}, + 'itemId': 12309, + 'laborFee': '0', + 'oneTimeFee': '0', + 'recurringFee': '0', + 'setupFee': '0'}], + 'primaryDiskPartitionId': '', + 'privateCloudOrderFlag': False, + 'proratedInitialCharge': '0', + 'proratedOrderTotal': '0', + 'quantity': 10, + 'resourceGroupId': '', + 'resourceGroupTemplateId': '', + 'sendQuoteEmailFlag': '', + 'serverCoreCount': '', + 'sourceVirtualGuestId': '', + 'sshKeys': [], + 'stepId': '', + 'storageGroups': [], + 'taxCacheHash': '2ce690dee89b73a1653785d3032af6c5d5dd88de', + 'taxCompletedFlag': False, + 'totalRecurringTax': '0', + 'totalSetupTax': '0', + 'useHourlyPricing': True}], + 'packageId': '', + 'paymentType': 'ADD_TO_BALANCE', + 'postTaxRecurring': '6.17', + 'postTaxRecurringHourly': '6.17', + 'postTaxRecurringMonthly': '0', + 'postTaxSetup': '0', + 'preTaxRecurring': '6.17', + 'preTaxRecurringHourly': '6.17', + 'preTaxRecurringMonthly': '0', + 'preTaxSetup': '0', + 'presetId': '', + 'prices': [], + 'primaryDiskPartitionId': '', + 'privateCloudOrderFlag': False, + 'properties': [], + 'proratedInitialCharge': '0', + 'proratedOrderTotal': '0', + 'quantity': '', + 'resourceGroupId': '', + 'resourceGroupTemplateId': '', + 'sendQuoteEmailFlag': '', + 'serverCoreCount': '', + 'sourceVirtualGuestId': '', + 'sshKeys': [], + 'stepId': '', + 'storageGroups': [], + 'taxCompletedFlag': False, + 'totalRecurringTax': '0', + 'totalSetupTax': '0', + 'useHourlyPricing': ''}, + 'orderId': 29297264, + 'placedOrder': {'account': {'brandId': 2, + 'companyName': 'Customer 307608', + 'id': 307608}, + 'accountId': 307608, + 'id': 29297264, + 'items': [{'categoryCode': 'reserved_capacity', + 'children': [{'categoryCode': 'guest_core', + 'description': '16 x 2.0 GHz or ' + 'higher Cores', + 'hourlyRecurringFee': '0', + 'id': 371550142, + 'itemId': 1194, + 'itemPriceId': '210185', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': 371550140, + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}, + {'categoryCode': 'ram', + 'description': '64 GB', + 'hourlyRecurringFee': '0', + 'id': 371550144, + 'itemId': 10575, + 'itemPriceId': '216607', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': 371550140, + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}], + 'description': 'B1.16x64 (1 Year Term)', + 'hourlyRecurringFee': '.617', + 'id': 371550140, + 'itemId': 12309, + 'itemPriceId': '217633', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': '', + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}, + {'categoryCode': 'guest_core', + 'description': '16 x 2.0 GHz or higher Cores', + 'hourlyRecurringFee': '0', + 'id': 371550142, + 'itemId': 1194, + 'itemPriceId': '210185', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': 371550140, + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}, + {'categoryCode': 'ram', + 'description': '64 GB', + 'hourlyRecurringFee': '0', + 'id': 371550144, + 'itemId': 10575, + 'itemPriceId': '216607', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': 371550140, + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}, + {'categoryCode': 'reserved_capacity', + 'children': [{'categoryCode': 'guest_core', + 'description': '16 x 2.0 GHz or ' + 'higher Cores', + 'hourlyRecurringFee': '0', + 'id': 371550148, + 'itemId': 1194, + 'itemPriceId': '210185', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': 371550146, + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}, + {'categoryCode': 'ram', + 'description': '64 GB', + 'hourlyRecurringFee': '0', + 'id': 371550150, + 'itemId': 10575, + 'itemPriceId': '216607', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': 371550146, + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}], + 'description': 'B1.16x64 (1 Year Term)', + 'hourlyRecurringFee': '.617', + 'id': 371550146, + 'itemId': 12309, + 'itemPriceId': '217633', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': '', + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}, + {'categoryCode': 'guest_core', + 'description': '16 x 2.0 GHz or higher Cores', + 'hourlyRecurringFee': '0', + 'id': 371550148, + 'itemId': 1194, + 'itemPriceId': '210185', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': 371550146, + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}, + {'categoryCode': 'ram', + 'description': '64 GB', + 'hourlyRecurringFee': '0', + 'id': 371550150, + 'itemId': 10575, + 'itemPriceId': '216607', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': 371550146, + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}, + {'categoryCode': 'reserved_capacity', + 'children': [{'categoryCode': 'guest_core', + 'description': '16 x 2.0 GHz or ' + 'higher Cores', + 'hourlyRecurringFee': '0', + 'id': 371550154, + 'itemId': 1194, + 'itemPriceId': '210185', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': 371550152, + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}, + {'categoryCode': 'ram', + 'description': '64 GB', + 'hourlyRecurringFee': '0', + 'id': 371550156, + 'itemId': 10575, + 'itemPriceId': '216607', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': 371550152, + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}], + 'description': 'B1.16x64 (1 Year Term)', + 'hourlyRecurringFee': '.617', + 'id': 371550152, + 'itemId': 12309, + 'itemPriceId': '217633', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': '', + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}, + {'categoryCode': 'guest_core', + 'description': '16 x 2.0 GHz or higher Cores', + 'hourlyRecurringFee': '0', + 'id': 371550154, + 'itemId': 1194, + 'itemPriceId': '210185', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': 371550152, + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}, + {'categoryCode': 'ram', + 'description': '64 GB', + 'hourlyRecurringFee': '0', + 'id': 371550156, + 'itemId': 10575, + 'itemPriceId': '216607', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': 371550152, + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}, + {'categoryCode': 'reserved_capacity', + 'children': [{'categoryCode': 'guest_core', + 'description': '16 x 2.0 GHz or ' + 'higher Cores', + 'hourlyRecurringFee': '0', + 'id': 371550160, + 'itemId': 1194, + 'itemPriceId': '210185', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': 371550158, + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}, + {'categoryCode': 'ram', + 'description': '64 GB', + 'hourlyRecurringFee': '0', + 'id': 371550162, + 'itemId': 10575, + 'itemPriceId': '216607', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': 371550158, + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}], + 'description': 'B1.16x64 (1 Year Term)', + 'hourlyRecurringFee': '.617', + 'id': 371550158, + 'itemId': 12309, + 'itemPriceId': '217633', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': '', + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}, + {'categoryCode': 'guest_core', + 'description': '16 x 2.0 GHz or higher Cores', + 'hourlyRecurringFee': '0', + 'id': 371550160, + 'itemId': 1194, + 'itemPriceId': '210185', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': 371550158, + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}, + {'categoryCode': 'ram', + 'description': '64 GB', + 'hourlyRecurringFee': '0', + 'id': 371550162, + 'itemId': 10575, + 'itemPriceId': '216607', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': 371550158, + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}, + {'categoryCode': 'reserved_capacity', + 'children': [{'categoryCode': 'guest_core', + 'description': '16 x 2.0 GHz or ' + 'higher Cores', + 'hourlyRecurringFee': '0', + 'id': 371550166, + 'itemId': 1194, + 'itemPriceId': '210185', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': 371550164, + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}, + {'categoryCode': 'ram', + 'description': '64 GB', + 'hourlyRecurringFee': '0', + 'id': 371550168, + 'itemId': 10575, + 'itemPriceId': '216607', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': 371550164, + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}], + 'description': 'B1.16x64 (1 Year Term)', + 'hourlyRecurringFee': '.617', + 'id': 371550164, + 'itemId': 12309, + 'itemPriceId': '217633', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': '', + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}, + {'categoryCode': 'guest_core', + 'description': '16 x 2.0 GHz or higher Cores', + 'hourlyRecurringFee': '0', + 'id': 371550166, + 'itemId': 1194, + 'itemPriceId': '210185', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': 371550164, + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}, + {'categoryCode': 'ram', + 'description': '64 GB', + 'hourlyRecurringFee': '0', + 'id': 371550168, + 'itemId': 10575, + 'itemPriceId': '216607', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': 371550164, + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}, + {'categoryCode': 'reserved_capacity', + 'children': [{'categoryCode': 'guest_core', + 'description': '16 x 2.0 GHz or ' + 'higher Cores', + 'hourlyRecurringFee': '0', + 'id': 371550172, + 'itemId': 1194, + 'itemPriceId': '210185', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': 371550170, + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}, + {'categoryCode': 'ram', + 'description': '64 GB', + 'hourlyRecurringFee': '0', + 'id': 371550174, + 'itemId': 10575, + 'itemPriceId': '216607', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': 371550170, + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}], + 'description': 'B1.16x64 (1 Year Term)', + 'hourlyRecurringFee': '.617', + 'id': 371550170, + 'itemId': 12309, + 'itemPriceId': '217633', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': '', + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}, + {'categoryCode': 'guest_core', + 'description': '16 x 2.0 GHz or higher Cores', + 'hourlyRecurringFee': '0', + 'id': 371550172, + 'itemId': 1194, + 'itemPriceId': '210185', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': 371550170, + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}, + {'categoryCode': 'ram', + 'description': '64 GB', + 'hourlyRecurringFee': '0', + 'id': 371550174, + 'itemId': 10575, + 'itemPriceId': '216607', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': 371550170, + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}, + {'categoryCode': 'reserved_capacity', + 'children': [{'categoryCode': 'guest_core', + 'description': '16 x 2.0 GHz or ' + 'higher Cores', + 'hourlyRecurringFee': '0', + 'id': 371550178, + 'itemId': 1194, + 'itemPriceId': '210185', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': 371550176, + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}, + {'categoryCode': 'ram', + 'description': '64 GB', + 'hourlyRecurringFee': '0', + 'id': 371550180, + 'itemId': 10575, + 'itemPriceId': '216607', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': 371550176, + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}], + 'description': 'B1.16x64 (1 Year Term)', + 'hourlyRecurringFee': '.617', + 'id': 371550176, + 'itemId': 12309, + 'itemPriceId': '217633', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': '', + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}, + {'categoryCode': 'guest_core', + 'description': '16 x 2.0 GHz or higher Cores', + 'hourlyRecurringFee': '0', + 'id': 371550178, + 'itemId': 1194, + 'itemPriceId': '210185', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': 371550176, + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}, + {'categoryCode': 'ram', + 'description': '64 GB', + 'hourlyRecurringFee': '0', + 'id': 371550180, + 'itemId': 10575, + 'itemPriceId': '216607', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': 371550176, + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}, + {'categoryCode': 'reserved_capacity', + 'children': [{'categoryCode': 'guest_core', + 'description': '16 x 2.0 GHz or ' + 'higher Cores', + 'hourlyRecurringFee': '0', + 'id': 371550184, + 'itemId': 1194, + 'itemPriceId': '210185', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': 371550182, + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}, + {'categoryCode': 'ram', + 'description': '64 GB', + 'hourlyRecurringFee': '0', + 'id': 371550186, + 'itemId': 10575, + 'itemPriceId': '216607', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': 371550182, + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}], + 'description': 'B1.16x64 (1 Year Term)', + 'hourlyRecurringFee': '.617', + 'id': 371550182, + 'itemId': 12309, + 'itemPriceId': '217633', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': '', + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}, + {'categoryCode': 'guest_core', + 'description': '16 x 2.0 GHz or higher Cores', + 'hourlyRecurringFee': '0', + 'id': 371550184, + 'itemId': 1194, + 'itemPriceId': '210185', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': 371550182, + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}, + {'categoryCode': 'ram', + 'description': '64 GB', + 'hourlyRecurringFee': '0', + 'id': 371550186, + 'itemId': 10575, + 'itemPriceId': '216607', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': 371550182, + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}, + {'categoryCode': 'reserved_capacity', + 'children': [{'categoryCode': 'guest_core', + 'description': '16 x 2.0 GHz or ' + 'higher Cores', + 'hourlyRecurringFee': '0', + 'id': 371550190, + 'itemId': 1194, + 'itemPriceId': '210185', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': 371550188, + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}, + {'categoryCode': 'ram', + 'description': '64 GB', + 'hourlyRecurringFee': '0', + 'id': 371550192, + 'itemId': 10575, + 'itemPriceId': '216607', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': 371550188, + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}], + 'description': 'B1.16x64 (1 Year Term)', + 'hourlyRecurringFee': '.617', + 'id': 371550188, + 'itemId': 12309, + 'itemPriceId': '217633', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': '', + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}, + {'categoryCode': 'guest_core', + 'description': '16 x 2.0 GHz or higher Cores', + 'hourlyRecurringFee': '0', + 'id': 371550190, + 'itemId': 1194, + 'itemPriceId': '210185', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': 371550188, + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}, + {'categoryCode': 'ram', + 'description': '64 GB', + 'hourlyRecurringFee': '0', + 'id': 371550192, + 'itemId': 10575, + 'itemPriceId': '216607', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': 371550188, + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}, + {'categoryCode': 'reserved_capacity', + 'children': [{'categoryCode': 'guest_core', + 'description': '16 x 2.0 GHz or ' + 'higher Cores', + 'hourlyRecurringFee': '0', + 'id': 371550196, + 'itemId': 1194, + 'itemPriceId': '210185', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': 371550194, + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}, + {'categoryCode': 'ram', + 'description': '64 GB', + 'hourlyRecurringFee': '0', + 'id': 371550198, + 'itemId': 10575, + 'itemPriceId': '216607', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': 371550194, + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}], + 'description': 'B1.16x64 (1 Year Term)', + 'hourlyRecurringFee': '.617', + 'id': 371550194, + 'itemId': 12309, + 'itemPriceId': '217633', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': '', + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}, + {'categoryCode': 'guest_core', + 'description': '16 x 2.0 GHz or higher Cores', + 'hourlyRecurringFee': '0', + 'id': 371550196, + 'itemId': 1194, + 'itemPriceId': '210185', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': 371550194, + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}, + {'categoryCode': 'ram', + 'description': '64 GB', + 'hourlyRecurringFee': '0', + 'id': 371550198, + 'itemId': 10575, + 'itemPriceId': '216607', + 'laborFee': '0', + 'oneTimeFee': '0', + 'parentId': 371550194, + 'promoCodeId': '', + 'recurringFee': '0', + 'setupFee': '0'}], + 'orderQuoteId': '', + 'orderTypeId': 4, + 'presaleEventId': '', + 'status': 'PENDING_AUTO_APPROVAL', + 'userRecord': {'accountId': 307608, + 'firstName': 'Christopher', + 'id': 167758, + 'lastName': 'Gallo', + 'username': 'SL307608'}, + 'userRecordId': 167758}} +""" \ No newline at end of file From af4fd92d79883dd2eb61321d63946420d9425df0 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Mon, 24 Sep 2018 17:14:00 -0500 Subject: [PATCH 04/18] list and detail support for capacity groups --- SoftLayer/CLI/virt/capacity/detail.py | 33 +++++++++++++++++++++++++++ SoftLayer/CLI/virt/capacity/list.py | 21 ++++++++++++++++- SoftLayer/managers/vs_capacity.py | 9 +++++++- 3 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 SoftLayer/CLI/virt/capacity/detail.py diff --git a/SoftLayer/CLI/virt/capacity/detail.py b/SoftLayer/CLI/virt/capacity/detail.py new file mode 100644 index 000000000..3e621d60b --- /dev/null +++ b/SoftLayer/CLI/virt/capacity/detail.py @@ -0,0 +1,33 @@ +"""Shows the details of a reserved capacity group""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.managers.vs_capacity import CapacityManager as CapacityManager + + +from pprint import pprint as pp + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Reserved Capacity Group Details""" + manager = CapacityManager(env.client) + result = manager.get_object(identifier) + try: + flavor = result['instances'][0]['billingItem']['description'] + except KeyError: + flavor = "Pending Approval..." + + table = formatting.Table( + ["ID", "Created"], + title= "%s - %s" % (result.get('name'), flavor) + ) + for rci in result['instances']: + table.add_row([rci['guestId'], rci['createDate']]) + env.fout(table) + diff --git a/SoftLayer/CLI/virt/capacity/list.py b/SoftLayer/CLI/virt/capacity/list.py index 3d6811c8e..42d03e743 100644 --- a/SoftLayer/CLI/virt/capacity/list.py +++ b/SoftLayer/CLI/virt/capacity/list.py @@ -17,4 +17,23 @@ def cli(env): """List Reserved Capacity""" manager = CapacityManager(env.client) result = manager.list() - pp(result) + table = formatting.Table( + ["ID", "Name", "Capacity", "Flavor", "Instance Cost", "Created"], + title="Reserved Capacity" + ) + for rc in result: + occupied_string = "#" * int(rc.get('occupiedInstancesCount',0)) + available_string = "-" * int(rc.get('availableInstanceCount',0)) + + try: + flavor = rc['instances'][0]['billingItem']['description'] + cost = float(rc['instances'][0]['billingItem']['hourlyRecurringFee']) + instance_count = int(rc.get('instanceCount',0)) + cost_string = "%s * %s = %s" % (cost, instance_count, cost * instance_count) + except KeyError: + flavor = "Unknown Billing Item" + cost_string = "-" + capacity = "%s%s" % (occupied_string, available_string) + table.add_row([rc['id'], rc['name'], capacity, flavor, cost_string, rc['createDate']]) + env.fout(table) + # pp(result) diff --git a/SoftLayer/managers/vs_capacity.py b/SoftLayer/managers/vs_capacity.py index 23acf4457..39dec0698 100644 --- a/SoftLayer/managers/vs_capacity.py +++ b/SoftLayer/managers/vs_capacity.py @@ -33,14 +33,21 @@ 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) def list(self): - results = self.client.call('Account', 'getReservedCapacityGroups') + mask = "mask[availableInstanceCount, occupiedInstanceCount, instances[billingItem], instanceCount]" + results = self.client.call('Account', 'getReservedCapacityGroups', mask=mask) return results + def get_object(self, identifier): + mask = "mask[instances[billingItem]]" + result = self.client.call(self.rcg_service, 'getObject', id=identifier, mask=mask) + return result + def get_create_options(self): mask = "mask[attributes,prices[pricingLocationGroup]]" # mask = "mask[id, description, capacity, units]" From 87b51a93a3b542fdef537c3ff7b2be6fb9ad8fe3 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Mon, 24 Sep 2018 17:58:41 -0500 Subject: [PATCH 05/18] create-guest base files --- SoftLayer/CLI/virt/capacity/create-guest.py | 17 +++++++++++++++++ SoftLayer/CLI/virt/capacity/list.py | 2 +- SoftLayer/managers/vs_capacity.py | 3 +++ 3 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 SoftLayer/CLI/virt/capacity/create-guest.py diff --git a/SoftLayer/CLI/virt/capacity/create-guest.py b/SoftLayer/CLI/virt/capacity/create-guest.py new file mode 100644 index 000000000..4ef9fee98 --- /dev/null +++ b/SoftLayer/CLI/virt/capacity/create-guest.py @@ -0,0 +1,17 @@ +"""List Reserved Capacity""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.managers.vs_capacity import CapacityManager as CapacityManager + + +from pprint import pprint as pp + +@click.command() +@environment.pass_env +def cli(env): + print("This is where you would create a guest") \ No newline at end of file diff --git a/SoftLayer/CLI/virt/capacity/list.py b/SoftLayer/CLI/virt/capacity/list.py index 42d03e743..f43e304be 100644 --- a/SoftLayer/CLI/virt/capacity/list.py +++ b/SoftLayer/CLI/virt/capacity/list.py @@ -36,4 +36,4 @@ def cli(env): capacity = "%s%s" % (occupied_string, available_string) table.add_row([rc['id'], rc['name'], capacity, flavor, cost_string, rc['createDate']]) env.fout(table) - # pp(result) + pp(result) diff --git a/SoftLayer/managers/vs_capacity.py b/SoftLayer/managers/vs_capacity.py index 39dec0698..15bc6be98 100644 --- a/SoftLayer/managers/vs_capacity.py +++ b/SoftLayer/managers/vs_capacity.py @@ -92,6 +92,9 @@ def create(self, name, datacenter, backend_router_id, capacity, quantity, test=F receipt = self.ordering_manager.place_order(*args, **kwargs) return receipt + def create_guest(self): + + """ From 39f9e1b35920f90efe290e336c219aedf59867d2 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 27 Sep 2018 17:50:14 -0500 Subject: [PATCH 06/18] support for creating guests, some more features for list and detail --- SoftLayer/CLI/virt/capacity/create-guest.py | 130 ++- SoftLayer/CLI/virt/capacity/create.py | 3 +- SoftLayer/CLI/virt/capacity/detail.py | 60 +- SoftLayer/CLI/virt/capacity/list.py | 16 +- SoftLayer/managers/vs_capacity.py | 840 +------------------- 5 files changed, 230 insertions(+), 819 deletions(-) diff --git a/SoftLayer/CLI/virt/capacity/create-guest.py b/SoftLayer/CLI/virt/capacity/create-guest.py index 4ef9fee98..f693d336f 100644 --- a/SoftLayer/CLI/virt/capacity/create-guest.py +++ b/SoftLayer/CLI/virt/capacity/create-guest.py @@ -6,12 +6,138 @@ import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers +from SoftLayer.CLI.virt.create import _update_with_like_args as _update_with_like_args from SoftLayer.managers.vs_capacity import CapacityManager as CapacityManager from pprint import pprint as pp + + +def _parse_create_args(client, args): + """Parses CLI arguments into a single data structure to be used by vs_capacity::create_guest. + + :param dict args: CLI arguments + """ + data = { + "hourly": True, + "domain": args['domain'], + "hostname": args['hostname'], + "private": args['private'], + "disks": args['disk'], + "boot_mode": args.get('boot_mode', None), + "local_disk": None + } + if args.get('os'): + data['os_code'] = args['os'] + + if args.get('image'): + if args.get('image').isdigit(): + image_mgr = SoftLayer.ImageManager(client) + image_details = image_mgr.get_image(args.get('image'), + mask="id,globalIdentifier") + data['image_id'] = image_details['globalIdentifier'] + else: + data['image_id'] = args['image'] + + if args.get('network'): + data['nic_speed'] = args.get('network') + + if args.get('userdata'): + data['userdata'] = args['userdata'] + elif args.get('userfile'): + with open(args['userfile'], 'r') as userfile: + data['userdata'] = userfile.read() + + if args.get('postinstall'): + data['post_uri'] = args.get('postinstall') + + # Get the SSH keys + if args.get('key'): + keys = [] + for key in args.get('key'): + resolver = SoftLayer.SshKeyManager(client).resolve_ids + key_id = helpers.resolve_id(resolver, key, 'SshKey') + keys.append(key_id) + data['ssh_keys'] = keys + + if args.get('vlan_public'): + data['public_vlan'] = args['vlan_public'] + + if args.get('vlan_private'): + data['private_vlan'] = args['vlan_private'] + + data['public_subnet'] = args.get('subnet_public', None) + + data['private_subnet'] = args.get('subnet_private', None) + + if args.get('public_security_group'): + pub_groups = args.get('public_security_group') + data['public_security_groups'] = [group for group in pub_groups] + + if args.get('private_security_group'): + priv_groups = args.get('private_security_group') + data['private_security_groups'] = [group for group in priv_groups] + + if args.get('tag'): + data['tags'] = ','.join(args['tag']) + + if args.get('host_id'): + data['host_id'] = args['host_id'] + + if args.get('ipv6'): + data['ipv6'] = True + + data['primary_disk'] = args.get('primary_disk') + + return data + + @click.command() +@click.option('--capacity-id', type=click.INT, help="Reserve capacity Id to provision this guest into.") +@click.option('--primary-disk', type=click.Choice(['25','100']), default='25', help="Size of the main drive." ) +@click.option('--hostname', '-H', required=True, prompt=True, help="Host portion of the FQDN.") +@click.option('--domain', '-D', required=True, prompt=True, help="Domain portion of the FQDN.") +@click.option('--os', '-o', help="OS install code. Tip: you can specify _LATEST.") +@click.option('--image', help="Image ID. See: 'slcli image list' for reference.") +@click.option('--boot-mode', type=click.STRING, + help="Specify the mode to boot the OS in. Supported modes are HVM and PV.") +@click.option('--postinstall', '-i', help="Post-install script to download.") +@helpers.multi_option('--key', '-k', help="SSH keys to add to the root user.") +@helpers.multi_option('--disk', help="Additional disk sizes.") +@click.option('--private', is_flag=True, help="Forces the VS to only have access the private network.") +@click.option('--like', is_eager=True, callback=_update_with_like_args, + help="Use the configuration from an existing VS.") +@click.option('--network', '-n', help="Network port speed in Mbps.") +@helpers.multi_option('--tag', '-g', help="Tags to add to the instance.") +@click.option('--userdata', '-u', help="User defined metadata string.") +@click.option('--ipv6', is_flag=True, help="Adds an IPv6 address to this guest") +@click.option('--test', is_flag=True, + help="Test order, will return the order container, but not actually order a server.") @environment.pass_env -def cli(env): - print("This is where you would create a guest") \ No newline at end of file +def cli(env, **args): + create_args = _parse_create_args(env.client, args) + manager = CapacityManager(env.client) + capacity_id = args.get('capacity_id') + test = args.get('test') + + result = manager.create_guest(capacity_id, test, create_args) + + env.fout(_build_receipt(result, test)) + + +def _build_receipt(result, test=False): + title = "OrderId: %s" % (result.get('orderId', 'No order placed')) + table = formatting.Table(['Item Id', 'Description'], title=title) + table.align['Description'] = 'l' + + if test: + prices = result['prices'] + else: + prices = result['orderDetails']['prices'] + + for item in prices: + table.add_row([item['id'], item['item']['description']]) + return table + diff --git a/SoftLayer/CLI/virt/capacity/create.py b/SoftLayer/CLI/virt/capacity/create.py index 58e03dd7f..b1004fb97 100644 --- a/SoftLayer/CLI/virt/capacity/create.py +++ b/SoftLayer/CLI/virt/capacity/create.py @@ -11,7 +11,8 @@ from pprint import pprint as pp -@click.command(epilog="See 'slcli vs capacity create-options' for valid options") +@click.command(epilog=click.style("""WARNING: Reserved Capacity is on a yearly contract""" + """ and not cancelable until the contract is expired.""", fg='red')) @click.option('--name', '-n', required=True, prompt=True, help="Name for your new reserved capacity") @click.option('--datacenter', '-d', required=True, prompt=True, diff --git a/SoftLayer/CLI/virt/capacity/detail.py b/SoftLayer/CLI/virt/capacity/detail.py index 3e621d60b..0aced53f4 100644 --- a/SoftLayer/CLI/virt/capacity/detail.py +++ b/SoftLayer/CLI/virt/capacity/detail.py @@ -4,30 +4,74 @@ import click import SoftLayer +from SoftLayer.CLI import columns as column_helper from SoftLayer.CLI import environment from SoftLayer.CLI import formatting from SoftLayer.managers.vs_capacity import CapacityManager as CapacityManager +COLUMNS = [ + column_helper.Column('guid', ('globalIdentifier',)), + column_helper.Column('primary_ip', ('primaryIpAddress',)), + column_helper.Column('backend_ip', ('primaryBackendIpAddress',)), + column_helper.Column('datacenter', ('datacenter', 'name')), + column_helper.Column('action', lambda guest: formatting.active_txn(guest), + mask=''' + activeTransaction[ + id,transactionStatus[name,friendlyName] + ]'''), + column_helper.Column('power_state', ('powerState', 'name')), + column_helper.Column( + 'created_by', + ('billingItem', 'orderItem', 'order', 'userRecord', 'username')), + column_helper.Column( + 'tags', + lambda server: formatting.tags(server.get('tagReferences')), + mask="tagReferences.tag.name"), +] -from pprint import pprint as pp +DEFAULT_COLUMNS = [ + 'id', + 'hostname', + 'domain', + 'primary_ip', + 'backend_ip' +] -@click.command() +@click.command(epilog="Once provisioned, virtual guests can be managed with the slcli vs commands") @click.argument('identifier') +@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): +def cli(env, identifier, columns): """Reserved Capacity Group Details""" manager = CapacityManager(env.client) - result = manager.get_object(identifier) + mask = "mask[instances[billingItem[category], guest]]" + result = manager.get_object(identifier, mask) try: flavor = result['instances'][0]['billingItem']['description'] except KeyError: flavor = "Pending Approval..." - table = formatting.Table( - ["ID", "Created"], - title= "%s - %s" % (result.get('name'), flavor) + table = formatting.Table(columns.columns, + title = "%s - %s" % (result.get('name'), flavor) ) for rci in result['instances']: - table.add_row([rci['guestId'], rci['createDate']]) + guest = rci.get('guest', None) + guest_string = "---" + createDate = rci['createDate'] + if guest is not None: + guest_string = "%s (%s)" % ( + guest.get('fullyQualifiedDomainName', 'No FQDN'), + guest.get('primaryIpAddress', 'No Public Ip') + ) + createDate = guest['modifyDate'] + table.add_row([value or formatting.blank() for value in columns.row(guest)]) + else: + table.add_row(['-' for value in columns.columns]) env.fout(table) + diff --git a/SoftLayer/CLI/virt/capacity/list.py b/SoftLayer/CLI/virt/capacity/list.py index f43e304be..31f8d672c 100644 --- a/SoftLayer/CLI/virt/capacity/list.py +++ b/SoftLayer/CLI/virt/capacity/list.py @@ -18,22 +18,24 @@ def cli(env): manager = CapacityManager(env.client) result = manager.list() table = formatting.Table( - ["ID", "Name", "Capacity", "Flavor", "Instance Cost", "Created"], + ["ID", "Name", "Capacity", "Flavor", "Location", "Created"], title="Reserved Capacity" ) for rc in result: - occupied_string = "#" * int(rc.get('occupiedInstancesCount',0)) + occupied_string = "#" * int(rc.get('occupiedInstanceCount',0)) available_string = "-" * int(rc.get('availableInstanceCount',0)) try: flavor = rc['instances'][0]['billingItem']['description'] cost = float(rc['instances'][0]['billingItem']['hourlyRecurringFee']) - instance_count = int(rc.get('instanceCount',0)) - cost_string = "%s * %s = %s" % (cost, instance_count, cost * instance_count) + # instance_count = int(rc.get('instanceCount',0)) + # cost_string = "%s * %s = %s" % (cost, instance_count, cost * instance_count) except KeyError: flavor = "Unknown Billing Item" - cost_string = "-" + # cost_string = "-" + location = rc['backendRouter']['hostname'] capacity = "%s%s" % (occupied_string, available_string) - table.add_row([rc['id'], rc['name'], capacity, flavor, cost_string, rc['createDate']]) + table.add_row([rc['id'], rc['name'], capacity, flavor, location, rc['createDate']]) env.fout(table) - pp(result) + print("") + # pp(result) diff --git a/SoftLayer/managers/vs_capacity.py b/SoftLayer/managers/vs_capacity.py index 15bc6be98..7d6160240 100644 --- a/SoftLayer/managers/vs_capacity.py +++ b/SoftLayer/managers/vs_capacity.py @@ -10,6 +10,7 @@ import SoftLayer from SoftLayer.managers import ordering +from SoftLayer.managers.vs import VSManager from SoftLayer import utils from pprint import pprint as pp @@ -39,12 +40,14 @@ def __init__(self, client, ordering_manager=None): self.ordering_manager = ordering.OrderingManager(client) def list(self): - mask = "mask[availableInstanceCount, occupiedInstanceCount, instances[billingItem], instanceCount]" + mask = """mask[availableInstanceCount, occupiedInstanceCount, +instances[id, billingItem[description, hourlyRecurringFee]], instanceCount, backendRouter[datacenter]]""" results = self.client.call('Account', 'getReservedCapacityGroups', mask=mask) return results - def get_object(self, identifier): - mask = "mask[instances[billingItem]]" + def get_object(self, identifier, mask=None): + 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 @@ -92,805 +95,40 @@ def create(self, name, datacenter, backend_router_id, capacity, quantity, test=F receipt = self.ordering_manager.place_order(*args, **kwargs) return receipt - def create_guest(self): + def create_guest(self, capacity_id, test, guest_object): + vs_manager = VSManager(self.client) + mask = "mask[instances[id, billingItem[id, item[id,keyName]]], backendRouter[id, datacenter[name]]]" + capacity = self.get_object(capacity_id) + try: + capacity_flavor = capacity['instances'][0]['billingItem']['item']['keyName'] + flavor = _flavor_string(capacity_flavor, guest_object['primary_disk']) + except KeyError: + raise SoftLayer.SoftLayerError("Unable to find capacity Flavor.") + + guest_object['flavor'] = flavor + guest_object['datacenter'] = capacity['backendRouter']['datacenter']['name'] + # pp(guest_object) + 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]}) + + # pp(template) + if test: + result = self.client.call('Product_Order', 'verifyOrder', template) + else: + result = self.client.call('Product_Order', 'placeOrder', template) + return result -""" -{'orderDate': '2018-09-20T16:48:32-06:00', - 'orderDetails': {'bigDataOrderFlag': False, - 'billingInformation': {'billingAddressLine1': 'Addr1 307608', - 'billingAddressLine2': 'Addr2 307608', - 'billingCity': 'Dallas', - 'billingCountryCode': 'US', - 'billingEmail': 'noreply@softlayer.com', - 'billingNameCompany': 'Customer ' - '307608', - 'billingNameFirst': 'FName THREE ' - 'HUNDRED SEVEN ' - 'THOU', - 'billingNameLast': 'LName THREE ' - 'HUNDRED SEVEN ' - 'THOU', - 'billingPhoneVoice': '0000307608', - 'billingPostalCode': '75244-4608', - 'billingState': 'TX', - 'cardExpirationMonth': '', - 'cardExpirationYear': '', - 'euSupported': '', - 'taxExempt': 0}, - 'billingOrderItemId': '', - 'containerSplHash': '0000000065689e3f00007f66446132d6', - 'currencyShortName': 'USD', - 'extendedHardwareTesting': '', - 'gdprConsentFlag': '', - 'imageTemplateId': '', - 'isManagedOrder': '', - 'message': '', - 'orderContainers': [{'backendRouterId': 1079095, - 'bigDataOrderFlag': False, - 'billingOrderItemId': '', - 'containerSplHash': '0000000065689e3400007f66446132d6', - 'currencyShortName': 'USD', - 'extendedHardwareTesting': '', - 'gdprConsentFlag': '', - 'imageTemplateId': '', - 'isManagedOrder': '', - 'itemCategoryQuestionAnswers': [], - 'location': '1854895', - 'locationObject': {'id': 1854895, - 'longName': 'Dallas ' - '13', - 'name': 'dal13'}, - 'message': '', - 'name': 'cgallo-test-01', - 'packageId': 1059, - 'paymentType': '', - 'postTaxRecurring': '6.17', - 'postTaxRecurringHourly': '6.17', - 'postTaxRecurringMonthly': '0', - 'postTaxSetup': '0', - 'preTaxRecurring': '6.17', - 'preTaxRecurringHourly': '6.17', - 'preTaxRecurringMonthly': '0', - 'preTaxSetup': '0', - 'presetId': '', - 'prices': [{'categories': [{'categoryCode': 'reserved_capacity', - 'id': 2060, - 'name': 'Reserved ' - 'Capacity'}], - 'hourlyRecurringFee': '.617', - 'id': 217633, - 'item': {'bundle': [{'bundleItem': {'capacity': '12', - 'description': 'B1.16x64 ' - '(1 ' - 'Year ' - 'Term)', - 'id': 12309, - 'keyName': 'B1_16X64_1_YEAR_TERM', - 'units': 'MONTHS'}, - 'bundleItemId': 12309, - 'category': {'categoryCode': 'guest_core', - 'id': 80, - 'name': 'Computing ' - 'Instance'}, - 'id': 44720, - 'itemPrice': {'hourlyRecurringFee': '0', - 'id': 210185, - 'itemId': 1194, - 'laborFee': '0', - 'oneTimeFee': '0', - 'recurringFee': '0', - 'setupFee': '0'}, - 'itemPriceId': 210185}, - {'bundleItem': {'capacity': '12', - 'description': 'B1.16x64 ' - '(1 ' - 'Year ' - 'Term)', - 'id': 12309, - 'keyName': 'B1_16X64_1_YEAR_TERM', - 'units': 'MONTHS'}, - 'bundleItemId': 12309, - 'category': {'categoryCode': 'ram', - 'id': 3, - 'name': 'RAM'}, - 'id': 44726, - 'itemPrice': {'hourlyRecurringFee': '0', - 'id': 216607, - 'itemId': 10575, - 'laborFee': '0', - 'oneTimeFee': '0', - 'recurringFee': '0', - 'setupFee': '0'}, - 'itemPriceId': 216607}], - 'capacity': '12', - 'description': 'B1.16x64 ' - '(1 ' - 'Year ' - 'Term)', - 'id': 12309, - 'keyName': 'B1_16X64_1_YEAR_TERM', - 'thirdPartyPolicyAssignments': [], - 'units': 'MONTHS'}, - 'itemId': 12309, - 'laborFee': '0', - 'oneTimeFee': '0', - 'recurringFee': '0', - 'setupFee': '0'}], - 'primaryDiskPartitionId': '', - 'privateCloudOrderFlag': False, - 'proratedInitialCharge': '0', - 'proratedOrderTotal': '0', - 'quantity': 10, - 'resourceGroupId': '', - 'resourceGroupTemplateId': '', - 'sendQuoteEmailFlag': '', - 'serverCoreCount': '', - 'sourceVirtualGuestId': '', - 'sshKeys': [], - 'stepId': '', - 'storageGroups': [], - 'taxCacheHash': '2ce690dee89b73a1653785d3032af6c5d5dd88de', - 'taxCompletedFlag': False, - 'totalRecurringTax': '0', - 'totalSetupTax': '0', - 'useHourlyPricing': True}], - 'packageId': '', - 'paymentType': 'ADD_TO_BALANCE', - 'postTaxRecurring': '6.17', - 'postTaxRecurringHourly': '6.17', - 'postTaxRecurringMonthly': '0', - 'postTaxSetup': '0', - 'preTaxRecurring': '6.17', - 'preTaxRecurringHourly': '6.17', - 'preTaxRecurringMonthly': '0', - 'preTaxSetup': '0', - 'presetId': '', - 'prices': [], - 'primaryDiskPartitionId': '', - 'privateCloudOrderFlag': False, - 'properties': [], - 'proratedInitialCharge': '0', - 'proratedOrderTotal': '0', - 'quantity': '', - 'resourceGroupId': '', - 'resourceGroupTemplateId': '', - 'sendQuoteEmailFlag': '', - 'serverCoreCount': '', - 'sourceVirtualGuestId': '', - 'sshKeys': [], - 'stepId': '', - 'storageGroups': [], - 'taxCompletedFlag': False, - 'totalRecurringTax': '0', - 'totalSetupTax': '0', - 'useHourlyPricing': ''}, - 'orderId': 29297264, - 'placedOrder': {'account': {'brandId': 2, - 'companyName': 'Customer 307608', - 'id': 307608}, - 'accountId': 307608, - 'id': 29297264, - 'items': [{'categoryCode': 'reserved_capacity', - 'children': [{'categoryCode': 'guest_core', - 'description': '16 x 2.0 GHz or ' - 'higher Cores', - 'hourlyRecurringFee': '0', - 'id': 371550142, - 'itemId': 1194, - 'itemPriceId': '210185', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': 371550140, - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}, - {'categoryCode': 'ram', - 'description': '64 GB', - 'hourlyRecurringFee': '0', - 'id': 371550144, - 'itemId': 10575, - 'itemPriceId': '216607', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': 371550140, - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}], - 'description': 'B1.16x64 (1 Year Term)', - 'hourlyRecurringFee': '.617', - 'id': 371550140, - 'itemId': 12309, - 'itemPriceId': '217633', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': '', - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}, - {'categoryCode': 'guest_core', - 'description': '16 x 2.0 GHz or higher Cores', - 'hourlyRecurringFee': '0', - 'id': 371550142, - 'itemId': 1194, - 'itemPriceId': '210185', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': 371550140, - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}, - {'categoryCode': 'ram', - 'description': '64 GB', - 'hourlyRecurringFee': '0', - 'id': 371550144, - 'itemId': 10575, - 'itemPriceId': '216607', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': 371550140, - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}, - {'categoryCode': 'reserved_capacity', - 'children': [{'categoryCode': 'guest_core', - 'description': '16 x 2.0 GHz or ' - 'higher Cores', - 'hourlyRecurringFee': '0', - 'id': 371550148, - 'itemId': 1194, - 'itemPriceId': '210185', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': 371550146, - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}, - {'categoryCode': 'ram', - 'description': '64 GB', - 'hourlyRecurringFee': '0', - 'id': 371550150, - 'itemId': 10575, - 'itemPriceId': '216607', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': 371550146, - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}], - 'description': 'B1.16x64 (1 Year Term)', - 'hourlyRecurringFee': '.617', - 'id': 371550146, - 'itemId': 12309, - 'itemPriceId': '217633', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': '', - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}, - {'categoryCode': 'guest_core', - 'description': '16 x 2.0 GHz or higher Cores', - 'hourlyRecurringFee': '0', - 'id': 371550148, - 'itemId': 1194, - 'itemPriceId': '210185', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': 371550146, - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}, - {'categoryCode': 'ram', - 'description': '64 GB', - 'hourlyRecurringFee': '0', - 'id': 371550150, - 'itemId': 10575, - 'itemPriceId': '216607', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': 371550146, - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}, - {'categoryCode': 'reserved_capacity', - 'children': [{'categoryCode': 'guest_core', - 'description': '16 x 2.0 GHz or ' - 'higher Cores', - 'hourlyRecurringFee': '0', - 'id': 371550154, - 'itemId': 1194, - 'itemPriceId': '210185', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': 371550152, - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}, - {'categoryCode': 'ram', - 'description': '64 GB', - 'hourlyRecurringFee': '0', - 'id': 371550156, - 'itemId': 10575, - 'itemPriceId': '216607', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': 371550152, - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}], - 'description': 'B1.16x64 (1 Year Term)', - 'hourlyRecurringFee': '.617', - 'id': 371550152, - 'itemId': 12309, - 'itemPriceId': '217633', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': '', - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}, - {'categoryCode': 'guest_core', - 'description': '16 x 2.0 GHz or higher Cores', - 'hourlyRecurringFee': '0', - 'id': 371550154, - 'itemId': 1194, - 'itemPriceId': '210185', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': 371550152, - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}, - {'categoryCode': 'ram', - 'description': '64 GB', - 'hourlyRecurringFee': '0', - 'id': 371550156, - 'itemId': 10575, - 'itemPriceId': '216607', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': 371550152, - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}, - {'categoryCode': 'reserved_capacity', - 'children': [{'categoryCode': 'guest_core', - 'description': '16 x 2.0 GHz or ' - 'higher Cores', - 'hourlyRecurringFee': '0', - 'id': 371550160, - 'itemId': 1194, - 'itemPriceId': '210185', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': 371550158, - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}, - {'categoryCode': 'ram', - 'description': '64 GB', - 'hourlyRecurringFee': '0', - 'id': 371550162, - 'itemId': 10575, - 'itemPriceId': '216607', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': 371550158, - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}], - 'description': 'B1.16x64 (1 Year Term)', - 'hourlyRecurringFee': '.617', - 'id': 371550158, - 'itemId': 12309, - 'itemPriceId': '217633', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': '', - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}, - {'categoryCode': 'guest_core', - 'description': '16 x 2.0 GHz or higher Cores', - 'hourlyRecurringFee': '0', - 'id': 371550160, - 'itemId': 1194, - 'itemPriceId': '210185', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': 371550158, - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}, - {'categoryCode': 'ram', - 'description': '64 GB', - 'hourlyRecurringFee': '0', - 'id': 371550162, - 'itemId': 10575, - 'itemPriceId': '216607', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': 371550158, - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}, - {'categoryCode': 'reserved_capacity', - 'children': [{'categoryCode': 'guest_core', - 'description': '16 x 2.0 GHz or ' - 'higher Cores', - 'hourlyRecurringFee': '0', - 'id': 371550166, - 'itemId': 1194, - 'itemPriceId': '210185', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': 371550164, - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}, - {'categoryCode': 'ram', - 'description': '64 GB', - 'hourlyRecurringFee': '0', - 'id': 371550168, - 'itemId': 10575, - 'itemPriceId': '216607', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': 371550164, - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}], - 'description': 'B1.16x64 (1 Year Term)', - 'hourlyRecurringFee': '.617', - 'id': 371550164, - 'itemId': 12309, - 'itemPriceId': '217633', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': '', - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}, - {'categoryCode': 'guest_core', - 'description': '16 x 2.0 GHz or higher Cores', - 'hourlyRecurringFee': '0', - 'id': 371550166, - 'itemId': 1194, - 'itemPriceId': '210185', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': 371550164, - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}, - {'categoryCode': 'ram', - 'description': '64 GB', - 'hourlyRecurringFee': '0', - 'id': 371550168, - 'itemId': 10575, - 'itemPriceId': '216607', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': 371550164, - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}, - {'categoryCode': 'reserved_capacity', - 'children': [{'categoryCode': 'guest_core', - 'description': '16 x 2.0 GHz or ' - 'higher Cores', - 'hourlyRecurringFee': '0', - 'id': 371550172, - 'itemId': 1194, - 'itemPriceId': '210185', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': 371550170, - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}, - {'categoryCode': 'ram', - 'description': '64 GB', - 'hourlyRecurringFee': '0', - 'id': 371550174, - 'itemId': 10575, - 'itemPriceId': '216607', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': 371550170, - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}], - 'description': 'B1.16x64 (1 Year Term)', - 'hourlyRecurringFee': '.617', - 'id': 371550170, - 'itemId': 12309, - 'itemPriceId': '217633', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': '', - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}, - {'categoryCode': 'guest_core', - 'description': '16 x 2.0 GHz or higher Cores', - 'hourlyRecurringFee': '0', - 'id': 371550172, - 'itemId': 1194, - 'itemPriceId': '210185', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': 371550170, - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}, - {'categoryCode': 'ram', - 'description': '64 GB', - 'hourlyRecurringFee': '0', - 'id': 371550174, - 'itemId': 10575, - 'itemPriceId': '216607', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': 371550170, - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}, - {'categoryCode': 'reserved_capacity', - 'children': [{'categoryCode': 'guest_core', - 'description': '16 x 2.0 GHz or ' - 'higher Cores', - 'hourlyRecurringFee': '0', - 'id': 371550178, - 'itemId': 1194, - 'itemPriceId': '210185', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': 371550176, - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}, - {'categoryCode': 'ram', - 'description': '64 GB', - 'hourlyRecurringFee': '0', - 'id': 371550180, - 'itemId': 10575, - 'itemPriceId': '216607', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': 371550176, - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}], - 'description': 'B1.16x64 (1 Year Term)', - 'hourlyRecurringFee': '.617', - 'id': 371550176, - 'itemId': 12309, - 'itemPriceId': '217633', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': '', - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}, - {'categoryCode': 'guest_core', - 'description': '16 x 2.0 GHz or higher Cores', - 'hourlyRecurringFee': '0', - 'id': 371550178, - 'itemId': 1194, - 'itemPriceId': '210185', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': 371550176, - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}, - {'categoryCode': 'ram', - 'description': '64 GB', - 'hourlyRecurringFee': '0', - 'id': 371550180, - 'itemId': 10575, - 'itemPriceId': '216607', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': 371550176, - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}, - {'categoryCode': 'reserved_capacity', - 'children': [{'categoryCode': 'guest_core', - 'description': '16 x 2.0 GHz or ' - 'higher Cores', - 'hourlyRecurringFee': '0', - 'id': 371550184, - 'itemId': 1194, - 'itemPriceId': '210185', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': 371550182, - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}, - {'categoryCode': 'ram', - 'description': '64 GB', - 'hourlyRecurringFee': '0', - 'id': 371550186, - 'itemId': 10575, - 'itemPriceId': '216607', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': 371550182, - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}], - 'description': 'B1.16x64 (1 Year Term)', - 'hourlyRecurringFee': '.617', - 'id': 371550182, - 'itemId': 12309, - 'itemPriceId': '217633', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': '', - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}, - {'categoryCode': 'guest_core', - 'description': '16 x 2.0 GHz or higher Cores', - 'hourlyRecurringFee': '0', - 'id': 371550184, - 'itemId': 1194, - 'itemPriceId': '210185', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': 371550182, - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}, - {'categoryCode': 'ram', - 'description': '64 GB', - 'hourlyRecurringFee': '0', - 'id': 371550186, - 'itemId': 10575, - 'itemPriceId': '216607', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': 371550182, - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}, - {'categoryCode': 'reserved_capacity', - 'children': [{'categoryCode': 'guest_core', - 'description': '16 x 2.0 GHz or ' - 'higher Cores', - 'hourlyRecurringFee': '0', - 'id': 371550190, - 'itemId': 1194, - 'itemPriceId': '210185', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': 371550188, - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}, - {'categoryCode': 'ram', - 'description': '64 GB', - 'hourlyRecurringFee': '0', - 'id': 371550192, - 'itemId': 10575, - 'itemPriceId': '216607', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': 371550188, - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}], - 'description': 'B1.16x64 (1 Year Term)', - 'hourlyRecurringFee': '.617', - 'id': 371550188, - 'itemId': 12309, - 'itemPriceId': '217633', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': '', - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}, - {'categoryCode': 'guest_core', - 'description': '16 x 2.0 GHz or higher Cores', - 'hourlyRecurringFee': '0', - 'id': 371550190, - 'itemId': 1194, - 'itemPriceId': '210185', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': 371550188, - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}, - {'categoryCode': 'ram', - 'description': '64 GB', - 'hourlyRecurringFee': '0', - 'id': 371550192, - 'itemId': 10575, - 'itemPriceId': '216607', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': 371550188, - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}, - {'categoryCode': 'reserved_capacity', - 'children': [{'categoryCode': 'guest_core', - 'description': '16 x 2.0 GHz or ' - 'higher Cores', - 'hourlyRecurringFee': '0', - 'id': 371550196, - 'itemId': 1194, - 'itemPriceId': '210185', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': 371550194, - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}, - {'categoryCode': 'ram', - 'description': '64 GB', - 'hourlyRecurringFee': '0', - 'id': 371550198, - 'itemId': 10575, - 'itemPriceId': '216607', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': 371550194, - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}], - 'description': 'B1.16x64 (1 Year Term)', - 'hourlyRecurringFee': '.617', - 'id': 371550194, - 'itemId': 12309, - 'itemPriceId': '217633', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': '', - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}, - {'categoryCode': 'guest_core', - 'description': '16 x 2.0 GHz or higher Cores', - 'hourlyRecurringFee': '0', - 'id': 371550196, - 'itemId': 1194, - 'itemPriceId': '210185', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': 371550194, - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}, - {'categoryCode': 'ram', - 'description': '64 GB', - 'hourlyRecurringFee': '0', - 'id': 371550198, - 'itemId': 10575, - 'itemPriceId': '216607', - 'laborFee': '0', - 'oneTimeFee': '0', - 'parentId': 371550194, - 'promoCodeId': '', - 'recurringFee': '0', - 'setupFee': '0'}], - 'orderQuoteId': '', - 'orderTypeId': 4, - 'presaleEventId': '', - 'status': 'PENDING_AUTO_APPROVAL', - 'userRecord': {'accountId': 307608, - 'firstName': 'Christopher', - 'id': 167758, - 'lastName': 'Gallo', - 'username': 'SL307608'}, - 'userRecordId': 167758}} -""" \ No newline at end of file +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 + From 9f99ed364983dd9c689c67a7c30c1ecea9a62f87 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Fri, 28 Sep 2018 14:31:17 -0500 Subject: [PATCH 07/18] #1026 mostly done with the bits that actually do things. still need unit tests and detox --- SoftLayer/CLI/virt/capacity/__init__.py | 9 +++++---- SoftLayer/CLI/virt/capacity/create-guest.py | 1 + SoftLayer/CLI/virt/capacity/create.py | 7 +++++-- SoftLayer/CLI/virt/capacity/detail.py | 2 +- SoftLayer/CLI/virt/capacity/list.py | 2 +- 5 files changed, 13 insertions(+), 8 deletions(-) diff --git a/SoftLayer/CLI/virt/capacity/__init__.py b/SoftLayer/CLI/virt/capacity/__init__.py index baf47c453..6157a7b93 100644 --- a/SoftLayer/CLI/virt/capacity/__init__.py +++ b/SoftLayer/CLI/virt/capacity/__init__.py @@ -9,6 +9,8 @@ from SoftLayer.CLI import formatting from pprint import pprint as pp +CONTEXT = {'help_option_names': ['-h', '--help'], + 'max_content_width': 999} class capacityCommands(click.MultiCommand): """Loads module for capacity related commands.""" @@ -18,7 +20,6 @@ def __init__(self, *path, **attrs): def list_commands(self, ctx): """List all sub-commands.""" - rv = [] for filename in os.listdir(self.path): if filename == '__init__.py': @@ -34,9 +35,9 @@ def get_command(self, ctx, name): module = importlib.import_module(path) return getattr(module, 'cli') -@click.group(cls=capacityCommands, - help="Manages virtual server reserved capacity") +@click.group(cls=capacityCommands, + context_settings=CONTEXT) @environment.pass_env def cli(env): - """Manages Capacity""" + """Manages Reserved Capacity""" pass diff --git a/SoftLayer/CLI/virt/capacity/create-guest.py b/SoftLayer/CLI/virt/capacity/create-guest.py index f693d336f..7fda2a494 100644 --- a/SoftLayer/CLI/virt/capacity/create-guest.py +++ b/SoftLayer/CLI/virt/capacity/create-guest.py @@ -117,6 +117,7 @@ def _parse_create_args(client, args): help="Test order, will return the order container, but not actually order a server.") @environment.pass_env def cli(env, **args): + """Allows for creating a virtual guest in a reserved capacity.""" create_args = _parse_create_args(env.client, args) manager = CapacityManager(env.client) capacity_id = args.get('capacity_id') diff --git a/SoftLayer/CLI/virt/capacity/create.py b/SoftLayer/CLI/virt/capacity/create.py index b1004fb97..22c69a9b4 100644 --- a/SoftLayer/CLI/virt/capacity/create.py +++ b/SoftLayer/CLI/virt/capacity/create.py @@ -1,4 +1,7 @@ -"""Create a Reserved Capacity instance""" +"""Create a Reserved Capacity instance. + + +""" # :license: MIT, see LICENSE for more details. import click @@ -27,7 +30,7 @@ help="Do not actually create the virtual server") @environment.pass_env def cli(env, name, datacenter, backend_router_id, capacity, quantity, test=False): - """Create a Reserved Capacity instance""" + """Create a Reserved Capacity instance. *WARNING*: Reserved Capacity is on a yearly contract and not cancelable until the contract is expired.""" manager = CapacityManager(env.client) result = manager.create( name=name, diff --git a/SoftLayer/CLI/virt/capacity/detail.py b/SoftLayer/CLI/virt/capacity/detail.py index 0aced53f4..9ef2aeef5 100644 --- a/SoftLayer/CLI/virt/capacity/detail.py +++ b/SoftLayer/CLI/virt/capacity/detail.py @@ -47,7 +47,7 @@ show_default=True) @environment.pass_env def cli(env, identifier, columns): - """Reserved Capacity Group Details""" + """Reserved Capacity Group details. Will show which guests are assigned to a reservation.""" manager = CapacityManager(env.client) mask = "mask[instances[billingItem[category], guest]]" result = manager.get_object(identifier, mask) diff --git a/SoftLayer/CLI/virt/capacity/list.py b/SoftLayer/CLI/virt/capacity/list.py index 31f8d672c..c1ca476dd 100644 --- a/SoftLayer/CLI/virt/capacity/list.py +++ b/SoftLayer/CLI/virt/capacity/list.py @@ -14,7 +14,7 @@ @click.command() @environment.pass_env def cli(env): - """List Reserved Capacity""" + """List Reserved Capacity groups.""" manager = CapacityManager(env.client) result = manager.list() table = formatting.Table( From 53492eea1fe89516147d16d0e2aa9603a62741ba Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Fri, 28 Sep 2018 17:42:31 -0500 Subject: [PATCH 08/18] #1026 unit tests and fixtures for ReservedCapacityGroup --- SoftLayer/CLI/virt/capacity/create-options.py | 3 - SoftLayer/CLI/virt/capacity/detail.py | 3 +- SoftLayer/fixtures/SoftLayer_Account.py | 34 +++ SoftLayer/fixtures/SoftLayer_Network_Pod.py | 22 ++ SoftLayer/fixtures/SoftLayer_Product_Order.py | 1 + .../fixtures/SoftLayer_Product_Package.py | 80 +++++++ SoftLayer/fixtures/SoftLayer_Virtual_Guest.py | 3 + ...SoftLayer_Virtual_ReservedCapacityGroup.py | 57 +++++ SoftLayer/managers/__init__.py | 2 + SoftLayer/managers/vs_capacity.py | 43 +++- tests/managers/vs_capacity_tests.py | 195 ++++++++++++++++++ 11 files changed, 431 insertions(+), 12 deletions(-) create mode 100644 SoftLayer/fixtures/SoftLayer_Network_Pod.py create mode 100644 SoftLayer/fixtures/SoftLayer_Virtual_ReservedCapacityGroup.py create mode 100644 tests/managers/vs_capacity_tests.py diff --git a/SoftLayer/CLI/virt/capacity/create-options.py b/SoftLayer/CLI/virt/capacity/create-options.py index 7edefdb73..0f321298d 100644 --- a/SoftLayer/CLI/virt/capacity/create-options.py +++ b/SoftLayer/CLI/virt/capacity/create-options.py @@ -43,6 +43,3 @@ def get_price(item): if price.get('locationGroupId') == '': the_price = "%0.4f" % float(price['hourlyRecurringFee']) return the_price - -def get_router_ids(): - pass diff --git a/SoftLayer/CLI/virt/capacity/detail.py b/SoftLayer/CLI/virt/capacity/detail.py index 9ef2aeef5..3e0c3693c 100644 --- a/SoftLayer/CLI/virt/capacity/detail.py +++ b/SoftLayer/CLI/virt/capacity/detail.py @@ -49,7 +49,8 @@ def cli(env, identifier, columns): """Reserved Capacity Group details. Will show which guests are assigned to a reservation.""" manager = CapacityManager(env.client) - mask = "mask[instances[billingItem[category], guest]]" + mask = """mask[instances[id,createDate,guestId,billingItem[id, recurringFee, category[name]], + guest[modifyDate,id, primaryBackendIpAddress, primaryIpAddress,domain, hostname]]]""" result = manager.get_object(identifier, mask) try: flavor = result['instances'][0]['billingItem']['description'] diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index 586e597a9..072cd9d79 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -575,3 +575,37 @@ 'username': 'sl1234-abob', 'virtualGuestCount': 99} ] + +getReservedCapacityGroups = [ + {'accountId': 1234, + 'backendRouterId': 1411193, + 'createDate': '2018-09-24T16:33:09-06:00', + 'id': 3103, + 'modifyDate': '', + 'name': 'test-capacity', + 'availableInstanceCount': 1, + 'instanceCount': 2, + 'occupiedInstanceCount': 1, + 'backendRouter': + {'accountId': 1, + 'bareMetalInstanceFlag': 0, + 'domain': 'softlayer.com', + 'fullyQualifiedDomainName': 'bcr02a.dal13.softlayer.com', + 'hardwareStatusId': 5, + 'hostname': 'bcr02a.dal13', + 'id': 1411193, + 'notes': '', + 'provisionDate': '', + 'serviceProviderId': 1, + 'serviceProviderResourceId': '', + 'primaryIpAddress': '10.0.144.28', + 'datacenter': {'id': 1854895, 'longName': 'Dallas 13', 'name': 'dal13', 'statusId': 2}, + 'hardwareFunction': {'code': 'ROUTER', 'description': 'Router', 'id': 1}, + 'topLevelLocation': {'id': 1854895, 'longName': 'Dallas 13', 'name': 'dal13', 'statusId': 2} + }, + 'instances': [ + {'id': 3501, 'billingItem': {'description': 'B1.1x2 (1 Year Term)', 'hourlyRecurringFee': '.032'}}, + {'id': 3519, 'billingItem': {'description': 'B1.1x2 (1 Year Term)', 'hourlyRecurringFee': '.032'}} + ] + } +] \ No newline at end of file diff --git a/SoftLayer/fixtures/SoftLayer_Network_Pod.py b/SoftLayer/fixtures/SoftLayer_Network_Pod.py new file mode 100644 index 000000000..4e6088270 --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Network_Pod.py @@ -0,0 +1,22 @@ +getAllObjects = [ + { + 'backendRouterId': 117917, + 'backendRouterName': 'bcr01a.ams01', + 'datacenterId': 265592, + 'datacenterLongName': 'Amsterdam 1', + 'datacenterName': 'ams01', + 'frontendRouterId': 117960, + 'frontendRouterName': 'fcr01a.ams01', + 'name': 'ams01.pod01' + }, + { + 'backendRouterId': 1115295, + 'backendRouterName': 'bcr01a.wdc07', + 'datacenterId': 2017603, + 'datacenterLongName': 'Washington 7', + 'datacenterName': 'wdc07', + 'frontendRouterId': 1114993, + 'frontendRouterName': 'fcr01a.wdc07', + 'name': 'wdc07.pod01' + } +] diff --git a/SoftLayer/fixtures/SoftLayer_Product_Order.py b/SoftLayer/fixtures/SoftLayer_Product_Order.py index 5b3cf27ca..a4d7e98c1 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Order.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Order.py @@ -14,3 +14,4 @@ 'item': {'id': 1, 'description': 'this is a thing'}, }]} placeOrder = verifyOrder + diff --git a/SoftLayer/fixtures/SoftLayer_Product_Package.py b/SoftLayer/fixtures/SoftLayer_Product_Package.py index b7b008788..c21ba80cb 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Package.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Package.py @@ -1542,3 +1542,83 @@ ] getAccountRestrictedActivePresets = [] + +RESERVED_CAPACITY = [{"id": 1059}] +getItems_RESERVED_CAPACITY = [ + { + 'id': 12273, + 'keyName': 'B1_1X2_1_YEAR_TERM', + 'itemCategory': { + 'categoryCode': 'reserved_capacity', + 'id': 2060, + 'name': 'Reserved Capacity', + 'quantityLimit': 20, + 'sortOrder': '' + }, + 'prices': [ + { + 'currentPriceFlag': '', + 'hourlyRecurringFee': '.032', + 'id': 217561, + 'itemId': 12273, + 'laborFee': '0', + 'locationGroupId': '', + 'onSaleFlag': '', + 'oneTimeFee': '0', + 'quantity': '', + 'setupFee': '0', + 'sort': 0, + 'tierMinimumThreshold': '', + 'categories': [ + { + 'categoryCode': 'reserved_capacity', + 'id': 2060, + 'name': 'Reserved Capacity', + 'quantityLimit': 20, + 'sortOrder': '' + } + ] + } + ] + } +] + +getItems_1_IPV6_ADDRESS = [ + { + 'id': 4097, + 'keyName': '1_IPV6_ADDRESS', + 'itemCategory': { + 'categoryCode': 'pri_ipv6_addresses', + 'id': 325, + 'name': 'Primary IPv6 Addresses', + 'quantityLimit': 0, + 'sortOrder': 34 + }, + 'prices': [ + { + 'currentPriceFlag': '', + 'hourlyRecurringFee': '0', + 'id': 17129, + 'itemId': 4097, + 'laborFee': '0', + 'locationGroupId': '', + 'onSaleFlag': '', + 'oneTimeFee': '0', + 'quantity': '', + 'recurringFee': '0', + 'setupFee': '0', + 'sort': 0, + 'tierMinimumThreshold': '', + 'categories': [ + { + 'categoryCode': 'pri_ipv6_addresses', + 'id': 325, + 'name': 'Primary IPv6 Addresses', + 'quantityLimit': 0, + 'sortOrder': 34 + } + ] + } + ] + } +] diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py index 69a1b95e6..732d9b812 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py @@ -617,3 +617,6 @@ } }, ] + + +# RESERVED_ORDER_TEMPLATE = {'imageTemplateId': '', 'location': '1854895', 'packageId': 835, 'presetId': 215, 'quantity': 1, 'sourceVirtualGuestId': '', 'useHourlyPricing': True, 'complexType': 'SoftLayer_Container_Product_Order_Virtual_Guest', 'prices': [{'hourlyRecurringFee': '0', 'id': 211451, 'recurringFee': '0', 'item': {'description': 'Ubuntu Linux 18.04 LTS Bionic Beaver Minimal Install (64 bit) '}}, {'hourlyRecurringFee': '0', 'id': 2202, 'recurringFee': '0', 'item': {'description': '25 GB (SAN)'}}, {'hourlyRecurringFee': '0', 'id': 905, 'recurringFee': '0', 'item': {'description': 'Reboot / Remote Console'}}, {'hourlyRecurringFee': '0', 'id': 273, 'recurringFee': '0', 'item': {'description': '100 Mbps Public & Private Network Uplinks'}}, {'hourlyRecurringFee': '0', 'id': 1800, 'item': {'description': '0 GB Bandwidth Allotment'}}, {'hourlyRecurringFee': '0', 'id': 21, 'recurringFee': '0', 'item': {'description': '1 IP Address'}}, {'hourlyRecurringFee': '0', 'id': 55, 'recurringFee': '0', 'item': {'description': 'Host Ping'}}, {'hourlyRecurringFee': '0', 'id': 57, 'recurringFee': '0', 'item': {'description': 'Email and Ticket'}}, {'hourlyRecurringFee': '0', 'id': 58, 'recurringFee': '0', 'item': {'description': 'Automated Notification'}}, {'hourlyRecurringFee': '0', 'id': 420, 'recurringFee': '0', 'item': {'description': 'Unlimited SSL VPN Users & 1 PPTP VPN User per account'}}, {'hourlyRecurringFee': '0', 'id': 418, 'recurringFee': '0', 'item': {'description': 'Nessus Vulnerability Assessment & Reporting'}}, {'id': 17129}], 'sshKeys': [{'sshKeyIds': [87634]}], 'virtualGuests': [{'domain': 'cgallo.com', 'hostname': 'A1538172419'}], 'reservedCapacityId': 3103} \ No newline at end of file diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_ReservedCapacityGroup.py b/SoftLayer/fixtures/SoftLayer_Virtual_ReservedCapacityGroup.py new file mode 100644 index 000000000..c6e034634 --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Virtual_ReservedCapacityGroup.py @@ -0,0 +1,57 @@ +getObject = { + 'accountId': 1234, + 'backendRouterId': 1411193, + 'backendRouter': { + 'fullyQualifiedDomainName': 'bcr02a.dal13.softlayer.com', + 'hostname': 'bcr02a.dal13', + 'id': 1411193, + 'datacenter': { + 'id': 1854895, + 'longName': 'Dallas 13', + 'name': 'dal13', + + } + }, + 'createDate': '2018-09-24T16:33:09-06:00', + 'id': 3103, + 'modifyDate': '', + 'name': 'test-capacity', + 'instances': [ + { + 'createDate': '2018-09-24T16:33:09-06:00', + 'guestId': 62159257, + 'id': 3501, + 'billingItem': { + 'id': 348319479, + 'recurringFee': '3.04', + 'category': { 'name': 'Reserved Capacity' }, + 'item': { + 'keyName': 'B1_1X2_1_YEAR_TERM' + } + }, + 'guest': { + 'domain': 'cgallo.com', + 'hostname': 'test-reserved-instance', + 'id': 62159257, + 'modifyDate': '2018-09-27T16:49:26-06:00', + 'primaryBackendIpAddress': '10.73.150.179', + 'primaryIpAddress': '169.62.147.165' + } + }, + { + 'createDate': '2018-09-24T16:33:10-06:00', + 'guestId': 62159275, + 'id': 3519, + 'billingItem': { + 'id': 348319443, + 'recurringFee': '3.04', + 'category': { + 'name': 'Reserved Capacity' + }, + 'item': { + 'keyName': 'B1_1X2_1_YEAR_TERM' + } + } + } + ] +} diff --git a/SoftLayer/managers/__init__.py b/SoftLayer/managers/__init__.py index f0602579e..c6a8688d7 100644 --- a/SoftLayer/managers/__init__.py +++ b/SoftLayer/managers/__init__.py @@ -27,10 +27,12 @@ from SoftLayer.managers.ticket import TicketManager from SoftLayer.managers.user import UserManager from SoftLayer.managers.vs import VSManager +from SoftLayer.managers.vs_capacity import CapacityManager __all__ = [ 'BlockStorageManager', + 'CapacityManager', 'CDNManager', 'DedicatedHostManager', 'DNSManager', diff --git a/SoftLayer/managers/vs_capacity.py b/SoftLayer/managers/vs_capacity.py index 7d6160240..9dbacce26 100644 --- a/SoftLayer/managers/vs_capacity.py +++ b/SoftLayer/managers/vs_capacity.py @@ -53,34 +53,49 @@ def get_object(self, identifier, mask=None): def get_create_options(self): mask = "mask[attributes,prices[pricingLocationGroup]]" - # mask = "mask[id, description, capacity, units]" results = self.ordering_manager.list_items(self.capacity_package, mask=mask) return results - def get_available_routers(self): - """Pulls down all backendRouterIds that are available""" + 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) - _filter = {'datacenterName': {'operation': ''}} + 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'] = list() - _filter['datacenterName']['operation'] = location['location']['name'] - location['location']['pods'] = self.client.call('Network_Pod', 'getAllObjects', filter=_filter) + for pod in pods: + if pod['datacenterName'] == location['location']['name']: + location['location']['pods'].append(pod) # Step 4, return the data. return regions def create(self, name, datacenter, backend_router_id, capacity, quantity, test=False): - """Orders a Virtual_ReservedCapacityGroup""" + """Orders a Virtual_ReservedCapacityGroup + + :params string name: Name for the new reserved capacity + :params string datacenter: like 'dal13' + :params int backend_router_id: This selects the pod. See create_options for a list + :params string capacity: Capacity KeyName, see create_options for a list + :params int quantity: Number of guest this capacity can support + :params bool test: If True, don't actually order, just test. + """ args = (self.capacity_package, datacenter, [capacity]) extras = {"backendRouterId": backend_router_id, "name": name} kwargs = { @@ -96,6 +111,18 @@ def create(self, name, datacenter, backend_router_id, capacity, quantity, test=F return receipt def create_guest(self, capacity_id, test, guest_object): + """Turns an empty Reserve Capacity into a real Virtual Guest + + :params int capacity_id: ID of the RESERVED_CAPACITY_GROUP to create this guest into + :params bool test: True will use verifyOrder, False will use placeOrder + :params 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) diff --git a/tests/managers/vs_capacity_tests.py b/tests/managers/vs_capacity_tests.py new file mode 100644 index 000000000..c6f4d68d9 --- /dev/null +++ b/tests/managers/vs_capacity_tests.py @@ -0,0 +1,195 @@ +""" + SoftLayer.tests.managers.vs_capacity_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. + +""" +import mock + +import SoftLayer +from SoftLayer import exceptions +from SoftLayer import fixtures +from SoftLayer.fixtures import SoftLayer_Product_Package +from SoftLayer import testing + +from pprint import pprint as pp +class VSCapacityTests(testing.TestCase): + + def set_up(self): + self.manager = SoftLayer.CapacityManager(self.client) + amock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') + amock.return_value = fixtures.SoftLayer_Product_Package.RESERVED_CAPACITY + + def test_list(self): + result = self.manager.list() + self.assert_called_with('SoftLayer_Account', 'getReservedCapacityGroups') + + def test_get_object(self): + result = self.manager.get_object(100) + self.assert_called_with('SoftLayer_Virtual_ReservedCapacityGroup', 'getObject', identifier=100) + + def test_get_object_mask(self): + mask = "mask[id]" + result = self.manager.get_object(100, mask=mask) + self.assert_called_with('SoftLayer_Virtual_ReservedCapacityGroup', 'getObject', identifier=100, mask=mask) + + def test_get_create_options(self): + result = self.manager.get_create_options() + self.assert_called_with('SoftLayer_Product_Package', 'getItems', identifier=1059, mask=mock.ANY) + + def test_get_available_routers(self): + + result = self.manager.get_available_routers() + package_filter = {'keyName': {'operation': 'RESERVED_CAPACITY'}} + self.assert_called_with('SoftLayer_Product_Package', 'getAllObjects', mask=mock.ANY, filter=package_filter) + self.assert_called_with('SoftLayer_Product_Package', 'getRegions', mask=mock.ANY) + self.assert_called_with('SoftLayer_Network_Pod', 'getAllObjects') + self.assertEqual(result[0]['keyname'], 'WASHINGTON07') + + def test_create(self): + item_mock = self.set_mock('SoftLayer_Product_Package', 'getItems') + item_mock.return_value = SoftLayer_Product_Package.getItems_RESERVED_CAPACITY + result = self.manager.create( + name='TEST', datacenter='dal13', backend_router_id=1, capacity='B1_1X2_1_YEAR_TERM', quantity=5) + + expected_args = { + 'orderContainers': [ + { + 'backendRouterId': 1, + 'name': 'TEST', + 'packageId': 1059, + 'location': 1854895, + 'quantity': 5, + 'useHourlyPricing': True, + 'complexType': 'SoftLayer_Container_Product_Order_Virtual_ReservedCapacity', + 'prices': [ { 'id': 217561 } + ] + } + ] + } + + self.assert_called_with('SoftLayer_Product_Package', 'getAllObjects') + self.assert_called_with('SoftLayer_Location', 'getDatacenters') + self.assert_called_with('SoftLayer_Product_Package', 'getItems', identifier=1059) + self.assert_called_with('SoftLayer_Product_Order', 'placeOrder', args=(expected_args,)) + + + def test_create_test(self): + item_mock = self.set_mock('SoftLayer_Product_Package', 'getItems') + item_mock.return_value = SoftLayer_Product_Package.getItems_RESERVED_CAPACITY + result = self.manager.create( + name='TEST', datacenter='dal13', backend_router_id=1, capacity='B1_1X2_1_YEAR_TERM', quantity=5, test=True) + + expected_args = { + 'orderContainers': [ + { + 'backendRouterId': 1, + 'name': 'TEST', + 'packageId': 1059, + 'location': 1854895, + 'quantity': 5, + 'useHourlyPricing': True, + 'complexType': 'SoftLayer_Container_Product_Order_Virtual_ReservedCapacity', + 'prices': [ { 'id': 217561 } + ] + } + ] + } + + self.assert_called_with('SoftLayer_Product_Package', 'getAllObjects') + self.assert_called_with('SoftLayer_Location', 'getDatacenters') + self.assert_called_with('SoftLayer_Product_Package', 'getItems', identifier=1059) + self.assert_called_with('SoftLayer_Product_Order', 'verifyOrder', args=(expected_args,)) + + + def test_create_guest(self): + amock = self.set_mock('SoftLayer_Product_Package', 'getItems') + amock.return_value = fixtures.SoftLayer_Product_Package.getItems_1_IPV6_ADDRESS + guest_object = { + 'boot_mode': None, + 'disks': (), + 'domain': 'test.com', + 'hostname': 'A1538172419', + 'hourly': True, + 'ipv6': True, + 'local_disk': None, + 'os_code': 'UBUNTU_LATEST_64', + 'primary_disk': '25', + 'private': False, + 'private_subnet': None, + 'public_subnet': None, + 'ssh_keys': [1234] + } + result = self.manager.create_guest(123, False, guest_object) + expectedGenerate = { + 'startCpus': None, + 'maxMemory': None, + 'hostname': 'A1538172419', + 'domain': 'test.com', + 'localDiskFlag': None, + 'hourlyBillingFlag': True, + 'supplementalCreateObjectOptions': { + 'bootMode': None, + 'flavorKeyName': 'B1_1X2X25' + }, + 'operatingSystemReferenceCode': 'UBUNTU_LATEST_64', + 'datacenter': { + 'name': 'dal13' + }, + 'sshKeys': [ + { + 'id': 1234 + } + ] + } + + self.assert_called_with('SoftLayer_Virtual_ReservedCapacityGroup', 'getObject', mask=mock.ANY) + self.assert_called_with('SoftLayer_Virtual_Guest', 'generateOrderTemplate', args=(expectedGenerate,)) + self.assert_called_with('SoftLayer_Product_Package', 'getAllObjects') + # id=1059 comes from fixtures.SoftLayer_Product_Order.RESERVED_CAPACITY, production is 859 + self.assert_called_with('SoftLayer_Product_Package', 'getItems', identifier=1059) + self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') + + def test_create_guest_no_flavor(self): + guest_object = { + 'boot_mode': None, + 'disks': (), + 'domain': 'test.com', + 'hostname': 'A1538172419', + 'hourly': True, + 'ipv6': True, + 'local_disk': None, + 'os_code': 'UBUNTU_LATEST_64', + 'private': False, + 'private_subnet': None, + 'public_subnet': None, + 'ssh_keys': [1234] + } + self.assertRaises(SoftLayer.SoftLayerError, self.manager.create_guest, 123, False, guest_object) + + def test_create_guest_testing(self): + amock = self.set_mock('SoftLayer_Product_Package', 'getItems') + amock.return_value = fixtures.SoftLayer_Product_Package.getItems_1_IPV6_ADDRESS + guest_object = { + 'boot_mode': None, + 'disks': (), + 'domain': 'test.com', + 'hostname': 'A1538172419', + 'hourly': True, + 'ipv6': True, + 'local_disk': None, + 'os_code': 'UBUNTU_LATEST_64', + 'primary_disk': '25', + 'private': False, + 'private_subnet': None, + 'public_subnet': None, + 'ssh_keys': [1234] + } + result = self.manager.create_guest(123, True, guest_object) + self.assert_called_with('SoftLayer_Product_Order', 'verifyOrder') + + def test_flavor_string(self): + from SoftLayer.managers.vs_capacity import _flavor_string as _flavor_string + result = _flavor_string('B1_1X2_1_YEAR_TERM', '25') + self.assertEqual('B1_1X2X25', result) From 4815cdbacbd1d228aee4277729dfdfbd33eecd9a Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Mon, 1 Oct 2018 17:33:33 -0500 Subject: [PATCH 09/18] #1026 unit tests --- SoftLayer/CLI/columns.py | 2 +- SoftLayer/CLI/virt/capacity/create.py | 6 +- SoftLayer/CLI/virt/capacity/detail.py | 20 ++--- SoftLayer/CLI/virt/capacity/list.py | 7 -- SoftLayer/fixtures/SoftLayer_Product_Order.py | 66 +++++++++++++++ .../fixtures/SoftLayer_Product_Package.py | 2 + .../fixtures/SoftLayer_Security_Ssh_Key.py | 1 + ...SoftLayer_Virtual_ReservedCapacityGroup.py | 28 +++++++ tests/CLI/modules/vs_capacity_tests.py | 80 +++++++++++++++++++ 9 files changed, 186 insertions(+), 26 deletions(-) create mode 100644 tests/CLI/modules/vs_capacity_tests.py diff --git a/SoftLayer/CLI/columns.py b/SoftLayer/CLI/columns.py index 50bf89763..486d12ffc 100644 --- a/SoftLayer/CLI/columns.py +++ b/SoftLayer/CLI/columns.py @@ -57,7 +57,7 @@ def mask(self): def get_formatter(columns): """This function returns a callback to use with click options. - The retuend function parses a comma-separated value and returns a new + The returend function parses a comma-separated value and returns a new ColumnFormatter. :param columns: a list of Column instances diff --git a/SoftLayer/CLI/virt/capacity/create.py b/SoftLayer/CLI/virt/capacity/create.py index 22c69a9b4..da4ef997c 100644 --- a/SoftLayer/CLI/virt/capacity/create.py +++ b/SoftLayer/CLI/virt/capacity/create.py @@ -32,6 +32,7 @@ def cli(env, name, datacenter, backend_router_id, capacity, quantity, test=False): """Create a Reserved Capacity instance. *WARNING*: Reserved Capacity is on a yearly contract and not cancelable until the contract is expired.""" manager = CapacityManager(env.client) + result = manager.create( name=name, datacenter=datacenter, @@ -40,12 +41,11 @@ def cli(env, name, datacenter, backend_router_id, capacity, quantity, test=False quantity=quantity, test=test) - pp(result) if test: - table = formating.Table(['Name', 'Value'], "Test Order") + table = formatting.Table(['Name', 'Value'], "Test Order") container = result['orderContainers'][0] table.add_row(['Name', container['name']]) - table.add_row(['Location'], container['locationObject']['longName']) + table.add_row(['Location', container['locationObject']['longName']]) for price in container['prices']: table.add_row([price['item']['keyName'], price['item']['description']]) table.add_row(['Total', result['postTaxRecurring']]) diff --git a/SoftLayer/CLI/virt/capacity/detail.py b/SoftLayer/CLI/virt/capacity/detail.py index 3e0c3693c..574905b06 100644 --- a/SoftLayer/CLI/virt/capacity/detail.py +++ b/SoftLayer/CLI/virt/capacity/detail.py @@ -10,23 +10,11 @@ from SoftLayer.managers.vs_capacity import CapacityManager as CapacityManager COLUMNS = [ - column_helper.Column('guid', ('globalIdentifier',)), + column_helper.Column('Id', ('id',)), + column_helper.Column('hostname', ('hostname',)), + column_helper.Column('domain', ('domain',)), column_helper.Column('primary_ip', ('primaryIpAddress',)), column_helper.Column('backend_ip', ('primaryBackendIpAddress',)), - column_helper.Column('datacenter', ('datacenter', 'name')), - column_helper.Column('action', lambda guest: formatting.active_txn(guest), - mask=''' - activeTransaction[ - id,transactionStatus[name,friendlyName] - ]'''), - column_helper.Column('power_state', ('powerState', 'name')), - column_helper.Column( - 'created_by', - ('billingItem', 'orderItem', 'order', 'userRecord', 'username')), - column_helper.Column( - 'tags', - lambda server: formatting.tags(server.get('tagReferences')), - mask="tagReferences.tag.name"), ] DEFAULT_COLUMNS = [ @@ -48,6 +36,7 @@ @environment.pass_env def cli(env, identifier, columns): """Reserved Capacity Group details. Will show which guests are assigned to a reservation.""" + manager = CapacityManager(env.client) mask = """mask[instances[id,createDate,guestId,billingItem[id, recurringFee, category[name]], guest[modifyDate,id, primaryBackendIpAddress, primaryIpAddress,domain, hostname]]]""" @@ -60,6 +49,7 @@ def cli(env, identifier, columns): table = formatting.Table(columns.columns, title = "%s - %s" % (result.get('name'), flavor) ) + # RCI = Reserved Capacity Instance for rci in result['instances']: guest = rci.get('guest', None) guest_string = "---" diff --git a/SoftLayer/CLI/virt/capacity/list.py b/SoftLayer/CLI/virt/capacity/list.py index c1ca476dd..cbbb17a94 100644 --- a/SoftLayer/CLI/virt/capacity/list.py +++ b/SoftLayer/CLI/virt/capacity/list.py @@ -9,8 +9,6 @@ from SoftLayer.managers.vs_capacity import CapacityManager as CapacityManager -from pprint import pprint as pp - @click.command() @environment.pass_env def cli(env): @@ -28,14 +26,9 @@ def cli(env): try: flavor = rc['instances'][0]['billingItem']['description'] cost = float(rc['instances'][0]['billingItem']['hourlyRecurringFee']) - # instance_count = int(rc.get('instanceCount',0)) - # cost_string = "%s * %s = %s" % (cost, instance_count, cost * instance_count) except KeyError: flavor = "Unknown Billing Item" - # cost_string = "-" location = rc['backendRouter']['hostname'] capacity = "%s%s" % (occupied_string, available_string) table.add_row([rc['id'], rc['name'], capacity, flavor, location, rc['createDate']]) env.fout(table) - print("") - # pp(result) diff --git a/SoftLayer/fixtures/SoftLayer_Product_Order.py b/SoftLayer/fixtures/SoftLayer_Product_Order.py index a4d7e98c1..4eb21f249 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Order.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Order.py @@ -15,3 +15,69 @@ }]} placeOrder = verifyOrder +#Reserved Capacity Stuff + +rsc_verifyOrder = { + 'orderContainers': [ + { + 'locationObject': { + 'id': 1854895, + 'longName': 'Dallas 13', + 'name': 'dal13' + }, + 'name': 'test-capacity', + 'postTaxRecurring': '0.32', + 'prices': [ + { + 'item': { + 'id': 1, + 'description': 'B1.1x2 (1 Year ''Term)', + 'keyName': 'B1_1X2_1_YEAR_TERM', + } + } + ] + } + ], + 'postTaxRecurring': '0.32', +} + +rsc_placeOrder = { + 'orderDate': '2013-08-01 15:23:45', + 'orderId': 1234, + 'orderDetails': { + 'postTaxRecurring': '0.32', + }, + 'placedOrder': { + 'status': 'Great, thanks for asking', + 'locationObject': { + 'id': 1854895, + 'longName': 'Dallas 13', + 'name': 'dal13' + }, + 'name': 'test-capacity', + 'items': [ + { + 'description': 'B1.1x2 (1 Year ''Term)', + 'keyName': 'B1_1X2_1_YEAR_TERM', + 'categoryCode': 'guest_core', + + } + ] + } +} + +rsi_placeOrder = { + 'orderId': 1234, + 'orderDetails': { + 'prices': [ + { + 'id': 4, + 'item': { + 'id': 1, + 'description': 'B1.1x2 (1 Year ''Term)', + 'keyName': 'B1_1X2_1_YEAR_TERM', + } + } + ] + } +} diff --git a/SoftLayer/fixtures/SoftLayer_Product_Package.py b/SoftLayer/fixtures/SoftLayer_Product_Package.py index c21ba80cb..5553b0458 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Package.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Package.py @@ -1548,6 +1548,8 @@ { 'id': 12273, 'keyName': 'B1_1X2_1_YEAR_TERM', + 'description': 'B1 1x2 1 year term', + 'capacity': 12, 'itemCategory': { 'categoryCode': 'reserved_capacity', 'id': 2060, diff --git a/SoftLayer/fixtures/SoftLayer_Security_Ssh_Key.py b/SoftLayer/fixtures/SoftLayer_Security_Ssh_Key.py index 9d1f99571..9380cc601 100644 --- a/SoftLayer/fixtures/SoftLayer_Security_Ssh_Key.py +++ b/SoftLayer/fixtures/SoftLayer_Security_Ssh_Key.py @@ -6,3 +6,4 @@ 'notes': 'notes', 'key': 'ssh-rsa AAAAB3N...pa67 user@example.com'} createObject = getObject +getAllObjects = [getObject] \ No newline at end of file diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_ReservedCapacityGroup.py b/SoftLayer/fixtures/SoftLayer_Virtual_ReservedCapacityGroup.py index c6e034634..34ef87ea6 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_ReservedCapacityGroup.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_ReservedCapacityGroup.py @@ -55,3 +55,31 @@ } ] } + + +getObject_pending = { + 'accountId': 1234, + 'backendRouterId': 1411193, + 'backendRouter': { + 'fullyQualifiedDomainName': 'bcr02a.dal13.softlayer.com', + 'hostname': 'bcr02a.dal13', + 'id': 1411193, + 'datacenter': { + 'id': 1854895, + 'longName': 'Dallas 13', + 'name': 'dal13', + + } + }, + 'createDate': '2018-09-24T16:33:09-06:00', + 'id': 3103, + 'modifyDate': '', + 'name': 'test-capacity', + 'instances': [ + { + 'createDate': '2018-09-24T16:33:09-06:00', + 'guestId': 62159257, + 'id': 3501, + } + ] +} \ No newline at end of file diff --git a/tests/CLI/modules/vs_capacity_tests.py b/tests/CLI/modules/vs_capacity_tests.py new file mode 100644 index 000000000..9794374ae --- /dev/null +++ b/tests/CLI/modules/vs_capacity_tests.py @@ -0,0 +1,80 @@ +""" + SoftLayer.tests.CLI.modules.vs_capacity_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" +import json + +import mock + +from SoftLayer.CLI import exceptions +from SoftLayer.fixtures import SoftLayer_Product_Package +from SoftLayer.fixtures import SoftLayer_Product_Order +from SoftLayer import SoftLayerAPIError +from SoftLayer import testing + + +from pprint import pprint as pp +class VSCapacityTests(testing.TestCase): + + def test_list(self): + result = self.run_command(['vs', 'capacity', 'list']) + self.assert_no_fail(result) + + def test_detail(self): + result = self.run_command(['vs', 'capacity', 'detail', '1234']) + self.assert_no_fail(result) + + def test_detail_pending(self): + # Instances don't have a billing item if they haven't been approved yet. + capacity_mock = self.set_mock('SoftLayer_Virtual_ReservedCapacityGroup', 'getObject') + get_object = { + 'name': 'test-capacity', + 'instances': [ + { + 'createDate': '2018-09-24T16:33:09-06:00', + 'guestId': 62159257, + 'id': 3501, + } + ] + } + capacity_mock.return_value = get_object + result = self.run_command(['vs', 'capacity', 'detail', '1234']) + self.assert_no_fail(result) + + def test_create_test(self): + item_mock = self.set_mock('SoftLayer_Product_Package', 'getItems') + item_mock.return_value = SoftLayer_Product_Package.getItems_RESERVED_CAPACITY + order_mock = self.set_mock('SoftLayer_Product_Order', 'verifyOrder') + order_mock.return_value = SoftLayer_Product_Order.rsc_verifyOrder + result = self.run_command(['vs', 'capacity', 'create', '--name=TEST', '--datacenter=dal13', + '--backend_router_id=1234', '--capacity=B1_1X2_1_YEAR_TERM', '--quantity=10', '--test']) + self.assert_no_fail(result) + + def test_create(self): + item_mock = self.set_mock('SoftLayer_Product_Package', 'getItems') + item_mock.return_value = SoftLayer_Product_Package.getItems_RESERVED_CAPACITY + order_mock = self.set_mock('SoftLayer_Product_Order', 'placeOrder') + order_mock.return_value = SoftLayer_Product_Order.rsc_placeOrder + result = self.run_command(['vs', 'capacity', 'create', '--name=TEST', '--datacenter=dal13', + '--backend_router_id=1234', '--capacity=B1_1X2_1_YEAR_TERM', '--quantity=10']) + self.assert_no_fail(result) + + def test_create_options(self): + item_mock = self.set_mock('SoftLayer_Product_Package', 'getItems') + item_mock.return_value = SoftLayer_Product_Package.getItems_RESERVED_CAPACITY + result = self.run_command(['vs', 'capacity', 'create-options']) + self.assert_no_fail(result) + + def test_create_guest_test(self): + result = self.run_command(['vs', 'capacity', 'create-guest', '--capacity-id=3103', '--primary-disk=25', + '-H ABCDEFG', '-D test_list.com', '-o UBUNTU_LATEST_64', '-kTest 1', '--test']) + self.assert_no_fail(result) + + def test_create_guest(self): + order_mock = self.set_mock('SoftLayer_Product_Order', 'placeOrder') + order_mock.return_value = SoftLayer_Product_Order.rsi_placeOrder + result = self.run_command(['vs', 'capacity', 'create-guest', '--capacity-id=3103', '--primary-disk=25', + '-H ABCDEFG', '-D test_list.com', '-o UBUNTU_LATEST_64', '-kTest 1']) + self.assert_no_fail(result) \ No newline at end of file From ac15931cc89e2822ffd69e20cbef317d0e598dd4 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Mon, 1 Oct 2018 18:55:40 -0500 Subject: [PATCH 10/18] #1026 pylint fixes --- SoftLayer/CLI/virt/capacity/__init__.py | 37 +++-- SoftLayer/CLI/virt/capacity/create.py | 34 +--- .../{create-guest.py => create_guest.py} | 93 +---------- .../{create-options.py => create_options.py} | 8 +- SoftLayer/CLI/virt/capacity/detail.py | 18 +-- SoftLayer/CLI/virt/capacity/list.py | 18 +-- SoftLayer/CLI/virt/create.py | 8 +- SoftLayer/fixtures/SoftLayer_Account.py | 88 ++++++---- SoftLayer/fixtures/SoftLayer_Product_Order.py | 33 ++-- .../fixtures/SoftLayer_Security_Ssh_Key.py | 2 +- SoftLayer/fixtures/SoftLayer_Virtual_Guest.py | 3 - ...SoftLayer_Virtual_ReservedCapacityGroup.py | 4 +- SoftLayer/managers/__init__.py | 2 +- SoftLayer/managers/ordering.py | 1 - SoftLayer/managers/vs_capacity.py | 26 +-- tests/CLI/modules/dedicatedhost_tests.py | 152 +++++++++--------- tests/CLI/modules/ticket_tests.py | 12 +- tests/CLI/modules/vs_capacity_tests.py | 28 ++-- tests/managers/dns_tests.py | 48 +++--- tests/managers/vs_capacity_tests.py | 29 ++-- tests/managers/vs_tests.py | 8 +- 21 files changed, 275 insertions(+), 377 deletions(-) rename SoftLayer/CLI/virt/capacity/{create-guest.py => create_guest.py} (50%) rename SoftLayer/CLI/virt/capacity/{create-options.py => create_options.py} (89%) diff --git a/SoftLayer/CLI/virt/capacity/__init__.py b/SoftLayer/CLI/virt/capacity/__init__.py index 6157a7b93..0dbaa754d 100644 --- a/SoftLayer/CLI/virt/capacity/__init__.py +++ b/SoftLayer/CLI/virt/capacity/__init__.py @@ -1,43 +1,42 @@ """Manages Reserved Capacity.""" # :license: MIT, see LICENSE for more details. + import importlib -import click -import types -import SoftLayer import os -from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting -from pprint import pprint as pp +import click + CONTEXT = {'help_option_names': ['-h', '--help'], 'max_content_width': 999} -class capacityCommands(click.MultiCommand): + + +class CapacityCommands(click.MultiCommand): """Loads module for capacity related commands.""" - def __init__(self, *path, **attrs): + def __init__(self, **attrs): click.MultiCommand.__init__(self, **attrs) self.path = os.path.dirname(__file__) def list_commands(self, ctx): """List all sub-commands.""" - rv = [] + commands = [] for filename in os.listdir(self.path): if filename == '__init__.py': continue if filename.endswith('.py'): - rv.append(filename[:-3]) - rv.sort() - return rv + commands.append(filename[:-3]) + commands.sort() + return commands - def get_command(self, ctx, name): + def get_command(self, ctx, cmd_name): """Get command for click.""" - path = "%s.%s" % (__name__, name) + path = "%s.%s" % (__name__, cmd_name) module = importlib.import_module(path) return getattr(module, 'cli') -@click.group(cls=capacityCommands, - context_settings=CONTEXT) -@environment.pass_env -def cli(env): - """Manages Reserved Capacity""" + +# Required to get the sub-sub-sub command to work. +@click.group(cls=CapacityCommands, context_settings=CONTEXT) +def cli(): + """Base command for all capacity related concerns""" pass diff --git a/SoftLayer/CLI/virt/capacity/create.py b/SoftLayer/CLI/virt/capacity/create.py index da4ef997c..2fd4ace77 100644 --- a/SoftLayer/CLI/virt/capacity/create.py +++ b/SoftLayer/CLI/virt/capacity/create.py @@ -1,19 +1,13 @@ -"""Create a Reserved Capacity instance. - - -""" -# :license: MIT, see LICENSE for more details. +"""Create a Reserved Capacity instance.""" import click -import SoftLayer + from SoftLayer.CLI import environment from SoftLayer.CLI import formatting from SoftLayer.managers.vs_capacity import CapacityManager as CapacityManager -from pprint import pprint as pp - @click.command(epilog=click.style("""WARNING: Reserved Capacity is on a yearly contract""" """ and not cancelable until the contract is expired.""", fg='red')) @click.option('--name', '-n', required=True, prompt=True, @@ -30,7 +24,10 @@ help="Do not actually create the virtual server") @environment.pass_env def cli(env, name, datacenter, backend_router_id, capacity, quantity, test=False): - """Create a Reserved Capacity instance. *WARNING*: Reserved Capacity is on a yearly contract and not cancelable until the contract is expired.""" + """Create a Reserved Capacity instance. + + *WARNING*: Reserved Capacity is on a yearly contract and not cancelable until the contract is expired. + """ manager = CapacityManager(env.client) result = manager.create( @@ -58,22 +55,3 @@ def cli(env, name, datacenter, backend_router_id, capacity, quantity, test=False table.add_row([item['categoryCode'], item['description']]) table.add_row(['Total', result['orderDetails']['postTaxRecurring']]) env.fout(table) - - -""" -Calling: SoftLayer_Product_Order::placeOrder( -id=None, -mask='', -filter='None', -args=( - {'orderContainers': [ - {'backendRouterId': 1079095, - 'name': 'cgallo-test-capacity', - 'quantity': 1, - 'packageId': 1059, - 'location': 1854895, - 'useHourlyPricing': True, - 'complexType': 'SoftLayer_Container_Product_Order_Virtual_ReservedCapacity', - 'prices': [{'id': 217633}]}]},), limit=None, offset=None)) -Resetting dropped connection: r237377.application.qadal0501.softlayer.local -""" \ No newline at end of file diff --git a/SoftLayer/CLI/virt/capacity/create-guest.py b/SoftLayer/CLI/virt/capacity/create_guest.py similarity index 50% rename from SoftLayer/CLI/virt/capacity/create-guest.py rename to SoftLayer/CLI/virt/capacity/create_guest.py index 7fda2a494..854fe3f94 100644 --- a/SoftLayer/CLI/virt/capacity/create-guest.py +++ b/SoftLayer/CLI/virt/capacity/create_guest.py @@ -3,100 +3,17 @@ import click -import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import formatting from SoftLayer.CLI import helpers +from SoftLayer.CLI.virt.create import _parse_create_args as _parse_create_args from SoftLayer.CLI.virt.create import _update_with_like_args as _update_with_like_args from SoftLayer.managers.vs_capacity import CapacityManager as CapacityManager -from pprint import pprint as pp - - - -def _parse_create_args(client, args): - """Parses CLI arguments into a single data structure to be used by vs_capacity::create_guest. - - :param dict args: CLI arguments - """ - data = { - "hourly": True, - "domain": args['domain'], - "hostname": args['hostname'], - "private": args['private'], - "disks": args['disk'], - "boot_mode": args.get('boot_mode', None), - "local_disk": None - } - if args.get('os'): - data['os_code'] = args['os'] - - if args.get('image'): - if args.get('image').isdigit(): - image_mgr = SoftLayer.ImageManager(client) - image_details = image_mgr.get_image(args.get('image'), - mask="id,globalIdentifier") - data['image_id'] = image_details['globalIdentifier'] - else: - data['image_id'] = args['image'] - - if args.get('network'): - data['nic_speed'] = args.get('network') - - if args.get('userdata'): - data['userdata'] = args['userdata'] - elif args.get('userfile'): - with open(args['userfile'], 'r') as userfile: - data['userdata'] = userfile.read() - - if args.get('postinstall'): - data['post_uri'] = args.get('postinstall') - - # Get the SSH keys - if args.get('key'): - keys = [] - for key in args.get('key'): - resolver = SoftLayer.SshKeyManager(client).resolve_ids - key_id = helpers.resolve_id(resolver, key, 'SshKey') - keys.append(key_id) - data['ssh_keys'] = keys - - if args.get('vlan_public'): - data['public_vlan'] = args['vlan_public'] - - if args.get('vlan_private'): - data['private_vlan'] = args['vlan_private'] - - data['public_subnet'] = args.get('subnet_public', None) - - data['private_subnet'] = args.get('subnet_private', None) - - if args.get('public_security_group'): - pub_groups = args.get('public_security_group') - data['public_security_groups'] = [group for group in pub_groups] - - if args.get('private_security_group'): - priv_groups = args.get('private_security_group') - data['private_security_groups'] = [group for group in priv_groups] - - if args.get('tag'): - data['tags'] = ','.join(args['tag']) - - if args.get('host_id'): - data['host_id'] = args['host_id'] - - if args.get('ipv6'): - data['ipv6'] = True - - data['primary_disk'] = args.get('primary_disk') - - return data - - @click.command() @click.option('--capacity-id', type=click.INT, help="Reserve capacity Id to provision this guest into.") -@click.option('--primary-disk', type=click.Choice(['25','100']), default='25', help="Size of the main drive." ) +@click.option('--primary-disk', type=click.Choice(['25', '100']), default='25', help="Size of the main drive.") @click.option('--hostname', '-H', required=True, prompt=True, help="Host portion of the FQDN.") @click.option('--domain', '-D', required=True, prompt=True, help="Domain portion of the FQDN.") @click.option('--os', '-o', help="OS install code. Tip: you can specify _LATEST.") @@ -113,12 +30,15 @@ def _parse_create_args(client, args): @helpers.multi_option('--tag', '-g', help="Tags to add to the instance.") @click.option('--userdata', '-u', help="User defined metadata string.") @click.option('--ipv6', is_flag=True, help="Adds an IPv6 address to this guest") -@click.option('--test', is_flag=True, +@click.option('--test', is_flag=True, help="Test order, will return the order container, but not actually order a server.") @environment.pass_env def cli(env, **args): """Allows for creating a virtual guest in a reserved capacity.""" create_args = _parse_create_args(env.client, args) + if args.get('ipv6'): + create_args['ipv6'] = True + create_args['primary_disk'] = args.get('primary_disk') manager = CapacityManager(env.client) capacity_id = args.get('capacity_id') test = args.get('test') @@ -141,4 +61,3 @@ def _build_receipt(result, test=False): for item in prices: table.add_row([item['id'], item['item']['description']]) return table - diff --git a/SoftLayer/CLI/virt/capacity/create-options.py b/SoftLayer/CLI/virt/capacity/create_options.py similarity index 89% rename from SoftLayer/CLI/virt/capacity/create-options.py rename to SoftLayer/CLI/virt/capacity/create_options.py index 0f321298d..b8aacdd12 100644 --- a/SoftLayer/CLI/virt/capacity/create-options.py +++ b/SoftLayer/CLI/virt/capacity/create_options.py @@ -3,14 +3,11 @@ import click -import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import formatting from SoftLayer.managers.vs_capacity import CapacityManager as CapacityManager -from pprint import pprint as pp - @click.command() @environment.pass_env def cli(env): @@ -38,8 +35,9 @@ def cli(env): def get_price(item): + """Finds the price with the default locationGroupId""" the_price = "No Default Pricing" - for price in item.get('prices',[]): + for price in item.get('prices', []): if price.get('locationGroupId') == '': - the_price = "%0.4f" % float(price['hourlyRecurringFee']) + the_price = "%0.4f" % float(price['hourlyRecurringFee']) return the_price diff --git a/SoftLayer/CLI/virt/capacity/detail.py b/SoftLayer/CLI/virt/capacity/detail.py index 574905b06..485192e4c 100644 --- a/SoftLayer/CLI/virt/capacity/detail.py +++ b/SoftLayer/CLI/virt/capacity/detail.py @@ -1,9 +1,7 @@ """Shows the details of a reserved capacity group""" -# :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 @@ -25,6 +23,7 @@ 'backend_ip' ] + @click.command(epilog="Once provisioned, virtual guests can be managed with the slcli vs commands") @click.argument('identifier') @click.option('--columns', @@ -38,7 +37,7 @@ def cli(env, identifier, columns): """Reserved Capacity Group details. Will show which guests are assigned to a reservation.""" manager = CapacityManager(env.client) - mask = """mask[instances[id,createDate,guestId,billingItem[id, recurringFee, category[name]], + mask = """mask[instances[id,createDate,guestId,billingItem[id, recurringFee, category[name]], guest[modifyDate,id, primaryBackendIpAddress, primaryIpAddress,domain, hostname]]]""" result = manager.get_object(identifier, mask) try: @@ -46,23 +45,12 @@ def cli(env, identifier, columns): except KeyError: flavor = "Pending Approval..." - table = formatting.Table(columns.columns, - title = "%s - %s" % (result.get('name'), flavor) - ) + table = formatting.Table(columns.columns, title="%s - %s" % (result.get('name'), flavor)) # RCI = Reserved Capacity Instance for rci in result['instances']: guest = rci.get('guest', None) - guest_string = "---" - createDate = rci['createDate'] if guest is not None: - guest_string = "%s (%s)" % ( - guest.get('fullyQualifiedDomainName', 'No FQDN'), - guest.get('primaryIpAddress', 'No Public Ip') - ) - createDate = guest['modifyDate'] table.add_row([value or formatting.blank() for value in columns.row(guest)]) else: table.add_row(['-' for value in columns.columns]) env.fout(table) - - diff --git a/SoftLayer/CLI/virt/capacity/list.py b/SoftLayer/CLI/virt/capacity/list.py index cbbb17a94..a5a5445c1 100644 --- a/SoftLayer/CLI/virt/capacity/list.py +++ b/SoftLayer/CLI/virt/capacity/list.py @@ -1,9 +1,7 @@ """List Reserved Capacity""" -# :license: MIT, see LICENSE for more details. import click -import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import formatting from SoftLayer.managers.vs_capacity import CapacityManager as CapacityManager @@ -16,19 +14,19 @@ def cli(env): manager = CapacityManager(env.client) result = manager.list() table = formatting.Table( - ["ID", "Name", "Capacity", "Flavor", "Location", "Created"], + ["ID", "Name", "Capacity", "Flavor", "Location", "Created"], title="Reserved Capacity" ) - for rc in result: - occupied_string = "#" * int(rc.get('occupiedInstanceCount',0)) - available_string = "-" * int(rc.get('availableInstanceCount',0)) + for r_c in result: + occupied_string = "#" * int(r_c.get('occupiedInstanceCount', 0)) + available_string = "-" * int(r_c.get('availableInstanceCount', 0)) try: - flavor = rc['instances'][0]['billingItem']['description'] - cost = float(rc['instances'][0]['billingItem']['hourlyRecurringFee']) + flavor = r_c['instances'][0]['billingItem']['description'] + # cost = float(r_c['instances'][0]['billingItem']['hourlyRecurringFee']) except KeyError: flavor = "Unknown Billing Item" - location = rc['backendRouter']['hostname'] + location = r_c['backendRouter']['hostname'] capacity = "%s%s" % (occupied_string, available_string) - table.add_row([rc['id'], rc['name'], capacity, flavor, location, rc['createDate']]) + table.add_row([r_c['id'], r_c['name'], capacity, flavor, location, r_c['createDate']]) env.fout(table) diff --git a/SoftLayer/CLI/virt/create.py b/SoftLayer/CLI/virt/create.py index ee904ba6a..79af92585 100644 --- a/SoftLayer/CLI/virt/create.py +++ b/SoftLayer/CLI/virt/create.py @@ -72,11 +72,11 @@ def _parse_create_args(client, args): :param dict args: CLI arguments """ data = { - "hourly": args['billing'] == 'hourly', + "hourly": args.get('billing', 'hourly') == 'hourly', "domain": args['domain'], "hostname": args['hostname'], - "private": args['private'], - "dedicated": args['dedicated'], + "private": args.get('private', None), + "dedicated": args.get('dedicated', None), "disks": args['disk'], "cpus": args.get('cpu', None), "memory": args.get('memory', None), @@ -89,7 +89,7 @@ def _parse_create_args(client, args): if not args.get('san') and args.get('flavor'): data['local_disk'] = None else: - data['local_disk'] = not args['san'] + data['local_disk'] = not args.get('san') if args.get('os'): data['os_code'] = args['os'] diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index 072cd9d79..1ad75a90b 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -1,5 +1,6 @@ # -*- coding: UTF-8 -*- +# # pylint: disable=bad-continuation getPrivateBlockDeviceTemplateGroups = [{ 'accountId': 1234, 'blockDevices': [], @@ -577,35 +578,62 @@ ] getReservedCapacityGroups = [ - {'accountId': 1234, - 'backendRouterId': 1411193, - 'createDate': '2018-09-24T16:33:09-06:00', - 'id': 3103, - 'modifyDate': '', - 'name': 'test-capacity', - 'availableInstanceCount': 1, - 'instanceCount': 2, - 'occupiedInstanceCount': 1, - 'backendRouter': - {'accountId': 1, - 'bareMetalInstanceFlag': 0, - 'domain': 'softlayer.com', - 'fullyQualifiedDomainName': 'bcr02a.dal13.softlayer.com', - 'hardwareStatusId': 5, - 'hostname': 'bcr02a.dal13', - 'id': 1411193, - 'notes': '', - 'provisionDate': '', - 'serviceProviderId': 1, - 'serviceProviderResourceId': '', - 'primaryIpAddress': '10.0.144.28', - 'datacenter': {'id': 1854895, 'longName': 'Dallas 13', 'name': 'dal13', 'statusId': 2}, - 'hardwareFunction': {'code': 'ROUTER', 'description': 'Router', 'id': 1}, - 'topLevelLocation': {'id': 1854895, 'longName': 'Dallas 13', 'name': 'dal13', 'statusId': 2} - }, - 'instances': [ - {'id': 3501, 'billingItem': {'description': 'B1.1x2 (1 Year Term)', 'hourlyRecurringFee': '.032'}}, - {'id': 3519, 'billingItem': {'description': 'B1.1x2 (1 Year Term)', 'hourlyRecurringFee': '.032'}} + { + 'accountId': 1234, + 'backendRouterId': 1411193, + 'createDate': '2018-09-24T16:33:09-06:00', + 'id': 3103, + 'modifyDate': '', + 'name': 'test-capacity', + 'availableInstanceCount': 1, + 'instanceCount': 2, + 'occupiedInstanceCount': 1, + 'backendRouter': { + 'accountId': 1, + 'bareMetalInstanceFlag': 0, + 'domain': 'softlayer.com', + 'fullyQualifiedDomainName': 'bcr02a.dal13.softlayer.com', + 'hardwareStatusId': 5, + 'hostname': 'bcr02a.dal13', + 'id': 1411193, + 'notes': '', + 'provisionDate': '', + 'serviceProviderId': 1, + 'serviceProviderResourceId': '', + 'primaryIpAddress': '10.0.144.28', + 'datacenter': { + 'id': 1854895, + 'longName': 'Dallas 13', + 'name': 'dal13', + 'statusId': 2 + }, + 'hardwareFunction': { + 'code': 'ROUTER', + 'description': 'Router', + 'id': 1 + }, + 'topLevelLocation': { + 'id': 1854895, + 'longName': 'Dallas 13', + 'name': 'dal13', + 'statusId': 2 + } + }, + 'instances': [ + { + 'id': 3501, + 'billingItem': { + 'description': 'B1.1x2 (1 Year Term)', + 'hourlyRecurringFee': '.032' + } + }, + { + 'id': 3519, + 'billingItem': { + 'description': 'B1.1x2 (1 Year Term)', + 'hourlyRecurringFee': '.032' + } + } ] } -] \ No newline at end of file +] diff --git a/SoftLayer/fixtures/SoftLayer_Product_Order.py b/SoftLayer/fixtures/SoftLayer_Product_Order.py index 4eb21f249..5be637c9b 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Order.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Order.py @@ -15,7 +15,7 @@ }]} placeOrder = verifyOrder -#Reserved Capacity Stuff +# Reserved Capacity Stuff rsc_verifyOrder = { 'orderContainers': [ @@ -48,21 +48,20 @@ 'postTaxRecurring': '0.32', }, 'placedOrder': { - 'status': 'Great, thanks for asking', - 'locationObject': { - 'id': 1854895, - 'longName': 'Dallas 13', - 'name': 'dal13' - }, - 'name': 'test-capacity', - 'items': [ - { - 'description': 'B1.1x2 (1 Year ''Term)', - 'keyName': 'B1_1X2_1_YEAR_TERM', - 'categoryCode': 'guest_core', - - } - ] + 'status': 'Great, thanks for asking', + 'locationObject': { + 'id': 1854895, + 'longName': 'Dallas 13', + 'name': 'dal13' + }, + 'name': 'test-capacity', + 'items': [ + { + 'description': 'B1.1x2 (1 Year ''Term)', + 'keyName': 'B1_1X2_1_YEAR_TERM', + 'categoryCode': 'guest_core', + } + ] } } @@ -70,7 +69,7 @@ 'orderId': 1234, 'orderDetails': { 'prices': [ - { + { 'id': 4, 'item': { 'id': 1, diff --git a/SoftLayer/fixtures/SoftLayer_Security_Ssh_Key.py b/SoftLayer/fixtures/SoftLayer_Security_Ssh_Key.py index 9380cc601..a7ecdb29a 100644 --- a/SoftLayer/fixtures/SoftLayer_Security_Ssh_Key.py +++ b/SoftLayer/fixtures/SoftLayer_Security_Ssh_Key.py @@ -6,4 +6,4 @@ 'notes': 'notes', 'key': 'ssh-rsa AAAAB3N...pa67 user@example.com'} createObject = getObject -getAllObjects = [getObject] \ No newline at end of file +getAllObjects = [getObject] diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py index 732d9b812..69a1b95e6 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py @@ -617,6 +617,3 @@ } }, ] - - -# RESERVED_ORDER_TEMPLATE = {'imageTemplateId': '', 'location': '1854895', 'packageId': 835, 'presetId': 215, 'quantity': 1, 'sourceVirtualGuestId': '', 'useHourlyPricing': True, 'complexType': 'SoftLayer_Container_Product_Order_Virtual_Guest', 'prices': [{'hourlyRecurringFee': '0', 'id': 211451, 'recurringFee': '0', 'item': {'description': 'Ubuntu Linux 18.04 LTS Bionic Beaver Minimal Install (64 bit) '}}, {'hourlyRecurringFee': '0', 'id': 2202, 'recurringFee': '0', 'item': {'description': '25 GB (SAN)'}}, {'hourlyRecurringFee': '0', 'id': 905, 'recurringFee': '0', 'item': {'description': 'Reboot / Remote Console'}}, {'hourlyRecurringFee': '0', 'id': 273, 'recurringFee': '0', 'item': {'description': '100 Mbps Public & Private Network Uplinks'}}, {'hourlyRecurringFee': '0', 'id': 1800, 'item': {'description': '0 GB Bandwidth Allotment'}}, {'hourlyRecurringFee': '0', 'id': 21, 'recurringFee': '0', 'item': {'description': '1 IP Address'}}, {'hourlyRecurringFee': '0', 'id': 55, 'recurringFee': '0', 'item': {'description': 'Host Ping'}}, {'hourlyRecurringFee': '0', 'id': 57, 'recurringFee': '0', 'item': {'description': 'Email and Ticket'}}, {'hourlyRecurringFee': '0', 'id': 58, 'recurringFee': '0', 'item': {'description': 'Automated Notification'}}, {'hourlyRecurringFee': '0', 'id': 420, 'recurringFee': '0', 'item': {'description': 'Unlimited SSL VPN Users & 1 PPTP VPN User per account'}}, {'hourlyRecurringFee': '0', 'id': 418, 'recurringFee': '0', 'item': {'description': 'Nessus Vulnerability Assessment & Reporting'}}, {'id': 17129}], 'sshKeys': [{'sshKeyIds': [87634]}], 'virtualGuests': [{'domain': 'cgallo.com', 'hostname': 'A1538172419'}], 'reservedCapacityId': 3103} \ No newline at end of file diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_ReservedCapacityGroup.py b/SoftLayer/fixtures/SoftLayer_Virtual_ReservedCapacityGroup.py index 34ef87ea6..67f496d6e 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_ReservedCapacityGroup.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_ReservedCapacityGroup.py @@ -24,7 +24,7 @@ 'billingItem': { 'id': 348319479, 'recurringFee': '3.04', - 'category': { 'name': 'Reserved Capacity' }, + 'category': {'name': 'Reserved Capacity'}, 'item': { 'keyName': 'B1_1X2_1_YEAR_TERM' } @@ -82,4 +82,4 @@ 'id': 3501, } ] -} \ No newline at end of file +} diff --git a/SoftLayer/managers/__init__.py b/SoftLayer/managers/__init__.py index c6a8688d7..b6cc1faa5 100644 --- a/SoftLayer/managers/__init__.py +++ b/SoftLayer/managers/__init__.py @@ -27,7 +27,7 @@ from SoftLayer.managers.ticket import TicketManager from SoftLayer.managers.user import UserManager from SoftLayer.managers.vs import VSManager -from SoftLayer.managers.vs_capacity import CapacityManager +from SoftLayer.managers.vs_capacity import CapacityManager __all__ = [ diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index 01a182ae1..65c3f941a 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -461,7 +461,6 @@ def place_order(self, package_keyname, location, item_keynames, complex_type=Non def place_quote(self, package_keyname, location, item_keynames, complex_type=None, preset_keyname=None, extras=None, quantity=1, quote_name=None, send_email=False): - """Place a quote with the given package and prices. This function takes in parameters needed for an order and places the quote. diff --git a/SoftLayer/managers/vs_capacity.py b/SoftLayer/managers/vs_capacity.py index 9dbacce26..358852603 100644 --- a/SoftLayer/managers/vs_capacity.py +++ b/SoftLayer/managers/vs_capacity.py @@ -13,12 +13,12 @@ from SoftLayer.managers.vs import VSManager from SoftLayer import utils -from pprint import pprint as pp # Invalid names are ignored due to long method names and short argument names # pylint: disable=invalid-name, no-self-use LOGGER = logging.getLogger(__name__) + class CapacityManager(utils.IdentifierMixin, object): """Manages SoftLayer Dedicated Hosts. @@ -40,18 +40,25 @@ def __init__(self, client, ordering_manager=None): self.ordering_manager = ordering.OrderingManager(client) def list(self): - mask = """mask[availableInstanceCount, occupiedInstanceCount, + """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 def get_object(self, identifier, mask=None): + """Get a Reserved Capacity Group + + :param int identifier: Id of the SoftLayer_Virtual_ReservedCapacityGroup + :parm 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 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 @@ -75,7 +82,7 @@ def get_available_routers(self, dc=None): # 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: + for region in regions: routers[region['keyname']] = [] for location in region['locations']: location['location']['pods'] = list() @@ -125,24 +132,22 @@ def create_guest(self, capacity_id, test, guest_object): """ vs_manager = VSManager(self.client) mask = "mask[instances[id, billingItem[id, item[id,keyName]]], backendRouter[id, datacenter[name]]]" - capacity = self.get_object(capacity_id) + 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: - raise SoftLayer.SoftLayerError("Unable to find capacity Flavor.") + raise SoftLayer.SoftLayerError("Unable to find capacity Flavor.") - guest_object['flavor'] = flavor + guest_object['flavor'] = flavor guest_object['datacenter'] = capacity['backendRouter']['datacenter']['name'] - # pp(guest_object) - + 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]}) - - # pp(template) + if test: result = self.client.call('Product_Order', 'verifyOrder', template) else: @@ -158,4 +163,3 @@ def _flavor_string(capacity_key, primary_disk): """ flavor = "%sX%s" % (capacity_key[:-12], primary_disk) return flavor - diff --git a/tests/CLI/modules/dedicatedhost_tests.py b/tests/CLI/modules/dedicatedhost_tests.py index 59f6cab7c..d43c6354f 100644 --- a/tests/CLI/modules/dedicatedhost_tests.py +++ b/tests/CLI/modules/dedicatedhost_tests.py @@ -116,8 +116,8 @@ def test_create_options(self): '56 Cores X 242 RAM X 1.2 TB', 'value': '56_CORES_X_242_RAM_X_1_4_TB' } - ]] - ) + ]] + ) def test_create_options_with_only_datacenter(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') @@ -149,7 +149,7 @@ def test_create_options_get_routers(self): 'Available Backend Routers': 'bcr04a.dal05' } ]] - ) + ) def test_create(self): SoftLayer.CLI.formatting.confirm = mock.Mock() @@ -166,23 +166,23 @@ def test_create(self): '--billing=hourly']) self.assert_no_fail(result) args = ({ - 'hardware': [{ - 'domain': 'example.com', - 'primaryBackendNetworkComponent': { - 'router': { - 'id': 51218 - } - }, - 'hostname': 'host' - }], - 'prices': [{ - 'id': 200269 - }], - 'location': 'DALLAS05', - 'packageId': 813, - 'complexType': 'SoftLayer_Container_Product_Order_Virtual_DedicatedHost', - 'useHourlyPricing': True, - 'quantity': 1},) + 'hardware': [{ + 'domain': 'example.com', + 'primaryBackendNetworkComponent': { + 'router': { + 'id': 51218 + } + }, + 'hostname': 'host' + }], + 'prices': [{ + 'id': 200269 + }], + 'location': 'DALLAS05', + 'packageId': 813, + 'complexType': 'SoftLayer_Container_Product_Order_Virtual_DedicatedHost', + 'useHourlyPricing': True, + 'quantity': 1},) self.assert_called_with('SoftLayer_Product_Order', 'placeOrder', args=args) @@ -202,23 +202,23 @@ def test_create_with_gpu(self): '--billing=hourly']) self.assert_no_fail(result) args = ({ - 'hardware': [{ - 'domain': 'example.com', - 'primaryBackendNetworkComponent': { - 'router': { - 'id': 51218 - } - }, - 'hostname': 'host' - }], - 'prices': [{ - 'id': 200269 - }], - 'location': 'DALLAS05', - 'packageId': 813, - 'complexType': 'SoftLayer_Container_Product_Order_Virtual_DedicatedHost', - 'useHourlyPricing': True, - 'quantity': 1},) + 'hardware': [{ + 'domain': 'example.com', + 'primaryBackendNetworkComponent': { + 'router': { + 'id': 51218 + } + }, + 'hostname': 'host' + }], + 'prices': [{ + 'id': 200269 + }], + 'location': 'DALLAS05', + 'packageId': 813, + 'complexType': 'SoftLayer_Container_Product_Order_Virtual_DedicatedHost', + 'useHourlyPricing': True, + 'quantity': 1},) self.assert_called_with('SoftLayer_Product_Order', 'placeOrder', args=args) @@ -241,22 +241,22 @@ def test_create_verify(self): self.assert_no_fail(result) args = ({ - 'useHourlyPricing': True, - 'hardware': [{ + 'useHourlyPricing': True, + 'hardware': [{ - 'hostname': 'host', - 'domain': 'example.com', + 'hostname': 'host', + 'domain': 'example.com', - 'primaryBackendNetworkComponent': { + 'primaryBackendNetworkComponent': { 'router': { 'id': 51218 } - } - }], - 'packageId': 813, 'prices': [{'id': 200269}], - 'location': 'DALLAS05', - 'complexType': 'SoftLayer_Container_Product_Order_Virtual_DedicatedHost', - 'quantity': 1},) + } + }], + 'packageId': 813, 'prices': [{'id': 200269}], + 'location': 'DALLAS05', + 'complexType': 'SoftLayer_Container_Product_Order_Virtual_DedicatedHost', + 'quantity': 1},) self.assert_called_with('SoftLayer_Product_Order', 'verifyOrder', args=args) @@ -271,20 +271,20 @@ def test_create_verify(self): self.assert_no_fail(result) args = ({ - 'useHourlyPricing': True, - 'hardware': [{ - 'hostname': 'host', - 'domain': 'example.com', - 'primaryBackendNetworkComponent': { + 'useHourlyPricing': True, + 'hardware': [{ + 'hostname': 'host', + 'domain': 'example.com', + 'primaryBackendNetworkComponent': { 'router': { 'id': 51218 } - } - }], - 'packageId': 813, 'prices': [{'id': 200269}], - 'location': 'DALLAS05', - 'complexType': 'SoftLayer_Container_Product_Order_Virtual_DedicatedHost', - 'quantity': 1},) + } + }], + 'packageId': 813, 'prices': [{'id': 200269}], + 'location': 'DALLAS05', + 'complexType': 'SoftLayer_Container_Product_Order_Virtual_DedicatedHost', + 'quantity': 1},) self.assert_called_with('SoftLayer_Product_Order', 'verifyOrder', args=args) @@ -340,22 +340,22 @@ def test_create_verify_no_price_or_more_than_one(self): self.assertIsInstance(result.exception, exceptions.ArgumentError) args = ({ - 'hardware': [{ - 'domain': 'example.com', - 'primaryBackendNetworkComponent': { - 'router': { - 'id': 51218 - } - }, - 'hostname': 'host' - }], - 'prices': [{ - 'id': 200269 - }], - 'location': 'DALLAS05', - 'packageId': 813, - 'complexType': 'SoftLayer_Container_Product_Order_Virtual_DedicatedHost', - 'useHourlyPricing': True, - 'quantity': 1},) + 'hardware': [{ + 'domain': 'example.com', + 'primaryBackendNetworkComponent': { + 'router': { + 'id': 51218 + } + }, + 'hostname': 'host' + }], + 'prices': [{ + 'id': 200269 + }], + 'location': 'DALLAS05', + 'packageId': 813, + 'complexType': 'SoftLayer_Container_Product_Order_Virtual_DedicatedHost', + 'useHourlyPricing': True, + 'quantity': 1},) self.assert_called_with('SoftLayer_Product_Order', 'verifyOrder', args=args) diff --git a/tests/CLI/modules/ticket_tests.py b/tests/CLI/modules/ticket_tests.py index 817b3e71f..657953c5e 100644 --- a/tests/CLI/modules/ticket_tests.py +++ b/tests/CLI/modules/ticket_tests.py @@ -277,12 +277,12 @@ def test_ticket_summary(self): expected = [ {'Status': 'Open', 'count': [ - {'Type': 'Accounting', 'count': 7}, - {'Type': 'Billing', 'count': 3}, - {'Type': 'Sales', 'count': 5}, - {'Type': 'Support', 'count': 6}, - {'Type': 'Other', 'count': 4}, - {'Type': 'Total', 'count': 1}]}, + {'Type': 'Accounting', 'count': 7}, + {'Type': 'Billing', 'count': 3}, + {'Type': 'Sales', 'count': 5}, + {'Type': 'Support', 'count': 6}, + {'Type': 'Other', 'count': 4}, + {'Type': 'Total', 'count': 1}]}, {'Status': 'Closed', 'count': 2} ] result = self.run_command(['ticket', 'summary']) diff --git a/tests/CLI/modules/vs_capacity_tests.py b/tests/CLI/modules/vs_capacity_tests.py index 9794374ae..5794dc823 100644 --- a/tests/CLI/modules/vs_capacity_tests.py +++ b/tests/CLI/modules/vs_capacity_tests.py @@ -4,18 +4,11 @@ :license: MIT, see LICENSE for more details. """ -import json - -import mock - -from SoftLayer.CLI import exceptions -from SoftLayer.fixtures import SoftLayer_Product_Package from SoftLayer.fixtures import SoftLayer_Product_Order -from SoftLayer import SoftLayerAPIError +from SoftLayer.fixtures import SoftLayer_Product_Package from SoftLayer import testing -from pprint import pprint as pp class VSCapacityTests(testing.TestCase): def test_list(self): @@ -29,7 +22,7 @@ def test_detail(self): def test_detail_pending(self): # Instances don't have a billing item if they haven't been approved yet. capacity_mock = self.set_mock('SoftLayer_Virtual_ReservedCapacityGroup', 'getObject') - get_object = { + get_object = { 'name': 'test-capacity', 'instances': [ { @@ -49,7 +42,8 @@ def test_create_test(self): order_mock = self.set_mock('SoftLayer_Product_Order', 'verifyOrder') order_mock.return_value = SoftLayer_Product_Order.rsc_verifyOrder result = self.run_command(['vs', 'capacity', 'create', '--name=TEST', '--datacenter=dal13', - '--backend_router_id=1234', '--capacity=B1_1X2_1_YEAR_TERM', '--quantity=10', '--test']) + '--backend_router_id=1234', '--capacity=B1_1X2_1_YEAR_TERM', '--quantity=10', + '--test']) self.assert_no_fail(result) def test_create(self): @@ -58,23 +52,23 @@ def test_create(self): order_mock = self.set_mock('SoftLayer_Product_Order', 'placeOrder') order_mock.return_value = SoftLayer_Product_Order.rsc_placeOrder result = self.run_command(['vs', 'capacity', 'create', '--name=TEST', '--datacenter=dal13', - '--backend_router_id=1234', '--capacity=B1_1X2_1_YEAR_TERM', '--quantity=10']) + '--backend_router_id=1234', '--capacity=B1_1X2_1_YEAR_TERM', '--quantity=10']) self.assert_no_fail(result) def test_create_options(self): item_mock = self.set_mock('SoftLayer_Product_Package', 'getItems') item_mock.return_value = SoftLayer_Product_Package.getItems_RESERVED_CAPACITY - result = self.run_command(['vs', 'capacity', 'create-options']) + result = self.run_command(['vs', 'capacity', 'create_options']) self.assert_no_fail(result) def test_create_guest_test(self): - result = self.run_command(['vs', 'capacity', 'create-guest', '--capacity-id=3103', '--primary-disk=25', - '-H ABCDEFG', '-D test_list.com', '-o UBUNTU_LATEST_64', '-kTest 1', '--test']) + result = self.run_command(['vs', 'capacity', 'create_guest', '--capacity-id=3103', '--primary-disk=25', + '-H ABCDEFG', '-D test_list.com', '-o UBUNTU_LATEST_64', '-kTest 1', '--test']) self.assert_no_fail(result) def test_create_guest(self): order_mock = self.set_mock('SoftLayer_Product_Order', 'placeOrder') order_mock.return_value = SoftLayer_Product_Order.rsi_placeOrder - result = self.run_command(['vs', 'capacity', 'create-guest', '--capacity-id=3103', '--primary-disk=25', - '-H ABCDEFG', '-D test_list.com', '-o UBUNTU_LATEST_64', '-kTest 1']) - self.assert_no_fail(result) \ No newline at end of file + result = self.run_command(['vs', 'capacity', 'create_guest', '--capacity-id=3103', '--primary-disk=25', + '-H ABCDEFG', '-D test_list.com', '-o UBUNTU_LATEST_64', '-kTest 1']) + self.assert_no_fail(result) diff --git a/tests/managers/dns_tests.py b/tests/managers/dns_tests.py index 070eed707..6b32af918 100644 --- a/tests/managers/dns_tests.py +++ b/tests/managers/dns_tests.py @@ -97,13 +97,13 @@ def test_create_record_mx(self): self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', 'createObject', args=({ - 'domainId': 1, - 'ttl': 1200, - 'host': 'test', - 'type': 'MX', - 'data': 'testing', - 'mxPriority': 21 - },)) + 'domainId': 1, + 'ttl': 1200, + 'host': 'test', + 'type': 'MX', + 'data': 'testing', + 'mxPriority': 21 + },)) self.assertEqual(res, {'name': 'example.com'}) def test_create_record_srv(self): @@ -113,18 +113,18 @@ def test_create_record_srv(self): self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', 'createObject', args=({ - 'complexType': 'SoftLayer_Dns_Domain_ResourceRecord_SrvType', - 'domainId': 1, - 'ttl': 1200, - 'host': 'record', - 'type': 'SRV', - 'data': 'test_data', - 'priority': 21, - 'weight': 15, - 'service': 'foobar', - 'port': 8080, - 'protocol': 'SLS' - },)) + 'complexType': 'SoftLayer_Dns_Domain_ResourceRecord_SrvType', + 'domainId': 1, + 'ttl': 1200, + 'host': 'record', + 'type': 'SRV', + 'data': 'test_data', + 'priority': 21, + 'weight': 15, + 'service': 'foobar', + 'port': 8080, + 'protocol': 'SLS' + },)) self.assertEqual(res, {'name': 'example.com'}) def test_create_record_ptr(self): @@ -133,11 +133,11 @@ def test_create_record_ptr(self): self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', 'createObject', args=({ - 'ttl': 1200, - 'host': 'test', - 'type': 'PTR', - 'data': 'testing' - },)) + 'ttl': 1200, + 'host': 'test', + 'type': 'PTR', + 'data': 'testing' + },)) self.assertEqual(res, {'name': 'example.com'}) def test_generate_create_dict(self): diff --git a/tests/managers/vs_capacity_tests.py b/tests/managers/vs_capacity_tests.py index c6f4d68d9..2b31f6b1e 100644 --- a/tests/managers/vs_capacity_tests.py +++ b/tests/managers/vs_capacity_tests.py @@ -8,12 +8,11 @@ import mock import SoftLayer -from SoftLayer import exceptions from SoftLayer import fixtures from SoftLayer.fixtures import SoftLayer_Product_Package from SoftLayer import testing -from pprint import pprint as pp + class VSCapacityTests(testing.TestCase): def set_up(self): @@ -22,20 +21,20 @@ def set_up(self): amock.return_value = fixtures.SoftLayer_Product_Package.RESERVED_CAPACITY def test_list(self): - result = self.manager.list() + self.manager.list() self.assert_called_with('SoftLayer_Account', 'getReservedCapacityGroups') def test_get_object(self): - result = self.manager.get_object(100) + self.manager.get_object(100) self.assert_called_with('SoftLayer_Virtual_ReservedCapacityGroup', 'getObject', identifier=100) def test_get_object_mask(self): mask = "mask[id]" - result = self.manager.get_object(100, mask=mask) + self.manager.get_object(100, mask=mask) self.assert_called_with('SoftLayer_Virtual_ReservedCapacityGroup', 'getObject', identifier=100, mask=mask) def test_get_create_options(self): - result = self.manager.get_create_options() + self.manager.get_create_options() self.assert_called_with('SoftLayer_Product_Package', 'getItems', identifier=1059, mask=mock.ANY) def test_get_available_routers(self): @@ -50,7 +49,7 @@ def test_get_available_routers(self): def test_create(self): item_mock = self.set_mock('SoftLayer_Product_Package', 'getItems') item_mock.return_value = SoftLayer_Product_Package.getItems_RESERVED_CAPACITY - result = self.manager.create( + self.manager.create( name='TEST', datacenter='dal13', backend_router_id=1, capacity='B1_1X2_1_YEAR_TERM', quantity=5) expected_args = { @@ -63,8 +62,8 @@ def test_create(self): 'quantity': 5, 'useHourlyPricing': True, 'complexType': 'SoftLayer_Container_Product_Order_Virtual_ReservedCapacity', - 'prices': [ { 'id': 217561 } - ] + 'prices': [{'id': 217561} + ] } ] } @@ -73,12 +72,11 @@ def test_create(self): self.assert_called_with('SoftLayer_Location', 'getDatacenters') self.assert_called_with('SoftLayer_Product_Package', 'getItems', identifier=1059) self.assert_called_with('SoftLayer_Product_Order', 'placeOrder', args=(expected_args,)) - def test_create_test(self): item_mock = self.set_mock('SoftLayer_Product_Package', 'getItems') item_mock.return_value = SoftLayer_Product_Package.getItems_RESERVED_CAPACITY - result = self.manager.create( + self.manager.create( name='TEST', datacenter='dal13', backend_router_id=1, capacity='B1_1X2_1_YEAR_TERM', quantity=5, test=True) expected_args = { @@ -91,8 +89,8 @@ def test_create_test(self): 'quantity': 5, 'useHourlyPricing': True, 'complexType': 'SoftLayer_Container_Product_Order_Virtual_ReservedCapacity', - 'prices': [ { 'id': 217561 } - ] + 'prices': [{'id': 217561} + ] } ] } @@ -102,7 +100,6 @@ def test_create_test(self): self.assert_called_with('SoftLayer_Product_Package', 'getItems', identifier=1059) self.assert_called_with('SoftLayer_Product_Order', 'verifyOrder', args=(expected_args,)) - def test_create_guest(self): amock = self.set_mock('SoftLayer_Product_Package', 'getItems') amock.return_value = fixtures.SoftLayer_Product_Package.getItems_1_IPV6_ADDRESS @@ -121,7 +118,7 @@ def test_create_guest(self): 'public_subnet': None, 'ssh_keys': [1234] } - result = self.manager.create_guest(123, False, guest_object) + self.manager.create_guest(123, False, guest_object) expectedGenerate = { 'startCpus': None, 'maxMemory': None, @@ -186,7 +183,7 @@ def test_create_guest_testing(self): 'public_subnet': None, 'ssh_keys': [1234] } - result = self.manager.create_guest(123, True, guest_object) + self.manager.create_guest(123, True, guest_object) self.assert_called_with('SoftLayer_Product_Order', 'verifyOrder') def test_flavor_string(self): diff --git a/tests/managers/vs_tests.py b/tests/managers/vs_tests.py index 97b6c5c4d..c24124dd6 100644 --- a/tests/managers/vs_tests.py +++ b/tests/managers/vs_tests.py @@ -792,10 +792,10 @@ def test_edit_full(self): self.assertEqual(result, True) args = ({ - 'hostname': 'new-host', - 'domain': 'new.sftlyr.ws', - 'notes': 'random notes', - },) + 'hostname': 'new-host', + 'domain': 'new.sftlyr.ws', + 'notes': 'random notes', + },) self.assert_called_with('SoftLayer_Virtual_Guest', 'editObject', identifier=100, args=args) From 87a8ded1192f750ddf2d1d343fe84c6a391dc690 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Wed, 3 Oct 2018 17:50:27 -0500 Subject: [PATCH 11/18] doc updates --- docs/api/client.rst | 24 +++++ tests/CLI/modules/dedicatedhost_tests.py | 120 +++++++++++------------ tests/CLI/modules/user_tests.py | 2 +- tests/managers/hardware_tests.py | 2 +- tests/managers/ordering_tests.py | 2 +- tests/managers/sshkey_tests.py | 2 +- 6 files changed, 88 insertions(+), 64 deletions(-) diff --git a/docs/api/client.rst b/docs/api/client.rst index a29974be2..550364cba 100644 --- a/docs/api/client.rst +++ b/docs/api/client.rst @@ -144,6 +144,9 @@ SoftLayer's XML-RPC API also allows for pagination. client.call('Account', 'getVirtualGuests', limit=10, offset=0) # Page 1 client.call('Account', 'getVirtualGuests', limit=10, offset=10) # Page 2 + #Automatic Pagination (v5.5.3+) + client.call('Account', 'getVirtualGuests', iter=True) # Page 2 + Here's how to create a new Cloud Compute Instance using `SoftLayer_Virtual_Guest.createObject `_. Be warned, this call actually creates an hourly virtual server so this will @@ -161,6 +164,27 @@ have billing implications. }) +Debugging +------------- +If you ever need to figure out what exact API call the client is making, you can do the following: + +*NOTE* the `print_reproduceable` method produces different output for REST and XML-RPC endpoints. If you are using REST, this will produce a CURL call. IF you are using XML-RPC, it will produce some pure python code you can use outside of the SoftLayer library. + +:: + # Setup the client as usual + client = SoftLayer.Client() + # Create an instance of the DebugTransport, which logs API calls + debugger = SoftLayer.DebugTransport(client.transport) + # Set that as the default client transport + client.transport = debugger + # Make your API call + client.call('Account', 'getObject') + + # Print out the reproduceable call + for call in client.transport.get_last_calls(): + print(client.transport.print_reproduceable(call)) + + API Reference ------------- diff --git a/tests/CLI/modules/dedicatedhost_tests.py b/tests/CLI/modules/dedicatedhost_tests.py index fb5c47543..1769d8cbf 100644 --- a/tests/CLI/modules/dedicatedhost_tests.py +++ b/tests/CLI/modules/dedicatedhost_tests.py @@ -161,23 +161,23 @@ def test_create(self): '--billing=hourly']) self.assert_no_fail(result) args = ({ - 'hardware': [{ - 'domain': 'test.com', - 'primaryBackendNetworkComponent': { - 'router': { - 'id': 12345 - } - }, - 'hostname': 'test-dedicated' - }], - 'useHourlyPricing': True, - 'location': 'DALLAS05', - 'packageId': 813, - 'complexType': 'SoftLayer_Container_Product_Order_Virtual_DedicatedHost', - 'prices': [{ + 'hardware': [{ + 'domain': 'test.com', + 'primaryBackendNetworkComponent': { + 'router': { + 'id': 12345 + } + }, + 'hostname': 'test-dedicated' + }], + 'useHourlyPricing': True, + 'location': 'DALLAS05', + 'packageId': 813, + 'complexType': 'SoftLayer_Container_Product_Order_Virtual_DedicatedHost', + 'prices': [{ 'id': 200269 - }], - 'quantity': 1},) + }], + 'quantity': 1},) self.assert_called_with('SoftLayer_Product_Order', 'placeOrder', args=args) @@ -197,23 +197,23 @@ def test_create_with_gpu(self): '--billing=hourly']) self.assert_no_fail(result) args = ({ - 'hardware': [{ - 'domain': 'test.com', - 'primaryBackendNetworkComponent': { - 'router': { - 'id': 12345 - } - }, - 'hostname': 'test-dedicated' - }], - 'prices': [{ - 'id': 200269 - }], - 'location': 'DALLAS05', - 'packageId': 813, - 'complexType': 'SoftLayer_Container_Product_Order_Virtual_DedicatedHost', - 'useHourlyPricing': True, - 'quantity': 1},) + 'hardware': [{ + 'domain': 'test.com', + 'primaryBackendNetworkComponent': { + 'router': { + 'id': 12345 + } + }, + 'hostname': 'test-dedicated' + }], + 'prices': [{ + 'id': 200269 + }], + 'location': 'DALLAS05', + 'packageId': 813, + 'complexType': 'SoftLayer_Container_Product_Order_Virtual_DedicatedHost', + 'useHourlyPricing': True, + 'quantity': 1},) self.assert_called_with('SoftLayer_Product_Order', 'placeOrder', args=args) @@ -239,13 +239,13 @@ def test_create_verify(self): 'useHourlyPricing': True, 'hardware': [{ - 'hostname': 'test-dedicated', - 'domain': 'test.com', + 'hostname': 'test-dedicated', + 'domain': 'test.com', 'primaryBackendNetworkComponent': { - 'router': { - 'id': 12345 - } + 'router': { + 'id': 12345 + } } }], 'packageId': 813, 'prices': [{'id': 200269}], @@ -266,11 +266,11 @@ def test_create_verify(self): self.assert_no_fail(result) args = ({ - 'useHourlyPricing': True, - 'hardware': [{ - 'hostname': 'test-dedicated', - 'domain': 'test.com', - 'primaryBackendNetworkComponent': { + 'useHourlyPricing': True, + 'hardware': [{ + 'hostname': 'test-dedicated', + 'domain': 'test.com', + 'primaryBackendNetworkComponent': { 'router': { 'id': 12345 } @@ -318,22 +318,22 @@ def test_create_verify_no_price_or_more_than_one(self): self.assertIsInstance(result.exception, exceptions.ArgumentError) args = ({ - 'hardware': [{ - 'domain': 'test.com', - 'primaryBackendNetworkComponent': { - 'router': { - 'id': 12345 - } - }, - 'hostname': 'test-dedicated' - }], - 'prices': [{ - 'id': 200269 - }], - 'location': 'DALLAS05', - 'packageId': 813, - 'complexType': 'SoftLayer_Container_Product_Order_Virtual_DedicatedHost', - 'useHourlyPricing': True, - 'quantity': 1},) + 'hardware': [{ + 'domain': 'test.com', + 'primaryBackendNetworkComponent': { + 'router': { + 'id': 12345 + } + }, + 'hostname': 'test-dedicated' + }], + 'prices': [{ + 'id': 200269 + }], + 'location': 'DALLAS05', + 'packageId': 813, + 'complexType': 'SoftLayer_Container_Product_Order_Virtual_DedicatedHost', + 'useHourlyPricing': True, + 'quantity': 1},) self.assert_called_with('SoftLayer_Product_Order', 'verifyOrder', args=args) diff --git a/tests/CLI/modules/user_tests.py b/tests/CLI/modules/user_tests.py index 0222a62b8..6910d5d5a 100644 --- a/tests/CLI/modules/user_tests.py +++ b/tests/CLI/modules/user_tests.py @@ -94,7 +94,7 @@ def test_print_hardware_access(self): 'fullyQualifiedDomainName': 'test.test.test', 'provisionDate': '2018-05-08T15:28:32-06:00', 'primaryBackendIpAddress': '175.125.126.118', - 'primaryIpAddress': '175.125.126.118'} + 'primaryIpAddress': '175.125.126.118'} ], 'dedicatedHosts': [ {'id': 1234, diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index add6389fa..b3c95a1d2 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -288,7 +288,7 @@ def test_cancel_hardware_no_billing_item(self): ex = self.assertRaises(SoftLayer.SoftLayerError, self.hardware.cancel_hardware, 6327) - self.assertEqual("Ticket #1234 already exists for this server", str(ex)) + self.assertEqual("Ticket #1234 already exists for this server", str(ex)) def test_cancel_hardware_monthly_now(self): mock = self.set_mock('SoftLayer_Hardware_Server', 'getObject') diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index 0ea7c7546..d3754facf 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -508,7 +508,7 @@ def test_get_location_id_keyname(self): def test_get_location_id_exception(self): locations = self.set_mock('SoftLayer_Location', 'getDatacenters') locations.return_value = [] - self.assertRaises(exceptions.SoftLayerError, self.ordering.get_location_id, "BURMUDA") + self.assertRaises(exceptions.SoftLayerError, self.ordering.get_location_id, "BURMUDA") def test_get_location_id_int(self): dc_id = self.ordering.get_location_id(1234) diff --git a/tests/managers/sshkey_tests.py b/tests/managers/sshkey_tests.py index b21d0131f..19a0e2317 100644 --- a/tests/managers/sshkey_tests.py +++ b/tests/managers/sshkey_tests.py @@ -19,7 +19,7 @@ def test_add_key(self): notes='My notes') args = ({ - 'key': 'pretend this is a public SSH key', + 'key': 'pretend this is a public SSH key', 'label': 'Test label', 'notes': 'My notes', },) From 9d87c90de5e007f816a7bc1ebb365f720cea429d Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 4 Oct 2018 17:04:10 -0500 Subject: [PATCH 12/18] vs capacity docs --- docs/api/client.rst | 1 + docs/cli/vs.rst | 221 ++---------------------------- docs/cli/vs/reserved_capacity.rst | 53 +++++++ docs/dev/index.rst | 39 ++++++ 4 files changed, 108 insertions(+), 206 deletions(-) create mode 100644 docs/cli/vs/reserved_capacity.rst diff --git a/docs/api/client.rst b/docs/api/client.rst index 550364cba..6c447bead 100644 --- a/docs/api/client.rst +++ b/docs/api/client.rst @@ -171,6 +171,7 @@ If you ever need to figure out what exact API call the client is making, you can *NOTE* the `print_reproduceable` method produces different output for REST and XML-RPC endpoints. If you are using REST, this will produce a CURL call. IF you are using XML-RPC, it will produce some pure python code you can use outside of the SoftLayer library. :: + # Setup the client as usual client = SoftLayer.Client() # Create an instance of the DebugTransport, which logs API calls diff --git a/docs/cli/vs.rst b/docs/cli/vs.rst index f61b9fd92..55ee3c189 100644 --- a/docs/cli/vs.rst +++ b/docs/cli/vs.rst @@ -28,6 +28,8 @@ virtual server (VS), we need to know what options are available to us: RAM, CPU, operating systems, disk sizes, disk types, datacenters, and so on. Luckily, there's a simple command to show all options: `slcli vs create-options`. +*Some values were ommitted for brevity* + :: $ slcli vs create-options @@ -36,182 +38,16 @@ Luckily, there's a simple command to show all options: `slcli vs create-options` :................................:.................................................................................: : datacenter : ams01 : : : ams03 : - : : che01 : - : : dal01 : - : : dal05 : - : : dal06 : - : : dal09 : - : : dal10 : - : : dal12 : - : : dal13 : - : : fra02 : - : : hkg02 : - : : hou02 : - : : lon02 : - : : lon04 : - : : lon06 : - : : mel01 : - : : mex01 : - : : mil01 : - : : mon01 : - : : osl01 : - : : par01 : - : : sao01 : - : : sea01 : - : : seo01 : - : : sjc01 : - : : sjc03 : - : : sjc04 : - : : sng01 : - : : syd01 : - : : syd04 : - : : tok02 : - : : tor01 : - : : wdc01 : - : : wdc04 : - : : wdc06 : : : wdc07 : : flavors (balanced) : B1_1X2X25 : : : B1_1X2X25 : : : B1_1X2X100 : - : : B1_1X2X100 : - : : B1_1X4X25 : - : : B1_1X4X25 : - : : B1_1X4X100 : - : : B1_1X4X100 : - : : B1_2X4X25 : - : : B1_2X4X25 : - : : B1_2X4X100 : - : : B1_2X4X100 : - : : B1_2X8X25 : - : : B1_2X8X25 : - : : B1_2X8X100 : - : : B1_2X8X100 : - : : B1_4X8X25 : - : : B1_4X8X25 : - : : B1_4X8X100 : - : : B1_4X8X100 : - : : B1_4X16X25 : - : : B1_4X16X25 : - : : B1_4X16X100 : - : : B1_4X16X100 : - : : B1_8X16X25 : - : : B1_8X16X25 : - : : B1_8X16X100 : - : : B1_8X16X100 : - : : B1_8X32X25 : - : : B1_8X32X25 : - : : B1_8X32X100 : - : : B1_8X32X100 : - : : B1_16X32X25 : - : : B1_16X32X25 : - : : B1_16X32X100 : - : : B1_16X32X100 : - : : B1_16X64X25 : - : : B1_16X64X25 : - : : B1_16X64X100 : - : : B1_16X64X100 : - : : B1_32X64X25 : - : : B1_32X64X25 : - : : B1_32X64X100 : - : : B1_32X64X100 : - : : B1_32X128X25 : - : : B1_32X128X25 : - : : B1_32X128X100 : - : : B1_32X128X100 : - : : B1_48X192X25 : - : : B1_48X192X25 : - : : B1_48X192X100 : - : : B1_48X192X100 : - : flavors (balanced local - hdd) : BL1_1X2X100 : - : : BL1_1X4X100 : - : : BL1_2X4X100 : - : : BL1_2X8X100 : - : : BL1_4X8X100 : - : : BL1_4X16X100 : - : : BL1_8X16X100 : - : : BL1_8X32X100 : - : : BL1_16X32X100 : - : : BL1_16X64X100 : - : : BL1_32X64X100 : - : : BL1_32X128X100 : - : : BL1_56X242X100 : - : flavors (balanced local - ssd) : BL2_1X2X100 : - : : BL2_1X4X100 : - : : BL2_2X4X100 : - : : BL2_2X8X100 : - : : BL2_4X8X100 : - : : BL2_4X16X100 : - : : BL2_8X16X100 : - : : BL2_8X32X100 : - : : BL2_16X32X100 : - : : BL2_16X64X100 : - : : BL2_32X64X100 : - : : BL2_32X128X100 : - : : BL2_56X242X100 : - : flavors (compute) : C1_1X1X25 : - : : C1_1X1X25 : - : : C1_1X1X100 : - : : C1_1X1X100 : - : : C1_2X2X25 : - : : C1_2X2X25 : - : : C1_2X2X100 : - : : C1_2X2X100 : - : : C1_4X4X25 : - : : C1_4X4X25 : - : : C1_4X4X100 : - : : C1_4X4X100 : - : : C1_8X8X25 : - : : C1_8X8X25 : - : : C1_8X8X100 : - : : C1_8X8X100 : - : : C1_16X16X25 : - : : C1_16X16X25 : - : : C1_16X16X100 : - : : C1_16X16X100 : - : : C1_32X32X25 : - : : C1_32X32X25 : - : : C1_32X32X100 : - : : C1_32X32X100 : - : flavors (memory) : M1_1X8X25 : - : : M1_1X8X25 : - : : M1_1X8X100 : - : : M1_1X8X100 : - : : M1_2X16X25 : - : : M1_2X16X25 : - : : M1_2X16X100 : - : : M1_2X16X100 : - : : M1_4X32X25 : - : : M1_4X32X25 : - : : M1_4X32X100 : - : : M1_4X32X100 : - : : M1_8X64X25 : - : : M1_8X64X25 : - : : M1_8X64X100 : - : : M1_8X64X100 : - : : M1_16X128X25 : - : : M1_16X128X25 : - : : M1_16X128X100 : - : : M1_16X128X100 : - : : M1_30X240X25 : - : : M1_30X240X25 : - : : M1_30X240X100 : - : : M1_30X240X100 : - : flavors (GPU) : AC1_8X60X25 : - : : AC1_8X60X100 : - : : AC1_16X120X25 : - : : AC1_16X120X100 : - : : ACL1_8X60X100 : - : : ACL1_16X120X100 : : cpus (standard) : 1,2,4,8,12,16,32,56 : : cpus (dedicated) : 1,2,4,8,16,32,56 : : cpus (dedicated host) : 1,2,4,8,12,16,32,56 : : memory : 1024,2048,4096,6144,8192,12288,16384,32768,49152,65536,131072,247808 : : memory (dedicated host) : 1024,2048,4096,6144,8192,12288,16384,32768,49152,65536,131072,247808 : : os (CENTOS) : CENTOS_5_64 : - : : CENTOS_6_64 : - : : CENTOS_7_64 : - : : CENTOS_LATEST : : : CENTOS_LATEST_64 : : os (CLOUDLINUX) : CLOUDLINUX_5_64 : : : CLOUDLINUX_6_64 : @@ -221,10 +57,6 @@ Luckily, there's a simple command to show all options: `slcli vs create-options` : : COREOS_LATEST : : : COREOS_LATEST_64 : : os (DEBIAN) : DEBIAN_6_64 : - : : DEBIAN_7_64 : - : : DEBIAN_8_64 : - : : DEBIAN_9_64 : - : : DEBIAN_LATEST : : : DEBIAN_LATEST_64 : : os (OTHERUNIXLINUX) : OTHERUNIXLINUX_1_64 : : : OTHERUNIXLINUX_LATEST : @@ -234,43 +66,11 @@ Luckily, there's a simple command to show all options: `slcli vs create-options` : : REDHAT_7_64 : : : REDHAT_LATEST : : : REDHAT_LATEST_64 : - : os (UBUNTU) : UBUNTU_12_64 : - : : UBUNTU_14_64 : - : : UBUNTU_16_64 : - : : UBUNTU_LATEST : - : : UBUNTU_LATEST_64 : - : os (VYATTACE) : VYATTACE_6.5_64 : - : : VYATTACE_6.6_64 : - : : VYATTACE_LATEST : - : : VYATTACE_LATEST_64 : - : os (WIN) : WIN_2003-DC-SP2-1_32 : - : : WIN_2003-DC-SP2-1_64 : - : : WIN_2003-ENT-SP2-5_32 : - : : WIN_2003-ENT-SP2-5_64 : - : : WIN_2003-STD-SP2-5_32 : - : : WIN_2003-STD-SP2-5_64 : - : : WIN_2008-STD-R2-SP1_64 : - : : WIN_2008-STD-SP2_32 : - : : WIN_2008-STD-SP2_64 : - : : WIN_2012-STD-R2_64 : - : : WIN_2012-STD_64 : - : : WIN_2016-STD_64 : - : : WIN_LATEST : - : : WIN_LATEST_32 : - : : WIN_LATEST_64 : : san disk(0) : 25,100 : : san disk(2) : 10,20,25,30,40,50,75,100,125,150,175,200,250,300,350,400,500,750,1000,1500,2000 : - : san disk(3) : 10,20,25,30,40,50,75,100,125,150,175,200,250,300,350,400,500,750,1000,1500,2000 : - : san disk(4) : 10,20,25,30,40,50,75,100,125,150,175,200,250,300,350,400,500,750,1000,1500,2000 : - : san disk(5) : 10,20,25,30,40,50,75,100,125,150,175,200,250,300,350,400,500,750,1000,1500,2000 : : local disk(0) : 25,100 : : local disk(2) : 25,100,150,200,300 : : local (dedicated host) disk(0) : 25,100 : - : local (dedicated host) disk(2) : 25,100,150,200,300,400 : - : local (dedicated host) disk(3) : 25,100,150,200,300,400 : - : local (dedicated host) disk(4) : 25,100,150,200,300,400 : - : local (dedicated host) disk(5) : 25,100,150,200,300,400 : - : nic : 10,100,1000 : : nic (dedicated host) : 100,1000 : :................................:.................................................................................: @@ -281,7 +81,7 @@ datacenter using the command `slcli vs create`. :: - $ slcli vs create --hostname=example --domain=softlayer.com --cpu 2 --memory 1024 -o UBUNTU_14_64 --datacenter=sjc01 --billing=hourly + $ slcli vs create --hostname=example --domain=softlayer.com --cpu 2 --memory 1024 -o DEBIAN_LATEST_64 --datacenter=ams01 --billing=hourly This action will incur charges on your account. Continue? [y/N]: y :.........:......................................: : name : value : @@ -301,7 +101,7 @@ instantly appear in your virtual server list now. :.........:............:.......................:.......:........:................:..............:....................: : id : datacenter : host : cores : memory : primary_ip : backend_ip : active_transaction : :.........:............:.......................:.......:........:................:..............:....................: - : 1234567 : sjc01 : example.softlayer.com : 2 : 1G : 108.168.200.11 : 10.54.80.200 : Assign Host : + : 1234567 : ams01 : example.softlayer.com : 2 : 1G : 108.168.200.11 : 10.54.80.200 : Assign Host : :.........:............:.......................:.......:........:................:..............:....................: Cool. You may ask, "It's creating... but how do I know when it's done?" Well, @@ -338,12 +138,12 @@ username is 'root' and password is 'ABCDEFGH'. : hostname : example.softlayer.com : : status : Active : : state : Running : - : datacenter : sjc01 : + : datacenter : ams01 : : cores : 2 : : memory : 1G : : public_ip : 108.168.200.11 : : private_ip : 10.54.80.200 : - : os : Ubuntu : + : os : Debian : : private_only : False : : private_cpu : False : : created : 2013-06-13T08:29:44-06:00 : @@ -385,3 +185,12 @@ use `slcli help vs`. rescue Reboot into a rescue image. resume Resumes a paused virtual server. upgrade Upgrade a virtual server. + + +Reserved Capacity +----------------- +.. toctree:: + :maxdepth: 2 + + vs/reserved_capacity + diff --git a/docs/cli/vs/reserved_capacity.rst b/docs/cli/vs/reserved_capacity.rst new file mode 100644 index 000000000..79efa8e14 --- /dev/null +++ b/docs/cli/vs/reserved_capacity.rst @@ -0,0 +1,53 @@ +.. _vs_reserved_capacity_user_docs: + +Working with Reserved Capacity +============================== +There are two main concepts for Reserved Capacity. The `Reserved Capacity Group `_ and the `Reserved Capacity Instance `_ +The Reserved Capacity Group, is a set block of capacity set aside for you at the time of the order. It will contain a set number of Instances which are all the same size. Instances can be ordered like normal VSIs, with the exception that you need to include the reservedCapacityGroupId, and it must be the same size as the group you are ordering the instance in. + +- `About Reserved Capacity `_ +- `Reserved Capacity FAQ `_ + +The SLCLI supports some basic Reserved Capacity Features. + + +.. _cli_vs_capacity_create: + +vs capacity create +------------------ +This command will create a Reserved Capacity Group. **These groups can not be canceled until their contract expires in 1 or 3 years!** + +:: + + $ slcli vs capacity create --name test-capacity -d dal13 -b 1411193 -c B1_1X2_1_YEAR_TERM -q 10 + +vs cacpacity create_options +--------------------------- +This command will print out the Flavors that can be used to create a Reserved Capacity Group, as well as the backend routers available, as those are needed when creating a new group. + +vs capacity create_guest +------------------------ +This command will create a virtual server (Reserved Capacity Instance) inside of your Reserved Capacity Group. This command works very similar to the `slcli vs create` command. + +:: + + $ slcli vs capacity create-guest --capacity-id 1234 --primary-disk 25 -H ABCD -D test.com -o UBUNTU_LATEST_64 --ipv6 -k test-key --test + +vs capacity detail +------------------ +This command will print out some basic information about the specified Reserved Capacity Group. + +vs capacity list +----------------- +This command will list out all Reserved Capacity Groups. a **#** symbol represents a filled instance, and a **-** symbol respresents an empty instance + +:: + + $ slcli vs capacity list + :............................................................................................................: + : Reserved Capacity : + :......:......................:............:......................:..............:...........................: + : ID : Name : Capacity : Flavor : Location : Created : + :......:......................:............:......................:..............:...........................: + : 1234 : test-capacity : ####------ : B1.1x2 (1 Year Term) : bcr02a.dal13 : 2018-09-24T16:33:09-06:00 : + :......:......................:............:......................:..............:...........................: \ No newline at end of file diff --git a/docs/dev/index.rst b/docs/dev/index.rst index 21bb0d403..a0abdcc13 100644 --- a/docs/dev/index.rst +++ b/docs/dev/index.rst @@ -87,6 +87,33 @@ is: py.test tests +Fixtures +~~~~~~~~ + +Testing of this project relies quite heavily on fixtures to simulate API calls. When running the unit tests, we use the FixtureTransport class, which instead of making actual API calls, loads data from `/fixtures/SoftLayer_Service_Name.py` and tries to find a variable that matches the method you are calling. + +When adding new Fixtures you should try to sanitize the data of any account identifiying results, such as account ids, username, and that sort of thing. It is ok to leave the id in place for things like datacenter ids, price ids. + +To Overwrite a fixture, you can use a mock object to do so. Like either of these two methods: + +:: + + # From tests/CLI/modules/vs_capacity_tests.py + from SoftLayer.fixtures import SoftLayer_Product_Package + + def test_create_test(self): + item_mock = self.set_mock('SoftLayer_Product_Package', 'getItems') + item_mock.return_value = SoftLayer_Product_Package.getItems_RESERVED_CAPACITY + + def test_detail_pending(self): + capacity_mock = self.set_mock('SoftLayer_Virtual_ReservedCapacityGroup', 'getObject') + get_object = { + 'name': 'test-capacity', + 'instances': [] + } + capacity_mock.return_value = get_object + + Documentation ------------- The project is documented in @@ -106,6 +133,7 @@ fabric, use the following commands. cd docs make html + sphinx-build -b html ./ ./html The primary docs are built at `Read the Docs `_. @@ -121,6 +149,17 @@ Flake8, with project-specific exceptions, can be run by using tox: tox -e analysis +Autopep8 can fix a lot of the simple flake8 errors about whitespace and indention. + +:: + + autopep8 -r -a -v -i --max-line-length 119 + + + + + + Contributing ------------ From b2e6784a3b68f8c825f249f39b336d1e4cf70253 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 4 Oct 2018 17:06:31 -0500 Subject: [PATCH 13/18] Fixed an object mask --- SoftLayer/CLI/virt/capacity/detail.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/virt/capacity/detail.py b/SoftLayer/CLI/virt/capacity/detail.py index 485192e4c..60dc644f8 100644 --- a/SoftLayer/CLI/virt/capacity/detail.py +++ b/SoftLayer/CLI/virt/capacity/detail.py @@ -37,9 +37,10 @@ def cli(env, identifier, columns): """Reserved Capacity Group details. Will show which guests are assigned to a reservation.""" manager = CapacityManager(env.client) - mask = """mask[instances[id,createDate,guestId,billingItem[id, recurringFee, category[name]], + mask = """mask[instances[id,createDate,guestId,billingItem[id, description, recurringFee, category[name]], guest[modifyDate,id, primaryBackendIpAddress, primaryIpAddress,domain, hostname]]]""" result = manager.get_object(identifier, mask) + try: flavor = result['instances'][0]['billingItem']['description'] except KeyError: From 082c1eacfae75f86e0dccbd0dee55c77731bb1a3 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 4 Oct 2018 17:14:58 -0500 Subject: [PATCH 14/18] more docs --- SoftLayer/managers/vs_capacity.py | 37 ++++++++++++++++++------------- docs/api/managers/vs_capacity.rst | 5 +++++ docs/cli/vs/reserved_capacity.rst | 6 ++++- 3 files changed, 31 insertions(+), 17 deletions(-) create mode 100644 docs/api/managers/vs_capacity.rst diff --git a/SoftLayer/managers/vs_capacity.py b/SoftLayer/managers/vs_capacity.py index 358852603..6185eb3c9 100644 --- a/SoftLayer/managers/vs_capacity.py +++ b/SoftLayer/managers/vs_capacity.py @@ -20,9 +20,13 @@ class CapacityManager(utils.IdentifierMixin, object): - """Manages SoftLayer Dedicated Hosts. + """Manages SoftLayer Reserved Capacity Groups. - See product information here https://www.ibm.com/cloud/dedicated + Product Information + + - https://console.bluemix.net/docs/vsi/vsi_about_reserved.html + - 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 @@ -50,7 +54,7 @@ def get_object(self, identifier, mask=None): """Get a Reserved Capacity Group :param int identifier: Id of the SoftLayer_Virtual_ReservedCapacityGroup - :parm string mask: override default object Mask + :param string mask: override default object Mask """ if mask is None: mask = "mask[instances[billingItem[item[keyName],category], guest], backendRouter[datacenter]]" @@ -96,12 +100,12 @@ def get_available_routers(self, dc=None): def create(self, name, datacenter, backend_router_id, capacity, quantity, test=False): """Orders a Virtual_ReservedCapacityGroup - :params string name: Name for the new reserved capacity - :params string datacenter: like 'dal13' - :params int backend_router_id: This selects the pod. See create_options for a list - :params string capacity: Capacity KeyName, see create_options for a list - :params int quantity: Number of guest this capacity can support - :params bool test: If True, don't actually order, just test. + :param string name: Name for the new reserved capacity + :param string datacenter: like 'dal13' + :param int backend_router_id: This selects the pod. See create_options for a list + :param string capacity: Capacity KeyName, see create_options for a list + :param int quantity: Number of guest this capacity can support + :param bool test: If True, don't actually order, just test. """ args = (self.capacity_package, datacenter, [capacity]) extras = {"backendRouterId": backend_router_id, "name": name} @@ -120,15 +124,16 @@ def create(self, name, datacenter, backend_router_id, capacity, quantity, test=F def create_guest(self, capacity_id, test, guest_object): """Turns an empty Reserve Capacity into a real Virtual Guest - :params int capacity_id: ID of the RESERVED_CAPACITY_GROUP to create this guest into - :params bool test: True will use verifyOrder, False will use placeOrder - :params dictionary guest_object: Below is the minimum info you need to send in + :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', + '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]]]" diff --git a/docs/api/managers/vs_capacity.rst b/docs/api/managers/vs_capacity.rst new file mode 100644 index 000000000..3255a40b1 --- /dev/null +++ b/docs/api/managers/vs_capacity.rst @@ -0,0 +1,5 @@ +.. _vs_capacity: + +.. automodule:: SoftLayer.managers.vs_capacity + :members: + :inherited-members: diff --git a/docs/cli/vs/reserved_capacity.rst b/docs/cli/vs/reserved_capacity.rst index 79efa8e14..3193febff 100644 --- a/docs/cli/vs/reserved_capacity.rst +++ b/docs/cli/vs/reserved_capacity.rst @@ -15,7 +15,11 @@ The SLCLI supports some basic Reserved Capacity Features. vs capacity create ------------------ -This command will create a Reserved Capacity Group. **These groups can not be canceled until their contract expires in 1 or 3 years!** +This command will create a Reserved Capacity Group. + +.. warning:: + + **These groups can not be canceled until their contract expires in 1 or 3 years!** :: From 893ff903b7d262b0c99ec6d8f052afc215ad7cf5 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 4 Oct 2018 17:22:22 -0500 Subject: [PATCH 15/18] fixed whitespace issue --- SoftLayer/managers/vs_capacity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/managers/vs_capacity.py b/SoftLayer/managers/vs_capacity.py index 6185eb3c9..07d93b4af 100644 --- a/SoftLayer/managers/vs_capacity.py +++ b/SoftLayer/managers/vs_capacity.py @@ -23,7 +23,7 @@ class CapacityManager(utils.IdentifierMixin, object): """Manages SoftLayer Reserved Capacity Groups. Product Information - + - https://console.bluemix.net/docs/vsi/vsi_about_reserved.html - https://softlayer.github.io/reference/services/SoftLayer_Virtual_ReservedCapacityGroup/ - https://softlayer.github.io/reference/services/SoftLayer_Virtual_ReservedCapacityGroup_Instance/ From 03d3c8e1ccc45157826adac29429fb32530a19bc Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 8 Oct 2018 15:58:57 -0500 Subject: [PATCH 16/18] #1026 resolving pull request feedback --- SoftLayer/CLI/columns.py | 2 +- SoftLayer/CLI/virt/capacity/create.py | 21 ++++++++----------- SoftLayer/CLI/virt/capacity/create_options.py | 7 +++++-- SoftLayer/managers/vs_capacity.py | 16 ++++++++------ 4 files changed, 25 insertions(+), 21 deletions(-) diff --git a/SoftLayer/CLI/columns.py b/SoftLayer/CLI/columns.py index 486d12ffc..8cfdf0bd7 100644 --- a/SoftLayer/CLI/columns.py +++ b/SoftLayer/CLI/columns.py @@ -57,7 +57,7 @@ def mask(self): def get_formatter(columns): """This function returns a callback to use with click options. - The returend function parses a comma-separated value and returns a new + The returned function parses a comma-separated value and returns a new ColumnFormatter. :param columns: a list of Column instances diff --git a/SoftLayer/CLI/virt/capacity/create.py b/SoftLayer/CLI/virt/capacity/create.py index 2fd4ace77..32e9f9d5d 100644 --- a/SoftLayer/CLI/virt/capacity/create.py +++ b/SoftLayer/CLI/virt/capacity/create.py @@ -7,23 +7,21 @@ from SoftLayer.CLI import formatting from SoftLayer.managers.vs_capacity import CapacityManager as CapacityManager - +from pprint import pprint as pp @click.command(epilog=click.style("""WARNING: Reserved Capacity is on a yearly contract""" """ and not cancelable until the contract is expired.""", fg='red')) @click.option('--name', '-n', required=True, prompt=True, help="Name for your new reserved capacity") -@click.option('--datacenter', '-d', required=True, prompt=True, - help="Datacenter shortname") @click.option('--backend_router_id', '-b', required=True, prompt=True, help="backendRouterId, create-options has a list of valid ids to use.") -@click.option('--capacity', '-c', required=True, prompt=True, +@click.option('--flavor', '-f', required=True, prompt=True, help="Capacity keyname (C1_2X2_1_YEAR_TERM for example).") -@click.option('--quantity', '-q', required=True, prompt=True, +@click.option('--instances', '-i', required=True, prompt=True, help="Number of VSI instances this capacity reservation can support.") @click.option('--test', is_flag=True, help="Do not actually create the virtual server") @environment.pass_env -def cli(env, name, datacenter, backend_router_id, capacity, quantity, test=False): +def cli(env, name, backend_router_id, flavor, instances, test=False): """Create a Reserved Capacity instance. *WARNING*: Reserved Capacity is on a yearly contract and not cancelable until the contract is expired. @@ -32,10 +30,9 @@ def cli(env, name, datacenter, backend_router_id, capacity, quantity, test=False result = manager.create( name=name, - datacenter=datacenter, backend_router_id=backend_router_id, - capacity=capacity, - quantity=quantity, + flavor=flavor, + instances=instances, test=test) if test: @@ -44,8 +41,8 @@ def cli(env, name, datacenter, backend_router_id, capacity, quantity, test=False table.add_row(['Name', container['name']]) table.add_row(['Location', container['locationObject']['longName']]) for price in container['prices']: - table.add_row([price['item']['keyName'], price['item']['description']]) - table.add_row(['Total', result['postTaxRecurring']]) + table.add_row(['Contract', price['item']['description']]) + table.add_row(['Hourly Total', result['postTaxRecurring']]) else: table = formatting.Table(['Name', 'Value'], "Reciept") table.add_row(['Order Date', result['orderDate']]) @@ -53,5 +50,5 @@ def cli(env, name, datacenter, backend_router_id, capacity, quantity, test=False table.add_row(['status', result['placedOrder']['status']]) for item in result['placedOrder']['items']: table.add_row([item['categoryCode'], item['description']]) - table.add_row(['Total', result['orderDetails']['postTaxRecurring']]) + table.add_row(['Hourly Total', result['orderDetails']['postTaxRecurring']]) env.fout(table) diff --git a/SoftLayer/CLI/virt/capacity/create_options.py b/SoftLayer/CLI/virt/capacity/create_options.py index b8aacdd12..37f2af753 100644 --- a/SoftLayer/CLI/virt/capacity/create_options.py +++ b/SoftLayer/CLI/virt/capacity/create_options.py @@ -14,8 +14,10 @@ def cli(env): """List options for creating Reserved Capacity""" manager = CapacityManager(env.client) items = manager.get_create_options() + # pp(items) items.sort(key=lambda term: int(term['capacity'])) - table = formatting.Table(["KeyName", "Description", "Term", "Hourly Price"], title="Reserved Capacity Options") + table = formatting.Table(["KeyName", "Description", "Term", "Default Hourly Price Per Instance"], + title="Reserved Capacity Options") table.align["Hourly Price"] = "l" table.align["Description"] = "l" table.align["KeyName"] = "l" @@ -25,6 +27,7 @@ def cli(env): ]) env.fout(table) + regions = manager.get_available_routers() location_table = formatting.Table(['Location', 'POD', 'BackendRouterId'], 'Orderable Locations') for region in regions: @@ -38,6 +41,6 @@ def get_price(item): """Finds the price with the default locationGroupId""" the_price = "No Default Pricing" for price in item.get('prices', []): - if price.get('locationGroupId') == '': + if not price.get('locationGroupId'): the_price = "%0.4f" % float(price['hourlyRecurringFee']) return the_price diff --git a/SoftLayer/managers/vs_capacity.py b/SoftLayer/managers/vs_capacity.py index 07d93b4af..c2be6a615 100644 --- a/SoftLayer/managers/vs_capacity.py +++ b/SoftLayer/managers/vs_capacity.py @@ -97,21 +97,22 @@ def get_available_routers(self, dc=None): # Step 4, return the data. return regions - def create(self, name, datacenter, backend_router_id, capacity, quantity, test=False): + 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 string datacenter: like 'dal13' :param int backend_router_id: This selects the pod. See create_options for a list - :param string capacity: Capacity KeyName, see create_options for a list - :param int quantity: Number of guest this capacity can support + :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. """ - args = (self.capacity_package, datacenter, [capacity]) + + # 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': quantity, + 'quantity': instances, 'complex_type': 'SoftLayer_Container_Product_Order_Virtual_ReservedCapacity', 'hourly': True } @@ -135,6 +136,7 @@ def create_guest(self, capacity_id, test, guest_object): } """ + 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) @@ -147,6 +149,8 @@ def create_guest(self, capacity_id, test, guest_object): guest_object['flavor'] = flavor guest_object['datacenter'] = capacity['backendRouter']['datacenter']['name'] + # Reserved capacity only supports SAN as of 20181008 + guest_object['local_disk'] = False template = vs_manager.verify_create_instance(**guest_object) template['reservedCapacityId'] = capacity_id if guest_object.get('ipv6'): From 0d22da918ab5ed5e58683af8c82ea45a7b409103 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 8 Oct 2018 16:47:58 -0500 Subject: [PATCH 17/18] fixed unit tests --- SoftLayer/CLI/virt/capacity/create.py | 5 +--- SoftLayer/CLI/virt/capacity/create_options.py | 3 +-- tests/CLI/modules/vs_capacity_tests.py | 9 +++---- tests/managers/vs_capacity_tests.py | 25 +++++++------------ 4 files changed, 15 insertions(+), 27 deletions(-) diff --git a/SoftLayer/CLI/virt/capacity/create.py b/SoftLayer/CLI/virt/capacity/create.py index 32e9f9d5d..abe30176a 100644 --- a/SoftLayer/CLI/virt/capacity/create.py +++ b/SoftLayer/CLI/virt/capacity/create.py @@ -7,7 +7,7 @@ from SoftLayer.CLI import formatting from SoftLayer.managers.vs_capacity import CapacityManager as CapacityManager -from pprint import pprint as pp + @click.command(epilog=click.style("""WARNING: Reserved Capacity is on a yearly contract""" """ and not cancelable until the contract is expired.""", fg='red')) @click.option('--name', '-n', required=True, prompt=True, @@ -34,7 +34,6 @@ def cli(env, name, backend_router_id, flavor, instances, test=False): flavor=flavor, instances=instances, test=test) - if test: table = formatting.Table(['Name', 'Value'], "Test Order") container = result['orderContainers'][0] @@ -48,7 +47,5 @@ def cli(env, name, backend_router_id, flavor, instances, test=False): table.add_row(['Order Date', result['orderDate']]) table.add_row(['Order ID', result['orderId']]) table.add_row(['status', result['placedOrder']['status']]) - for item in result['placedOrder']['items']: - table.add_row([item['categoryCode'], item['description']]) table.add_row(['Hourly Total', result['orderDetails']['postTaxRecurring']]) env.fout(table) diff --git a/SoftLayer/CLI/virt/capacity/create_options.py b/SoftLayer/CLI/virt/capacity/create_options.py index 37f2af753..4e7ab6cb0 100644 --- a/SoftLayer/CLI/virt/capacity/create_options.py +++ b/SoftLayer/CLI/virt/capacity/create_options.py @@ -16,7 +16,7 @@ def cli(env): items = manager.get_create_options() # pp(items) items.sort(key=lambda term: int(term['capacity'])) - table = formatting.Table(["KeyName", "Description", "Term", "Default Hourly Price Per Instance"], + table = formatting.Table(["KeyName", "Description", "Term", "Default Hourly Price Per Instance"], title="Reserved Capacity Options") table.align["Hourly Price"] = "l" table.align["Description"] = "l" @@ -27,7 +27,6 @@ def cli(env): ]) env.fout(table) - regions = manager.get_available_routers() location_table = formatting.Table(['Location', 'POD', 'BackendRouterId'], 'Orderable Locations') for region in regions: diff --git a/tests/CLI/modules/vs_capacity_tests.py b/tests/CLI/modules/vs_capacity_tests.py index 5794dc823..34d94a00d 100644 --- a/tests/CLI/modules/vs_capacity_tests.py +++ b/tests/CLI/modules/vs_capacity_tests.py @@ -41,9 +41,8 @@ def test_create_test(self): item_mock.return_value = SoftLayer_Product_Package.getItems_RESERVED_CAPACITY order_mock = self.set_mock('SoftLayer_Product_Order', 'verifyOrder') order_mock.return_value = SoftLayer_Product_Order.rsc_verifyOrder - result = self.run_command(['vs', 'capacity', 'create', '--name=TEST', '--datacenter=dal13', - '--backend_router_id=1234', '--capacity=B1_1X2_1_YEAR_TERM', '--quantity=10', - '--test']) + result = self.run_command(['vs', 'capacity', 'create', '--name=TEST', '--test', + '--backend_router_id=1234', '--flavor=B1_1X2_1_YEAR_TERM', '--instances=10']) self.assert_no_fail(result) def test_create(self): @@ -51,8 +50,8 @@ def test_create(self): item_mock.return_value = SoftLayer_Product_Package.getItems_RESERVED_CAPACITY order_mock = self.set_mock('SoftLayer_Product_Order', 'placeOrder') order_mock.return_value = SoftLayer_Product_Order.rsc_placeOrder - result = self.run_command(['vs', 'capacity', 'create', '--name=TEST', '--datacenter=dal13', - '--backend_router_id=1234', '--capacity=B1_1X2_1_YEAR_TERM', '--quantity=10']) + result = self.run_command(['vs', 'capacity', 'create', '--name=TEST', '--instances=10', + '--backend_router_id=1234', '--flavor=B1_1X2_1_YEAR_TERM']) self.assert_no_fail(result) def test_create_options(self): diff --git a/tests/managers/vs_capacity_tests.py b/tests/managers/vs_capacity_tests.py index 2b31f6b1e..43db16afb 100644 --- a/tests/managers/vs_capacity_tests.py +++ b/tests/managers/vs_capacity_tests.py @@ -50,7 +50,7 @@ def test_create(self): item_mock = self.set_mock('SoftLayer_Product_Package', 'getItems') item_mock.return_value = SoftLayer_Product_Package.getItems_RESERVED_CAPACITY self.manager.create( - name='TEST', datacenter='dal13', backend_router_id=1, capacity='B1_1X2_1_YEAR_TERM', quantity=5) + name='TEST', backend_router_id=1, flavor='B1_1X2_1_YEAR_TERM', instances=5) expected_args = { 'orderContainers': [ @@ -58,7 +58,7 @@ def test_create(self): 'backendRouterId': 1, 'name': 'TEST', 'packageId': 1059, - 'location': 1854895, + 'location': 0, 'quantity': 5, 'useHourlyPricing': True, 'complexType': 'SoftLayer_Container_Product_Order_Virtual_ReservedCapacity', @@ -69,7 +69,6 @@ def test_create(self): } self.assert_called_with('SoftLayer_Product_Package', 'getAllObjects') - self.assert_called_with('SoftLayer_Location', 'getDatacenters') self.assert_called_with('SoftLayer_Product_Package', 'getItems', identifier=1059) self.assert_called_with('SoftLayer_Product_Order', 'placeOrder', args=(expected_args,)) @@ -77,7 +76,7 @@ def test_create_test(self): item_mock = self.set_mock('SoftLayer_Product_Package', 'getItems') item_mock.return_value = SoftLayer_Product_Package.getItems_RESERVED_CAPACITY self.manager.create( - name='TEST', datacenter='dal13', backend_router_id=1, capacity='B1_1X2_1_YEAR_TERM', quantity=5, test=True) + name='TEST', backend_router_id=1, flavor='B1_1X2_1_YEAR_TERM', instances=5, test=True) expected_args = { 'orderContainers': [ @@ -85,18 +84,17 @@ def test_create_test(self): 'backendRouterId': 1, 'name': 'TEST', 'packageId': 1059, - 'location': 1854895, + 'location': 0, 'quantity': 5, 'useHourlyPricing': True, 'complexType': 'SoftLayer_Container_Product_Order_Virtual_ReservedCapacity', - 'prices': [{'id': 217561} - ] + 'prices': [{'id': 217561}], + } ] } self.assert_called_with('SoftLayer_Product_Package', 'getAllObjects') - self.assert_called_with('SoftLayer_Location', 'getDatacenters') self.assert_called_with('SoftLayer_Product_Package', 'getItems', identifier=1059) self.assert_called_with('SoftLayer_Product_Order', 'verifyOrder', args=(expected_args,)) @@ -131,14 +129,9 @@ def test_create_guest(self): 'flavorKeyName': 'B1_1X2X25' }, 'operatingSystemReferenceCode': 'UBUNTU_LATEST_64', - 'datacenter': { - 'name': 'dal13' - }, - 'sshKeys': [ - { - 'id': 1234 - } - ] + 'datacenter': {'name': 'dal13'}, + 'sshKeys': [{'id': 1234}], + 'localDiskFlag': False } self.assert_called_with('SoftLayer_Virtual_ReservedCapacityGroup', 'getObject', mask=mock.ANY) From 4cff83c869677aa23fbbdbea75c0de63fb2baff8 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 10 Oct 2018 18:22:10 -0500 Subject: [PATCH 18/18] some final touches, ended up auto-translating commands so they conform with the hypen delimiter like the rest of the slcli --- SoftLayer/CLI/virt/capacity/__init__.py | 10 ++++- SoftLayer/CLI/virt/capacity/create.py | 4 +- SoftLayer/CLI/virt/capacity/create_options.py | 2 +- .../fixtures/SoftLayer_Product_Package.py | 39 ++++++++++++++----- tests/CLI/modules/vs_capacity_tests.py | 6 +-- 5 files changed, 43 insertions(+), 18 deletions(-) diff --git a/SoftLayer/CLI/virt/capacity/__init__.py b/SoftLayer/CLI/virt/capacity/__init__.py index 0dbaa754d..2b10885df 100644 --- a/SoftLayer/CLI/virt/capacity/__init__.py +++ b/SoftLayer/CLI/virt/capacity/__init__.py @@ -11,7 +11,12 @@ class CapacityCommands(click.MultiCommand): - """Loads module for capacity related commands.""" + """Loads module for capacity related commands. + + Will automatically replace _ with - where appropriate. + I'm not sure if this is better or worse than using a long list of manual routes, so I'm trying it here. + CLI/virt/capacity/create_guest.py -> slcli vs capacity create-guest + """ def __init__(self, **attrs): click.MultiCommand.__init__(self, **attrs) @@ -24,13 +29,14 @@ def list_commands(self, ctx): if filename == '__init__.py': continue if filename.endswith('.py'): - commands.append(filename[:-3]) + commands.append(filename[:-3].replace("_", "-")) commands.sort() return commands def get_command(self, ctx, cmd_name): """Get command for click.""" path = "%s.%s" % (__name__, cmd_name) + path = path.replace("-", "_") module = importlib.import_module(path) return getattr(module, 'cli') diff --git a/SoftLayer/CLI/virt/capacity/create.py b/SoftLayer/CLI/virt/capacity/create.py index abe30176a..92da7745c 100644 --- a/SoftLayer/CLI/virt/capacity/create.py +++ b/SoftLayer/CLI/virt/capacity/create.py @@ -12,11 +12,11 @@ """ and not cancelable until the contract is expired.""", fg='red')) @click.option('--name', '-n', required=True, prompt=True, help="Name for your new reserved capacity") -@click.option('--backend_router_id', '-b', required=True, prompt=True, +@click.option('--backend_router_id', '-b', required=True, prompt=True, type=int, help="backendRouterId, create-options has a list of valid ids to use.") @click.option('--flavor', '-f', required=True, prompt=True, help="Capacity keyname (C1_2X2_1_YEAR_TERM for example).") -@click.option('--instances', '-i', required=True, prompt=True, +@click.option('--instances', '-i', required=True, prompt=True, type=int, help="Number of VSI instances this capacity reservation can support.") @click.option('--test', is_flag=True, help="Do not actually create the virtual server") diff --git a/SoftLayer/CLI/virt/capacity/create_options.py b/SoftLayer/CLI/virt/capacity/create_options.py index 4e7ab6cb0..14203cb48 100644 --- a/SoftLayer/CLI/virt/capacity/create_options.py +++ b/SoftLayer/CLI/virt/capacity/create_options.py @@ -14,7 +14,7 @@ def cli(env): """List options for creating Reserved Capacity""" manager = CapacityManager(env.client) items = manager.get_create_options() - # pp(items) + items.sort(key=lambda term: int(term['capacity'])) table = formatting.Table(["KeyName", "Description", "Term", "Default Hourly Price Per Instance"], title="Reserved Capacity Options") diff --git a/SoftLayer/fixtures/SoftLayer_Product_Package.py b/SoftLayer/fixtures/SoftLayer_Product_Package.py index 5553b0458..a98ec4b89 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Package.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Package.py @@ -789,131 +789,152 @@ getItems = [ { 'id': 1234, + 'keyName': 'KeyName01', 'capacity': '1000', 'description': 'Public & Private Networks', 'itemCategory': {'categoryCode': 'Uplink Port Speeds'}, 'prices': [{'id': 1122, + 'hourlyRecurringFee': 0.0, 'categories': [{'id': 26, 'name': 'Uplink Port Speeds', 'categoryCode': 'port_speed'}]}], }, { 'id': 2233, + 'keyName': 'KeyName02', 'capacity': '1000', 'description': 'Public & Private Networks', 'itemCategory': {'categoryCode': 'Uplink Port Speeds'}, 'prices': [{'id': 4477, + 'hourlyRecurringFee': 0.0, 'categories': [{'id': 26, 'name': 'Uplink Port Speeds', 'categoryCode': 'port_speed'}]}], }, { 'id': 1239, + 'keyName': 'KeyName03', 'capacity': '2', 'description': 'RAM', 'itemCategory': {'categoryCode': 'RAM'}, 'prices': [{'id': 1133, + 'hourlyRecurringFee': 0.0, 'categories': [{'id': 3, 'name': 'RAM', 'categoryCode': 'ram'}]}], }, { 'id': 1240, + 'keyName': 'KeyName014', 'capacity': '4', 'units': 'PRIVATE_CORE', 'description': 'Computing Instance (Dedicated)', 'itemCategory': {'categoryCode': 'Computing Instance'}, 'prices': [{'id': 1007, + 'hourlyRecurringFee': 0.0, 'categories': [{'id': 80, 'name': 'Computing Instance', 'categoryCode': 'guest_core'}]}], }, { 'id': 1250, + 'keyName': 'KeyName015', 'capacity': '4', 'units': 'CORE', 'description': 'Computing Instance', 'itemCategory': {'categoryCode': 'Computing Instance'}, 'prices': [{'id': 1144, 'locationGroupId': None, + 'hourlyRecurringFee': 0.0, 'categories': [{'id': 80, 'name': 'Computing Instance', 'categoryCode': 'guest_core'}]}], }, { 'id': 112233, + 'keyName': 'KeyName016', 'capacity': '55', 'units': 'CORE', 'description': 'Computing Instance', 'itemCategory': {'categoryCode': 'Computing Instance'}, 'prices': [{'id': 332211, 'locationGroupId': 1, + 'hourlyRecurringFee': 0.0, 'categories': [{'id': 80, 'name': 'Computing Instance', 'categoryCode': 'guest_core'}]}], }, { 'id': 4439, + 'keyName': 'KeyName017', 'capacity': '1', 'description': '1 GB iSCSI Storage', 'itemCategory': {'categoryCode': 'iscsi'}, - 'prices': [{'id': 2222}], + 'prices': [{'id': 2222, 'hourlyRecurringFee': 0.0}], }, { 'id': 1121, + 'keyName': 'KeyName081', 'capacity': '20', 'description': '20 GB iSCSI snapshot', 'itemCategory': {'categoryCode': 'iscsi_snapshot_space'}, - 'prices': [{'id': 2014}], + 'prices': [{'id': 2014, 'hourlyRecurringFee': 0.0}], }, { 'id': 4440, + 'keyName': 'KeyName019', 'capacity': '4', 'description': '4 Portable Public IP Addresses', 'itemCategory': {'categoryCode': 'sov_sec_ip_addresses_pub'}, - 'prices': [{'id': 4444}], + 'prices': [{'id': 4444, 'hourlyRecurringFee': 0.0}], }, { 'id': 8880, + 'keyName': 'KeyName0199', 'capacity': '8', 'description': '8 Portable Public IP Addresses', 'itemCategory': {'categoryCode': 'sov_sec_ip_addresses_pub'}, - 'prices': [{'id': 8888}], + 'prices': [{'id': 8888, 'hourlyRecurringFee': 0.0}], }, { 'id': 44400, + 'keyName': 'KeyName0155', 'capacity': '4', 'description': '4 Portable Private IP Addresses', 'itemCategory': {'categoryCode': 'sov_sec_ip_addresses_priv'}, - 'prices': [{'id': 44441}], + 'prices': [{'id': 44441, 'hourlyRecurringFee': 0.0}], }, { 'id': 88800, + 'keyName': 'KeyName0144', 'capacity': '8', 'description': '8 Portable Private IP Addresses', 'itemCategory': {'categoryCode': 'sov_sec_ip_addresses_priv'}, - 'prices': [{'id': 88881}], + 'prices': [{'id': 88881, 'hourlyRecurringFee': 0.0}], }, { 'id': 10, + 'keyName': 'KeyName0341', 'capacity': '0', 'description': 'Global IPv4', 'itemCategory': {'categoryCode': 'global_ipv4'}, - 'prices': [{'id': 11}], + 'prices': [{'id': 11, 'hourlyRecurringFee': 0.0}], }, { 'id': 66464, + 'keyName': 'KeyName0211', 'capacity': '64', 'description': '/64 Block Portable Public IPv6 Addresses', 'itemCategory': {'categoryCode': 'static_ipv6_addresses'}, - 'prices': [{'id': 664641}], + 'prices': [{'id': 664641, 'hourlyRecurringFee': 0.0}], }, { 'id': 610, + 'keyName': 'KeyName031', 'capacity': '0', 'description': 'Global IPv6', 'itemCategory': {'categoryCode': 'global_ipv6'}, - 'prices': [{'id': 611}], + 'prices': [{'id': 611, 'hourlyRecurringFee': 0.0}], }] getItemPricesISCSI = [ diff --git a/tests/CLI/modules/vs_capacity_tests.py b/tests/CLI/modules/vs_capacity_tests.py index 34d94a00d..922bf2118 100644 --- a/tests/CLI/modules/vs_capacity_tests.py +++ b/tests/CLI/modules/vs_capacity_tests.py @@ -55,19 +55,17 @@ def test_create(self): self.assert_no_fail(result) def test_create_options(self): - item_mock = self.set_mock('SoftLayer_Product_Package', 'getItems') - item_mock.return_value = SoftLayer_Product_Package.getItems_RESERVED_CAPACITY result = self.run_command(['vs', 'capacity', 'create_options']) self.assert_no_fail(result) def test_create_guest_test(self): - result = self.run_command(['vs', 'capacity', 'create_guest', '--capacity-id=3103', '--primary-disk=25', + result = self.run_command(['vs', 'capacity', 'create-guest', '--capacity-id=3103', '--primary-disk=25', '-H ABCDEFG', '-D test_list.com', '-o UBUNTU_LATEST_64', '-kTest 1', '--test']) self.assert_no_fail(result) def test_create_guest(self): order_mock = self.set_mock('SoftLayer_Product_Order', 'placeOrder') order_mock.return_value = SoftLayer_Product_Order.rsi_placeOrder - result = self.run_command(['vs', 'capacity', 'create_guest', '--capacity-id=3103', '--primary-disk=25', + result = self.run_command(['vs', 'capacity', 'create-guest', '--capacity-id=3103', '--primary-disk=25', '-H ABCDEFG', '-D test_list.com', '-o UBUNTU_LATEST_64', '-kTest 1']) self.assert_no_fail(result)