diff --git a/docker/run_jormungandr.sh b/docker/run_jormungandr.sh index fa226b8ea9..df1caab960 100755 --- a/docker/run_jormungandr.sh +++ b/docker/run_jormungandr.sh @@ -66,7 +66,8 @@ fi if [ $monitor_processes -eq 1 ] then echo "!!!!!!!!!!!!!!!!!!!!! Start Jormungandr with monitoring service !!!!!!!!!!!!!!!!!!!!!" - uwsgi --cache2 $jormungandr_cache2 $max_requests --http :9090 --stats :5050 --lazy-apps --file $file & uwsgi --cache2 $monitor_cache2 --http :9091 --lazy-apps --file $file --processes 1 --listen 5 + # JORMUNGANDR_IS_PUBLIC is set to True only for the use of /v1/backends_status + uwsgi --cache2 $jormungandr_cache2 $max_requests --http :9090 --stats :5050 --lazy-apps --file $file & JORMUNGANDR_IS_PUBLIC=True uwsgi --cache2 $monitor_cache2 --http :9091 --lazy-apps --file $file --processes 1 --listen 5 else echo "!!!!!!!!!!!!!!!!!!!!! Start Jormungandr without monitoring service !!!!!!!!!!!!!!!!!!!!!" uwsgi --cache2 $jormungandr_cache2 $max_requests --http :9090 --stats :5050 --lazy-apps --file $file diff --git a/source/jormungandr/jormungandr/default_settings.py b/source/jormungandr/jormungandr/default_settings.py index f6c773a581..3c136046cf 100644 --- a/source/jormungandr/jormungandr/default_settings.py +++ b/source/jormungandr/jormungandr/default_settings.py @@ -412,3 +412,5 @@ USE_EXCLUDED_ZONES = boolean(os.getenv('JORMUNGANDR_USE_EXCLUDED_ZONES', False)) ASGARD_S3_BUCKET = os.getenv('JORMUNGANDR_ASGARD_S3_BUCKET', '') + +BACKENDS_STATUS_GREENLET_POOL_SIZE = os.getenv('JORMUNGANDR_BACKENDS_STATUS_GREENLET_POOL_SIZE', 10) diff --git a/source/jormungandr/jormungandr/instance.py b/source/jormungandr/jormungandr/instance.py index 975bdc5f2e..a40deffba0 100644 --- a/source/jormungandr/jormungandr/instance.py +++ b/source/jormungandr/jormungandr/instance.py @@ -904,6 +904,9 @@ def get_pt_planner(self, pt_planner_id=None): pt_planner_id = pt_planner_id or self.default_pt_planner return self._pt_planner_manager.get_pt_planner(pt_planner_id) + def get_all_pt_planners(self): + return self._pt_planner_manager.get_all_pt_planners() + def get_pt_journey_fare(self, loki_pt_journey_fare_id=None): pt_journey_fare_id = loki_pt_journey_fare_id or self.loki_pt_journey_fare return self._pt_journey_fare_backend_manager.get_pt_journey_fare(pt_journey_fare_id) diff --git a/source/jormungandr/jormungandr/interfaces/v1/backends_status.py b/source/jormungandr/jormungandr/interfaces/v1/backends_status.py new file mode 100644 index 0000000000..de86395aab --- /dev/null +++ b/source/jormungandr/jormungandr/interfaces/v1/backends_status.py @@ -0,0 +1,82 @@ +from jormungandr import i_manager +from jormungandr.protobuf_to_dict import protobuf_to_dict +from jormungandr.interfaces.v1.StatedResource import StatedResource +from navitiacommon import type_pb2, request_pb2 +from jormungandr import exceptions +from collections import defaultdict +import gevent, gevent.pool +from jormungandr import app + + +class BackendsStatus(StatedResource): + def __init__(self, *args, **kwargs): + super().__init__(self, *args, **kwargs) + + def get(self): + regions = i_manager.get_regions() + + response = { + 'lokis': {}, + 'krakens': {}, + 'errors': [], + } + + pool = gevent.pool.Pool(app.config.get('BACKENDS_STATUS_GREENLET_POOL_SIZE', 10)) + + def do(instance_name, pt_planner_type, pt_planner, req): + try: + resp = pt_planner.send_and_receive(req, request_id='backend_status') + status = protobuf_to_dict(resp, use_enum_labels=True) + is_loaded = status.get('status', {}).get('loaded') is True + if not is_loaded: + return instance_name, pt_planner_type, status, 'data is not loaded' + return instance_name, pt_planner_type, status, None + except exceptions.DeadSocketException as e: + return ( + instance_name, + pt_planner_type, + None, + 'instance {} backend {} did not respond because: {}'.format( + instance_name, pt_planner_type, str(e) + ), + ) + + futures = [] + + req = request_pb2.Request() + req.requested_api = type_pb2.STATUS + + for key_region in regions: + instance = i_manager.instances[key_region] + for pt_planner_type, pt_planner in instance.get_all_pt_planners(): + futures.append(pool.spawn(do, instance.name, pt_planner_type, pt_planner, req)) + + found_err = False + status_code = 200 + + for future in gevent.iwait(futures): + instance_name, pt_planner_type, status, err = future.get() + found_err |= err is not None + if err: + response['errors'].append( + {'backend_type': pt_planner_type, 'backend_name': instance_name, 'error': err} + ) + + key = 'krakens' if pt_planner_type == 'kraken' else 'lokis' + status_ = status.get('status', {}) if status is not None else {} + response[key][instance_name] = { + 'status': status_.get('status'), + 'backend_version': status_.get('navitia_version'), + 'start_date': status_.get('start_production_date'), + 'end_date': status_.get('end_production_date'), + 'loaded': status_.get('loaded'), + 'last_load_at': status_.get('last_load_at'), + 'last_load_status': status_.get('last_load_status'), + 'is_realtime_loaded': status_.get('is_realtime_loaded'), + 'last_rt_data_loaded': status_.get('last_rt_data_loaded'), + } + + if found_err: + status_code = 503 + + return response, status_code diff --git a/source/jormungandr/jormungandr/modules/v1_routing/v1_routing.py b/source/jormungandr/jormungandr/modules/v1_routing/v1_routing.py index a4f86c8b64..fd2dfd85ed 100644 --- a/source/jormungandr/jormungandr/modules/v1_routing/v1_routing.py +++ b/source/jormungandr/jormungandr/modules/v1_routing/v1_routing.py @@ -54,6 +54,7 @@ users, opg_status, opg_excluded_zones, + backends_status, ) from werkzeug.routing import BaseConverter, FloatConverter, PathConverter from jormungandr.modules_loader import AModule @@ -131,6 +132,8 @@ def setup(self): self.add_resource(Readyness.Readyness, '/readyness', endpoint='readyness') self.module_resources_manager.register_resource(Index.TechnicalStatus()) self.add_resource(Index.TechnicalStatus, '/status', endpoint='technical_status') + self.add_resource(backends_status.BackendsStatus, '/backends_status', endpoint='backends_status') + lon_lat = ';/' coverage = '/coverage/' region = coverage + '/' diff --git a/source/jormungandr/tests/end_point_tests.py b/source/jormungandr/tests/end_point_tests.py index f3c2030571..5077f94e59 100644 --- a/source/jormungandr/tests/end_point_tests.py +++ b/source/jormungandr/tests/end_point_tests.py @@ -301,3 +301,25 @@ def test_parameters_in_one_status(self): assert parameters['max_bss_direct_path_duration'] == 86400 assert parameters['max_car_direct_path_duration'] == 86400 assert parameters['max_ridesharing_direct_path_duration'] == 86400 + + def test_backends_status(self): + json_response = self.query("/v1/backends_status") + + assert len(json_response['lokis']) == 2 + assert len(json_response['krakens']) == 2 + + for attribute in [ + 'status', + 'backend_version', + 'start_date', + 'end_date', + 'loaded', + 'last_load_at', + 'last_load_status', + 'is_realtime_loaded', + 'last_rt_data_loaded', + ]: + assert attribute in json_response['lokis']['main_ptref_test'] + assert attribute in json_response['krakens']['main_ptref_test'] + assert attribute in json_response['lokis']['main_routing_test'] + assert attribute in json_response['krakens']['main_routing_test']