Ejemplo n.º 1
0
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
Ejemplo n.º 2
0
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))
Ejemplo n.º 3
0
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))
Ejemplo n.º 4
0
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
Ejemplo n.º 5
0
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)))
Ejemplo n.º 6
0
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