Repository: softlayer/softlayer-python
Pull request #917 (2018-01-05 20:19:48)

Issues588 - hardware wait for ready
adds wait for ready to HW
adds slcli hw ready command
refactors VS wait_for_Ready to have less code repitition


Patch:
diff --git a/SoftLayer/CLI/__init__.py b/SoftLayer/CLI/__init__.py
index 3d5d6bf7..31d8f4b8 100644
--- a/SoftLayer/CLI/__init__.py
+++ b/SoftLayer/CLI/__init__.py
@@ -6,5 +6,10 @@
     :license: MIT, see LICENSE for more details.
 """
 # pylint: disable=w0401, invalid-name
+import logging
 
 from SoftLayer.CLI.helpers import *  # NOQA
+
+logger = logging.getLogger()
+logger.addHandler(logging.StreamHandler())
+logger.setLevel(logging.INFO)
diff --git a/SoftLayer/CLI/hardware/ready.py b/SoftLayer/CLI/hardware/ready.py
new file mode 100644
index 00000000..ec2cf994
--- /dev/null
+++ b/SoftLayer/CLI/hardware/ready.py
@@ -0,0 +1,25 @@
+"""Check if a virtual server is ready."""
+# :license: MIT, see LICENSE for more details.
+
+import click
+
+import SoftLayer
+from SoftLayer.CLI import environment
+from SoftLayer.CLI import exceptions
+from SoftLayer.CLI import helpers
+
+
+@click.command()
+@click.argument('identifier')
+@click.option('--wait', default=0, show_default=True, type=click.INT, help="Seconds to wait")
+@environment.pass_env
+def cli(env, identifier, wait):
+    """Check if a virtual server is ready."""
+
+    compute = SoftLayer.HardwareManager(env.client)
+    compute_id = helpers.resolve_id(compute.resolve_ids, identifier, 'hardware')
+    ready = compute.wait_for_ready(compute_id, wait)
+    if ready:
+        env.fout("READY")
+    else:
+        raise exceptions.CLIAbort("Instance %s not ready" % compute_id)
diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py
index 3fa11582..f191e73b 100644
--- a/SoftLayer/CLI/routes.py
+++ b/SoftLayer/CLI/routes.py
@@ -223,6 +223,7 @@
     ('hardware:credentials', 'SoftLayer.CLI.hardware.credentials:cli'),
     ('hardware:update-firmware', 'SoftLayer.CLI.hardware.update_firmware:cli'),
     ('hardware:rescue', 'SoftLayer.CLI.hardware.power:rescue'),
+    ('hardware:ready', 'SoftLayer.CLI.hardware.ready:cli'),
 
     ('securitygroup', 'SoftLayer.CLI.securitygroup'),
     ('securitygroup:list', 'SoftLayer.CLI.securitygroup.list:cli'),
diff --git a/SoftLayer/CLI/virt/ready.py b/SoftLayer/CLI/virt/ready.py
index 1be2e1b4..c4168043 100644
--- a/SoftLayer/CLI/virt/ready.py
+++ b/SoftLayer/CLI/virt/ready.py
@@ -11,11 +11,7 @@
 
 @click.command()
 @click.argument('identifier')
-@click.option('--wait',
-              default=0,
-              show_default=True,
-              type=click.INT,
-              help="Name of the image")
+@click.option('--wait', default=0, show_default=True, type=click.INT, help="Seconds to wait")
 @environment.pass_env
 def cli(env, identifier, wait):
     """Check if a virtual server is ready."""
diff --git a/SoftLayer/decoration.py b/SoftLayer/decoration.py
index 8fb75989..66ce62cc 100644
--- a/SoftLayer/decoration.py
+++ b/SoftLayer/decoration.py
@@ -9,8 +9,17 @@
 from random import randint
 from time import sleep
 
+from SoftLayer import exceptions
 
-def retry(ex, tries=4, delay=5, backoff=2, logger=None):
+RETRIABLE = (
+    exceptions.ServerError,
+    exceptions.ApplicationError,
+    exceptions.RemoteSystemError,
+    exceptions.TransportError
+)
+
+
+def retry(ex=RETRIABLE, tries=4, delay=5, backoff=2, logger=None):
     """Retry calling the decorated function using an exponential backoff.
 
     http://www.saltycrane.com/blog/2009/11/trying-out-retry-decorator-python/
