From e7f4f5727cad4a56fef5e59ec829c5affafa0748 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 16 Aug 2018 17:22:57 -0400 Subject: [PATCH 1/3] Adding the base of 'slcli order quote' command, a new method was added on ordering manager. --- SoftLayer/CLI/order/place.py | 2 +- SoftLayer/CLI/order/quote.py | 93 ++++++++++++++++++++++++++++++++++ SoftLayer/managers/ordering.py | 31 ++++++++++++ 3 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 SoftLayer/CLI/order/quote.py diff --git a/SoftLayer/CLI/order/place.py b/SoftLayer/CLI/order/place.py index 1e21e544a..6d51ab935 100644 --- a/SoftLayer/CLI/order/place.py +++ b/SoftLayer/CLI/order/place.py @@ -43,7 +43,7 @@ def cli(env, package_keyname, location, preset, verify, billing, complex_type, can then be converted to be made programmatically by calling SoftLayer.OrderingManager.place_order() with the same keynames. - Packages for ordering can be retrived from `slcli order package-list` + Packages for ordering can be retrieved from `slcli order package-list` Presets for ordering can be retrieved from `slcli order preset-list` (not all packages have presets) diff --git a/SoftLayer/CLI/order/quote.py b/SoftLayer/CLI/order/quote.py new file mode 100644 index 000000000..8cf393359 --- /dev/null +++ b/SoftLayer/CLI/order/quote.py @@ -0,0 +1,93 @@ +"""Save an order as quote""" +# :license: MIT, see LICENSE for more details. + +import json + +import click + +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.managers import ordering + +COLUMNS = ['keyName', + 'description', + 'cost', ] + +@click.command() +@click.argument('package_keyname') +@click.argument('location') +@click.option('--preset', + help="The order preset (if required by the package)") +@click.option('--name', + help="Quote name (optional)") +@click.option('--send-email', + is_flag=True, + help="Quote will be sent to the email address") +@click.option('--complex-type', help=("The complex type of the order. This typically begins" + " with 'SoftLayer_Container_Product_Order_'.")) +@click.option('--extras', + help="JSON string denoting extra data that needs to be sent with the order") +@click.argument('order_items', nargs=-1) +@environment.pass_env +def cli(env, package_keyname, location, preset, name, email, complex_type, + extras, order_items): + """Save an order as quote. + + This CLI command is used for saving an order in quote of the specified package in + the given location (denoted by a datacenter's long name). Orders made via the CLI + can then be converted to be made programmatically by calling + SoftLayer.OrderingManager.place_order() with the same keynames. + + Packages for ordering can be retrieved from `slcli order package-list` + Presets for ordering can be retrieved from `slcli order preset-list` (not all packages + have presets) + + Items can be retrieved from `slcli order item-list`. In order to find required + items for the order, use `slcli order category-list`, and then provide the + --category option for each category code in `slcli order item-list`. + + \b + Example: + # Order an hourly VSI with 4 CPU, 16 GB RAM, 100 GB SAN disk, + # Ubuntu 16.04, and 1 Gbps public & private uplink in dal13 + slcli order quote --name " My quote name" --email CLOUD_SERVER DALLAS13 \\ + GUEST_CORES_4 \\ + RAM_16_GB \\ + REBOOT_REMOTE_CONSOLE \\ + 1_GBPS_PUBLIC_PRIVATE_NETWORK_UPLINKS \\ + BANDWIDTH_0_GB_2 \\ + 1_IP_ADDRESS \\ + GUEST_DISK_100_GB_SAN \\ + OS_UBUNTU_16_04_LTS_XENIAL_XERUS_MINIMAL_64_BIT_FOR_VSI \\ + MONITORING_HOST_PING \\ + NOTIFICATION_EMAIL_AND_TICKET \\ + AUTOMATED_NOTIFICATION \\ + UNLIMITED_SSL_VPN_USERS_1_PPTP_VPN_USER_PER_ACCOUNT \\ + NESSUS_VULNERABILITY_ASSESSMENT_REPORTING \\ + --extras '{"virtualGuests": [{"hostname": "test", "domain": "softlayer.com"}]}' \\ + --complex-type SoftLayer_Container_Product_Order_Virtual_Guest + + """ + manager = ordering.OrderingManager(env.client) + + if extras: + extras = json.loads(extras) + + args = (package_keyname, location, order_items) + kwargs = {'preset_keyname': preset, + 'extras': extras, + 'quantity': 1, + 'quoteName': name, + 'sendQuoteEmailFlag': email, + 'complex_type': complex_type} + + order = manager.save_quote(*args, **kwargs) + + table = formatting.KeyValueTable(['name', 'value']) + table.align['name'] = 'r' + table.align['value'] = 'l' + table.add_row(['id', order['orderId']]) + table.add_row(['created', order['orderDate']]) + table.add_row(['status', order['placedOrder']['status']]) + env.fout(table) \ No newline at end of file diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index 9b1fadec0..fca243d21 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -430,6 +430,37 @@ def place_order(self, package_keyname, location, item_keynames, complex_type=Non extras=extras, quantity=quantity) return self.order_svc.placeOrder(order) + def save_quote(self, package_keyname, location, item_keynames, complex_type=None, + preset_keyname=None, extras=None, quantity=1): + + """Save an order as Quote with the given package and prices. + + This function takes in parameters needed for an order and places the order. + + :param str package_keyname: The keyname for the package being ordered + :param str location: The datacenter location string for ordering (Ex: DALLAS13) + :param list item_keynames: The list of item keyname strings to order. To see list of + possible keynames for a package, use list_items() + (or `slcli order item-list`) + :param str complex_type: The complex type to send with the order. Typically begins + with `SoftLayer_Container_Product_Order_`. + :param string preset_keyname: If needed, specifies a preset to use for that package. + To see a list of possible keynames for a package, use + list_preset() (or `slcli order preset-list`) + :param dict extras: The extra data for the order in dictionary format. + Example: A VSI order requires hostname and domain to be set, so + extras will look like the following: + {'virtualGuests': [{'hostname': 'test', domain': 'softlayer.com'}]} + :param int quantity: The number of resources to order + + """ + order = self.generate_order(package_keyname, location, item_keynames, + complex_type=complex_type, hourly=False, + preset_keyname=preset_keyname, + extras=extras, quantity=quantity) + return self.order_svc.placeOrder(order, True) + + def generate_order(self, package_keyname, location, item_keynames, complex_type=None, hourly=True, preset_keyname=None, extras=None, quantity=1): """Generates an order with the given package and prices. From a3f2c58ece354045ffc59a18c6e8610de65c559a Mon Sep 17 00:00:00 2001 From: Albert Camacho Date: Tue, 21 Aug 2018 15:27:41 -0400 Subject: [PATCH 2/3] previous issues were fixed, now the place quote method creates a quote with pending status --- .../CLI/order/{quote.py => place_quote.py} | 36 ++++++----- SoftLayer/CLI/routes.py | 1 + SoftLayer/managers/ordering.py | 61 ++++++++++--------- 3 files changed, 50 insertions(+), 48 deletions(-) rename SoftLayer/CLI/order/{quote.py => place_quote.py} (74%) diff --git a/SoftLayer/CLI/order/quote.py b/SoftLayer/CLI/order/place_quote.py similarity index 74% rename from SoftLayer/CLI/order/quote.py rename to SoftLayer/CLI/order/place_quote.py index 8cf393359..c65e65552 100644 --- a/SoftLayer/CLI/order/quote.py +++ b/SoftLayer/CLI/order/place_quote.py @@ -1,4 +1,4 @@ -"""Save an order as quote""" +"""Place quote""" # :license: MIT, see LICENSE for more details. import json @@ -6,13 +6,9 @@ import click from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting from SoftLayer.managers import ordering -COLUMNS = ['keyName', - 'description', - 'cost', ] @click.command() @click.argument('package_keyname') @@ -20,24 +16,24 @@ @click.option('--preset', help="The order preset (if required by the package)") @click.option('--name', - help="Quote name (optional)") + help="A custom name to be assigned to the quote (optional)") @click.option('--send-email', is_flag=True, - help="Quote will be sent to the email address") + help="The quote will be sent to the email address associated.") @click.option('--complex-type', help=("The complex type of the order. This typically begins" " with 'SoftLayer_Container_Product_Order_'.")) @click.option('--extras', help="JSON string denoting extra data that needs to be sent with the order") @click.argument('order_items', nargs=-1) @environment.pass_env -def cli(env, package_keyname, location, preset, name, email, complex_type, +def cli(env, package_keyname, location, preset, name, send_email, complex_type, extras, order_items): - """Save an order as quote. + """Place a quote. - This CLI command is used for saving an order in quote of the specified package in + This CLI command is used for placing a quote of the specified package in the given location (denoted by a datacenter's long name). Orders made via the CLI can then be converted to be made programmatically by calling - SoftLayer.OrderingManager.place_order() with the same keynames. + SoftLayer.OrderingManager.place_quote() with the same keynames. Packages for ordering can be retrieved from `slcli order package-list` Presets for ordering can be retrieved from `slcli order preset-list` (not all packages @@ -49,9 +45,9 @@ def cli(env, package_keyname, location, preset, name, email, complex_type, \b Example: - # Order an hourly VSI with 4 CPU, 16 GB RAM, 100 GB SAN disk, + # Place quote a VSI with 4 CPU, 16 GB RAM, 100 GB SAN disk, # Ubuntu 16.04, and 1 Gbps public & private uplink in dal13 - slcli order quote --name " My quote name" --email CLOUD_SERVER DALLAS13 \\ + slcli order place-quote --name " My quote name" --send-email CLOUD_SERVER DALLAS13 \\ GUEST_CORES_4 \\ RAM_16_GB \\ REBOOT_REMOTE_CONSOLE \\ @@ -78,16 +74,18 @@ def cli(env, package_keyname, location, preset, name, email, complex_type, kwargs = {'preset_keyname': preset, 'extras': extras, 'quantity': 1, - 'quoteName': name, - 'sendQuoteEmailFlag': email, + 'quote_name': name, + 'send_email': send_email, 'complex_type': complex_type} - order = manager.save_quote(*args, **kwargs) + order = manager.place_quote(*args, **kwargs) table = formatting.KeyValueTable(['name', 'value']) table.align['name'] = 'r' table.align['value'] = 'l' - table.add_row(['id', order['orderId']]) + table.add_row(['id', order['quote']['id']]) + table.add_row(['name', order['quote']['name']]) table.add_row(['created', order['orderDate']]) - table.add_row(['status', order['placedOrder']['status']]) - env.fout(table) \ No newline at end of file + table.add_row(['expires', order['quote']['expirationDate']]) + table.add_row(['status', order['quote']['status']]) + env.fout(table) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 196616a8e..b1c9fb0e2 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -210,6 +210,7 @@ ('order:place', 'SoftLayer.CLI.order.place:cli'), ('order:preset-list', 'SoftLayer.CLI.order.preset_list:cli'), ('order:package-locations', 'SoftLayer.CLI.order.package_locations:cli'), + ('order:place-quote', 'SoftLayer.CLI.order.place_quote:cli'), ('rwhois', 'SoftLayer.CLI.rwhois'), ('rwhois:edit', 'SoftLayer.CLI.rwhois.edit:cli'), diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index fca243d21..3677ecedd 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -430,36 +430,39 @@ def place_order(self, package_keyname, location, item_keynames, complex_type=Non extras=extras, quantity=quantity) return self.order_svc.placeOrder(order) - def save_quote(self, package_keyname, location, item_keynames, complex_type=None, - preset_keyname=None, extras=None, quantity=1): - - """Save an order as Quote with the given package and prices. - - This function takes in parameters needed for an order and places the order. - - :param str package_keyname: The keyname for the package being ordered - :param str location: The datacenter location string for ordering (Ex: DALLAS13) - :param list item_keynames: The list of item keyname strings to order. To see list of - possible keynames for a package, use list_items() - (or `slcli order item-list`) - :param str complex_type: The complex type to send with the order. Typically begins - with `SoftLayer_Container_Product_Order_`. - :param string preset_keyname: If needed, specifies a preset to use for that package. - To see a list of possible keynames for a package, use - list_preset() (or `slcli order preset-list`) - :param dict extras: The extra data for the order in dictionary format. - Example: A VSI order requires hostname and domain to be set, so - extras will look like the following: - {'virtualGuests': [{'hostname': 'test', domain': 'softlayer.com'}]} - :param int quantity: The number of resources to order - - """ - order = self.generate_order(package_keyname, location, item_keynames, - complex_type=complex_type, hourly=False, - preset_keyname=preset_keyname, - extras=extras, quantity=quantity) - return self.order_svc.placeOrder(order, True) + 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. + + :param str package_keyname: The keyname for the package being ordered + :param str location: The datacenter location string for ordering (Ex: DALLAS13) + :param list item_keynames: The list of item keyname strings to order. To see list of + possible keynames for a package, use list_items() + (or `slcli order item-list`) + :param str complex_type: The complex type to send with the order. Typically begins + with `SoftLayer_Container_Product_Order_`. + :param string preset_keyname: If needed, specifies a preset to use for that package. + To see a list of possible keynames for a package, use + list_preset() (or `slcli order preset-list`) + :param dict extras: The extra data for the order in dictionary format. + Example: A VSI order requires hostname and domain to be set, so + extras will look like the following: + {'virtualGuests': [{'hostname': 'test', domain': 'softlayer.com'}]} + :param int quantity: The number of resources to order + :param string quote_name: A custom name to be assigned to the quote (optional). + :param bool send_email: This flag indicates that the quote should be sent to the email + address associated with the account or order. + """ + order = self.generate_order(package_keyname, location, item_keynames, complex_type=complex_type, + hourly=False, preset_keyname=preset_keyname, extras=extras, quantity=quantity) + + order['quoteName'] = quote_name + order['sendQuoteEmailFlag'] = send_email + return self.order_svc.placeQuote(order) def generate_order(self, package_keyname, location, item_keynames, complex_type=None, hourly=True, preset_keyname=None, extras=None, quantity=1): From 93c403691e951cb9a4b9e7c4ec52181797199a2f Mon Sep 17 00:00:00 2001 From: Albert Camacho Date: Tue, 21 Aug 2018 16:40:57 -0400 Subject: [PATCH 3/3] Adding unittests --- SoftLayer/CLI/order/place_quote.py | 2 +- tests/CLI/modules/order_tests.py | 29 +++++++++++++++++++++++++++++ tests/managers/ordering_tests.py | 27 +++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/order/place_quote.py b/SoftLayer/CLI/order/place_quote.py index c65e65552..3f5215c40 100644 --- a/SoftLayer/CLI/order/place_quote.py +++ b/SoftLayer/CLI/order/place_quote.py @@ -47,7 +47,7 @@ def cli(env, package_keyname, location, preset, name, send_email, complex_type, Example: # Place quote a VSI with 4 CPU, 16 GB RAM, 100 GB SAN disk, # Ubuntu 16.04, and 1 Gbps public & private uplink in dal13 - slcli order place-quote --name " My quote name" --send-email CLOUD_SERVER DALLAS13 \\ + slcli order place-quote --name "foobar" --send-email CLOUD_SERVER DALLAS13 \\ GUEST_CORES_4 \\ RAM_16_GB \\ REBOOT_REMOTE_CONSOLE \\ diff --git a/tests/CLI/modules/order_tests.py b/tests/CLI/modules/order_tests.py index b48cb8a53..ab3f3e2fd 100644 --- a/tests/CLI/modules/order_tests.py +++ b/tests/CLI/modules/order_tests.py @@ -114,6 +114,35 @@ def test_place(self): 'status': 'APPROVED'}, json.loads(result.output)) + def test_place_quote(self): + order_date = '2018-04-04 07:39:20' + expiration_date = '2018-05-04 07:39:20' + quote_name = 'foobar' + order = {'orderDate': order_date, + 'quote': { + 'id': 1234, + 'name': quote_name, + 'expirationDate': expiration_date, + 'status': 'PENDING' + }} + place_quote_mock = self.set_mock('SoftLayer_Product_Order', 'placeQuote') + items_mock = self.set_mock('SoftLayer_Product_Package', 'getItems') + + place_quote_mock.return_value = order + items_mock.return_value = self._get_order_items() + + result = self.run_command(['order', 'place-quote', '--name', 'foobar', 'package', 'DALLAS13', + 'ITEM1', '--complex-type', 'SoftLayer_Container_Product_Order_Thing']) + + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Product_Order', 'placeQuote') + self.assertEqual({'id': 1234, + 'name': quote_name, + 'created': order_date, + 'expires': expiration_date, + 'status': 'PENDING'}, + json.loads(result.output)) + def test_verify_hourly(self): order_date = '2017-04-04 07:39:20' order = {'orderId': 1234, 'orderDate': order_date, diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index 729659ba6..5149f6ed8 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -431,6 +431,33 @@ def test_place_order(self): extras=extras, quantity=quantity) self.assertEqual(ord_mock.return_value, order) + def test_place_quote(self): + ord_mock = self.set_mock('SoftLayer_Product_Order', 'placeQuote') + ord_mock.return_value = {'id': 1234} + pkg = 'PACKAGE_KEYNAME' + location = 'DALLAS13' + items = ['ITEM1', 'ITEM2'] + hourly = False + preset_keyname = 'PRESET' + complex_type = 'Complex_Type' + extras = {'foo': 'bar'} + quantity = 1 + name = 'wombat' + send_email = True + + with mock.patch.object(self.ordering, 'generate_order') as gen_mock: + gen_mock.return_value = {'order': {}} + + order = self.ordering.place_quote(pkg, location, items, preset_keyname=preset_keyname, + complex_type=complex_type, extras=extras, quantity=quantity, + quote_name=name, send_email=send_email) + + gen_mock.assert_called_once_with(pkg, location, items, hourly=hourly, + preset_keyname=preset_keyname, + complex_type=complex_type, + extras=extras, quantity=quantity) + self.assertEqual(ord_mock.return_value, order) + def test_locations(self): locations = self.ordering.package_locations('BARE_METAL_CPU') self.assertEqual('WASHINGTON07', locations[0]['keyname'])