diff --git a/SoftLayer/CLI/__init__.py b/SoftLayer/CLI/__init__.py index 3d5d6bf79..31d8f4b88 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 000000000..ec2cf9940 --- /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 3fa115828..f191e73bc 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 1be2e1b42..c41680436 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 8fb759893..66ce62ccb 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 13da8474b..450dc23e0 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 97ef6cd67..ae0a4a429 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 c0456a87b..fa20ef86f 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 97d67a5b9..fef83496e 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 f39ffc0fe..07eb72edb 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 diff --git a/tests/CLI/core_tests.py b/tests/CLI/core_tests.py index 534fcccca..bf5879c45 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 083798bd5..5f8c85d31 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 dced520e0..e9b8cf46c 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 cfaf6e259..b430a3d5e 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 16c96d44e..d45e631a3 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 c9e9304c3..d4145e823 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)