Repository: softlayer/softlayer-python
Pull request #924 (2018-01-11 00:35:03)

Add categories to item-list and package location support
#918 


```bash
$ ./slcli order package-locations CLOUD_SERVER
265592   ams01  AMS01 - Amsterdam       AMSTERDAM
814994   ams03  AMS03 - Amsterdam       AMSTERDAM03
1004997  che01  CHE01 - Chennai         CHENNAI
```

```bash
./slcli order item-list 2U_DUAL_E52600_V3_12_DRIVES                                                                                                      av_spyware_protection    MCAFEE_VIRUSSCAN_ANTIVIRUS_WINDOWS                                     McAfee VirusScan Enterprise                                     
bandwidth                BANDWIDTH_0_GB                                                         0 GB Bandwidth                                                  
bandwidth                BANDWIDTH_500_GB                                                       500 GB Bandwidth                                                
bandwidth                BANDWIDTH_UNLIMITED_100_MBPS_UPLINK                                    Unlimited Bandwidth (100 Mbps Uplink)                           
bandwidth                BANDWIDTH_1000_GB                                                      1000 GB Bandwidth                                               
bandwidth                BANDWIDTH_5000_GB                                                      5000 GB Bandwidth                                               
bandwidth                BANDWIDTH_10000_GB                                                     10000 GB Bandwidth                                              
bandwidth                BANDWIDTH_20000_GB                                                     20000 GB Bandwidth                                              
bc_insurance             BUSINESS_CONTINUANCE_INSURANCE                                         Business Continuance Insurance           
```


Patch:
diff --git a/SoftLayer/CLI/order/item_list.py b/SoftLayer/CLI/order/item_list.py
index 61841d26..74f8fd4e 100644
--- a/SoftLayer/CLI/order/item_list.py
+++ b/SoftLayer/CLI/order/item_list.py
@@ -1,22 +1,19 @@
 """List package items."""
 # :license: MIT, see LICENSE for more details.
-
 import click
 
 from SoftLayer.CLI import environment
 from SoftLayer.CLI import formatting
 from SoftLayer.managers import ordering
+from SoftLayer.utils import lookup
 
-COLUMNS = ['keyName',
-           'description', ]
+COLUMNS = ['category', 'keyName', 'description']
 
 
 @click.command()
 @click.argument('package_keyname')
