From 42ba6f53da239667beccd356891816b8dc793b86 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Mon, 17 Sep 2018 17:43:01 -0500 Subject: [PATCH 01/39] #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/39] #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/39] 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/39] 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/39] 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/39] 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/39] #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/39] #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/39] #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/39] #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 0b1f63778bdb02dd7f956be00dc9986e805faa2c Mon Sep 17 00:00:00 2001 From: Michael Wurtz Date: Wed, 3 Oct 2018 14:16:38 -0500 Subject: [PATCH 11/39] Add export/import capabilities to/from IBM Cloud Object Storage Change image manager to include IBM Cloud Object Storage support and add unit tests for it. --- ...rtual_Guest_Block_Device_Template_Group.py | 8 ++- SoftLayer/managers/image.py | 60 +++++++++++++++---- tests/managers/image_tests.py | 41 +++++++++++++ 3 files changed, 98 insertions(+), 11 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_Guest_Block_Device_Template_Group.py b/SoftLayer/fixtures/SoftLayer_Virtual_Guest_Block_Device_Template_Group.py index ee58ad762..785ed3b05 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_Guest_Block_Device_Template_Group.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_Guest_Block_Device_Template_Group.py @@ -29,5 +29,11 @@ 'id': 100, 'name': 'test_image', }] - +createFromIcos = [{ + 'createDate': '2013-12-05T21:53:03-06:00', + 'globalIdentifier': '0B5DEAF4-643D-46CA-A695-CECBE8832C9D', + 'id': 100, + 'name': 'test_image', +}] copyToExternalSource = True +copyToIcos = True diff --git a/SoftLayer/managers/image.py b/SoftLayer/managers/image.py index 81eee9282..7ebf1fd98 100644 --- a/SoftLayer/managers/image.py +++ b/SoftLayer/managers/image.py @@ -120,28 +120,68 @@ def edit(self, image_id, name=None, note=None, tag=None): return bool(name or note or tag) - def import_image_from_uri(self, name, uri, os_code=None, note=None): + def import_image_from_uri(self, name, uri, os_code=None, note=None, + ibm_api_key=None, root_key_id=None, + wrapped_dek=None, kp_id=None, cloud_init=None, + byol=None, is_encrypted=None): """Import a new image from object storage. :param string name: Name of the new image :param string uri: The URI for an object storage object (.vhd/.iso file) of the format: swift://@// + or (.vhd/.iso/.raw file) of the format: + cos://// if using IBM Cloud + Object Storage :param string os_code: The reference code of the operating system :param string note: Note to add to the image + :param string ibm_api_key: Ibm Api Key needed to communicate with ICOS + and Key Protect + :param string root_key_id: ID of the root key in Key Protect + :param string wrapped_dek: Wrapped Decryption Key provided by IBM + KeyProtect + :param string kp_id: ID of the IBM Key Protect Instance + :param bool cloud_init: Specifies if image is cloud init + :param bool byol: Specifies if image is bring your own license + :param bool is_encrypted: Specifies if image is encrypted """ - return self.vgbdtg.createFromExternalSource({ - 'name': name, - 'note': note, - 'operatingSystemReferenceCode': os_code, - 'uri': uri, - }) - - def export_image_to_uri(self, image_id, uri): + if 'cos://' in uri: + return self.vgbdtg.createFromIcos({ + 'name': name, + 'note': note, + 'operatingSystemReferenceCode': os_code, + 'uri': uri, + 'ibmApiKey': ibm_api_key, + 'rootKeyid': root_key_id, + 'wrappedDek': wrapped_dek, + 'keyProtectId': kp_id, + 'cloudInit': cloud_init, + 'byol': byol, + 'isEncrypted': is_encrypted + }) + else: + return self.vgbdtg.createFromExternalSource({ + 'name': name, + 'note': note, + 'operatingSystemReferenceCode': os_code, + 'uri': uri, + }) + + def export_image_to_uri(self, image_id, uri, ibm_api_key=None): """Export image into the given object storage :param int image_id: The ID of the image :param string uri: The URI for object storage of the format swift://@// + or cos://// if using IBM Cloud + Object Storage + :param string ibm_api_key: Ibm Api Key needed to communicate with IBM + Cloud Object Storage """ - return self.vgbdtg.copyToExternalSource({'uri': uri}, id=image_id) + if 'cos://' in uri: + return self.vgbdtg.copyToIcos({ + 'uri': uri, + 'ibmApiKey': ibm_api_key + }, id=image_id) + else: + return self.vgbdtg.copyToExternalSource({'uri': uri}, id=image_id) diff --git a/tests/managers/image_tests.py b/tests/managers/image_tests.py index 5347d4e71..ba410b646 100644 --- a/tests/managers/image_tests.py +++ b/tests/managers/image_tests.py @@ -145,6 +145,36 @@ def test_import_image(self): 'uri': 'someuri', 'operatingSystemReferenceCode': 'UBUNTU_LATEST'},)) + def test_import_image_cos(self): + self.image.import_image_from_uri(name='test_image', + note='testimage', + uri='cos://some_uri', + os_code='UBUNTU_LATEST', + ibm_api_key='some_ibm_key', + root_key_id='some_root_key_id', + wrapped_dek='some_dek', + kp_id='some_id', + cloud_init=False, + byol=False, + is_encrypted=False + ) + + self.assert_called_with( + IMAGE_SERVICE, + 'createFromIcos', + args=({'name': 'test_image', + 'note': 'testimage', + 'operatingSystemReferenceCode': 'UBUNTU_LATEST', + 'uri': 'cos://some_uri', + 'ibmApiKey': 'some_ibm_key', + 'rootKeyid': 'some_root_key_id', + 'wrappedDek': 'some_dek', + 'keyProtectId': 'some_id', + 'cloudInit': False, + 'byol': False, + 'isEncrypted': False + },)) + def test_export_image(self): self.image.export_image_to_uri(1234, 'someuri') @@ -153,3 +183,14 @@ def test_export_image(self): 'copyToExternalSource', args=({'uri': 'someuri'},), identifier=1234) + + def test_export_image_cos(self): + self.image.export_image_to_uri(1234, + 'cos://someuri', + ibm_api_key='someApiKey') + + self.assert_called_with( + IMAGE_SERVICE, + 'copyToIcos', + args=({'uri': 'cos://someuri', 'ibmApiKey': 'someApiKey'},), + identifier=1234) From ec04e850fb19314f453b4e7da98b4f6793805702 Mon Sep 17 00:00:00 2001 From: Khuong-Nguyen Date: Wed, 3 Oct 2018 15:34:07 -0500 Subject: [PATCH 12/39] Fix unit tests --- SoftLayer/fixtures/SoftLayer_Account.py | 4 +- .../SoftLayer_Virtual_DedicatedHost.py | 38 +++--- tests/CLI/modules/dedicatedhost_tests.py | 119 ++++++++---------- tests/managers/dedicated_host_tests.py | 58 ++++----- 4 files changed, 99 insertions(+), 120 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index 586e597a9..f588417ad 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -553,11 +553,11 @@ 'name': 'dal05' }, 'memoryCapacity': 242, - 'name': 'khnguyendh', + 'name': 'test-dedicated', 'diskCapacity': 1200, 'guestCount': 1, 'cpuCount': 56, - 'id': 44701 + 'id': 12345 }] diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_DedicatedHost.py b/SoftLayer/fixtures/SoftLayer_Virtual_DedicatedHost.py index ea1775eb9..94c8e5cc4 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_DedicatedHost.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_DedicatedHost.py @@ -11,57 +11,57 @@ getAvailableRouters = [ - {'hostname': 'bcr01a.dal05', 'id': 51218}, - {'hostname': 'bcr02a.dal05', 'id': 83361}, - {'hostname': 'bcr03a.dal05', 'id': 122762}, - {'hostname': 'bcr04a.dal05', 'id': 147566} + {'hostname': 'bcr01a.dal05', 'id': 12345}, + {'hostname': 'bcr02a.dal05', 'id': 12346}, + {'hostname': 'bcr03a.dal05', 'id': 12347}, + {'hostname': 'bcr04a.dal05', 'id': 12348} ] getObjectById = { 'datacenter': { - 'id': 138124, + 'id': 12345, 'name': 'dal05', 'longName': 'Dallas 5' }, 'memoryCapacity': 242, 'modifyDate': '2017-11-06T11:38:20-06:00', - 'name': 'khnguyendh', + 'name': 'test-dedicated', 'diskCapacity': 1200, 'backendRouter': { - 'domain': 'softlayer.com', + 'domain': 'test.com', 'hostname': 'bcr01a.dal05', - 'id': 51218 + 'id': 12345 }, 'guestCount': 1, 'cpuCount': 56, 'guests': [{ - 'domain': 'Softlayer.com', - 'hostname': 'khnguyenDHI', - 'id': 43546081, - 'uuid': '806a56ec-0383-4c2e-e6a9-7dc89c4b29a2' + 'domain': 'test.com', + 'hostname': 'test-dedicated', + 'id': 12345, + 'uuid': 'F9329795-4220-4B0A-B970-C86B950667FA' }], 'billingItem': { 'nextInvoiceTotalRecurringAmount': 1515.556, 'orderItem': { - 'id': 263060473, + 'id': 12345, 'order': { 'status': 'APPROVED', 'privateCloudOrderFlag': False, 'modifyDate': '2017-11-02T11:42:50-07:00', 'orderQuoteId': '', - 'userRecordId': 6908745, + 'userRecordId': 12345, 'createDate': '2017-11-02T11:40:56-07:00', 'impersonatingUserRecordId': '', 'orderTypeId': 7, 'presaleEventId': '', 'userRecord': { - 'username': '232298_khuong' + 'username': 'test-dedicated' }, - 'id': 20093269, - 'accountId': 232298 + 'id': 12345, + 'accountId': 12345 } }, - 'id': 235379377, + 'id': 12345, 'children': [ { 'nextInvoiceTotalRecurringAmount': 0.0, @@ -73,6 +73,6 @@ } ] }, - 'id': 44701, + 'id': 12345, 'createDate': '2017-11-02T11:40:56-07:00' } diff --git a/tests/CLI/modules/dedicatedhost_tests.py b/tests/CLI/modules/dedicatedhost_tests.py index 59f6cab7c..3cc675749 100644 --- a/tests/CLI/modules/dedicatedhost_tests.py +++ b/tests/CLI/modules/dedicatedhost_tests.py @@ -29,21 +29,17 @@ def test_list_dedicated_hosts(self): 'datacenter': 'dal05', 'diskCapacity': 1200, 'guestCount': 1, - 'id': 44701, + 'id': 12345, 'memoryCapacity': 242, - 'name': 'khnguyendh' + 'name': 'test-dedicated' }] ) - def tear_down(self): - if os.path.exists("test.txt"): - os.remove("test.txt") - def test_details(self): mock = self.set_mock('SoftLayer_Virtual_DedicatedHost', 'getObject') mock.return_value = SoftLayer_Virtual_DedicatedHost.getObjectById - result = self.run_command(['dedicatedhost', 'detail', '44701', '--price', '--guests']) + result = self.run_command(['dedicatedhost', 'detail', '12345', '--price', '--guests']) self.assert_no_fail(result) self.assertEqual(json.loads(result.output), @@ -54,19 +50,19 @@ def test_details(self): 'disk capacity': 1200, 'guest count': 1, 'guests': [{ - 'domain': 'Softlayer.com', - 'hostname': 'khnguyenDHI', - 'id': 43546081, - 'uuid': '806a56ec-0383-4c2e-e6a9-7dc89c4b29a2' + 'domain': 'test.com', + 'hostname': 'test-dedicated', + 'id': 12345, + 'uuid': 'F9329795-4220-4B0A-B970-C86B950667FA' }], - 'id': 44701, + 'id': 12345, 'memory capacity': 242, 'modify date': '2017-11-06T11:38:20-06:00', - 'name': 'khnguyendh', - 'owner': '232298_khuong', + 'name': 'test-dedicated', + 'owner': 'test-dedicated', 'price_rate': 1515.556, 'router hostname': 'bcr01a.dal05', - 'router id': 51218} + 'router id': 12345} ) def test_details_no_owner(self): @@ -85,18 +81,18 @@ def test_details_no_owner(self): 'disk capacity': 1200, 'guest count': 1, 'guests': [{ - 'domain': 'Softlayer.com', - 'hostname': 'khnguyenDHI', - 'id': 43546081, - 'uuid': '806a56ec-0383-4c2e-e6a9-7dc89c4b29a2'}], - 'id': 44701, + 'domain': 'test.com', + 'hostname': 'test-dedicated', + 'id': 12345, + 'uuid': 'F9329795-4220-4B0A-B970-C86B950667FA'}], + 'id': 12345, 'memory capacity': 242, 'modify date': '2017-11-06T11:38:20-06:00', - 'name': 'khnguyendh', + 'name': 'test-dedicated', 'owner': None, 'price_rate': 0, 'router hostname': 'bcr01a.dal05', - 'router id': 51218} + 'router id': 12345} ) def test_create_options(self): @@ -159,29 +155,29 @@ def test_create(self): mock_package_obj.return_value = SoftLayer_Product_Package.getAllObjectsDH result = self.run_command(['dedicatedhost', 'create', - '--hostname=host', - '--domain=example.com', + '--hostname=test-dedicated', + '--domain=test.com', '--datacenter=dal05', '--flavor=56_CORES_X_242_RAM_X_1_4_TB', '--billing=hourly']) self.assert_no_fail(result) args = ({ 'hardware': [{ - 'domain': 'example.com', + 'domain': 'test.com', 'primaryBackendNetworkComponent': { 'router': { - 'id': 51218 + 'id': 12345 } }, - 'hostname': 'host' - }], - 'prices': [{ - 'id': 200269 + 'hostname': 'test-dedicated' }], + 'useHourlyPricing': True, 'location': 'DALLAS05', 'packageId': 813, 'complexType': 'SoftLayer_Container_Product_Order_Virtual_DedicatedHost', - 'useHourlyPricing': True, + 'prices': [{ + 'id': 200269 + }], 'quantity': 1},) self.assert_called_with('SoftLayer_Product_Order', 'placeOrder', @@ -195,21 +191,21 @@ def test_create_with_gpu(self): mock_package_obj.return_value = SoftLayer_Product_Package.getAllObjectsDHGpu result = self.run_command(['dedicatedhost', 'create', - '--hostname=host', - '--domain=example.com', + '--hostname=test-dedicated', + '--domain=test.com', '--datacenter=dal05', '--flavor=56_CORES_X_484_RAM_X_1_5_TB_X_2_GPU_P100', '--billing=hourly']) self.assert_no_fail(result) args = ({ 'hardware': [{ - 'domain': 'example.com', + 'domain': 'test.com', 'primaryBackendNetworkComponent': { 'router': { - 'id': 51218 + 'id': 12345 } }, - 'hostname': 'host' + 'hostname': 'test-dedicated' }], 'prices': [{ 'id': 200269 @@ -233,8 +229,8 @@ def test_create_verify(self): result = self.run_command(['dedicatedhost', 'create', '--verify', - '--hostname=host', - '--domain=example.com', + '--hostname=test-dedicated', + '--domain=test.com', '--datacenter=dal05', '--flavor=56_CORES_X_242_RAM_X_1_4_TB', '--billing=hourly']) @@ -244,12 +240,12 @@ def test_create_verify(self): 'useHourlyPricing': True, 'hardware': [{ - 'hostname': 'host', - 'domain': 'example.com', + 'hostname': 'test-dedicated', + 'domain': 'test.com', 'primaryBackendNetworkComponent': { 'router': { - 'id': 51218 + 'id': 12345 } } }], @@ -263,8 +259,8 @@ def test_create_verify(self): result = self.run_command(['dh', 'create', '--verify', - '--hostname=host', - '--domain=example.com', + '--hostname=test-dedicated', + '--domain=test.com', '--datacenter=dal05', '--flavor=56_CORES_X_242_RAM_X_1_4_TB', '--billing=monthly']) @@ -273,11 +269,11 @@ def test_create_verify(self): args = ({ 'useHourlyPricing': True, 'hardware': [{ - 'hostname': 'host', - 'domain': 'example.com', + 'hostname': 'test-dedicated', + 'domain': 'test.com', 'primaryBackendNetworkComponent': { 'router': { - 'id': 51218 + 'id': 12345 } } }], @@ -296,8 +292,8 @@ def test_create_aborted(self): mock_package_obj.return_value = SoftLayer_Product_Package.getAllObjectsDH result = self.run_command(['dh', 'create', - '--hostname=host', - '--domain=example.com', + '--hostname=test-dedicated', + '--domain=test.com', '--datacenter=dal05', '--flavor=56_CORES_X_242_RAM_X_1_4_TB', '--billing=monthly']) @@ -305,23 +301,6 @@ def test_create_aborted(self): self.assertEqual(result.exit_code, 2) self.assertIsInstance(result.exception, exceptions.CLIAbort) - def test_create_export(self): - mock_package_obj = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock_package_obj.return_value = SoftLayer_Product_Package.getAllObjectsDH - mock_package = self.set_mock('SoftLayer_Product_Order', 'verifyOrder') - mock_package.return_value = SoftLayer_Product_Package.verifyOrderDH - - self.run_command(['dedicatedhost', 'create', - '--verify', - '--hostname=host', - '--domain=example.com', - '--datacenter=dal05', - '--flavor=56_CORES_X_242_RAM_X_1_4_TB', - '--billing=hourly', - '--export=test.txt']) - - self.assertEqual(os.path.exists("test.txt"), True) - def test_create_verify_no_price_or_more_than_one(self): mock_package_obj = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') mock_package_obj.return_value = SoftLayer_Product_Package.getAllObjectsDH @@ -332,8 +311,8 @@ def test_create_verify_no_price_or_more_than_one(self): result = self.run_command(['dedicatedhost', 'create', '--verify', - '--hostname=host', - '--domain=example.com', + '--hostname=test-dedicated', + '--domain=test.com', '--datacenter=dal05', '--flavor=56_CORES_X_242_RAM_X_1_4_TB', '--billing=hourly']) @@ -341,13 +320,13 @@ def test_create_verify_no_price_or_more_than_one(self): self.assertIsInstance(result.exception, exceptions.ArgumentError) args = ({ 'hardware': [{ - 'domain': 'example.com', + 'domain': 'test.com', 'primaryBackendNetworkComponent': { 'router': { - 'id': 51218 + 'id': 12345 } }, - 'hostname': 'host' + 'hostname': 'test-dedicated' }], 'prices': [{ 'id': 200269 diff --git a/tests/managers/dedicated_host_tests.py b/tests/managers/dedicated_host_tests.py index 2f21edacf..bdfa6d3f6 100644 --- a/tests/managers/dedicated_host_tests.py +++ b/tests/managers/dedicated_host_tests.py @@ -90,7 +90,7 @@ def test_place_order(self): { 'primaryBackendNetworkComponent': { 'router': { - 'id': 51218 + 'id': 12345 } }, 'domain': u'test.com', @@ -103,7 +103,7 @@ def test_place_order(self): 'complexType': 'SoftLayer_Container_Product_Order_Virtual_DedicatedHost', 'prices': [ { - 'id': 200269 + 'id': 12345 } ], 'quantity': 1 @@ -141,7 +141,7 @@ def test_place_order_with_gpu(self): { 'primaryBackendNetworkComponent': { 'router': { - 'id': 51218 + 'id': 12345 } }, 'domain': u'test.com', @@ -154,7 +154,7 @@ def test_place_order_with_gpu(self): 'complexType': 'SoftLayer_Container_Product_Order_Virtual_DedicatedHost', 'prices': [ { - 'id': 200269 + 'id': 12345 } ], 'quantity': 1 @@ -192,7 +192,7 @@ def test_verify_order(self): { 'primaryBackendNetworkComponent': { 'router': { - 'id': 51218 + 'id': 12345 } }, 'domain': 'test.com', @@ -205,7 +205,7 @@ def test_verify_order(self): 'complexType': 'SoftLayer_Container_Product_Order_Virtual_DedicatedHost', 'prices': [ { - 'id': 200269 + 'id': 12345 } ], 'quantity': 1 @@ -259,7 +259,7 @@ def test_generate_create_dict_without_router(self): { 'primaryBackendNetworkComponent': { 'router': { - 'id': 51218 + 'id': 12345 } }, 'domain': 'test.com', @@ -272,7 +272,7 @@ def test_generate_create_dict_without_router(self): 'complexType': 'SoftLayer_Container_Product_Order_Virtual_DedicatedHost', 'prices': [ { - 'id': 200269 + 'id': 12345 } ], 'quantity': 1 @@ -284,10 +284,10 @@ def test_generate_create_dict_with_router(self): self.dedicated_host._get_package = mock.MagicMock() self.dedicated_host._get_package.return_value = self._get_package() self.dedicated_host._get_default_router = mock.Mock() - self.dedicated_host._get_default_router.return_value = 51218 + self.dedicated_host._get_default_router.return_value = 12345 location = 'dal05' - router = 51218 + router = 12345 hostname = 'test' domain = 'test.com' hourly = True @@ -306,7 +306,7 @@ def test_generate_create_dict_with_router(self): { 'primaryBackendNetworkComponent': { 'router': { - 'id': 51218 + 'id': 12345 } }, 'domain': 'test.com', @@ -320,7 +320,7 @@ def test_generate_create_dict_with_router(self): 'SoftLayer_Container_Product_Order_Virtual_DedicatedHost', 'prices': [ { - 'id': 200269 + 'id': 12345 } ], 'quantity': 1 @@ -421,7 +421,7 @@ def test_get_create_options(self): def test_get_price(self): package = self._get_package() item = package['items'][0] - price_id = 200269 + price_id = 12345 self.assertEqual(self.dedicated_host._get_price(item), price_id) @@ -454,15 +454,15 @@ def test_get_item(self): }], 'capacity': '56', 'description': '56 Cores X 242 RAM X 1.2 TB', - 'id': 10195, + 'id': 12345, 'itemCategory': { 'categoryCode': 'dedicated_virtual_hosts' }, 'keyName': '56_CORES_X_242_RAM_X_1_4_TB', 'prices': [{ 'hourlyRecurringFee': '3.164', - 'id': 200269, - 'itemId': 10195, + 'id': 12345, + 'itemId': 12345, 'recurringFee': '2099', }] } @@ -481,7 +481,7 @@ def test_get_backend_router(self): location = [ { 'isAvailable': 1, - 'locationId': 138124, + 'locationId': 12345, 'packageId': 813 } ] @@ -528,7 +528,7 @@ def test_get_backend_router_no_routers_found(self): def test_get_default_router(self): routers = self._get_routers_sample() - router = 51218 + router = 12345 router_test = self.dedicated_host._get_default_router(routers, 'bcr01a.dal05') @@ -544,19 +544,19 @@ def _get_routers_sample(self): routers = [ { 'hostname': 'bcr01a.dal05', - 'id': 51218 + 'id': 12345 }, { 'hostname': 'bcr02a.dal05', - 'id': 83361 + 'id': 12346 }, { 'hostname': 'bcr03a.dal05', - 'id': 122762 + 'id': 12347 }, { 'hostname': 'bcr04a.dal05', - 'id': 147566 + 'id': 12348 } ] @@ -590,14 +590,14 @@ def _get_package(self): ], "prices": [ { - "itemId": 10195, + "itemId": 12345, "recurringFee": "2099", "hourlyRecurringFee": "3.164", - "id": 200269, + "id": 12345, } ], "keyName": "56_CORES_X_242_RAM_X_1_4_TB", - "id": 10195, + "id": 12345, "itemCategory": { "categoryCode": "dedicated_virtual_hosts" }, @@ -608,12 +608,12 @@ def _get_package(self): "location": { "locationPackageDetails": [ { - "locationId": 265592, + "locationId": 12345, "packageId": 813 } ], "location": { - "id": 265592, + "id": 12345, "name": "ams01", "longName": "Amsterdam 1" } @@ -627,12 +627,12 @@ def _get_package(self): "locationPackageDetails": [ { "isAvailable": 1, - "locationId": 138124, + "locationId": 12345, "packageId": 813 } ], "location": { - "id": 138124, + "id": 12345, "name": "dal05", "longName": "Dallas 5" } From 363266b214f12db21001fad23c4cb03285570da9 Mon Sep 17 00:00:00 2001 From: Khuong-Nguyen Date: Wed, 3 Oct 2018 15:40:40 -0500 Subject: [PATCH 13/39] Removed unused import --- tests/CLI/modules/dedicatedhost_tests.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/CLI/modules/dedicatedhost_tests.py b/tests/CLI/modules/dedicatedhost_tests.py index 3cc675749..82c694ff9 100644 --- a/tests/CLI/modules/dedicatedhost_tests.py +++ b/tests/CLI/modules/dedicatedhost_tests.py @@ -6,7 +6,6 @@ """ import json import mock -import os import SoftLayer from SoftLayer.CLI import exceptions From fbd80340adf6af9e9a4e962f88b4e05c4cc0b83b Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Wed, 3 Oct 2018 16:56:49 -0500 Subject: [PATCH 14/39] 5.5.3 changelog --- CHANGELOG.md | 12 +++++++++++- SoftLayer/consts.py | 2 +- setup.py | 2 +- snap/snapcraft.yaml | 2 +- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e587211a6..5313e78b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,17 @@ # Change Log -## [5.5.1] - 2018-08-31 +## [5.5.3] - 2018-08-31 +- Changes: https://github.com/softlayer/softlayer-python/compare/v5.5.2...v5.5.3 + ++ Added `slcli user delete` ++ #1023 Added `slcli order quote` to let users create a quote from the slcli. ++ #1032 Fixed vs upgrades when using flavors. ++ #1034 Added pagination to ticket list commands ++ #1037 Fixed DNS manager to be more flexible and support more zone types. ++ #1044 Pinned Click library version at >=5 < 7 + +## [5.5.2] - 2018-08-31 - Changes: https://github.com/softlayer/softlayer-python/compare/v5.5.1...v5.5.2 + #1018 Fixed hardware credentials. diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index bbb8582b3..cdac01d30 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.5.2' +VERSION = 'v5.5.3' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/setup.py b/setup.py index 2753aff95..b0d08fc17 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ setup( name='SoftLayer', - version='5.5.2', + version='5.5.3', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer Technologies, Inc.', diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 9d059f357..5ebcf39a4 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: slcli # check to see if it's available -version: '5.5.2+git' # check versioning +version: '5.5.3+git' # check versioning summary: Python based SoftLayer API Tool. # 79 char long summary description: | A command-line interface is also included and can be used to manage various SoftLayer products and services. From 87a8ded1192f750ddf2d1d343fe84c6a391dc690 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Wed, 3 Oct 2018 17:50:27 -0500 Subject: [PATCH 15/39] 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 ecd5d1be75433b84fa9bf3b842dd2336c3bb6993 Mon Sep 17 00:00:00 2001 From: "Jorge Rodriguez (A.K.A. Tiriel)" Date: Thu, 4 Oct 2018 09:12:27 +0200 Subject: [PATCH 16/39] Fix `post_uri` parameter name on docstring --- SoftLayer/managers/hardware.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index c105b3b5d..6980f4397 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -268,7 +268,7 @@ def reload(self, hardware_id, post_uri=None, ssh_keys=None): """Perform an OS reload of a server with its current configuration. :param integer hardware_id: the instance ID to reload - :param string post_url: The URI of the post-install script to run + :param string post_uri: The URI of the post-install script to run after reload :param list ssh_keys: The SSH keys to add to the root user """ From df0f47f62fb4aefeab9f1868a1547db330ea110d Mon Sep 17 00:00:00 2001 From: Michael Wurtz Date: Thu, 4 Oct 2018 14:41:54 -0500 Subject: [PATCH 17/39] Fix manager and add CLI support --- SoftLayer/CLI/image/export.py | 10 ++++++++-- SoftLayer/CLI/image/import.py | 36 +++++++++++++++++++++++++++++++++-- SoftLayer/managers/image.py | 8 ++++---- 3 files changed, 46 insertions(+), 8 deletions(-) diff --git a/SoftLayer/CLI/image/export.py b/SoftLayer/CLI/image/export.py index 327cef475..2dd5f4568 100644 --- a/SoftLayer/CLI/image/export.py +++ b/SoftLayer/CLI/image/export.py @@ -12,17 +12,23 @@ @click.command() @click.argument('identifier') @click.argument('uri') +@click.option('--ibm_api_key', + default="", + help="The IBM Cloud API Key with access to IBM Cloud Object " + "Storage instance.") @environment.pass_env -def cli(env, identifier, uri): +def cli(env, identifier, uri, ibm_api_key): """Export an image to object storage. The URI for an object storage object (.vhd/.iso file) of the format: swift://@// + or cos://// if using IBM Cloud + Object Storage """ image_mgr = SoftLayer.ImageManager(env.client) image_id = helpers.resolve_id(image_mgr.resolve_ids, identifier, 'image') - result = image_mgr.export_image_to_uri(image_id, uri) + result = image_mgr.export_image_to_uri(image_id, uri, ibm_api_key) if not result: raise exceptions.CLIAbort("Failed to export Image") diff --git a/SoftLayer/CLI/image/import.py b/SoftLayer/CLI/image/import.py index 03ec25acb..bd19b5ca7 100644 --- a/SoftLayer/CLI/image/import.py +++ b/SoftLayer/CLI/image/import.py @@ -18,13 +18,38 @@ @click.option('--os-code', default="", help="The referenceCode of the operating system software" - " description for the imported VHD") + " description for the imported VHD, ISO, or RAW image") +@click.option('--ibm-api-key', + default="", + help="The IBM Cloud API Key with access to IBM Cloud Object " + "Storage instance.") +@click.option('--root-key-id', + default="", + help="ID of the root key in Key Protect") +@click.option('--wrapped-dek', + default="", + help="Wrapped Decryption Key provided by IBM KeyProtect") +@click.option('--kp-id', + default="", + help="ID of the IBM Key Protect Instance") +@click.option('--cloud-init', + default="", + help="Specifies if image is cloud init") +@click.option('--byol', + default="", + help="Specifies if image is bring your own license") +@click.option('--is-encrypted', + default="", + help="Specifies if image is encrypted") @environment.pass_env -def cli(env, name, note, os_code, uri): +def cli(env, name, note, os_code, uri, ibm_api_key, root_key_id, wrapped_dek, + kp_id, cloud_init, byol, is_encrypted): """Import an image. The URI for an object storage object (.vhd/.iso file) of the format: swift://@// + or cos://// if using IBM Cloud + Object Storage """ image_mgr = SoftLayer.ImageManager(env.client) @@ -33,6 +58,13 @@ def cli(env, name, note, os_code, uri): note=note, os_code=os_code, uri=uri, + ibm_api_key=ibm_api_key, + root_key_id=root_key_id, + wrapped_dek=wrapped_dek, + kp_id=kp_id, + cloud_init=cloud_init, + byol=byol, + is_encrypted=is_encrypted ) if not result: diff --git a/SoftLayer/managers/image.py b/SoftLayer/managers/image.py index 7ebf1fd98..c11c0a890 100644 --- a/SoftLayer/managers/image.py +++ b/SoftLayer/managers/image.py @@ -141,9 +141,9 @@ def import_image_from_uri(self, name, uri, os_code=None, note=None, :param string wrapped_dek: Wrapped Decryption Key provided by IBM KeyProtect :param string kp_id: ID of the IBM Key Protect Instance - :param bool cloud_init: Specifies if image is cloud init - :param bool byol: Specifies if image is bring your own license - :param bool is_encrypted: Specifies if image is encrypted + :param boolean cloud_init: Specifies if image is cloud init + :param boolean byol: Specifies if image is bring your own license + :param boolean is_encrypted: Specifies if image is encrypted """ if 'cos://' in uri: return self.vgbdtg.createFromIcos({ @@ -152,7 +152,7 @@ def import_image_from_uri(self, name, uri, os_code=None, note=None, 'operatingSystemReferenceCode': os_code, 'uri': uri, 'ibmApiKey': ibm_api_key, - 'rootKeyid': root_key_id, + 'rootKeyId': root_key_id, 'wrappedDek': wrapped_dek, 'keyProtectId': kp_id, 'cloudInit': cloud_init, From f4b797dce67c9ecfa3cbaf99f3d765ac3d4d4002 Mon Sep 17 00:00:00 2001 From: Michael Wurtz Date: Thu, 4 Oct 2018 16:37:08 -0500 Subject: [PATCH 18/39] Fix test --- tests/managers/image_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/managers/image_tests.py b/tests/managers/image_tests.py index ba410b646..50a081988 100644 --- a/tests/managers/image_tests.py +++ b/tests/managers/image_tests.py @@ -167,7 +167,7 @@ def test_import_image_cos(self): 'operatingSystemReferenceCode': 'UBUNTU_LATEST', 'uri': 'cos://some_uri', 'ibmApiKey': 'some_ibm_key', - 'rootKeyid': 'some_root_key_id', + 'rootKeyId': 'some_root_key_id', 'wrappedDek': 'some_dek', 'keyProtectId': 'some_id', 'cloudInit': False, From 9d87c90de5e007f816a7bc1ebb365f720cea429d Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 4 Oct 2018 17:04:10 -0500 Subject: [PATCH 19/39] 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 20/39] 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 21/39] 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 22/39] 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 a0da453e4fcca1ac4a21089374c43a041e2b7efe Mon Sep 17 00:00:00 2001 From: Michael Wurtz Date: Fri, 5 Oct 2018 15:47:55 -0500 Subject: [PATCH 23/39] Fixed name of ibm-api-key in cli --- SoftLayer/CLI/image/export.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/image/export.py b/SoftLayer/CLI/image/export.py index 2dd5f4568..76ef5f164 100644 --- a/SoftLayer/CLI/image/export.py +++ b/SoftLayer/CLI/image/export.py @@ -12,7 +12,7 @@ @click.command() @click.argument('identifier') @click.argument('uri') -@click.option('--ibm_api_key', +@click.option('--ibm-api-key', default="", help="The IBM Cloud API Key with access to IBM Cloud Object " "Storage instance.") From 192b192e6d975245993de56a932a9c2f3e77dcf7 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Fri, 5 Oct 2018 18:21:49 -0400 Subject: [PATCH 24/39] fixed suspend cloud server order. --- .../fixtures/SoftLayer_Product_Package.py | 5 +++ .../SoftLayer_Product_Package_Preset.py | 1 + SoftLayer/managers/ordering.py | 31 ++++++++++++++++--- tests/managers/ordering_tests.py | 17 +++++----- 4 files changed, 42 insertions(+), 12 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Product_Package.py b/SoftLayer/fixtures/SoftLayer_Product_Package.py index b7b008788..66a558205 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Package.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Package.py @@ -1346,6 +1346,11 @@ "hourlyRecurringFee": ".093", "id": 204015, "recurringFee": "62", + "categories": [ + { + "categoryCode": "guest_core" + } + ], "item": { "description": "4 x 2.0 GHz or higher Cores", "id": 859, diff --git a/SoftLayer/fixtures/SoftLayer_Product_Package_Preset.py b/SoftLayer/fixtures/SoftLayer_Product_Package_Preset.py index d111b9595..ec3356c1d 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Package_Preset.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Package_Preset.py @@ -49,6 +49,7 @@ "id": 209595, "recurringFee": "118.26", "item": { + "capacity": 8, "description": "8 x 2.0 GHz or higher Cores", "id": 11307, "keyName": "GUEST_CORE_8", diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index 01a182ae1..a6e71f1b7 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -322,7 +322,7 @@ def get_preset_by_key(self, package_keyname, preset_keyname, mask=None): return presets[0] - def get_price_id_list(self, package_keyname, item_keynames): + def get_price_id_list(self, package_keyname, item_keynames, core): """Converts a list of item keynames to a list of price IDs. This function is used to convert a list of item keynames into @@ -331,6 +331,7 @@ def get_price_id_list(self, package_keyname, item_keynames): :param str package_keyname: The package associated with the prices :param list item_keynames: A list of item keyname strings + :param str core: preset guest core capacity. :returns: A list of price IDs associated with the given item keynames in the given package @@ -356,8 +357,11 @@ def get_price_id_list(self, package_keyname, item_keynames): # can take that ID and create the proper price for us in the location # in which the order is made if matching_item['itemCategory']['categoryCode'] != "gpu0": - price_id = [p['id'] for p in matching_item['prices'] - if not p['locationGroupId']][0] + price_id = None + category_code = [] + for price in matching_item['prices']: + if not price['locationGroupId']: + price_id = self.save_price_id(category_code, core, price, price_id) else: # GPU items has two generic prices and they are added to the list # according to the number of gpu items added in the order. @@ -370,6 +374,20 @@ def get_price_id_list(self, package_keyname, item_keynames): return prices + @staticmethod + def save_price_id(category_code, core, price, price_id): + """Save item prices ids""" + if 'capacityRestrictionMinimum' not in price: + if price['categories'][0]['categoryCode'] not in category_code: + category_code.append(price['categories'][0]['categoryCode']) + price_id = price['id'] + elif int(price['capacityRestrictionMinimum']) <= int(core) <= int( + price['capacityRestrictionMaximum']): + if price['categories'][0]['categoryCode'] not in category_code: + category_code.append(price['categories'][0]['categoryCode']) + price_id = price['id'] + return price_id + def get_preset_prices(self, preset): """Get preset item prices. @@ -534,15 +552,20 @@ def generate_order(self, package_keyname, location, item_keynames, complex_type= order['quantity'] = quantity order['useHourlyPricing'] = hourly + preset_core = None if preset_keyname: preset_id = self.get_preset_by_key(package_keyname, preset_keyname)['id'] + preset_items = self.get_preset_prices(preset_id) + for item in preset_items['prices']: + if item['item']['itemCategory']['categoryCode'] == "guest_core": + preset_core = item['item']['capacity'] order['presetId'] = preset_id if not complex_type: raise exceptions.SoftLayerError("A complex type must be specified with the order") order['complexType'] = complex_type - price_ids = self.get_price_id_list(package_keyname, item_keynames) + price_ids = self.get_price_id_list(package_keyname, item_keynames, preset_core) order['prices'] = [{'id': price_id} for price_id in price_ids] container['orderContainers'] = [order] diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index 0ea7c7546..093c68ccf 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -296,7 +296,8 @@ def test_get_preset_by_key_preset_not_found(self): def test_get_price_id_list(self): category1 = {'categoryCode': 'cat1'} - price1 = {'id': 1234, 'locationGroupId': None, 'itemCategory': [category1]} + price1 = {'id': 1234, 'locationGroupId': None, 'categories': [{"categoryCode": "guest_core"}], + 'itemCategory': [category1]} item1 = {'id': 1111, 'keyName': 'ITEM1', 'itemCategory': category1, 'prices': [price1]} category2 = {'categoryCode': 'cat2'} price2 = {'id': 5678, 'locationGroupId': None, 'categories': [category2]} @@ -305,7 +306,7 @@ def test_get_price_id_list(self): with mock.patch.object(self.ordering, 'list_items') as list_mock: list_mock.return_value = [item1, item2] - prices = self.ordering.get_price_id_list('PACKAGE_KEYNAME', ['ITEM1', 'ITEM2']) + prices = self.ordering.get_price_id_list('PACKAGE_KEYNAME', ['ITEM1', 'ITEM2'], "8") list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, itemCategory, keyName, prices[categories]') self.assertEqual([price1['id'], price2['id']], prices) @@ -320,7 +321,7 @@ def test_get_price_id_list_item_not_found(self): exc = self.assertRaises(exceptions.SoftLayerError, self.ordering.get_price_id_list, - 'PACKAGE_KEYNAME', ['ITEM2']) + 'PACKAGE_KEYNAME', ['ITEM2'], "8") list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, itemCategory, keyName, prices[categories]') self.assertEqual("Item ITEM2 does not exist for package PACKAGE_KEYNAME", str(exc)) @@ -333,7 +334,7 @@ def test_get_price_id_list_gpu_items_with_two_categories(self): with mock.patch.object(self.ordering, 'list_items') as list_mock: list_mock.return_value = [item1, item1] - prices = self.ordering.get_price_id_list('PACKAGE_KEYNAME', ['ITEM1', 'ITEM1']) + prices = self.ordering.get_price_id_list('PACKAGE_KEYNAME', ['ITEM1', 'ITEM1'], "8") list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, itemCategory, keyName, prices[categories]') self.assertEqual([price2['id'], price1['id']], prices) @@ -366,7 +367,7 @@ def test_generate_order_with_preset(self): mock_pkg.assert_called_once_with(pkg, mask='id') mock_preset.assert_called_once_with(pkg, preset) - mock_get_ids.assert_called_once_with(pkg, items) + mock_get_ids.assert_called_once_with(pkg, items, 8) self.assertEqual(expected_order, order) def test_generate_order(self): @@ -388,7 +389,7 @@ def test_generate_order(self): mock_pkg.assert_called_once_with(pkg, mask='id') mock_preset.assert_not_called() - mock_get_ids.assert_called_once_with(pkg, items) + mock_get_ids.assert_called_once_with(pkg, items, None) self.assertEqual(expected_order, order) def test_verify_order(self): @@ -526,7 +527,7 @@ def test_location_group_id_none(self): with mock.patch.object(self.ordering, 'list_items') as list_mock: list_mock.return_value = [item1, item2] - prices = self.ordering.get_price_id_list('PACKAGE_KEYNAME', ['ITEM1', 'ITEM2']) + prices = self.ordering.get_price_id_list('PACKAGE_KEYNAME', ['ITEM1', 'ITEM2'], "8") list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, itemCategory, keyName, prices[categories]') self.assertEqual([price1['id'], price2['id']], prices) @@ -543,7 +544,7 @@ def test_location_groud_id_empty(self): with mock.patch.object(self.ordering, 'list_items') as list_mock: list_mock.return_value = [item1, item2] - prices = self.ordering.get_price_id_list('PACKAGE_KEYNAME', ['ITEM1', 'ITEM2']) + prices = self.ordering.get_price_id_list('PACKAGE_KEYNAME', ['ITEM1', 'ITEM2'], "8") list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, itemCategory, keyName, prices[categories]') self.assertEqual([price1['id'], price2['id']], prices) From d4a72b32073d0d9b0fbc7bae413071106ca9d668 Mon Sep 17 00:00:00 2001 From: Michael Wurtz Date: Mon, 8 Oct 2018 14:42:25 -0500 Subject: [PATCH 25/39] Address comments and add appropriate code --- SoftLayer/CLI/image/export.py | 4 +++- SoftLayer/CLI/image/import.py | 16 ++++++++++------ SoftLayer/managers/image.py | 10 +++++----- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/SoftLayer/CLI/image/export.py b/SoftLayer/CLI/image/export.py index 76ef5f164..fec494e5f 100644 --- a/SoftLayer/CLI/image/export.py +++ b/SoftLayer/CLI/image/export.py @@ -15,7 +15,9 @@ @click.option('--ibm-api-key', default="", help="The IBM Cloud API Key with access to IBM Cloud Object " - "Storage instance.") + "Storage instance. For help creating this key see " + "https://console.bluemix.net/docs/services/cloud-object-" + "storage/iam/users-serviceids.html#serviceidapikeys") @environment.pass_env def cli(env, identifier, uri, ibm_api_key): """Export an image to object storage. diff --git a/SoftLayer/CLI/image/import.py b/SoftLayer/CLI/image/import.py index bd19b5ca7..a0e65030c 100644 --- a/SoftLayer/CLI/image/import.py +++ b/SoftLayer/CLI/image/import.py @@ -22,24 +22,28 @@ @click.option('--ibm-api-key', default="", help="The IBM Cloud API Key with access to IBM Cloud Object " - "Storage instance.") + "Storage instance. For help creating this key see " + "https://console.bluemix.net/docs/services/cloud-object-" + "storage/iam/users-serviceids.html#serviceidapikeys") @click.option('--root-key-id', default="", help="ID of the root key in Key Protect") @click.option('--wrapped-dek', default="", - help="Wrapped Decryption Key provided by IBM KeyProtect") + help="Wrapped Data Encryption Key provided by IBM KeyProtect. " + "For more info see https://console.bluemix.net/docs/" + "services/key-protect/wrap-keys.html#wrap-keys") @click.option('--kp-id', default="", help="ID of the IBM Key Protect Instance") @click.option('--cloud-init', - default="", - help="Specifies if image is cloud init") + is_flag=True, + help="Specifies if image is cloud-init") @click.option('--byol', - default="", + is_flag=True, help="Specifies if image is bring your own license") @click.option('--is-encrypted', - default="", + is_flag=True, help="Specifies if image is encrypted") @environment.pass_env def cli(env, name, note, os_code, uri, ibm_api_key, root_key_id, wrapped_dek, diff --git a/SoftLayer/managers/image.py b/SoftLayer/managers/image.py index c11c0a890..abd60ba8a 100644 --- a/SoftLayer/managers/image.py +++ b/SoftLayer/managers/image.py @@ -122,8 +122,8 @@ def edit(self, image_id, name=None, note=None, tag=None): def import_image_from_uri(self, name, uri, os_code=None, note=None, ibm_api_key=None, root_key_id=None, - wrapped_dek=None, kp_id=None, cloud_init=None, - byol=None, is_encrypted=None): + wrapped_dek=None, kp_id=None, cloud_init=False, + byol=False, is_encrypted=False): """Import a new image from object storage. :param string name: Name of the new image @@ -138,10 +138,10 @@ def import_image_from_uri(self, name, uri, os_code=None, note=None, :param string ibm_api_key: Ibm Api Key needed to communicate with ICOS and Key Protect :param string root_key_id: ID of the root key in Key Protect - :param string wrapped_dek: Wrapped Decryption Key provided by IBM - KeyProtect + :param string wrapped_dek: Wrapped Data Encryption Key provided by + IBM KeyProtect :param string kp_id: ID of the IBM Key Protect Instance - :param boolean cloud_init: Specifies if image is cloud init + :param boolean cloud_init: Specifies if image is cloud-init :param boolean byol: Specifies if image is bring your own license :param boolean is_encrypted: Specifies if image is encrypted """ From ad84d58bcaccd688ae4f81f32977b3b05144aa2b Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Mon, 8 Oct 2018 15:42:25 -0400 Subject: [PATCH 26/39] unit test suspend cloud server --- SoftLayer/managers/ordering.py | 8 ++++---- tests/managers/ordering_tests.py | 25 +++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index a6e71f1b7..af27fbffb 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -358,10 +358,9 @@ def get_price_id_list(self, package_keyname, item_keynames, core): # in which the order is made if matching_item['itemCategory']['categoryCode'] != "gpu0": price_id = None - category_code = [] for price in matching_item['prices']: if not price['locationGroupId']: - price_id = self.save_price_id(category_code, core, price, price_id) + price_id = self.get_item_price_id(core, price, price_id) else: # GPU items has two generic prices and they are added to the list # according to the number of gpu items added in the order. @@ -375,8 +374,9 @@ def get_price_id_list(self, package_keyname, item_keynames, core): return prices @staticmethod - def save_price_id(category_code, core, price, price_id): - """Save item prices ids""" + def get_item_price_id(core, price, price_id): + """get item price id""" + category_code = [] if 'capacityRestrictionMinimum' not in price: if price['categories'][0]['categoryCode'] not in category_code: category_code.append(price['categories'][0]['categoryCode']) diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index 093c68ccf..4928c4bd5 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -548,3 +548,28 @@ def test_location_groud_id_empty(self): list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, itemCategory, keyName, prices[categories]') self.assertEqual([price1['id'], price2['id']], prices) + + def test_get_item_price_id_without_capacity_restriction(self): + category1 = {'categoryCode': 'cat1'} + price1 = {'id': 1234, 'locationGroupId': '', 'categories': [category1]} + + with mock.patch.object(self.ordering, 'get_item_price_id') as list_mock: + list_mock.return_value = [price1] + + prices = self.ordering.get_item_price_id("8", price1) + + list_mock.assert_called_once_with("8", price1) + self.assertEqual(1234, prices[0]['id']) + + def test_get_item_price_id_with_capacity_restriction(self): + category1 = {'categoryCode': 'cat1'} + price1 = {'id': 1234, 'locationGroupId': '', "capacityRestrictionMaximum": "16", + "capacityRestrictionMinimum": "1", 'categories': [category1]} + + with mock.patch.object(self.ordering, 'get_item_price_id') as list_mock: + list_mock.return_value = [price1] + + prices = self.ordering.get_item_price_id("8", price1) + + list_mock.assert_called_once_with("8", price1) + self.assertEqual(1234, prices[0]['id']) From 2385bf24c8a06812ebcc64f38937ce7203f5b987 Mon Sep 17 00:00:00 2001 From: Michael Wurtz Date: Mon, 8 Oct 2018 14:47:23 -0500 Subject: [PATCH 27/39] Add KeyProtect instance in help text --- SoftLayer/CLI/image/import.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/image/import.py b/SoftLayer/CLI/image/import.py index a0e65030c..1eb7e1658 100644 --- a/SoftLayer/CLI/image/import.py +++ b/SoftLayer/CLI/image/import.py @@ -22,9 +22,10 @@ @click.option('--ibm-api-key', default="", help="The IBM Cloud API Key with access to IBM Cloud Object " - "Storage instance. For help creating this key see " - "https://console.bluemix.net/docs/services/cloud-object-" - "storage/iam/users-serviceids.html#serviceidapikeys") + "Storage instance and IBM KeyProtect instance. For help " + "creating this key see https://console.bluemix.net/docs/" + "services/cloud-object-storage/iam/users-serviceids.html" + "#serviceidapikeys") @click.option('--root-key-id', default="", help="ID of the root key in Key Protect") From 03d3c8e1ccc45157826adac29429fb32530a19bc Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 8 Oct 2018 15:58:57 -0500 Subject: [PATCH 28/39] #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 29/39] 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 3a6b7b30b286fef55f713b49ab5a2ffc2d7a90bc Mon Sep 17 00:00:00 2001 From: Michael Wurtz Date: Tue, 9 Oct 2018 13:35:56 -0500 Subject: [PATCH 30/39] Change defaults to None --- SoftLayer/CLI/image/export.py | 2 +- SoftLayer/CLI/image/import.py | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/SoftLayer/CLI/image/export.py b/SoftLayer/CLI/image/export.py index fec494e5f..375de7842 100644 --- a/SoftLayer/CLI/image/export.py +++ b/SoftLayer/CLI/image/export.py @@ -13,7 +13,7 @@ @click.argument('identifier') @click.argument('uri') @click.option('--ibm-api-key', - default="", + default=None, help="The IBM Cloud API Key with access to IBM Cloud Object " "Storage instance. For help creating this key see " "https://console.bluemix.net/docs/services/cloud-object-" diff --git a/SoftLayer/CLI/image/import.py b/SoftLayer/CLI/image/import.py index 1eb7e1658..525564416 100644 --- a/SoftLayer/CLI/image/import.py +++ b/SoftLayer/CLI/image/import.py @@ -16,26 +16,25 @@ default="", help="The note to be applied to the imported template") @click.option('--os-code', - default="", help="The referenceCode of the operating system software" " description for the imported VHD, ISO, or RAW image") @click.option('--ibm-api-key', - default="", + default=None, help="The IBM Cloud API Key with access to IBM Cloud Object " "Storage instance and IBM KeyProtect instance. For help " "creating this key see https://console.bluemix.net/docs/" "services/cloud-object-storage/iam/users-serviceids.html" "#serviceidapikeys") @click.option('--root-key-id', - default="", + default=None, help="ID of the root key in Key Protect") @click.option('--wrapped-dek', - default="", + default=None, help="Wrapped Data Encryption Key provided by IBM KeyProtect. " "For more info see https://console.bluemix.net/docs/" "services/key-protect/wrap-keys.html#wrap-keys") @click.option('--kp-id', - default="", + default=None, help="ID of the IBM Key Protect Instance") @click.option('--cloud-init', is_flag=True, From f5da2d60308fe86d41320d1d5eb80d376641de9b Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Tue, 9 Oct 2018 14:43:52 -0400 Subject: [PATCH 31/39] Unit test suspend cloud server order --- tests/managers/ordering_tests.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index 4928c4bd5..4319ff149 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -553,23 +553,15 @@ def test_get_item_price_id_without_capacity_restriction(self): category1 = {'categoryCode': 'cat1'} price1 = {'id': 1234, 'locationGroupId': '', 'categories': [category1]} - with mock.patch.object(self.ordering, 'get_item_price_id') as list_mock: - list_mock.return_value = [price1] + price_id = self.ordering.get_item_price_id("8", price1, None) - prices = self.ordering.get_item_price_id("8", price1) - - list_mock.assert_called_once_with("8", price1) - self.assertEqual(1234, prices[0]['id']) + self.assertEqual(1234, price_id) def test_get_item_price_id_with_capacity_restriction(self): category1 = {'categoryCode': 'cat1'} price1 = {'id': 1234, 'locationGroupId': '', "capacityRestrictionMaximum": "16", "capacityRestrictionMinimum": "1", 'categories': [category1]} - with mock.patch.object(self.ordering, 'get_item_price_id') as list_mock: - list_mock.return_value = [price1] - - prices = self.ordering.get_item_price_id("8", price1) + price_id = self.ordering.get_item_price_id("8", price1, None) - list_mock.assert_called_once_with("8", price1) - self.assertEqual(1234, prices[0]['id']) + self.assertEqual(1234, price_id) From 6f58b94b8ded045161b12cf1fd488ca87553d3cc Mon Sep 17 00:00:00 2001 From: Felix Yan Date: Wed, 10 Oct 2018 06:11:47 +0800 Subject: [PATCH 32/39] Update to use click 7 --- SoftLayer/CLI/core.py | 2 +- SoftLayer/CLI/vpn/ipsec/subnet/add.py | 4 +-- SoftLayer/CLI/vpn/ipsec/subnet/remove.py | 2 +- SoftLayer/CLI/vpn/ipsec/update.py | 36 ++++++++++++------------ setup.py | 2 +- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py index a02bf65a4..a05ffaa54 100644 --- a/SoftLayer/CLI/core.py +++ b/SoftLayer/CLI/core.py @@ -137,7 +137,7 @@ def cli(env, @cli.resultcallback() @environment.pass_env -def output_diagnostics(env, verbose=0, **kwargs): +def output_diagnostics(env, result, verbose=0, **kwargs): """Output diagnostic information.""" if verbose > 0: diff --git a/SoftLayer/CLI/vpn/ipsec/subnet/add.py b/SoftLayer/CLI/vpn/ipsec/subnet/add.py index 438dfc5fc..08d0bc5ec 100644 --- a/SoftLayer/CLI/vpn/ipsec/subnet/add.py +++ b/SoftLayer/CLI/vpn/ipsec/subnet/add.py @@ -18,14 +18,14 @@ type=int, help='Subnet identifier to add') @click.option('-t', - '--type', '--subnet-type', + '--type', required=True, type=click.Choice(['internal', 'remote', 'service']), help='Subnet type to add') @click.option('-n', - '--network', '--network-identifier', + '--network', default=None, type=NetworkParamType(), help='Subnet network identifier to create') diff --git a/SoftLayer/CLI/vpn/ipsec/subnet/remove.py b/SoftLayer/CLI/vpn/ipsec/subnet/remove.py index 2d8b34d9b..41d450a33 100644 --- a/SoftLayer/CLI/vpn/ipsec/subnet/remove.py +++ b/SoftLayer/CLI/vpn/ipsec/subnet/remove.py @@ -16,8 +16,8 @@ type=int, help='Subnet identifier to remove') @click.option('-t', - '--type', '--subnet-type', + '--type', required=True, type=click.Choice(['internal', 'remote', 'service']), help='Subnet type to add') diff --git a/SoftLayer/CLI/vpn/ipsec/update.py b/SoftLayer/CLI/vpn/ipsec/update.py index 68e09b0a9..4056f3b8f 100644 --- a/SoftLayer/CLI/vpn/ipsec/update.py +++ b/SoftLayer/CLI/vpn/ipsec/update.py @@ -20,48 +20,48 @@ @click.option('--preshared-key', default=None, help='Preshared key value') -@click.option('--p1-auth', - '--phase1-auth', +@click.option('--phase1-auth', + '--p1-auth', default=None, type=click.Choice(['MD5', 'SHA1', 'SHA256']), help='Phase 1 authentication value') -@click.option('--p1-crypto', - '--phase1-crypto', +@click.option('--phase1-crypto', + '--p1-crypto', default=None, type=click.Choice(['DES', '3DES', 'AES128', 'AES192', 'AES256']), help='Phase 1 encryption value') -@click.option('--p1-dh', - '--phase1-dh', +@click.option('--phase1-dh', + '--p1-dh', default=None, type=click.Choice(['0', '1', '2', '5']), help='Phase 1 diffie hellman group value') -@click.option('--p1-key-ttl', - '--phase1-key-ttl', +@click.option('--phase1-key-ttl', + '--p1-key-ttl', default=None, type=click.IntRange(120, 172800), help='Phase 1 key life value') -@click.option('--p2-auth', - '--phase2-auth', +@click.option('--phase2-auth', + '--p2-auth', default=None, type=click.Choice(['MD5', 'SHA1', 'SHA256']), help='Phase 2 authentication value') -@click.option('--p2-crypto', - '--phase2-crypto', +@click.option('--phase2-crypto', + '--p2-crypto', default=None, type=click.Choice(['DES', '3DES', 'AES128', 'AES192', 'AES256']), help='Phase 2 encryption value') -@click.option('--p2-dh', - '--phase2-dh', +@click.option('--phase2-dh', + '--p2-dh', default=None, type=click.Choice(['0', '1', '2', '5']), help='Phase 2 diffie hellman group value') -@click.option('--p2-forward-secrecy', - '--phase2-forward-secrecy', +@click.option('--phase2-forward-secrecy', + '--p2-forward-secrecy', default=None, type=click.IntRange(0, 1), help='Phase 2 perfect forward secrecy value') -@click.option('--p2-key-ttl', - '--phase2-key-ttl', +@click.option('--phase2-key-ttl', + '--p2-key-ttl', default=None, type=click.IntRange(120, 172800), help='Phase 2 key life value') diff --git a/setup.py b/setup.py index b0d08fc17..1cca86a2a 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ install_requires=[ 'six >= 1.7.0', 'ptable >= 0.9.2', - 'click >= 5, < 7', + 'click >= 7', 'requests >= 2.18.4', 'prompt_toolkit >= 0.53', 'pygments >= 2.0.0', From 3b7689984d5ddc2d38987f8a0f4caac14c161862 Mon Sep 17 00:00:00 2001 From: Felix Yan Date: Wed, 10 Oct 2018 13:04:59 +0800 Subject: [PATCH 33/39] Fix exit code of edit-permissions test --- tests/CLI/modules/user_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CLI/modules/user_tests.py b/tests/CLI/modules/user_tests.py index 0222a62b8..0683ed98f 100644 --- a/tests/CLI/modules/user_tests.py +++ b/tests/CLI/modules/user_tests.py @@ -129,7 +129,7 @@ def test_edit_perms_on(self): def test_edit_perms_on_bad(self): result = self.run_command(['user', 'edit-permissions', '11100', '--enable', '-p', 'TEST_NOt_exist']) - self.assertEqual(result.exit_code, -1) + self.assertEqual(result.exit_code, 1) def test_edit_perms_off(self): result = self.run_command(['user', 'edit-permissions', '11100', '--disable', '-p', 'TEST']) From 8056e816185bb13a80194abd5808cdb925d2c13c Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 10 Oct 2018 09:56:33 -0400 Subject: [PATCH 34/39] Refactored suspend cloud server order --- SoftLayer/managers/ordering.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index af27fbffb..17feb0580 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -322,7 +322,7 @@ def get_preset_by_key(self, package_keyname, preset_keyname, mask=None): return presets[0] - def get_price_id_list(self, package_keyname, item_keynames, core): + def get_price_id_list(self, package_keyname, item_keynames, core=None): """Converts a list of item keynames to a list of price IDs. This function is used to convert a list of item keynames into @@ -377,12 +377,13 @@ def get_price_id_list(self, package_keyname, item_keynames, core): def get_item_price_id(core, price, price_id): """get item price id""" category_code = [] - if 'capacityRestrictionMinimum' not in price: + capacity_min = int(price.get('capacityRestrictionMinimum', -1)) + capacity_max = int(price.get('capacityRestrictionMaximum', -1)) + if capacity_min is -1: if price['categories'][0]['categoryCode'] not in category_code: category_code.append(price['categories'][0]['categoryCode']) price_id = price['id'] - elif int(price['capacityRestrictionMinimum']) <= int(core) <= int( - price['capacityRestrictionMaximum']): + elif capacity_min <= int(core) <= capacity_max: if price['categories'][0]['categoryCode'] not in category_code: category_code.append(price['categories'][0]['categoryCode']) price_id = price['id'] From d7473db02bd9bd06ff3ca9e7478bfbbac570e49b Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 10 Oct 2018 10:17:01 -0400 Subject: [PATCH 35/39] Refactored suspend cloud server order --- SoftLayer/managers/ordering.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index 17feb0580..361ce0102 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -379,7 +379,7 @@ def get_item_price_id(core, price, price_id): category_code = [] capacity_min = int(price.get('capacityRestrictionMinimum', -1)) capacity_max = int(price.get('capacityRestrictionMaximum', -1)) - if capacity_min is -1: + if capacity_min == -1: if price['categories'][0]['categoryCode'] not in category_code: category_code.append(price['categories'][0]['categoryCode']) price_id = price['id'] From 4cff83c869677aa23fbbdbea75c0de63fb2baff8 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 10 Oct 2018 18:22:10 -0500 Subject: [PATCH 36/39] 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) From d989dfd18b950d1261311e0901c48614b7b936a8 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 11 Oct 2018 10:34:50 -0400 Subject: [PATCH 37/39] Refactored suspend cloud server order --- SoftLayer/managers/ordering.py | 27 +++++++++++---------------- tests/managers/ordering_tests.py | 14 +++++++++----- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index 361ce0102..5b6927369 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -357,10 +357,7 @@ def get_price_id_list(self, package_keyname, item_keynames, core=None): # can take that ID and create the proper price for us in the location # in which the order is made if matching_item['itemCategory']['categoryCode'] != "gpu0": - price_id = None - for price in matching_item['prices']: - if not price['locationGroupId']: - price_id = self.get_item_price_id(core, price, price_id) + price_id = self.get_item_price_id(core, matching_item['prices']) else: # GPU items has two generic prices and they are added to the list # according to the number of gpu items added in the order. @@ -374,19 +371,17 @@ def get_price_id_list(self, package_keyname, item_keynames, core=None): return prices @staticmethod - def get_item_price_id(core, price, price_id): + def get_item_price_id(core, prices): """get item price id""" - category_code = [] - capacity_min = int(price.get('capacityRestrictionMinimum', -1)) - capacity_max = int(price.get('capacityRestrictionMaximum', -1)) - if capacity_min == -1: - if price['categories'][0]['categoryCode'] not in category_code: - category_code.append(price['categories'][0]['categoryCode']) - price_id = price['id'] - elif capacity_min <= int(core) <= capacity_max: - if price['categories'][0]['categoryCode'] not in category_code: - category_code.append(price['categories'][0]['categoryCode']) - price_id = price['id'] + price_id = None + for price in prices: + if not price['locationGroupId']: + capacity_min = int(price.get('capacityRestrictionMinimum', -1)) + capacity_max = int(price.get('capacityRestrictionMaximum', -1)) + if capacity_min == -1: + price_id = price['id'] + elif capacity_min <= int(core) <= capacity_max: + price_id = price['id'] return price_id def get_preset_prices(self, preset): diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index 4319ff149..a0a253a9f 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -551,17 +551,21 @@ def test_location_groud_id_empty(self): def test_get_item_price_id_without_capacity_restriction(self): category1 = {'categoryCode': 'cat1'} - price1 = {'id': 1234, 'locationGroupId': '', 'categories': [category1]} + category2 = {'categoryCode': 'cat2'} + prices = [{'id': 1234, 'locationGroupId': '', 'categories': [category1]}, + {'id': 2222, 'locationGroupId': 509, 'categories': [category2]}] - price_id = self.ordering.get_item_price_id("8", price1, None) + price_id = self.ordering.get_item_price_id("8", prices) self.assertEqual(1234, price_id) def test_get_item_price_id_with_capacity_restriction(self): category1 = {'categoryCode': 'cat1'} - price1 = {'id': 1234, 'locationGroupId': '', "capacityRestrictionMaximum": "16", - "capacityRestrictionMinimum": "1", 'categories': [category1]} + price1 = [{'id': 1234, 'locationGroupId': '', "capacityRestrictionMaximum": "16", + "capacityRestrictionMinimum": "1", 'categories': [category1]}, + {'id': 2222, 'locationGroupId': '', "capacityRestrictionMaximum": "56", + "capacityRestrictionMinimum": "36", 'categories': [category1]}] - price_id = self.ordering.get_item_price_id("8", price1, None) + price_id = self.ordering.get_item_price_id("8", price1) self.assertEqual(1234, price_id) From ef4d507f1081e1cff693cf0fa7bac44c5fe542c6 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 16 Oct 2018 17:20:48 -0500 Subject: [PATCH 38/39] 5.6.0 release --- CHANGELOG.md | 14 ++++++++++++++ SoftLayer/consts.py | 2 +- setup.py | 2 +- snap/snapcraft.yaml | 2 +- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5313e78b5..7c408f0ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Change Log +## [5.6.0] - 2018-10-16 +- Changes: https://github.com/softlayer/softlayer-python/compare/v5.5.3...v5.6.0 + ++ #1026 Support for [Reserved Capacity](https://console.bluemix.net/docs/vsi/vsi_about_reserved.html#about-reserved-virtual-servers) + * `slcli vs capacity create` + * `slcli vs capacity create-guest` + * `slcli vs capacity create-options` + * `slcli vs capacity detail` + * `slcli vs capacity list` ++ #1050 Fix `post_uri` parameter name on docstring ++ #1039 Fixed suspend cloud server order. ++ #1055 Update to use click 7 ++ #1053 Add export/import capabilities to/from IBM Cloud Object Storage to the image manager as well as the slcli. + ## [5.5.3] - 2018-08-31 - Changes: https://github.com/softlayer/softlayer-python/compare/v5.5.2...v5.5.3 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index cdac01d30..29e3f58d2 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.5.3' +VERSION = 'v5.6.0' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/setup.py b/setup.py index 1cca86a2a..c3a952888 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ setup( name='SoftLayer', - version='5.5.3', + version='5.6.0', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer Technologies, Inc.', diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 5ebcf39a4..224313b06 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: slcli # check to see if it's available -version: '5.5.3+git' # check versioning +version: '5.6.0+git' # check versioning summary: Python based SoftLayer API Tool. # 79 char long summary description: | A command-line interface is also included and can be used to manage various SoftLayer products and services. From a929d9ced13b53c733d49b591c8d8a4dbb6cd297 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 16 Oct 2018 18:08:03 -0500 Subject: [PATCH 39/39] pinning urllib3 and request since the newest version of urllib3 is incompatible with the newest request lib --- setup.py | 4 ++-- tools/requirements.txt | 4 ++-- tools/test-requirements.txt | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/setup.py b/setup.py index c3a952888..b4c86c2f5 100644 --- a/setup.py +++ b/setup.py @@ -33,10 +33,10 @@ 'six >= 1.7.0', 'ptable >= 0.9.2', 'click >= 7', - 'requests >= 2.18.4', + 'requests == 2.19.1', 'prompt_toolkit >= 0.53', 'pygments >= 2.0.0', - 'urllib3 >= 1.22' + 'urllib3 == 1.22' ], keywords=['softlayer', 'cloud'], classifiers=[ diff --git a/tools/requirements.txt b/tools/requirements.txt index bed36edb5..17bba9467 100644 --- a/tools/requirements.txt +++ b/tools/requirements.txt @@ -1,6 +1,6 @@ -requests >= 2.18.4 +requests == 2.19.1 click >= 5, < 7 prettytable >= 0.7.0 six >= 1.7.0 prompt_toolkit -urllib3 +urllib3 == 1.22 diff --git a/tools/test-requirements.txt b/tools/test-requirements.txt index c9a94de27..138fe84db 100644 --- a/tools/test-requirements.txt +++ b/tools/test-requirements.txt @@ -4,5 +4,5 @@ pytest-cov mock sphinx testtools -urllib3 -requests >= 2.18.4 +urllib3 == 1.22 +requests == 2.19.1