diff --git a/SoftLayer/exceptions.py b/SoftLayer/exceptions.py
index 13da8474..450dc23e 100644
--- a/SoftLayer/exceptions.py
+++ b/SoftLayer/exceptions.py
@@ -27,14 +27,10 @@ def __init__(self, fault_code, fault_string, *args):
         self.reason = self.faultString = fault_string
 
     def __repr__(self):
-        return '<%s(%s): %s>' % (self.__class__.__name__,
-                                 self.faultCode,
-                                 self.faultString)
+        return '<%s(%s): %s>' % (self.__class__.__name__, self.faultCode, self.faultString)
 
     def __str__(self):
-        return '%s(%s): %s' % (self.__class__.__name__,
-                               self.faultCode,
-                               self.faultString)
+        return '%s(%s): %s' % (self.__class__.__name__, self.faultCode, self.faultString)
 
 
 class ParseError(SoftLayerAPIError):
@@ -78,12 +74,12 @@ class SpecViolation(ServerError):
     pass
 
 
-class MethodNotFound(ServerError):
+class MethodNotFound(SoftLayerAPIError):
     """Method name not found."""
     pass
 
 
-class InvalidMethodParameters(ServerError):
+class InvalidMethodParameters(SoftLayerAPIError):
     """Invalid method paramters."""
     pass
 
diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py
index 97ef6cd6..ae0a4a42 100644
--- a/SoftLayer/managers/hardware.py
+++ b/SoftLayer/managers/hardware.py
@@ -7,10 +7,10 @@
 """
 import logging
 import socket
+import time
 
 import SoftLayer
 from SoftLayer.decoration import retry
-from SoftLayer import exceptions
 from SoftLayer.managers import ordering
 from SoftLayer import utils
 
@@ -88,7 +88,7 @@ def cancel_hardware(self, hardware_id, reason='unneeded', comment='',
                                 immediate, False, cancel_reason, comment,
                                 id=billing_id)
 
-    @retry(exceptions.SoftLayerAPIError, logger=LOGGER)
+    @retry(logger=LOGGER)
     def list_hardware(self, tags=None, cpus=None, memory=None, hostname=None,
                       domain=None, datacenter=None, nic_speed=None,
                       public_ip=None, private_ip=None, **kwargs):
@@ -176,7 +176,7 @@ def list_hardware(self, tags=None, cpus=None, memory=None, hostname=None,
         kwargs['filter'] = _filter.to_dict()
         return self.account.getHardware(**kwargs)
 
-    @retry(exceptions.SoftLayerAPIError, logger=LOGGER)
+    @retry(logger=LOGGER)
     def get_hardware(self, hardware_id, **kwargs):
         """Get details about a hardware device.
 
@@ -343,7 +343,7 @@ def get_cancellation_reasons(self):
             'moving': 'Moving to competitor',
         }
 
-    @retry(exceptions.SoftLayerAPIError, logger=LOGGER)
+    @retry(logger=LOGGER)
     def get_create_options(self):
         """Returns valid options for ordering hardware."""
 
@@ -404,7 +404,7 @@ def get_create_options(self):
             'extras': extras,
         }
 
