async def test_get(self): with patch.object(Service._logger, 'info') as log_info: s = Service.register("StorageService", "Storage", "localhost", 8881, 1888) c = Service.register("CoreService", "Core", "localhost", 7771, 1777) d = Service.register("SouthService", "Southbound", "127.0.0.1", 9991, 1999, "https") assert 3 == log_info.call_count _service = Service.get() assert 3 == len(_service) assert s == _service[0]._id assert "StorageService" == _service[0]._name assert "Storage" == _service[0]._type assert "localhost" == _service[0]._address assert 8881 == int(_service[0]._port) assert 1888 == int(_service[0]._management_port) # validates default set to HTTP assert "http" == _service[0]._protocol assert c == _service[1]._id assert "CoreService" == _service[1]._name assert "Core" == _service[1]._type assert "localhost" == _service[1]._address assert 7771 == int(_service[1]._port) assert 1777 == int(_service[1]._management_port) # validates default set to HTTP assert "http" == _service[1]._protocol assert d == _service[2]._id assert "SouthService" == _service[2]._name assert "Southbound" == _service[2]._type assert "127.0.0.1" == _service[2]._address assert 9991 == int(_service[2]._port) assert 1999 == int(_service[2]._management_port) assert "https" == _service[2]._protocol
def test_register_with_bad_management_port(self): """raise NonNumericPortError""" with pytest.raises(Exception) as excinfo: with patch.object(ServiceRegistry._logger, 'info') as log_info: ServiceRegistry.register("B name", "Storage", "127.0.0.1", 1234, "m01", 'http') assert 0 == len(ServiceRegistry._registry) assert 0 == log_info.call_count assert excinfo.type is NonNumericPortError
def test_get_storage(self): with patch.object(ServiceRegistry._logger, 'info') as log_info: ServiceRegistry.register("Fledge Storage", "Storage", "127.0.0.1", 37449, 37843) storage_client = connect.get_storage_async() assert isinstance(storage_client, StorageClientAsync) assert 1 == log_info.call_count args, kwargs = log_info.call_args assert args[0].startswith('Registered service instance id=') assert args[0].endswith( ': <Fledge Storage, type=Storage, protocol=http, address=127.0.0.1, service port=37449,' ' management port=37843, status=1>')
async def test_get_fail(self): with pytest.raises(DoesNotExist) as excinfo: with patch.object(Service._logger, 'info') as log_info: Service.register("StorageService", "Storage", "127.0.0.1", 8888, 9999) Service.get('incorrect_id') assert 1 == log_info.call_count args, kwargs = log_info.call_args assert args[0].startswith('Registered service instance id=') assert args[0].endswith( ': <StorageService1, type=Storage, protocol=http, address=127.0.0.1, service port=8888,' ' management port=9999, status=1>') assert str(excinfo).endswith('DoesNotExist')
async def test_duplicate_address_and_mgt_port_registration(self): with patch.object(Service._logger, 'info') as log_info: idx1 = Service.register("StorageService1", "Storage", "127.0.0.1", 9999, 1999) assert str(uuid.UUID(idx1, version=4)) == idx1 assert 1 == log_info.call_count args, kwargs = log_info.call_args assert args[0].startswith('Registered service instance id=') assert args[0].endswith(': <StorageService1, type=Storage, protocol=http, address=127.0.0.1, service port=9999,' ' management port=1999, status=1>') with pytest.raises(AlreadyExistsWithTheSameAddressAndManagementPort) as excinfo: Service.register("StorageService2", "Storage", "127.0.0.1", 9998, 1999) assert str(excinfo).endswith('AlreadyExistsWithTheSameAddressAndManagementPort')
def test_exception_when_non_fledge_storage(self, mock_logger): with patch.object(ServiceRegistry._logger, 'info') as log_info: ServiceRegistry.register("foo", "Storage", "127.0.0.1", 1, 2) assert 1 == log_info.call_count args, kwargs = log_info.call_args assert args[0].startswith('Registered service instance id=') assert args[0].endswith( ': <foo, type=Storage, protocol=http, address=127.0.0.1, service port=1, ' 'management port=2, status=1>') with pytest.raises(DoesNotExist) as excinfo: connect.get_storage_async() assert str(excinfo).endswith('DoesNotExist') mock_logger.exception.assert_called_once_with('')
async def test_unregister(self, mocker): # register a service with patch.object(Service._logger, 'info') as log_info: idx = Service.register("StorageService2", "Storage", "127.0.0.1", 8888, 1888) assert str(uuid.UUID(idx, version=4)) == idx assert 1 == log_info.call_count arg, kwarg = log_info.call_args assert arg[0].startswith('Registered service instance id=') assert arg[0].endswith(': <StorageService2, type=Storage, protocol=http, address=127.0.0.1, service port=8888,' ' management port=1888, status=1>') mocker.patch.object(InterestRegistry, '__init__', return_value=None) mocker.patch.object(InterestRegistry, 'get', return_value=list()) # deregister the same with patch.object(Service._logger, 'info') as log_info2: t = Service.unregister(idx) assert idx == t assert 1 == log_info2.call_count args, kwargs = log_info2.call_args assert args[0].startswith('Stopped service instance id=') assert args[0].endswith(': <StorageService2, type=Storage, protocol=http, address=127.0.0.1, ' 'service port=8888, management port=1888, status=2>') s = Service.get(idx) assert s[0]._status == 2 # Unregistered
def test_register_with_same_address_and_mgt_port(self): """raise AlreadyExistsWithTheSameAddressAndManagementPort""" with patch.object(ServiceRegistry._logger, 'info') as log_info1: ServiceRegistry.register("A name", "Storage", "127.0.0.1", 1, 1234, 'http') assert 1 == len(ServiceRegistry._registry) assert 1 == log_info1.call_count args, kwargs = log_info1.call_args assert args[0].startswith('Registered service instance id=') assert args[0].endswith(': <A name, type=Storage, protocol=http, address=127.0.0.1, service port=1,' ' management port=1234, status=1>') with pytest.raises(Exception) as excinfo: with patch.object(ServiceRegistry._logger, 'info') as log_info2: ServiceRegistry.register("B name", "Storage", "127.0.0.1", 2, 1234, 'http') assert 1 == len(ServiceRegistry._registry) assert 0 == log_info2.call_count assert excinfo.type is AlreadyExistsWithTheSameAddressAndManagementPort
async def test_register(self): with patch.object(Service._logger, 'info') as log_info: idx = Service.register("StorageService1", "Storage", "127.0.0.1", 9999, 1999) assert str(uuid.UUID(idx, version=4)) == idx assert 1 == log_info.call_count args, kwargs = log_info.call_args assert args[0].startswith('Registered service instance id=') assert args[0].endswith(': <StorageService1, type=Storage, protocol=http, address=127.0.0.1, service port=9999,' ' management port=1999, status=1>')
def test_register_with_service_port_none(self): with patch.object(ServiceRegistry._logger, 'info') as log_info: s_id = ServiceRegistry.register("A name", "Southbound", "127.0.0.1", None, 4321, 'http') assert 36 == len(s_id) # uuid version 4 len assert 1 == len(ServiceRegistry._registry) assert 1 == log_info.call_count args, kwargs = log_info.call_args assert args[0].startswith('Registered service instance id=') assert args[0].endswith(': <A name, type=Southbound, protocol=http, address=127.0.0.1, service port=None,' ' management port=4321, status=1>')
async def test_run_no_interests_in_cat(self): storage_client_mock = MagicMock(spec=StorageClientAsync) cfg_mgr = ConfigurationManager(storage_client_mock) with patch.object(ServiceRegistry._logger, 'info') as log_info: s_id_1 = ServiceRegistry.register('sname1', 'Storage', 'saddress1', 1, 1, 'http') s_id_2 = ServiceRegistry.register('sname2', 'Southbound', 'saddress2', 2, 2, 'http') s_id_3 = ServiceRegistry.register('sname3', 'Southbound', 'saddress3', 3, 3, 'http') assert 3 == log_info.call_count i_reg = InterestRegistry(cfg_mgr) i_reg.register(s_id_1, 'catname2') i_reg.register(s_id_2, 'catname2') i_reg.register(s_id_3, 'catname3') # used to mock client session context manager async def async_mock(return_value): return return_value class AsyncSessionContextManagerMock(MagicMock): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) async def __aenter__(self): client_response_mock = MagicMock(spec=aiohttp.ClientResponse) client_response_mock.text.side_effect = [async_mock(None)] status_mock = Mock() status_mock.side_effect = [200] client_response_mock.status = status_mock() return client_response_mock async def __aexit__(self, *args): return None with patch.object(ConfigurationManager, 'get_category_all_items') as cm_get_patch: with patch.object(aiohttp.ClientSession, 'post') as post_patch: await cb.run('catname1') post_patch.assert_not_called() cm_get_patch.assert_not_called()
async def test_run_general_exception(self): storage_client_mock = MagicMock(spec=StorageClientAsync) cfg_mgr = ConfigurationManager(storage_client_mock) with patch.object(ServiceRegistry._logger, 'info') as log_info: s_id_1 = ServiceRegistry.register('sname1', 'Storage', 'saddress1', 1, 1, 'http') assert 1 == log_info.call_count args, kwargs = log_info.call_args assert args[0].startswith('Registered service instance id=') assert args[0].endswith( ': <sname1, type=Storage, protocol=http, address=saddress1, service port=1, management port=1, status=1>' ) i_reg = InterestRegistry(cfg_mgr) i_reg.register(s_id_1, 'catname1') # used to mock client session context manager async def async_mock(return_value): return return_value class AsyncSessionContextManagerMock(MagicMock): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) async def __aenter__(self): raise Exception async def __aexit__(self, *args): return None with patch.object(ConfigurationManager, 'get_category_all_items', return_value=async_mock(None)) as cm_get_patch: with patch.object(aiohttp.ClientSession, 'post', return_value=AsyncSessionContextManagerMock() ) as post_patch: with patch.object(cb._LOGGER, 'exception') as exception_patch: await cb.run('catname1') exception_patch.assert_called_once_with( 'Unable to notify microservice with uuid %s due to exception: %s', s_id_1, '') post_patch.assert_has_calls([ call('http://saddress1:1/fledge/change', data='{"category": "catname1", "items": null}', headers={'content-type': 'application/json'}) ]) cm_get_patch.assert_called_once_with('catname1')
async def test__monitor_good_uptime(self): async def async_mock(return_value): return return_value # used to mock client session context manager class AsyncSessionContextManagerMock(MagicMock): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) async def __aenter__(self): client_response_mock = MagicMock(spec=aiohttp.ClientResponse) # mock response (good) client_response_mock.text.side_effect = [ async_mock('{"uptime": "bla"}')] return client_response_mock async def __aexit__(self, *args): return None # as monitor loop is as infinite loop, this exception is thrown when we need to exit the loop class TestMonitorException(Exception): pass # register a service with patch.object(ServiceRegistry._logger, 'info') as log_info: s_id_1 = ServiceRegistry.register( 'sname1', 'Storage', 'saddress1', 1, 1, 'protocol1') assert 1 == log_info.call_count args, kwargs = log_info.call_args assert args[0].startswith('Registered service instance id=') assert args[0].endswith(': <sname1, type=Storage, protocol=protocol1, address=saddress1, service port=1, ' 'management port=1, status=1>') monitor = Monitor() monitor._sleep_interval = Monitor._DEFAULT_SLEEP_INTERVAL monitor._max_attempts = Monitor._DEFAULT_MAX_ATTEMPTS # throw the TestMonitorException when sleep is called (end of infinite loop) with patch.object(Monitor, '_sleep', side_effect=TestMonitorException()): with patch.object(aiohttp.ClientSession, 'get', return_value=AsyncSessionContextManagerMock()): with pytest.raises(Exception) as excinfo: await monitor._monitor_loop() assert excinfo.type is TestMonitorException # service is good, so it should remain in the service registry assert len(ServiceRegistry.get(idx=s_id_1)) is 1 assert ServiceRegistry.get(idx=s_id_1)[0]._status is ServiceRecord.Status.Running
def test_unregister(self, mocker): mocker.patch.object(InterestRegistry, '__init__', return_value=None) mocker.patch.object(InterestRegistry, 'get', return_value=list()) with patch.object(ServiceRegistry._logger, 'info') as log_info1: reg_id = ServiceRegistry.register("A name", "Storage", "127.0.0.1", 1234, 4321, 'http') assert 1 == len(ServiceRegistry._registry) assert 1 == log_info1.call_count arg, kwarg = log_info1.call_args assert arg[0].startswith('Registered service instance id=') assert arg[0].endswith(': <A name, type=Storage, protocol=http, address=127.0.0.1, service port=1234,' ' management port=4321, status=1>') with patch.object(ServiceRegistry._logger, 'info') as log_info2: s_id = ServiceRegistry.unregister(reg_id) assert 36 == len(s_id) # uuid version 4 len assert 1 == len(ServiceRegistry._registry) s = ServiceRegistry.get(idx=s_id) assert s[0]._status == 2 assert 1 == log_info2.call_count args, kwargs = log_info2.call_args assert args[0].startswith('Stopped service instance id=') assert args[0].endswith(': <A name, type=Storage, protocol=http, address=127.0.0.1, service port=1234,' ' management port=4321, status=2>')
async def test__monitor_exceed_attempts(self, mocker): class AsyncSessionContextManagerMock(MagicMock): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) async def __aenter__(self): # mock response (error- exception) raise Exception("test") async def __aexit__(self, *args): return None # as monitor loop is as infinite loop, this exception is thrown when we need to exit the loop class TestMonitorException(Exception): pass # register a service s_id_1 = ServiceRegistry.register( 'sname1', 'Storage', 'saddress1', 1, 1, 'protocol1') monitor = Monitor() monitor._sleep_interval = Monitor._DEFAULT_SLEEP_INTERVAL monitor._max_attempts = Monitor._DEFAULT_MAX_ATTEMPTS sleep_side_effect_list = list() # _MAX_ATTEMPTS is 15 # throw exception on the 16th time sleep is called - the first 15 sleeps are used during retries for i in range(0, 15): sleep_side_effect_list.append(asyncio.sleep(.01)) sleep_side_effect_list.append(TestMonitorException()) with patch.object(Monitor, '_sleep', side_effect=sleep_side_effect_list): with patch.object(aiohttp.ClientSession, 'get', return_value=AsyncSessionContextManagerMock()): with pytest.raises(Exception) as excinfo: await monitor._monitor_loop() assert excinfo.type in [TestMonitorException, TypeError] assert ServiceRegistry.get(idx=s_id_1)[0]._status is ServiceRecord.Status.Failed
async def test_register_wrong_type(self): with pytest.raises(ServiceRecord.InvalidServiceType) as excinfo: Service.register("StorageService1", "WrongType", "127.0.0.1", 9999, 1999) assert str(excinfo).endswith('InvalidServiceType')
async def test_register_invalid_mgt_port(self): with pytest.raises(NonNumericPortError) as excinfo: Service.register("StorageService2", "Core", "127.0.0.1", 8888, "199a") assert str(excinfo).endswith('NonNumericPortError')
async def test_run_good(self): storage_client_mock = MagicMock(spec=StorageClientAsync) cfg_mgr = ConfigurationManager(storage_client_mock) with patch.object(ServiceRegistry._logger, 'info') as log_info: s_id_1 = ServiceRegistry.register('sname1', 'Storage', 'saddress1', 1, 1, 'http') s_id_2 = ServiceRegistry.register('sname2', 'Southbound', 'saddress2', 2, 2, 'http') s_id_3 = ServiceRegistry.register('sname3', 'Southbound', 'saddress3', 3, 3, 'http') assert 3 == log_info.call_count i_reg = InterestRegistry(cfg_mgr) i_reg.register(s_id_1, 'catname1') i_reg.register(s_id_1, 'catname2') i_reg.register(s_id_2, 'catname1') i_reg.register(s_id_2, 'catname2') i_reg.register(s_id_3, 'catname3') # used to mock client session context manager async def async_mock(return_value): return return_value class AsyncSessionContextManagerMock(MagicMock): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) async def __aenter__(self): client_response_mock = MagicMock(spec=aiohttp.ClientResponse) client_response_mock.text.side_effect = [async_mock(None)] status_mock = Mock() status_mock.side_effect = [200] client_response_mock.status = status_mock() return client_response_mock async def __aexit__(self, *args): return None with patch.object(ConfigurationManager, 'get_category_all_items', return_value=async_mock(None)) as cm_get_patch: with patch.object(aiohttp.ClientSession, 'post', return_value=AsyncSessionContextManagerMock() ) as post_patch: await cb.run('catname1') post_patch.assert_has_calls([ call('http://saddress1:1/fledge/change', data='{"category": "catname1", "items": null}', headers={'content-type': 'application/json'}), call('http://saddress2:2/fledge/change', data='{"category": "catname1", "items": null}', headers={'content-type': 'application/json'}) ]) cm_get_patch.assert_called_once_with('catname1') with patch.object(ConfigurationManager, 'get_category_all_items', return_value=async_mock(None)) as cm_get_patch: with patch.object(aiohttp.ClientSession, 'post', return_value=AsyncSessionContextManagerMock() ) as post_patch: await cb.run('catname2') post_patch.assert_has_calls([ call('http://saddress1:1/fledge/change', data='{"category": "catname2", "items": null}', headers={'content-type': 'application/json'}), call('http://saddress2:2/fledge/change', data='{"category": "catname2", "items": null}', headers={'content-type': 'application/json'}) ]) cm_get_patch.assert_called_once_with('catname2') with patch.object(ConfigurationManager, 'get_category_all_items', return_value=async_mock(None)) as cm_get_patch: with patch.object(aiohttp.ClientSession, 'post', return_value=AsyncSessionContextManagerMock() ) as post_patch: await cb.run('catname3') post_patch.assert_called_once_with( 'http://saddress3:3/fledge/change', data='{"category": "catname3", "items": null}', headers={'content-type': 'application/json'}) cm_get_patch.assert_called_once_with('catname3')
async def test_get_health(self, mocker, client): # empty service registry resp = await client.get('/fledge/service') assert 200 == resp.status result = await resp.text() json_response = json.loads(result) assert {'services': []} == json_response mocker.patch.object(InterestRegistry, "__init__", return_value=None) mocker.patch.object(InterestRegistry, "get", return_value=list()) with patch.object(ServiceRegistry._logger, 'info') as log_patch_info: # populated service registry ServiceRegistry.register( 'name1', 'Storage', 'address1', 1, 1, 'protocol1') ServiceRegistry.register( 'name2', 'Southbound', 'address2', 2, 2, 'protocol2') s_id_3 = ServiceRegistry.register( 'name3', 'Southbound', 'address3', 3, 3, 'protocol3') s_id_4 = ServiceRegistry.register( 'name4', 'Southbound', 'address4', 4, 4, 'protocol4') ServiceRegistry.unregister(s_id_3) ServiceRegistry.mark_as_failed(s_id_4) resp = await client.get('/fledge/service') assert 200 == resp.status result = await resp.text() json_response = json.loads(result) assert json_response == { 'services': [ { 'type': 'Storage', 'service_port': 1, 'address': 'address1', 'protocol': 'protocol1', 'status': 'running', 'name': 'name1', 'management_port': 1 }, { 'type': 'Southbound', 'service_port': 2, 'address': 'address2', 'protocol': 'protocol2', 'status': 'running', 'name': 'name2', 'management_port': 2 }, { 'type': 'Southbound', 'service_port': 3, 'address': 'address3', 'protocol': 'protocol3', 'status': 'shutdown', 'name': 'name3', 'management_port': 3 }, { 'type': 'Southbound', 'service_port': 4, 'address': 'address4', 'protocol': 'protocol4', 'status': 'failed', 'name': 'name4', 'management_port': 4 } ] } assert 6 == log_patch_info.call_count