-@click.option('--keyword',
-              help="A word (or string) used to filter item names.")
-@click.option('--category',
-              help="Category code to filter items by")
+@click.option('--keyword', help="A word (or string) used to filter item names.")
+@click.option('--category', help="Category code to filter items by")
 @environment.pass_env
 def cli(env, package_keyname, keyword, category):
     """List package items used for ordering.
@@ -26,13 +23,20 @@ def cli(env, package_keyname, keyword, category):
 
     Package keynames can be retrieved using `slcli order package-list`
 
+    \b
+    Note:
+        Items with a numbered category, like disk0 or gpu0, can be included
+        multiple times in an order to match how many of the item you want to order.
+
     \b
     Example:
         # List all items in the VSI package
         slcli order item-list CLOUD_SERVER
 
     The --keyword option is used to filter items by name.
+
     The --category option is used to filter items by category.
+
     Both --keyword and --category can be used together.
 
     \b
@@ -51,10 +55,23 @@ def cli(env, package_keyname, keyword, category):
         _filter['items']['categories'] = {'categoryCode': {'operation': '_= %s' % category}}
 
     items = manager.list_items(package_keyname, filter=_filter)
+    sorted_items = sort_items(items)
 
-    for item in items:
-        table.add_row([
-            item['keyName'],
-            item['description'],
-        ])
+    categories = sorted_items.keys()
+    for catname in sorted(categories):
+        for item in sorted_items[catname]:
+            table.add_row([catname, item['keyName'], item['description']])
     env.fout(table)
+
+
+def sort_items(items):
+    """sorts the items into a dictionary of categories, with a list of items"""
+
+    sorted_items = {}
+    for item in items:
+        category = lookup(item, 'itemCategory', 'categoryCode')
+        if sorted_items.get(category) is None:
+            sorted_items[category] = []
+        sorted_items[category].append(item)
+
+    return sorted_items
diff --git a/SoftLayer/CLI/order/package_locations.py b/SoftLayer/CLI/order/package_locations.py
new file mode 100644
index 00000000..9f8ffb65
--- /dev/null
+++ b/SoftLayer/CLI/order/package_locations.py
@@ -0,0 +1,32 @@
+"""List packages."""
+# :license: MIT, see LICENSE for more details.
+import click
+
+from SoftLayer.CLI import environment
+from SoftLayer.CLI import formatting
+from SoftLayer.managers import ordering
+
+COLUMNS = ['id', 'dc', 'description', 'keyName']
+
+
+@click.command()
+@click.argument('package_keyname')
+@environment.pass_env
+def cli(env, package_keyname):
+    """List Datacenters a package can be ordered in.
+
+    Use the location Key Name to place orders
+    """
+    manager = ordering.OrderingManager(env.client)
+    table = formatting.Table(COLUMNS)
+
+    locations = manager.package_locations(package_keyname)
+    for region in locations:
+        for datacenter in region['locations']:
+            table.add_row([
+                datacenter['location']['id'],
+                datacenter['location']['name'],
+                region['description'],
+                region['keyname']
+            ])
+    env.fout(table)
diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py
index a5152f90..9a91c80e 100644
--- a/SoftLayer/CLI/routes.py
+++ b/SoftLayer/CLI/routes.py
@@ -209,6 +209,7 @@
     ('order:package-list', 'SoftLayer.CLI.order.package_list:cli'),
     ('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'),
 
     ('rwhois', 'SoftLayer.CLI.rwhois'),
     ('rwhois:edit', 'SoftLayer.CLI.rwhois.edit:cli'),
diff --git a/SoftLayer/fixtures/SoftLayer_Product_Package.py b/SoftLayer/fixtures/SoftLayer_Product_Package.py
index f46176ad..4b01ef42 100644
--- a/SoftLayer/fixtures/SoftLayer_Product_Package.py
+++ b/SoftLayer/fixtures/SoftLayer_Product_Package.py
@@ -1121,3 +1121,21 @@
     "isActive": 1,
     "description": "Dedicated Host"
 }]
+
+getRegions = [{
+    "description": "WDC07 - Washington, DC",
+    "keyname": "WASHINGTON07",
+    "locations": [{
+        "location": {
+            "euCompliantFlag": False,
+            "id": 2017603,
+            "longName": "Washington 7",
+            "name": "wdc07",
+            "statusId": 2},
+        "locationPackageDetails": [{
+            "isAvailable": 1,
+            "locationId": 2017603,
+            "packageId": 46
+        }]
+    }]
+}]
diff --git a/SoftLayer/managers/dedicated_host.py b/SoftLayer/managers/dedicated_host.py
index ba8143f4..6f6fe596 100644
--- a/SoftLayer/managers/dedicated_host.py
+++ b/SoftLayer/managers/dedicated_host.py
@@ -249,12 +249,8 @@ def _get_package(self):
         '''
 
         package_keyname = 'DEDICATED_HOST'
+        package = self.ordering_manager.get_package_by_key(package_keyname, mask=mask)
 
-        package = self.ordering_manager.get_package_by_key(package_keyname,
-                                                           mask=mask)
-
-        if package is None:
-            raise SoftLayer.SoftLayerError("Ordering package not found")
         return package
 
     def _get_location(self, regions, datacenter):
@@ -264,8 +260,7 @@ def _get_location(self, regions, datacenter):
             if region['location']['location']['name'] == datacenter:
                 return region
 
-        raise SoftLayer.SoftLayerError("Could not find valid location for: '%s'"
-                                       % datacenter)
+        raise SoftLayer.SoftLayerError("Could not find valid location for: '%s'" % datacenter)
 
     def get_create_options(self):
         """Returns valid options for ordering a dedicated host."""
@@ -287,10 +282,7 @@ def get_create_options(self):
                     'key': item['keyName'],
                 })
 
-        return {
-            'locations': locations,
-            'dedicated_host': dedicated_host,
-        }
+        return {'locations': locations, 'dedicated_host': dedicated_host}
 
     def _get_price(self, package):
         """Returns valid price for ordering a dedicated host."""
@@ -299,8 +291,7 @@ def _get_price(self, package):
             if not price.get('locationGroupId'):
                 return price['id']
 
-        raise SoftLayer.SoftLayerError(
-            "Could not find valid price")
+        raise SoftLayer.SoftLayerError("Could not find valid price")
 
     def _get_item(self, package, flavor):
         """Returns the item for ordering a dedicated host."""
@@ -309,8 +300,7 @@ def _get_item(self, package, flavor):
             if item['keyName'] == flavor:
                 return item
 
-        raise SoftLayer.SoftLayerError("Could not find valid item for: '%s'"
-                                       % flavor)
+        raise SoftLayer.SoftLayerError("Could not find valid item for: '%s'" % flavor)
 
     def _get_backend_router(self, locations, item):
         """Returns valid router options for ordering a dedicated host."""
diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py
index ae0a4a42..02f67f74 100644
--- a/SoftLayer/managers/hardware.py
+++ b/SoftLayer/managers/hardware.py
@@ -408,25 +408,21 @@ def get_create_options(self):
     def _get_package(self):
         """Get the package related to simple hardware ordering."""
         mask = '''
-items[
-    keyName,
-    capacity,
-    description,
-    attributes[id,attributeTypeKeyName],
-    itemCategory[id,categoryCode],
-    softwareDescription[id,referenceCode,longDescription],
-    prices
-],
-activePresets,
-regions[location[location[priceGroups]]]
-'''
+            items[
+                keyName,
+                capacity,
+                description,
+                attributes[id,attributeTypeKeyName],
+                itemCategory[id,categoryCode],
+                softwareDescription[id,referenceCode,longDescription],
+                prices
+            ],
+            activePresets,
+            regions[location[location[priceGroups]]]
+            '''
 
         package_keyname = 'BARE_METAL_SERVER'
-        package = self.ordering_manager.get_package_by_key(package_keyname,
-                                                           mask=mask)
-        if package is None:
-            raise SoftLayer.SoftLayerError("Ordering package not found")
-
+        package = self.ordering_manager.get_package_by_key(package_keyname, mask=mask)
         return package
 
     def _generate_create_dict(self,
diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py
index 17e2fc8d..51bf1810 100644
--- a/SoftLayer/managers/ordering.py
+++ b/SoftLayer/managers/ordering.py
@@ -14,7 +14,7 @@
                    itemCategory[id, name, categoryCode]
                 '''
 
-ITEM_MASK = '''id, keyName, description'''
+ITEM_MASK = '''id, keyName, description, itemCategory, categories'''
 
 PACKAGE_MASK = '''id, name, keyName, isActive'''
 
@@ -196,8 +196,7 @@ def verify_quote(self, quote_id, extra, quantity=1):
         :param int quantity: Quantity to override default
         """
 