-    @retry(exceptions.SoftLayerAPIError, logger=LOGGER)
+    @retry(logger=LOGGER)
     def _get_package(self):
         """Get the package related to simple hardware ordering."""
         mask = '''
@@ -584,8 +584,33 @@ def update_firmware(self,
         """
 
         return self.hardware.createFirmwareUpdateTransaction(
-            bool(ipmi), bool(raid_controller), bool(bios), bool(hard_drive),
-            id=hardware_id)
+            bool(ipmi), bool(raid_controller), bool(bios), bool(hard_drive), id=hardware_id)
+
+    def wait_for_ready(self, instance_id, limit=14400, delay=10, pending=False):
+        """Determine if a Server is ready.
+
+        A server is ready when no transactions are running on it.
+
+        :param int instance_id: The instance ID with the pending transaction
+        :param int limit: The maximum amount of seconds to wait.
+        :param int delay: The number of seconds to sleep before checks. Defaults to 10.
+        """
+        now = time.time()
+        until = now + limit
+        mask = "mask[id, lastOperatingSystemReload[id], activeTransaction, provisionDate]"
+        instance = self.get_hardware(instance_id, mask=mask)
+        while now <= until:
+            if utils.is_ready(instance, pending):
+                return True
+            transaction = utils.lookup(instance, 'activeTransaction', 'transactionStatus', 'friendlyName')
+            snooze = min(delay, until - now)
+            LOGGER.info("%s - %d not ready. Auto retry in %ds", transaction, instance_id, snooze)
+            time.sleep(snooze)
+            instance = self.get_hardware(instance_id, mask=mask)
+            now = time.time()
+
+        LOGGER.info("Waiting for %d expired.", instance_id)
+        return False
 
 
 def _get_extra_price_id(items, key_name, hourly, location):
diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py
index c0456a87..fa20ef86 100644
--- a/SoftLayer/managers/vs.py
+++ b/SoftLayer/managers/vs.py
@@ -6,9 +6,7 @@
     :license: MIT, see LICENSE for more details.
 """
 import datetime
-import itertools
 import logging
-import random
 import socket
 import time
 import warnings
@@ -58,7 +56,7 @@ def __init__(self, client, ordering_manager=None):
         else:
             self.ordering_manager = ordering_manager
 
-    @retry(exceptions.SoftLayerAPIError, logger=LOGGER)
+    @retry(logger=LOGGER)
     def list_instances(self, hourly=True, monthly=True, tags=None, cpus=None,
                        memory=None, hostname=None, domain=None,
                        local_disk=None, datacenter=None, nic_speed=None,
@@ -162,7 +160,7 @@ def list_instances(self, hourly=True, monthly=True, tags=None, cpus=None,
         func = getattr(self.account, call)
         return func(**kwargs)
 
-    @retry(exceptions.SoftLayerAPIError, logger=LOGGER)
+    @retry(logger=LOGGER)
     def get_instance(self, instance_id, **kwargs):
         """Get details about a virtual server instance.
 
@@ -237,7 +235,7 @@ def get_instance(self, instance_id, **kwargs):
 
         return self.guest.getObject(id=instance_id, **kwargs)
 
-    @retry(exceptions.SoftLayerAPIError, logger=LOGGER)
+    @retry(logger=LOGGER)
     def get_create_options(self):
         """Retrieves the available options for creating a VS.
 
@@ -414,7 +412,7 @@ def _generate_create_dict(
 
         return data
 
-    @retry(exceptions.SoftLayerAPIError, logger=LOGGER)
+    @retry(logger=LOGGER)
     def wait_for_transaction(self, instance_id, limit, delay=10):
         """Waits on a VS transaction for the specified amount of time.
 
@@ -428,7 +426,7 @@ def wait_for_transaction(self, instance_id, limit, delay=10):
 
         return self.wait_for_ready(instance_id, limit, delay=delay, pending=True)
 
-    def wait_for_ready(self, instance_id, limit, delay=10, pending=False):
+    def wait_for_ready(self, instance_id, limit=3600, delay=10, pending=False):
         """Determine if a VS is ready and available.
 
         In some cases though, that can mean that no transactions are running.
@@ -439,7 +437,7 @@ def wait_for_ready(self, instance_id, limit, delay=10, pending=False):
         cancellations.
 
         :param int instance_id: The instance ID with the pending transaction
-        :param int limit: The maximum amount of time to wait.
+        :param int limit: The maximum amount of seconds to wait.
         :param int delay: The number of seconds to sleep before checks. Defaults to 10.
         :param bool pending: Wait for pending transactions not related to
                              provisioning or reloads such as monitoring.
@@ -449,43 +447,21 @@ def wait_for_ready(self, instance_id, limit, delay=10, pending=False):
             # Will return once vsi 12345 is ready, or after 10 checks
             ready = mgr.wait_for_ready(12345, 10)
         """
-        until = time.time() + limit
-        for new_instance in itertools.repeat(instance_id):
-            mask = """id,
-                      lastOperatingSystemReload.id,
-                      activeTransaction.id,provisionDate"""
-            try:
-                instance = self.get_instance(new_instance, mask=mask)
-                last_reload = utils.lookup(instance, 'lastOperatingSystemReload', 'id')
-                active_transaction = utils.lookup(instance, 'activeTransaction', 'id')
-
-                reloading = all((
-                    active_transaction,
-                    last_reload,
-                    last_reload == active_transaction,
-                ))
-
-                # only check for outstanding transactions if requested
-                outstanding = False
-                if pending:
-                    outstanding = active_transaction
-
-                # return True if the instance has finished provisioning
-                # and isn't currently reloading the OS.
-                if all([instance.get('provisionDate'),
-                        not reloading,
-                        not outstanding]):
-                    return True
-                LOGGER.info("%s not ready.", str(instance_id))
-            except exceptions.SoftLayerAPIError as exception:
-                delay = (delay * 2) + random.randint(0, 9)
-                LOGGER.info('Exception: %s', str(exception))
-
+        now = time.time()
+        until = now + limit
+        mask = "mask[id, lastOperatingSystemReload[id], activeTransaction, provisionDate]"
+
+        while now <= until:
+            instance = self.get_instance(instance_id, mask=mask)
+            if utils.is_ready(instance, pending):
+                return True
+            transaction = utils.lookup(instance, 'activeTransaction', 'transactionStatus', 'friendlyName')
+            snooze = min(delay, until - now)
+            LOGGER.info("%s - %d not ready. Auto retry in %ds", transaction, instance_id, snooze)
+            time.sleep(snooze)
             now = time.time()
-            if now >= until:
-                return False
-            LOGGER.info('Auto retry in %s seconds', str(min(delay, until - now)))
-            time.sleep(min(delay, until - now))
+
+        LOGGER.info("Waiting for %d expired.", instance_id)
         return False
 
     def verify_create_instance(self, **kwargs):
@@ -581,7 +557,7 @@ def create_instance(self, **kwargs):
             self.set_tags(tags, guest_id=inst['id'])
         return inst
 
-    @retry(exceptions.SoftLayerAPIError, logger=LOGGER)
+    @retry(logger=LOGGER)
     def set_tags(self, tags, guest_id):
         """Sets tags on a guest with a retry decorator
 
diff --git a/SoftLayer/transports.py b/SoftLayer/transports.py
index 97d67a5b..fef83496 100644
--- a/SoftLayer/transports.py
+++ b/SoftLayer/transports.py
@@ -174,7 +174,7 @@ def __call__(self, request):
             verify = self.verify
 
         LOGGER.debug("=== REQUEST ===")
-        LOGGER.info('POST %s', url)
+        LOGGER.debug('POST %s', url)
         LOGGER.debug(request.transport_headers)
         LOGGER.debug(payload)
 
@@ -302,7 +302,7 @@ def __call__(self, request):
             verify = self.verify
 
         LOGGER.debug("=== REQUEST ===")
-        LOGGER.info(url)
+        LOGGER.debug(url)
         LOGGER.debug(request.transport_headers)
         LOGGER.debug(raw_body)
         try:
diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py
index f39ffc0f..07eb72ed 100644
--- a/SoftLayer/utils.py
+++ b/SoftLayer/utils.py
@@ -185,3 +185,27 @@ def tzname(self, _):
 
     def dst(self, _):
         return datetime.timedelta(0)
+
+
+def is_ready(instance, pending=False):
+    """Returns True if instance is ready to be used
+
+    :param Object instance: Hardware or Virt with transaction data retrieved from the API
+    :param bool pending: Wait for ALL transactions to finish?
+    :returns bool:
+    """
+
+    last_reload = lookup(instance, 'lastOperatingSystemReload', 'id')
+    active_transaction = lookup(instance, 'activeTransaction', 'id')
+
+    reloading = all((
+        active_transaction,
+        last_reload,
+        last_reload == active_transaction,
+    ))
+    outstanding = False
+    if pending:
+        outstanding = active_transaction
+    if instance.get('provisionDate') and not reloading and not outstanding:
+        return True
+    return False


Test patch:
diff --git a/tests/CLI/core_tests.py b/tests/CLI/core_tests.py
index 534fcccc..bf5879c4 100644
--- a/tests/CLI/core_tests.py
+++ b/tests/CLI/core_tests.py
@@ -19,8 +19,7 @@
 class CoreTests(testing.TestCase):
 
     def test_load_all(self):
-        for path, cmd in recursive_subcommand_loader(core.cli,
-                                                     current_path='root'):
+        for path, cmd in recursive_subcommand_loader(core.cli, current_path='root'):
             try:
                 cmd.main(args=['--help'])
             except SystemExit as ex:
diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py
index 083798bd..5f8c85d3 100644
--- a/tests/CLI/modules/server_tests.py
+++ b/tests/CLI/modules/server_tests.py
@@ -462,3 +462,50 @@ def test_server_rescue_negative(self, confirm_mock):
 
         self.assertEqual(result.exit_code, 2)
         self.assertIsInstance(result.exception, exceptions.CLIAbort)
+
+    def test_ready(self):
+        mock = self.set_mock('SoftLayer_Hardware_Server', 'getObject')
+        mock.return_value = {
+            "provisionDate": "2017-10-17T11:21:53-07:00",
+            "id": 41957081
+        }
+        result = self.run_command(['hw', 'ready', '100'])
+        self.assert_no_fail(result)
+        self.assertEqual(result.output, '"READY"\n')
+
+    def test_not_ready(self):
+        mock = self.set_mock('SoftLayer_Hardware_Server', 'getObject')
+        not_ready = {
+            'activeTransaction': {
+                'transactionStatus': {'friendlyName': 'Attach Primary Disk'}
+            },
+            'provisionDate': '',
+            'id': 47392219
+        }
+        ready = {
+            "provisionDate": "2017-10-17T11:21:53-07:00",
+            "id": 41957081
+        }
+        mock.side_effect = [not_ready, ready]
+        result = self.run_command(['hw', 'ready', '100'])
+        self.assertEqual(result.exit_code, 2)
+        self.assertIsInstance(result.exception, exceptions.CLIAbort)
+
+    @mock.patch('time.sleep')
+    def test_going_ready(self, _sleep):
+        mock = self.set_mock('SoftLayer_Hardware_Server', 'getObject')
+        not_ready = {
+            'activeTransaction': {
+                'transactionStatus': {'friendlyName': 'Attach Primary Disk'}
+            },
+            'provisionDate': '',
+            'id': 47392219
+        }
+        ready = {
+            "provisionDate": "2017-10-17T11:21:53-07:00",
+            "id": 41957081
+        }
+        mock.side_effect = [not_ready, ready]
+        result = self.run_command(['hw', 'ready', '100', '--wait=100'])
+        self.assert_no_fail(result)
+        self.assertEqual(result.output, '"READY"\n')
diff --git a/tests/CLI/modules/vs_tests.py b/tests/CLI/modules/vs_tests.py
index dced520e..e9b8cf46 100644
--- a/tests/CLI/modules/vs_tests.py
+++ b/tests/CLI/modules/vs_tests.py
@@ -606,3 +606,50 @@ def test_edit(self):
             args=(100,),
             identifier=100,
         )
+
+    def test_ready(self):
+        mock = self.set_mock('SoftLayer_Virtual_Guest', 'getObject')
+        mock.return_value = {
+            "provisionDate": "2017-10-17T11:21:53-07:00",
+            "id": 41957081
+        }
+        result = self.run_command(['vs', 'ready', '100'])
+        self.assert_no_fail(result)
+        self.assertEqual(result.output, '"READY"\n')
+
+    def test_not_ready(self):
+        mock = self.set_mock('SoftLayer_Virtual_Guest', 'getObject')
+        not_ready = {
+            'activeTransaction': {
+                'transactionStatus': {'friendlyName': 'Attach Primary Disk'}
+            },
+            'provisionDate': '',
+            'id': 47392219
+        }
+        ready = {
+            "provisionDate": "2017-10-17T11:21:53-07:00",
+            "id": 41957081
+        }
+        mock.side_effect = [not_ready, ready]
+        result = self.run_command(['vs', 'ready', '100'])
+        self.assertEqual(result.exit_code, 2)
+        self.assertIsInstance(result.exception, exceptions.CLIAbort)
+
+    @mock.patch('time.sleep')
+    def test_going_ready(self, _sleep):
+        mock = self.set_mock('SoftLayer_Virtual_Guest', 'getObject')
+        not_ready = {
+            'activeTransaction': {
+                'transactionStatus': {'friendlyName': 'Attach Primary Disk'}
+            },
+            'provisionDate': '',
+            'id': 47392219
+        }
+        ready = {
+            "provisionDate": "2017-10-17T11:21:53-07:00",
+            "id": 41957081
+        }
+        mock.side_effect = [not_ready, ready]
+        result = self.run_command(['vs', 'ready', '100', '--wait=100'])
+        self.assert_no_fail(result)
+        self.assertEqual(result.output, '"READY"\n')
diff --git a/tests/basic_tests.py b/tests/basic_tests.py
index cfaf6e25..b430a3d5 100644
--- a/tests/basic_tests.py
+++ b/tests/basic_tests.py
@@ -5,6 +5,7 @@
 
     :license: MIT, see LICENSE for more details.
 """
+import datetime
 import SoftLayer
 from SoftLayer import testing
 
@@ -59,6 +60,25 @@ def test_query_filter(self):
         result = SoftLayer.utils.query_filter(10)
         self.assertEqual({'operation': 10}, result)
 
+    def test_query_filter_date(self):
+        result = SoftLayer.utils.query_filter_date("2018-01-01", "2018-01-02")
+        expected = {
+            'operation': 'betweenDate',
+            'options': [
+                {'name': 'startDate', 'value': ['1/1/2018 0:0:0']},
+                {'name': 'endDate', 'value': ['1/2/2018 0:0:0']}
+            ]
+        }
+        self.assertEqual(expected, result)
+
+    def test_timezone(self):
+        utc = SoftLayer.utils.UTC()
+        time = datetime.datetime(2018, 1, 1, tzinfo=utc)
+        self.assertEqual('2018-01-01 00:00:00+00:00', time.__str__())
+        self.assertEqual('UTC', time.tzname())
+        self.assertEqual(datetime.timedelta(0), time.dst())
+        self.assertEqual(datetime.timedelta(0), time.utcoffset())
+
 
 class TestNestedDict(testing.TestCase):
 
diff --git a/tests/managers/vs_tests.py b/tests/managers/vs_tests.py
index 16c96d44..d45e631a 100644
--- a/tests/managers/vs_tests.py
+++ b/tests/managers/vs_tests.py
@@ -754,9 +754,7 @@ def test_wait_interface(self, ready):
 
     def test_active_not_provisioned(self):
         # active transaction and no provision date should be false
-        self.guestObject.side_effect = [
-            {'activeTransaction': {'id': 1}},
-        ]
+        self.guestObject.return_value = {'activeTransaction': {'id': 1}}
         value = self.vs.wait_for_ready(1, 0)
         self.assertFalse(value)
 
@@ -773,7 +771,9 @@ def test_active_provision_pending(self):
         # active transaction and provision date
         # and pending should be false
         self.guestObject.side_effect = [
-            {'activeTransaction': {'id': 1}, 'provisionDate': 'aaa'},
+            {'activeTransaction': {'id': 1}},
+            {'activeTransaction': {'id': 1},
+             'provisionDate': 'aaa'},
         ]
         value = self.vs.wait_for_ready(1, 0, pending=True)
         self.assertFalse(value)
@@ -781,6 +781,7 @@ def test_active_provision_pending(self):
     def test_active_reload(self):
         # actively running reload
         self.guestObject.side_effect = [
+            {'activeTransaction': {'id': 1}},
             {
                 'activeTransaction': {'id': 1},
                 'provisionDate': 'aaa',
@@ -792,19 +793,19 @@ def test_active_reload(self):
 
     def test_reload_no_pending(self):
         # reload complete, maintance transactions
-        self.guestObject.side_effect = [
-            {
-                'activeTransaction': {'id': 2},
-                'provisionDate': 'aaa',
-                'lastOperatingSystemReload': {'id': 1},
-            },
-        ]
+        self.guestObject.return_value = {
+            'activeTransaction': {'id': 2},
+            'provisionDate': 'aaa',
+            'lastOperatingSystemReload': {'id': 1},
+        }
+
         value = self.vs.wait_for_ready(1, 1)
         self.assertTrue(value)
 
     def test_reload_pending(self):
         # reload complete, pending maintance transactions
         self.guestObject.side_effect = [
+            {'activeTransaction': {'id': 1}},
             {
                 'activeTransaction': {'id': 2},
                 'provisionDate': 'aaa',
@@ -816,22 +817,16 @@ def test_reload_pending(self):
 
     @mock.patch('time.sleep')
     def test_ready_iter_once_incomplete(self, _sleep):
-        self.guestObject = self.client['Virtual_Guest'].getObject
-
         # no iteration, false
-        self.guestObject.side_effect = [
-            {'activeTransaction': {'id': 1}},
-        ]
+        self.guestObject.return_value = {'activeTransaction': {'id': 1}}
         value = self.vs.wait_for_ready(1, 0, delay=1)
         self.assertFalse(value)
-        self.assertFalse(_sleep.called)
+        _sleep.assert_has_calls([mock.call(0)])
 
     @mock.patch('time.sleep')
     def test_iter_once_complete(self, _sleep):
         # no iteration, true
-        self.guestObject.side_effect = [
-            {'provisionDate': 'aaa'},
-        ]
+        self.guestObject.return_value = {'provisionDate': 'aaa'}
         value = self.vs.wait_for_ready(1, 1, delay=1)
         self.assertTrue(value)
         self.assertFalse(_sleep.called)
@@ -859,15 +854,16 @@ def test_iter_four_complete(self, _sleep):
     def test_iter_two_incomplete(self, _sleep, _time):
         # test 2 iterations, with no matches
         self.guestObject.side_effect = [
+            {'activeTransaction': {'id': 1}},
             {'activeTransaction': {'id': 1}},
             {'activeTransaction': {'id': 1}},
             {'provisionDate': 'aaa'}
         ]
         # logging calls time.time as of pytest3.3, not sure if there is a better way of getting around that.
-        _time.side_effect = [0, 0, 1, 1, 2, 2, 2]
+        _time.side_effect = [0, 1, 2, 3, 4, 5, 6]
         value = self.vs.wait_for_ready(1, 2, delay=1)
         self.assertFalse(value)
-        _sleep.assert_called_once_with(1)
+        _sleep.assert_has_calls([mock.call(1), mock.call(0)])
         self.guestObject.assert_has_calls([
             mock.call(id=1, mask=mock.ANY),
             mock.call(id=1, mask=mock.ANY),
@@ -886,22 +882,22 @@ def test_iter_20_incomplete(self, _sleep, _time):
 
         _sleep.assert_has_calls([mock.call(10)])
 
-    @mock.patch('SoftLayer.managers.vs.VSManager.get_instance')
-    @mock.patch('random.randint')
+    @mock.patch('SoftLayer.decoration.sleep')
+    @mock.patch('SoftLayer.transports.FixtureTransport.__call__')
     @mock.patch('time.time')
     @mock.patch('time.sleep')
-    def test_exception_from_api(self, _sleep, _time, _random, _vs):
+    def test_exception_from_api(self, _sleep, _time, _vs, _dsleep):
         """Tests escalating scale back when an excaption is thrown"""
-        self.guestObject.return_value = {'activeTransaction': {'id': 1}}
-        _vs.side_effect = exceptions.TransportError(104, "Its broken")
+        _dsleep.return_value = False
+
+        self.guestObject.side_effect = [
+            exceptions.TransportError(104, "Its broken"),
+            {'activeTransaction': {'id': 1}},
+            {'provisionDate': 'aaa'}
+        ]
         # logging calls time.time as of pytest3.3, not sure if there is a better way of getting around that.
-        _time.side_effect = [0, 0, 0, 0, 2, 2, 2, 6, 6, 6, 14, 14, 14, 20, 20, 20, 100, 100, 100]
-        _random.side_effect = [0, 0, 0, 0, 0]
+        _time.side_effect = [0, 1, 2, 3, 4]
         value = self.vs.wait_for_ready(1, 20, delay=1)
-        _sleep.assert_has_calls([
-            mock.call(2),
-            mock.call(4),
-            mock.call(8),
-            mock.call(6)
-        ])
-        self.assertFalse(value)
+        _sleep.assert_called_once()
+        _dsleep.assert_called_once()
+        self.assertTrue(value)
diff --git a/tests/transport_tests.py b/tests/transport_tests.py
index c9e9304c..d4145e82 100644
--- a/tests/transport_tests.py
+++ b/tests/transport_tests.py
@@ -366,8 +366,7 @@ def test_proxy_without_protocol(self):
         try:
             self.assertRaises(SoftLayer.TransportError, self.transport, req)
         except AssertionError:
-            warnings.warn("AssertionError raised instead of a "
-                          "SoftLayer.TransportError error")
+            warnings.warn("AssertionError raised instead of a SoftLayer.TransportError error")
 
     @mock.patch('SoftLayer.transports.requests.Session.request')
     def test_valid_proxy(self, request):
@@ -511,63 +510,67 @@ def test_with_mask(self, request):
             timeout=None)
 
     @mock.patch('SoftLayer.transports.requests.Session.request')
-    def test_unknown_error(self, request):
-        e = requests.RequestException('error')
-        e.response = mock.MagicMock()
-        e.response.status_code = 404
-        e.response.content = 'Error Code'
-        request().raise_for_status.side_effect = e
-
-        req = transports.Request()
-        req.service = 'SoftLayer_Service'
-        req.method = 'getObject'
-
-        self.assertRaises(SoftLayer.TransportError, self.transport, req)
-
-    @mock.patch('SoftLayer.transports.requests.Session.request')
-    def test_with_limits(self, request):
+    def test_with_limit_offset(self, request):
         request().text = '{}'
 
         req = transports.Request()
         req.service = 'SoftLayer_Service'
         req.method = 'getObject'
+        req.identifier = 2
         req.limit = 10
-        req.offset = 10
+        req.offset = 5
 
         resp = self.transport(req)
 
         self.assertEqual(resp, {})
         request.assert_called_with(
             'GET',
-            'http://something.com/SoftLayer_Service/getObject.json',
+            'http://something.com/SoftLayer_Service/2/getObject.json',
             headers=mock.ANY,
             auth=None,
             data=None,
-            params={'limit': 10, 'offset': 10},
+            params={'limit': 10, 'offset': 5},
             verify=True,
             cert=None,
             proxies=None,
             timeout=None)
 
     @mock.patch('SoftLayer.transports.requests.Session.request')
-    def test_with_username(self, request):
+    def test_unknown_error(self, request):
+        e = requests.RequestException('error')
+        e.response = mock.MagicMock()
+        e.response.status_code = 404
+        e.response.content = 'Error Code'
+        request().raise_for_status.side_effect = e
+
+        req = transports.Request()
+        req.service = 'SoftLayer_Service'
+        req.method = 'getObject'
+
+        self.assertRaises(SoftLayer.TransportError, self.transport, req)
+
+    @mock.patch('SoftLayer.transports.requests.Session.request')
+    @mock.patch('requests.auth.HTTPBasicAuth')
+    def test_with_special_auth(self, auth, request):
         request().text = '{}'
 
+        user = 'asdf'
+        password = 'zxcv'
         req = transports.Request()
         req.service = 'SoftLayer_Service'
         req.method = 'getObject'
-        req.transport_user = 'Bob'
-        req.transport_password = '123456'
-        auth = requests.auth.HTTPBasicAuth(req.transport_user, req.transport_password)
+        req.identifier = 2
+        req.transport_user = user
+        req.transport_password = password
 
         resp = self.transport(req)
-
         self.assertEqual(resp, {})
+        auth.assert_called_with(user, password)
         request.assert_called_with(
             'GET',
-            'http://something.com/SoftLayer_Service/getObject.json',
+            'http://something.com/SoftLayer_Service/2/getObject.json',
             headers=mock.ANY,
-            auth=auth,
+            auth=mock.ANY,
             data=None,
             params={},
             verify=True,
@@ -579,25 +582,23 @@ def test_with_username(self, request):
 class TestFixtureTransport(testing.TestCase):
 
     def set_up(self):
-        transport = testing.MockableTransport(SoftLayer.FixtureTransport())
-        self.env.client = SoftLayer.BaseClient(transport=transport)
+        self.transport = transports.FixtureTransport()
 
-    def test_base_case(self):
-        result = self.env.client.call('SoftLayer_Account', 'getObject')
-        self.assertEqual(result['accountId'], 1234)
+    def test_basic(self):
+        req = transports.Request()
+        req.service = 'SoftLayer_Account'
+        req.method = 'getObject'
+        resp = self.transport(req)
+        self.assertEqual(resp['accountId'], 1234)
 
-    def test_Import_Service_Error(self):
-        # assertRaises doesn't work here for some reason
-        try:
-            self.env.client.call('FAKE', 'getObject')
-            self.assertTrue(False)
-        except NotImplementedError:
-            self.assertTrue(True)
+    def test_no_module(self):
+        req = transports.Request()
+        req.service = 'Doesnt_Exist'
+        req.method = 'getObject'
+        self.assertRaises(NotImplementedError, self.transport, req)
 
-    def test_Import_Method_Error(self):
-        # assertRaises doesn't work here for some reason
-        try:
-            self.env.client.call('SoftLayer_Account', 'getObject111')
-            self.assertTrue(False)
-        except NotImplementedError:
-            self.assertTrue(True)
+    def test_no_method(self):
+        req = transports.Request()
+        req.service = 'SoftLayer_Account'
+        req.method = 'getObjectzzzz'
+        self.assertRaises(NotImplementedError, self.transport, req)