def flights(): if 'view' not in flask.request.args: return flask.jsonify( rid.ErrorResponse( message='Missing required "view" parameter')), 400 try: view = geo.make_latlng_rect(flask.request.args['view']) except ValueError as e: return flask.jsonify( rid.ErrorResponse(message='Error parsing view: {}'.format(e))), 400 include_recent_positions = flask.request.args.get( 'include_recent_positions', 'False').lower() == 'true' diagonal = view.lo().get_distance( view.hi()).degrees * geo.EARTH_CIRCUMFERENCE_KM / 360 if diagonal > rid.NetMaxDisplayAreaDiagonal: msg = 'Requested diagonal of {} km exceeds limit of {} km'.format( diagonal, rid.NetMaxDisplayAreaDiagonal) return flask.jsonify(rid.ErrorResponse(message=msg)), 413 now = arrow.utcnow().datetime flights = [] for test_id, record in db.tests.items(): for flight in record.flights: reported_flight = _get_report(flight, now, view, include_recent_positions) if reported_flight is not None: flights.append(reported_flight) return flask.jsonify( rid.GetFlightsResponse(timestamp=StringBasedDateTime(now), flights=flights)), 200
def create_test(test_id: str) -> Tuple[str, int]: """Implements test creation in RID automated testing injection API.""" try: json = flask.request.json if json is None: raise ValueError('Request did not contain a JSON payload') req_body: injection_api.CreateTestParameters = ImplicitDict.parse(json, injection_api.CreateTestParameters) record = database.TestRecord(version=str(uuid.uuid4()), flights=req_body.requested_flights) except ValueError as e: msg = 'Create test {} unable to parse JSON: {}'.format(test_id, e) return msg, 400 # Create ISA in DSS (t0, t1) = req_body.get_span() t1 += RECENT_POSITIONS_BUFFER rect = req_body.get_rect() flights_url = '{}/v1/uss/flights'.format(webapp.config.get(config.KEY_BASE_URL)) mutated_isa = mutate.put_isa(resources.dss_client, rect, t0, t1, flights_url, record.version) if not mutated_isa.dss_response.success: response = rid.ErrorResponse(message='Unable to create ISA in DSS') response['errors'] = mutated_isa.dss_response.errors return flask.jsonify(response), 412 record.isa_version = mutated_isa.dss_response.isa.version for (url, notification) in mutated_isa.notifications.items(): code = notification.response.status_code if code != 204 and code != 200: pass #TODO: Log notification failures (maybe also log incorrect 200s) db.tests[test_id] = record return flask.jsonify(injection_api.ChangeTestResponse(version=record.version, injected_flights=record.flights))
def delete_test(test_id: str) -> Tuple[str, int]: """Implements test deletion in RID automated testing injection API.""" record = db.value.tests.get(test_id, None) if record is None: return 'Test "{}" not found'.format(test_id), 404 # Delete ISA from DSS deleted_isa = mutate.delete_isa(resources.utm_client, record.version, record.isa_version) if not deleted_isa.dss_response.success: response = rid.ErrorResponse(message='Unable to delete ISA from DSS') response['errors'] = deleted_isa.dss_response.errors return flask.jsonify(response), 412 for (url, notification) in deleted_isa.notifications.items(): code = notification.response.status_code if code != 204 and code != 200: pass #TODO: Log notification failures (maybe also log incorrect 200s) with db as tx: del tx.tests[test_id] return flask.jsonify( injection_api.ChangeTestResponse(version=record.version, injected_flights=record.flights))
def flight_details(id: str): now = arrow.utcnow().datetime tx = db.value for test_id, record in tx.tests.items(): for flight in record.flights: details = flight.get_details(now) if details and details.id == id: return flask.jsonify(rid.GetFlightDetailsResponse(details=details)), 200 return flask.jsonify(rid.ErrorResponse(message='Flight {} not found'.format(id))), 404
def poll_display_data(dp_id: str) -> Tuple[str, int]: """Implements display data polling in RID automated testing observation API.""" # TODO: Validate token signature & scope # Retrieve view parameters if 'view' not in flask.request.args: return 'Missing "view" argument in request', 400 try: coords = [float(v) for v in flask.request.args['view'].split(',')] except ValueError as e: return '"view" argument not properly formatted: {}'.format(e), 400 if len(coords) != 4: return '"view" argument contains the wrong number of coordinates (expected 4, found {})'.format(len(coords)), 400 lat_min = min(coords[0], coords[2]) lat_max = max(coords[0], coords[2]) lng_min = min(coords[1], coords[3]) lng_max = max(coords[1], coords[3]) if (lat_min < -90 or lat_min >= 90 or lat_max <= -90 or lat_max > 90 or lng_min < -180 or lng_min >= 360 or lng_max <= -180 or lng_max > 360): return '"view" coordinates do not fall within the valid range of -90 <= lat <= 90 and -180 <= lng <= 360', 400 # Check view size view_min = s2sphere.LatLng.from_degrees(lat_min, lng_min) view_max = s2sphere.LatLng.from_degrees(lat_max, lng_max) diagonal = view_min.get_distance(view_max).degrees * geo.EARTH_CIRCUMFERENCE_KM / 360 if diagonal > 3.6: return flask.jsonify(rid.ErrorResponse(message='Requested diagonal was too large')), 413 # Get Display Provider behavior dp_behavior = db.get_dp(dp_id).behavior # Find flights to report t_now = datetime.datetime.now(datetime.timezone.utc) t_earliest = t_now - datetime.timedelta(seconds=60) flights: List[observation_api.Flight] = [] for sp_id, sp in db.sps.items(): if sp_id in dp_behavior.do_not_display_flights_from: continue sp_behavior = db.get_sp(sp_id).behavior for test_id, test in sp.tests.items(): for flight in test.flights: flights.append(_make_api_flight( flight, sp_behavior, dp_behavior, t_earliest, t_now, lat_min, lng_min, lat_max, lng_max)) flights = [flight for flight in flights if 'most_recent_position' in flight] if diagonal <= 1: return flask.jsonify(observation_api.GetDisplayDataResponse(flights=flights)) else: return flask.jsonify(observation_api.GetDisplayDataResponse(clusters=clustering.make_clusters(flights, view_min, view_max)))
def display_data() -> Tuple[str, int]: """Implements retrieval of current display data per automated testing API.""" if 'view' not in flask.request.args: return flask.jsonify( rid.ErrorResponse( message='Missing required "view" parameter')), 400 try: view = geo.make_latlng_rect(flask.request.args['view']) except ValueError as e: return flask.jsonify( rid.ErrorResponse(message='Error parsing view: {}'.format(e))), 400 # Determine what kind of response to produce diagonal = geo.get_latlngrect_diagonal_km(view) if diagonal > rid.NetMaxDisplayAreaDiagonal: return flask.jsonify( rid.ErrorResponse(message='Requested diagonal was too large')), 413 # Get ISAs in the DSS t = arrow.utcnow().datetime isas_response: fetch.FetchedISAs = fetch.isas(resources.utm_client, view, t, t) if not isas_response.success: response = rid.ErrorResponse(message='Unable to fetch ISAs from DSS') response['errors'] = [isas_response] return flask.jsonify(response), 412 # Fetch flights from each unique flights URL validated_flights: List[rid.RIDFlight] = [] tx = db.value flight_info: Dict[str, database.FlightInfo] = { k: v for k, v in tx.flights.items() } for flights_url in isas_response.flight_urls: flights_response = fetch.flights(resources.utm_client, flights_url, view, True) if not flights_response.success: response = rid.ErrorResponse( message='Error querying {}'.format(flights_url)) response['errors'] = [flights_response] return flask.jsonify(response), 412 for flight in flights_response.flights: try: validated_flight: rid.RIDFlight = ImplicitDict.parse( flight, rid.RIDFlight) except ValueError as e: response = rid.ErrorResponse( message='Error parsing flight from {}'.format(flights_url)) response['parse_error'] = str(e) response['flights'] = flights_response.flights return flask.jsonify(response), 412 validated_flights.append(validated_flight) flight_info[flight.id] = database.FlightInfo( flights_url=flights_url) if diagonal <= rid.NetDetailsMaxDisplayAreaDiagonal: # Construct detailed flights response response = observation_api.GetDisplayDataResponse(flights=[ _make_flight_observation(f, view) for f in validated_flights ]) with db as tx: for k, v in flight_info.items(): tx.flights[k] = v return flask.jsonify(response) else: # Construct clusters response return 'Cluster response not yet implemented', 500