-        container = self.generate_order_template(quote_id, extra,
-                                                 quantity=quantity)
+        container = self.generate_order_template(quote_id, extra, quantity=quantity)
         return self.order_svc.verifyOrder(container)
 
     def order_quote(self, quote_id, extra, quantity=1):
@@ -209,8 +208,7 @@ def order_quote(self, quote_id, extra, quantity=1):
         :param int quantity: Quantity to override default
         """
 
-        container = self.generate_order_template(quote_id, extra,
-                                                 quantity=quantity)
+        container = self.generate_order_template(quote_id, extra, quantity=quantity)
         return self.order_svc.placeOrder(container)
 
     def get_package_by_key(self, package_keyname, mask=None):
@@ -221,17 +219,13 @@ def get_package_by_key(self, package_keyname, mask=None):
         :param package_keyname: string representing the package key name we are interested in.
         :param string mask: Mask to specify the properties we want to retrieve
         """
-        _filter = {
-            'keyName': {
-                'operation': package_keyname,
-            },
-        }
+        _filter = {'keyName': {'operation': package_keyname}}
 
         packages = self.package_svc.getAllObjects(mask=mask, filter=_filter)
         if len(packages) == 0:
-            return None
-        else:
-            return packages.pop()
+            raise exceptions.SoftLayerError("Package {} does not exist".format(package_keyname))
+
+        return packages.pop()
 
     def list_categories(self, package_keyname, **kwargs):
         """List the categories for the given package.
@@ -246,9 +240,6 @@ def list_categories(self, package_keyname, **kwargs):
             get_kwargs['filter'] = kwargs['filter']
 
         package = self.get_package_by_key(package_keyname, mask='id')
-        if not package:
-            raise exceptions.SoftLayerError("Package {} does not exist".format(package_keyname))
-
         categories = self.package_svc.getConfiguration(id=package['id'], **get_kwargs)
         return categories
 
@@ -266,9 +257,6 @@ def list_items(self, package_keyname, **kwargs):
             get_kwargs['filter'] = kwargs['filter']
 
         package = self.get_package_by_key(package_keyname, mask='id')
-        if not package:
-            raise exceptions.SoftLayerError("Package {} does not exist".format(package_keyname))
-
         items = self.package_svc.getItems(id=package['id'], **get_kwargs)
         return items
 
@@ -302,11 +290,7 @@ def list_presets(self, package_keyname, **kwargs):
             get_kwargs['filter'] = kwargs['filter']
 
         package = self.get_package_by_key(package_keyname, mask='id')
-        if not package:
-            raise exceptions.SoftLayerError("Package {} does not exist".format(package_keyname))
-
-        acc_presets = self.package_svc.getAccountRestrictedActivePresets(
-            id=package['id'], **get_kwargs)
+        acc_presets = self.package_svc.getAccountRestrictedActivePresets(id=package['id'], **get_kwargs)
         active_presets = self.package_svc.getActivePresets(id=package['id'], **get_kwargs)
         return active_presets + acc_presets
 
@@ -452,8 +436,6 @@ def generate_order(self, package_keyname, location, item_keynames, complex_type=
         extras = extras or {}
 
         package = self.get_package_by_key(package_keyname, mask='id')
-        if not package:
-            raise exceptions.SoftLayerError("Package {} does not exist".format(package_keyname))
 
         # if there was extra data given for the order, add it to the order
         # example: VSIs require hostname and domain set on the order, so
@@ -476,3 +458,16 @@ def generate_order(self, package_keyname, location, item_keynames, complex_type=
         price_ids = self.get_price_id_list(package_keyname, item_keynames)
         order['prices'] = [{'id': price_id} for price_id in price_ids]
         return order
+
+    def package_locations(self, package_keyname):
+        """List datacenter locations for a package keyname
+
+        :param str package_keyname: The package for which to get the items.
+        :returns: List of locations a package is orderable in
+        """
+        mask = "mask[description, keyname, locations]"
+
+        package = self.get_package_by_key(package_keyname, mask='id')
+
+        regions = self.package_svc.getRegions(id=package['id'], mask=mask)
+        return regions
diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py
index fa20ef86..b98578ab 100644
--- a/SoftLayer/managers/vs.py
+++ b/SoftLayer/managers/vs.py
@@ -852,9 +852,6 @@ def _get_package_items(self):
         package_keyname = "CLOUD_SERVER"
         package = self.ordering_manager.get_package_by_key(package_keyname)
 
-        if package is None:
-            raise ValueError("No package found for key: " + package_keyname)
-
         package_service = self.client['Product_Package']
         return package_service.getItems(id=package['id'], mask=mask)
 


Test patch:
diff --git a/tests/CLI/modules/order_tests.py b/tests/CLI/modules/order_tests.py
index 2d704c82..0f35af22 100644
--- a/tests/CLI/modules/order_tests.py
+++ b/tests/CLI/modules/order_tests.py
@@ -10,10 +10,8 @@
 
 class OrderTests(testing.TestCase):
     def test_category_list(self):
-        cat1 = {'itemCategory': {'name': 'cat1', 'categoryCode': 'code1'},
-                'isRequired': 1}
-        cat2 = {'itemCategory': {'name': 'cat2', 'categoryCode': 'code2'},
-                'isRequired': 0}
+        cat1 = {'itemCategory': {'name': 'cat1', 'categoryCode': 'code1'}, 'isRequired': 1}
+        cat2 = {'itemCategory': {'name': 'cat2', 'categoryCode': 'code2'}, 'isRequired': 0}
         p_mock = self.set_mock('SoftLayer_Product_Package', 'getConfiguration')
         p_mock.return_value = [cat1, cat2]
 
@@ -30,8 +28,9 @@ def test_category_list(self):
                          json.loads(result.output))
 
     def test_item_list(self):
-        item1 = {'keyName': 'item1', 'description': 'description1'}
-        item2 = {'keyName': 'item2', 'description': 'description2'}
+        category = {'categoryCode': 'testing'}
+        item1 = {'keyName': 'item1', 'description': 'description1', 'itemCategory': category}
+        item2 = {'keyName': 'item2', 'description': 'description2', 'itemCategory': category}
         p_mock = self.set_mock('SoftLayer_Product_Package', 'getItems')
         p_mock.return_value = [item1, item2]
 
@@ -39,17 +38,17 @@ def test_item_list(self):
 
         self.assert_no_fail(result)
         self.assert_called_with('SoftLayer_Product_Package', 'getItems')
-        self.assertEqual([{'keyName': 'item1',
-                           'description': 'description1'},
-                          {'keyName': 'item2',
-                           'description': 'description2'}],
-                         json.loads(result.output))
+        expected_results = [{'category': 'testing',
+                             'keyName': 'item1',
+                             'description': 'description1'},
+                            {'category': 'testing',
+                             'keyName': 'item2',
+                             'description': 'description2'}]
+        self.assertEqual(expected_results, json.loads(result.output))
 
     def test_package_list(self):
-        item1 = {'name': 'package1', 'keyName': 'PACKAGE1',
-                 'isActive': 1}
-        item2 = {'name': 'package2', 'keyName': 'PACKAGE2',
-                 'isActive': 1}
+        item1 = {'name': 'package1', 'keyName': 'PACKAGE1', 'isActive': 1}
+        item2 = {'name': 'package2', 'keyName': 'PACKAGE2', 'isActive': 1}
         p_mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects')
         p_mock.return_value = [item1, item2]
 
@@ -57,16 +56,13 @@ def test_package_list(self):
 
         self.assert_no_fail(result)
         self.assert_called_with('SoftLayer_Product_Package', 'getAllObjects')
-        self.assertEqual([{'name': 'package1',
-                           'keyName': 'PACKAGE1'},
-                          {'name': 'package2',
-                           'keyName': 'PACKAGE2'}],
-                         json.loads(result.output))
+        expected_results = [{'name': 'package1', 'keyName': 'PACKAGE1'},
+                            {'name': 'package2', 'keyName': 'PACKAGE2'}]
+        self.assertEqual(expected_results, json.loads(result.output))
 
     def test_place(self):
         order_date = '2017-04-04 07:39:20'
-        order = {'orderId': 1234, 'orderDate': order_date,
-                 'placedOrder': {'status': 'APPROVED'}}
+        order = {'orderId': 1234, 'orderDate': order_date, 'placedOrder': {'status': 'APPROVED'}}
         verify_mock = self.set_mock('SoftLayer_Product_Order', 'verifyOrder')
         place_mock = self.set_mock('SoftLayer_Product_Order', 'placeOrder')
         items_mock = self.set_mock('SoftLayer_Product_Package', 'getItems')
@@ -169,6 +165,16 @@ def test_preset_list(self):
                            'description': 'description3'}],
                          json.loads(result.output))
 
+    def test_location_list(self):
+        result = self.run_command(['order', 'package-locations', 'package'])
+        self.assert_no_fail(result)
+        expected_results = [
+            {'id': 2017603, 'dc': 'wdc07', 'description': 'WDC07 - Washington, DC', 'keyName': 'WASHINGTON07'}
+        ]
+        print("FUCK")
+        print(result.output)
+        self.assertEqual(expected_results, json.loads(result.output))
+
     def _get_order_items(self):
         item1 = {'keyName': 'ITEM1', 'description': 'description1',
                  'prices': [{'id': 1111, 'locationGroupId': ''}]}
diff --git a/tests/managers/dedicated_host_tests.py b/tests/managers/dedicated_host_tests.py
index a20fc634..7cc884df 100644
--- a/tests/managers/dedicated_host_tests.py
+++ b/tests/managers/dedicated_host_tests.py
@@ -304,30 +304,10 @@ def test_get_package(self):
             assert_called_once_with(package_keyname, mask=mask)
 
     def test_get_package_no_package_found(self):
-        mask = '''
-        items[
-            id,
-            description,
-            prices,
-            capacity,
-            keyName,
-            itemCategory[categoryCode],
-            bundleItems[capacity, categories[categoryCode]]
-        ],
-        regions[location[location[priceGroups]]]
-        '''
-        self.dedicated_host.ordering_manager = mock.Mock()
-
-        self.dedicated_host.ordering_manager.get_package_by_key.return_value = \
-            None
+        packages = self.set_mock('SoftLayer_Product_Package', 'getAllObjects')
+        packages.return_value = []
 
-        package_keyname = 'DEDICATED_HOST'
-
-        self.assertRaises(exceptions.SoftLayerError,
-                          self.dedicated_host._get_package)
-
-        self.dedicated_host.ordering_manager.get_package_by_key. \
-            assert_called_once_with(package_keyname, mask=mask)
+        self.assertRaises(exceptions.SoftLayerError, self.dedicated_host._get_package)
 
     def test_get_location(self):
         regions = [{
@@ -398,8 +378,7 @@ def test_get_price_no_price_found(self):
         package['items'][0]['prices'][0]['locationGroupId'] = 33
         item = package['items'][0]
 
-        self.assertRaises(exceptions.SoftLayerError,
-                          self.dedicated_host._get_price, item)
+        self.assertRaises(exceptions.SoftLayerError, self.dedicated_host._get_price, item)
 
     def test_get_item(self):
         """Returns the item for ordering a dedicated host."""
@@ -442,8 +421,7 @@ def test_get_item_no_item_found(self):
         flavor = '56_CORES_X_242_RAM_X_1_4_TB'
         package['items'][0]['keyName'] = 'not found'
 
-        self.assertRaises(exceptions.SoftLayerError,
-                          self.dedicated_host._get_item, package, flavor)
+        self.assertRaises(exceptions.SoftLayerError, self.dedicated_host._get_item, package, flavor)
 
     def test_get_backend_router(self):
         location = [
@@ -480,8 +458,7 @@ def test_get_backend_router(self):
         routers_test = self.dedicated_host._get_backend_router(location, item)
 
         self.assertEqual(routers, routers_test)
-        self.dedicated_host.host.getAvailableRouters. \
-            assert_called_once_with(host, mask=mask)
+        self.dedicated_host.host.getAvailableRouters.assert_called_once_with(host, mask=mask)
 
     def test_get_backend_router_no_routers_found(self):
         location = []
diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py
index fc4b541c..d9d432be 100644
--- a/tests/managers/hardware_tests.py
+++ b/tests/managers/hardware_tests.py
@@ -138,9 +138,8 @@ def test_get_create_options_package_missing(self):
         packages = self.set_mock('SoftLayer_Product_Package', 'getAllObjects')
         packages.return_value = []
 
-        ex = self.assertRaises(SoftLayer.SoftLayerError,
-                               self.hardware.get_create_options)
-        self.assertEqual("Ordering package not found", str(ex))
+        ex = self.assertRaises(SoftLayer.SoftLayerError, self.hardware.get_create_options)
+        self.assertEqual("Package BARE_METAL_SERVER does not exist", str(ex))
 
     def test_generate_create_dict_no_items(self):
         packages = self.set_mock('SoftLayer_Product_Package', 'getAllObjects')
diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py
index 80c9f24d..50e3e220 100644
--- a/tests/managers/ordering_tests.py
+++ b/tests/managers/ordering_tests.py
@@ -136,10 +136,7 @@ def test_get_package_by_key_returns_if_found(self):
     def test_get_package_by_key_returns_none_if_not_found(self):
         p_mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects')
         p_mock.return_value = []
-
-        package = self.ordering.get_package_by_key("WILLY_NILLY_SERVERS")
-
-        self.assertIsNone(package)
+        self.assertRaises(exceptions.SoftLayerError, self.ordering.get_package_by_key, 'WILLY_NILLY_SERVERS')
 
     def test_list_categories(self):
         p_mock = self.set_mock('SoftLayer_Product_Package', 'getConfiguration')
@@ -153,10 +150,6 @@ def test_list_categories(self):
         mock_get_pkg.assert_called_once_with('PACKAGE_KEYNAME', mask='id')
         self.assertEqual(p_mock.return_value, cats)
 
-    def test_list_categories_package_not_found(self):
-        self._assert_package_error(self.ordering.list_categories,
-                                   'PACKAGE_KEYNAME')
-
     def test_list_items(self):
         p_mock = self.set_mock('SoftLayer_Product_Package', 'getItems')
         p_mock.return_value = ['item1', 'item2']
@@ -169,10 +162,6 @@ def test_list_items(self):
         mock_get_pkg.assert_called_once_with('PACKAGE_KEYNAME', mask='id')
         self.assertEqual(p_mock.return_value, items)
 
-    def test_list_items_package_not_found(self):
-        self._assert_package_error(self.ordering.list_items,
-                                   'PACKAGE_KEYNAME')
-
     def test_list_packages(self):
         packages = [{'id': 1234, 'isActive': True},
                     {'id': 1235, 'isActive': True}]
@@ -199,10 +188,8 @@ def test_list_presets(self):
         acct_presets = ['acctPreset1', 'acctPreset2']
         active_presets = ['activePreset3', 'activePreset4']
 
-        acct_preset_mock = self.set_mock('SoftLayer_Product_Package',
-                                         'getAccountRestrictedActivePresets')
-        active_preset_mock = self.set_mock('SoftLayer_Product_Package',
-                                           'getActivePresets')
+        acct_preset_mock = self.set_mock('SoftLayer_Product_Package', 'getAccountRestrictedActivePresets')
+        active_preset_mock = self.set_mock('SoftLayer_Product_Package', 'getActivePresets')
         acct_preset_mock.return_value = acct_presets
         active_preset_mock.return_value = active_presets
 
@@ -212,10 +199,6 @@ def test_list_presets(self):
         # account restricted presets
         self.assertEqual(active_presets + acct_presets, presets)
 
-    def test_list_presets_package_not_found(self):
-        self._assert_package_error(self.ordering.list_presets,
-                                   'PACKAGE_KEYNAME')
-
     def test_get_preset_by_key(self):
         keyname = 'PRESET_KEYNAME'
         preset_filter = {'activePresets': {'keyName': {'operation': '_= %s' % keyname}}}
@@ -225,8 +208,7 @@ def test_get_preset_by_key(self):
 
             preset = self.ordering.get_preset_by_key('PACKAGE_KEYNAME', keyname)
 
-        list_mock.assert_called_once_with('PACKAGE_KEYNAME', filter=preset_filter,
-                                          mask=None)
+        list_mock.assert_called_once_with('PACKAGE_KEYNAME', filter=preset_filter, mask=None)
         self.assertEqual(list_mock.return_value[0], preset)
 
     def test_get_preset_by_key_preset_not_found(self):
@@ -237,39 +219,28 @@ def test_get_preset_by_key_preset_not_found(self):
             list_mock.return_value = []
 
             exc = self.assertRaises(exceptions.SoftLayerError,
-                                    self.ordering.get_preset_by_key,
-                                    'PACKAGE_KEYNAME', keyname)
+                                    self.ordering.get_preset_by_key, 'PACKAGE_KEYNAME', keyname)
 
-        list_mock.assert_called_once_with('PACKAGE_KEYNAME', filter=preset_filter,
-                                          mask=None)
-        self.assertEqual('Preset {} does not exist in package {}'.format(keyname,
-                                                                         'PACKAGE_KEYNAME'),
-                         str(exc))
+        list_mock.assert_called_once_with('PACKAGE_KEYNAME', filter=preset_filter, mask=None)
+        self.assertEqual('Preset {} does not exist in package {}'.format(keyname, 'PACKAGE_KEYNAME'), str(exc))
 
     def test_get_price_id_list(self):
         price1 = {'id': 1234, 'locationGroupId': ''}
-        item1 = {'id': 1111,
-                 'keyName': 'ITEM1',
-                 'prices': [price1]}
+        item1 = {'id': 1111, 'keyName': 'ITEM1', 'prices': [price1]}
         price2 = {'id': 5678, 'locationGroupId': ''}
-        item2 = {'id': 2222,
-                 'keyName': 'ITEM2',
-                 'prices': [price2]}
+        item2 = {'id': 2222, 'keyName': 'ITEM2', 'prices': [price2]}
 
         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'])
 
         list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, keyName, prices')
         self.assertEqual([price1['id'], price2['id']], prices)
 
     def test_get_price_id_list_item_not_found(self):
         price1 = {'id': 1234, 'locationGroupId': ''}
-        item1 = {'id': 1111,
-                 'keyName': 'ITEM1',
-                 'prices': [price1]}
+        item1 = {'id': 1111, 'keyName': 'ITEM1', 'prices': [price1]}
 
         with mock.patch.object(self.ordering, 'list_items') as list_mock:
             list_mock.return_value = [item1]
@@ -278,23 +249,14 @@ def test_get_price_id_list_item_not_found(self):
                                     self.ordering.get_price_id_list,
                                     'PACKAGE_KEYNAME', ['ITEM2'])
         list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, keyName, prices')
-        self.assertEqual("Item ITEM2 does not exist for package PACKAGE_KEYNAME",
-                         str(exc))
-
-    def test_generate_order_package_not_found(self):
-        self._assert_package_error(self.ordering.generate_order,
-                                   'PACKAGE_KEYNAME', 'DALLAS13',
-                                   ['item1', 'item2'])
+        self.assertEqual("Item ITEM2 does not exist for package PACKAGE_KEYNAME", str(exc))
 
     def test_generate_no_complex_type(self):
         pkg = 'PACKAGE_KEYNAME'
         items = ['ITEM1', 'ITEM2']
-        exc = self.assertRaises(exceptions.SoftLayerError,
-                                self.ordering.generate_order,
-                                pkg, 'DALLAS13', items)
+        exc = self.assertRaises(exceptions.SoftLayerError, self.ordering.generate_order, pkg, 'DALLAS13', items)
 
-        self.assertEqual("A complex type must be specified with the order",
-                         str(exc))
+        self.assertEqual("A complex type must be specified with the order", str(exc))
 
     def test_generate_order_with_preset(self):
         pkg = 'PACKAGE_KEYNAME'
@@ -311,9 +273,7 @@ def test_generate_order_with_preset(self):
 
         mock_pkg, mock_preset, mock_get_ids = self._patch_for_generate()
 
-        order = self.ordering.generate_order(pkg, 'DALLAS13', items,
-                                             preset_keyname=preset,
-                                             complex_type=complex_type)
+        order = self.ordering.generate_order(pkg, 'DALLAS13', items, preset_keyname=preset, complex_type=complex_type)
 
         mock_pkg.assert_called_once_with(pkg, mask='id')
         mock_preset.assert_called_once_with(pkg, preset)
@@ -333,8 +293,7 @@ def test_generate_order(self):
 
         mock_pkg, mock_preset, mock_get_ids = self._patch_for_generate()
 
-        order = self.ordering.generate_order(pkg, 'DALLAS13', items,
-                                             complex_type=complex_type)
+        order = self.ordering.generate_order(pkg, 'DALLAS13', items, complex_type=complex_type)
 
         mock_pkg.assert_called_once_with(pkg, mask='id')
         mock_preset.assert_not_called()
@@ -393,6 +352,10 @@ def test_place_order(self):
                                          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'])
+
     def _patch_for_generate(self):
         # mock out get_package_by_key, get_preset_by_key, and get_price_id_list
         # with patchers
@@ -411,12 +374,3 @@ def _patch_for_generate(self):
         to_return[1].return_value = {'id': 5678}
         to_return[2].return_value = [1111, 2222]
         return to_return
-
-    def _assert_package_error(self, order_callable, pkg_key, *args, **kwargs):
-        with mock.patch.object(self.ordering, 'get_package_by_key') as mock_get_pkg:
-            mock_get_pkg.return_value = None
-
-            exc = self.assertRaises(exceptions.SoftLayerError, order_callable,
-                                    pkg_key, *args, **kwargs)
-            self.assertEqual('Package {} does not exist'.format(pkg_key),
-                             str(exc))
diff --git a/tests/managers/vs_tests.py b/tests/managers/vs_tests.py
index d45e631a..e36de7ac 100644
--- a/tests/managers/vs_tests.py
+++ b/tests/managers/vs_tests.py
@@ -700,12 +700,6 @@ def test_get_package_items(self):
         self.vs._get_package_items()
         self.assert_called_with('SoftLayer_Product_Package', 'getItems')
 
-    def test_get_package_items_errors(self):
-        mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects')
-        mock.return_value = []
-
-        self.assertRaises(ValueError, self.vs._get_package_items)
-
     def test_get_price_id_for_upgrade(self):
         package_items = self.vs._get_package_items()
 
@@ -767,15 +761,16 @@ def test_active_and_provisiondate(self):
         value = self.vs.wait_for_ready(1, 1)
         self.assertTrue(value)
 
-    def test_active_provision_pending(self):
+    @mock.patch('time.sleep')
+    @mock.patch('time.time')
+    def test_active_provision_pending(self, _now, _sleep):
+        _now.side_effect = [0, 0, 1, 1, 2, 2]
         # active transaction and provision date
         # and pending should be false
-        self.guestObject.side_effect = [
-            {'activeTransaction': {'id': 1}},
-            {'activeTransaction': {'id': 1},
-             'provisionDate': 'aaa'},
-        ]
-        value = self.vs.wait_for_ready(1, 0, pending=True)
+        self.guestObject.return_value = {'activeTransaction': {'id': 2}, 'provisionDate': 'aaa'}
+
+        value = self.vs.wait_for_ready(instance_id=1, limit=1, delay=1, pending=True)
+        _sleep.assert_has_calls([mock.call(0)])
         self.assertFalse(value)
 
     def test_active_reload(self):
@@ -802,17 +797,16 @@ def test_reload_no_pending(self):
         value = self.vs.wait_for_ready(1, 1)
         self.assertTrue(value)
 
-    def test_reload_pending(self):
+    @mock.patch('time.sleep')
+    @mock.patch('time.time')
+    def test_reload_pending(self, _now, _sleep):
+        _now.side_effect = [0, 0, 1, 1, 2, 2]
         # reload complete, pending maintance transactions
-        self.guestObject.side_effect = [
-            {'activeTransaction': {'id': 1}},
-            {
-                'activeTransaction': {'id': 2},
-                'provisionDate': 'aaa',
-                'lastOperatingSystemReload': {'id': 1},
-            },
-        ]
-        value = self.vs.wait_for_ready(1, 0, pending=True)
+        self.guestObject.return_value = {'activeTransaction': {'id': 2},
+                                         'provisionDate': 'aaa',
+                                         'lastOperatingSystemReload': {'id': 1}}
+        value = self.vs.wait_for_ready(instance_id=1, limit=1, delay=1, pending=True)
+        _sleep.assert_has_calls([mock.call(0)])
         self.assertFalse(value)
 
     @mock.patch('time.sleep')