async def unregister(cls, request): """ Unregister a service :Example: curl -X DELETE http://localhost:<core mgt port>/foglamp/service/dc9bfc01-066a-4cc0-b068-9c35486db87f """ try: service_id = request.match_info.get('service_id', None) try: services = ServiceRegistry.get(idx=service_id) except service_registry_exceptions.DoesNotExist: raise ValueError( 'Service with {} does not exist'.format(service_id)) ServiceRegistry.unregister(service_id) if cls._storage_client_async is not None and services[ 0]._name not in ("FogLAMP Storage", "FogLAMP Core"): try: cls._audit = AuditLogger(cls._storage_client_async) await cls._audit.information('SRVUN', {'name': services[0]._name}) except Exception as ex: _logger.exception(str(ex)) _resp = {'id': str(service_id), 'message': 'Service unregistered'} return web.json_response(_resp) except ValueError as ex: raise web.HTTPNotFound(reason=str(ex))
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 unregister(cls, request): """ Unregister a service :Example: curl -X DELETE http://localhost:8082/foglamp/service/dc9bfc01-066a-4cc0-b068-9c35486db87f """ try: service_id = request.match_info.get('service_id', None) if not service_id: raise web.HTTPBadRequest(reason='Service id is required') try: ServiceRegistry.get(idx=service_id) except service_registry_exceptions.DoesNotExist: raise web.HTTPBadRequest( reason='Service with {} does not exist'.format(service_id)) ServiceRegistry.unregister(service_id) _resp = {'id': str(service_id), 'message': 'Service unregistered'} return web.json_response(_resp) except ValueError as ex: raise web.HTTPNotFound(reason=str(ex))
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
async def _monitor_loop(self): """Main loop for the scheduler""" # check health of all micro-services every N seconds while True: for service_record in ServiceRegistry.all(): url = "{}://{}:{}/foglamp/service/ping".format( service_record._protocol, service_record._address, service_record._management_port) async with aiohttp.ClientSession() as session: try: async with session.get( url, timeout=self._ping_timeout) as resp: text = await resp.text() res = json.loads(text) if res["uptime"] is None: raise ValueError('Improper Response') except: service_record._status = 0 ServiceRegistry.unregister(service_record._id) self._logger.info( "Unregistered the failed micro-service %s", service_record.__repr__()) else: service_record._status = 1 await asyncio.ensure_future(asyncio.sleep(self._sleep_interval))
async def test_duplicate_address_port_registration(self): idx1 = Service.register("StorageService1", "Storage", "127.0.0.1", 9999, 1999) assert str(uuid.UUID(idx1, version=4)) == idx1 with pytest.raises(AlreadyExistsWithTheSameAddressAndPort) as excinfo: Service.register("StorageService2", "Storage", "127.0.0.1", 9999, 1998) assert str(excinfo).endswith('AlreadyExistsWithTheSameAddressAndPort')
async def test_run_missing_service_record(self, reset_state): storage_client_mock = MagicMock(spec=StorageClient) cfg_mgr = ConfigurationManager(storage_client_mock) 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') i_reg = InterestRegistry(cfg_mgr) id_fake_1 = i_reg.register('fakeid', 'catname1') id_1_1 = i_reg.register(s_id_1, 'catname1') id_1_2 = i_reg.register(s_id_1, 'catname2') id_2_1 = i_reg.register(s_id_2, 'catname1') id_2_2 = i_reg.register(s_id_2, 'catname2') id_3_3 = 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: with patch.object(cb._LOGGER, 'exception') as exception_patch: await cb.run('catname1') cm_get_patch.assert_called_once_with('catname1') exception_patch.assert_called_once_with( 'Unable to notify microservice with uuid %s as it is not found in the service registry', 'fakeid') post_patch.assert_has_calls([ call('http://saddress1:1/foglamp/change', data='{"category": "catname1", "items": null}', headers={'content-type': 'application/json'}), call('http://saddress2:2/foglamp/change', data='{"category": "catname1", "items": null}', headers={'content-type': 'application/json'}) ])
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
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
async def get_service(cls, request): """ Returns a list of all services or as per name &|| type filter :Example: curl -X GET http://localhost:<core mgt port>/foglamp/service curl -X GET http://localhost:<core mgt port>/foglamp/service?name=X&type=Storage """ service_name = request.query[ 'name'] if 'name' in request.query else None service_type = request.query[ 'type'] if 'type' in request.query else None try: if not service_name and not service_type: services_list = ServiceRegistry.all() elif service_name and not service_type: services_list = ServiceRegistry.get(name=service_name) elif not service_name and service_type: services_list = ServiceRegistry.get(s_type=service_type) else: services_list = ServiceRegistry.filter_by_name_and_type( name=service_name, s_type=service_type) except service_registry_exceptions.DoesNotExist as ex: if not service_name and not service_type: msg = 'No service found' elif service_name and not service_type: msg = 'Service with name {} does not exist'.format( service_name) elif not service_name and service_type: msg = 'Service with type {} does not exist'.format( service_type) else: msg = 'Service with name {} and type {} does not exist'.format( service_name, service_type) raise web.HTTPNotFound(reason=msg) services = [] for service in services_list: svc = dict() svc["id"] = service._id svc["name"] = service._name svc["type"] = service._type svc["address"] = service._address svc["management_port"] = service._management_port svc["protocol"] = service._protocol svc["status"] = ServiceRecord.Status(int( service._status)).name.lower() if service._port: svc["service_port"] = service._port services.append(svc) return web.json_response({"services": services})
async def delete_service(request): """ Delete an existing service :Example: curl -X DELETE http://localhost:8081/foglamp/service/<svc name> """ try: svc = request.match_info.get('service_name', None) storage = connect.get_storage_async() result = await get_schedule(storage, svc) if result['count'] == 0: return web.HTTPNotFound( reason='{} service does not exist.'.format(svc)) config_mgr = ConfigurationManager(storage) # In case of notification service, if notifications exists, then deletion is not allowed if 'notification' in result['rows'][0]['process_name']: notf_children = await config_mgr.get_category_child( category_name="Notifications") children = [x['key'] for x in notf_children] if len(notf_children) > 0: return web.HTTPBadRequest( reason= 'Notification service `{}` can not be deleted, as {} notification instances exist.' .format(svc, children)) # First disable the schedule svc_schedule = result['rows'][0] sch_id = uuid.UUID(svc_schedule['id']) if svc_schedule['enabled'].lower() == 't': await server.Server.scheduler.disable_schedule(sch_id) # Delete all configuration for the service name await config_mgr.delete_category_and_children_recursively(svc) # Remove from registry as it has been already shutdown via disable_schedule() and since # we intend to delete the schedule also, there is no use of its Service registry entry try: services = ServiceRegistry.get(name=svc) ServiceRegistry.remove_from_registry(services[0]._id) except service_registry_exceptions.DoesNotExist: pass # Delete schedule await server.Server.scheduler.delete_schedule(sch_id) except Exception as ex: raise web.HTTPInternalServerError(reason=ex) else: return web.json_response( {'result': 'Service {} deleted successfully.'.format(svc)})
def test_register_with_same_address_and_mgt_port(self): """raise AlreadyExistsWithTheSameAddressAndManagementPort""" ServiceRegistry.register("A name", "Storage", "127.0.0.1", 1, 1234, 'http') assert 1 == len(ServiceRegistry._registry) with pytest.raises(Exception) as excinfo: with patch.object(ServiceRegistry._logger, 'info') as log_i: ServiceRegistry.register("B name", "Storage", "127.0.0.1", 2, 1234, 'http') assert 0 == log_i.call_count assert excinfo.type is AlreadyExistsWithTheSameAddressAndManagementPort assert 1 == len(ServiceRegistry._registry)
async def test_unregister(self): # register a service idx = Service.register("StorageService2", "Storage", "127.0.0.1", 8888, 1888) assert str(uuid.UUID(idx, version=4)) == idx # deregister the same t = Service.unregister(idx) assert idx == t with pytest.raises(DoesNotExist) as excinfo: Service.get(idx) assert str(excinfo).endswith('DoesNotExist')
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')
def test_register_with_same_name(self): """raise AlreadyExistsWithTheSameName""" ServiceRegistry.register("A name", "Storage", "127.0.0.1", 1, 2, 'http') assert 1 == len(ServiceRegistry._registry) with pytest.raises(Exception) as excinfo: with patch.object(ServiceRegistry._logger, 'info') as log_i: ServiceRegistry.register("A name", "Storage", "127.0.0.2", 3, 4, 'http') assert 0 == log_i.call_count assert excinfo.type is AlreadyExistsWithTheSameName assert 1 == len(ServiceRegistry._registry)
async def test_duplicate_address_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(AlreadyExistsWithTheSameAddressAndPort) as excinfo: Service.register("StorageService2", "Storage", "127.0.0.1", 9999, 1998) assert str(excinfo).endswith('AlreadyExistsWithTheSameAddressAndPort')
async def get_plugin(request): """ GET lists of rule plugins and delivery plugins :Example: curl -X GET http://localhost:8081/foglamp/notification/plugin """ try: notification_service = ServiceRegistry.get( s_type=ServiceRecord.Type.Notification.name) _address, _port = notification_service[ 0]._address, notification_service[0]._port except service_registry_exceptions.DoesNotExist: raise web.HTTPNotFound(reason="No Notification service available.") try: url = 'http://{}:{}/notification/rules'.format(_address, _port) rule_plugins = json.loads(await _hit_get_url(url)) url = 'http://{}:{}/notification/delivery'.format(_address, _port) delivery_plugins = json.loads(await _hit_get_url(url)) except Exception as ex: raise web.HTTPInternalServerError(reason=ex) else: return web.json_response({ 'rules': rule_plugins, 'delivery': delivery_plugins })
def stop_storage(cls): """ stop Storage service """ # TODO: FOGL-653 shutdown implementation # remove me, and allow this call in service registry API found_services = ServiceRegistry.get(name="FogLAMP Storage") svc = found_services[0] if svc is None: return management_api_url = '{}:{}'.format(svc._address, svc._management_port) conn = http.client.HTTPConnection(management_api_url) # TODO: need to set http / https based on service protocol conn.request('POST', url='/foglamp/service/shutdown', body=None) r = conn.getresponse() # TODO: FOGL-615 # log error with message if status is 4xx or 5xx if r.status in range(400, 500): _logger.error("Client error code: %d", r.status) if r.status in range(500, 600): _logger.error("Server error code: %d", r.status) res = r.read().decode() conn.close() return json.loads(res)
async def _services_with_assets(storage_client, south_services): sr_list = list() try: try: services_from_registry = ServiceRegistry.get(s_type="Southbound") except DoesNotExist: services_from_registry = [] def is_svc_in_service_registry(name): return next( (svc for svc in services_from_registry if svc._name == name), None) for s_record in services_from_registry: sr_list.append({ 'name': s_record._name, 'address': s_record._address, 'management_port': s_record._management_port, 'service_port': s_record._port, 'protocol': s_record._protocol, 'status': ServiceRecord.Status(int(s_record._status)).name.lower(), 'assets': await _get_tracked_assets_and_readings(storage_client, s_record._name), 'schedule_enabled': await _get_schedule_status(storage_client, s_record._name) }) for s_name in south_services: south_svc = is_svc_in_service_registry(s_name) if not south_svc: sr_list.append({ 'name': s_name, 'address': '', 'management_port': '', 'service_port': '', 'protocol': '', 'status': '', 'assets': await _get_tracked_assets_and_readings(storage_client, s_name), 'schedule_enabled': await _get_schedule_status(storage_client, s_name) }) except: raise else: return sr_list
def _register_core(cls, host, mgt_port, service_port): core_service_id = ServiceRegistry.register(name="FogLAMP Core", s_type="Core", address=host, port=service_port, management_port=mgt_port) return core_service_id
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_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>')