def test_constraint_does_not_exist_query(ids, scd_api, scd_session): if scd_session is None: return time_now = datetime.datetime.utcnow() if scd_api == scd.API_0_3_5: auths = (SCOPE_SC, SCOPE_CI, SCOPE_CM) elif scd_api == scd.API_0_3_17: auths = (SCOPE_CM, SCOPE_CP) for scope in auths: resp = scd_session.post('/constraint_references/query', json={ 'area_of_interest': scd.make_vol4( time_now, time_now, 0, 5000, scd.make_circle(-56, 178, 300)) }, scope=scope) assert resp.status_code == 200, resp.content assert ids(CONSTRAINT_TYPE) not in [ constraint['id'] for constraint in resp.json().get('constraint_references', []) ]
def poll_scd_entities(resources: ResourceSet, resource_name: str, dss_resource_name: str, uss_resource_name: str) -> PollResult: # Query DSS for Entities in 4D volume of interest request_body = { 'area_of_interest': scd.make_vol4(resources.start_time, resources.end_time, 0, 3048, polygon=scd.make_polygon(latlngrect=resources.area)) } url = '/dss/v1/{}/query'.format(dss_resource_name) t0 = datetime.datetime.utcnow() resp = resources.dss_client.post(url, json=request_body, scope=scd.SCOPE_SC) t1 = datetime.datetime.utcnow() # Handle any errors if resp.status_code != 200: return PollResult( t0, t1, error=PollError( resp, 'Failed to search {}s in DSS'.format(resource_name))) try: ref_json = resp.json() except ValueError: return PollResult( t0, t1, error=PollError( resp, 'DSS response to search {}s was not valid JSON'.format( resource_name))) entity_ref_list = ref_json.get(dss_resource_name, []) for entity_ref in entity_ref_list: if 'id' not in entity_ref: return PollResult( t0, t1, error=PollError( resp, 'DSS response to search {}s included {} without id'.format( resource_name, resource_name))) if 'owner' not in entity_ref: return PollResult( t0, t1, error=PollError( resp, 'DSS response to search {} included {} without owner'. format(resource_name, resource_name))) if 'uss_base_url' not in entity_ref: return PollResult( t0, t1, error=PollError( resp, 'DSS response to search {} included {} without uss_base_url' .format(resource_name, resource_name))) # Obtain details for all Entities (using cache when appropriate) if resource_name not in resources.scd_cache: resources.scd_cache[resource_name] = {} cache = resources.scd_cache[resource_name] entities = {} for entity_ref in entity_ref_list: entity_key = '{} ({})'.format(entity_ref['id'], entity_ref['owner']) if (entity_key in cache and cache[entity_key]['dss']['reference'] == entity_ref and 'error' not in cache[entity_key]['uss']): # Entity reference data in DSS is identical to the cached reference; do # not re-retrieve Entity details from USS entities[entity_key] = cache[entity_key] continue entities[entity_key] = {'dss': {'reference': entity_ref}} # Query the USS for Entity details details_url = entity_ref['uss_base_url'] + '/uss/v1/{}/{}'.format( uss_resource_name, entity_ref['id']) t2 = datetime.datetime.utcnow() details_resp = resources.dss_client.get(details_url, scope=scd.SCOPE_SC) t3 = datetime.datetime.utcnow() # Handle any errors details_error_condition = None try: details_json = details_resp.json() except ValueError: details_json = None details_error_condition = 'did not return valid JSON' if resp.status_code != 200: details_error_condition = 'indicated failure' if 'operation' in details_json: if not details_error_condition: if 'reference' not in details_json['operation']: details_error_condition = 'did not contain reference field' if 'details' not in details_json['operation']: details_error_condition = 'did not contain details field' else: details_error_condition = 'did not contain operation field' if details_error_condition: entities[entity_key]['uss'] = {} entities[entity_key]['uss']['error'] = { 'description': 'USS query for {} details {}'.format(resource_name, details_error_condition), 'url': details_url, 'code': resp.status_code, } if details_json is not None: entities[entity_key]['uss']['error']['json'] = details_json else: entities[entity_key]['uss']['error'][ 'body'] = details_resp.content continue # Record details, and information about querying details, in the result entities[entity_key]['uss'] = details_json entities[entity_key]['uss']['tracer'] = { 'time_queried': t2.isoformat(), 'dt_s': round((t3 - t2).total_seconds(), 2), } # Cache the full result for this Entity cache[entity_key] = entities[entity_key] return PollResult(t0, t1, success=PollSuccess(entities))
def test_search_time(ids, scd_api, scd_session): time_start = datetime.datetime.utcnow() time_end = time_start + datetime.timedelta(minutes=1) resp = scd_session.post('/subscriptions/query', json={ "area_of_interest": scd.make_vol4( time_start, time_end, 0, 3000, scd.make_circle(LAT0, LNG0, FOOTPRINT_SPACING_M)) }) assert resp.status_code == 200, resp.content result_ids = [x['id'] for x in resp.json()['subscriptions']] assert ids(SUB1_TYPE) in result_ids assert ids(SUB2_TYPE) not in result_ids assert ids(SUB3_TYPE) not in result_ids resp = scd_session.post('/subscriptions/query', json={ "area_of_interest": scd.make_vol4( None, time_end, 0, 3000, scd.make_circle(LAT0, LNG0, FOOTPRINT_SPACING_M)) }) assert resp.status_code == 200, resp.content result_ids = [x['id'] for x in resp.json()['subscriptions']] assert ids(SUB1_TYPE) in result_ids assert ids(SUB2_TYPE) not in result_ids assert ids(SUB3_TYPE) not in result_ids time_start = datetime.datetime.utcnow() + datetime.timedelta(hours=4) time_end = time_start + datetime.timedelta(minutes=1) resp = scd_session.post('/subscriptions/query', json={ "area_of_interest": scd.make_vol4( time_start, time_end, 0, 3000, scd.make_circle(LAT0, LNG0, FOOTPRINT_SPACING_M)) }) assert resp.status_code == 200, resp.content result_ids = [x['id'] for x in resp.json()['subscriptions']] assert ids(SUB1_TYPE) not in result_ids assert ids(SUB2_TYPE) not in result_ids assert ids(SUB3_TYPE) in result_ids resp = scd_session.post('/subscriptions/query', json={ "area_of_interest": scd.make_vol4( time_start, None, 0, 3000, scd.make_circle(LAT0, LNG0, FOOTPRINT_SPACING_M)) }) assert resp.status_code == 200, resp.content result_ids = [x['id'] for x in resp.json()['subscriptions']] assert ids(SUB1_TYPE) not in result_ids assert ids(SUB2_TYPE) not in result_ids assert ids(SUB3_TYPE) in result_ids
def inject_flight(flight_id: str) -> Tuple[str, int]: """Implements flight injection in SCD automated testing injection API.""" try: json = flask.request.json if json is None: raise ValueError('Request did not contain a JSON payload') req_body: InjectFlightRequest = ImplicitDict.parse( json, InjectFlightRequest) except ValueError as e: msg = 'Create flight {} unable to parse JSON: {}'.format(flight_id, e) return msg, 400 if webapp.config[config.KEY_BEHAVIOR_LOCALITY].is_uspace_applicable: # Validate flight authorisation flight_auth = req_body.flight_authorisation if not flight_auth.uas_serial_number.valid: return flask.jsonify( InjectFlightResponse(result=InjectFlightResult.Rejected, notes='Invalid serial number')) if not flight_auth.operator_id.valid: return flask.jsonify( InjectFlightResponse(result=InjectFlightResult.Rejected, notes='Invalid operator ID')) if flight_auth.uas_class == scd_injection_api.UASClass.Other: return flask.jsonify( InjectFlightResponse(result=InjectFlightResult.Rejected, notes='Invalid UAS class')) if flight_auth.operation_category == scd_injection_api.OperationCategory.Unknown: return flask.jsonify( InjectFlightResponse(result=InjectFlightResult.Rejected, notes='Invalid operation category')) if flight_auth.endurance_minutes < 1 or flight_auth.endurance_minutes > 10 * 24 * 60: return flask.jsonify( InjectFlightResponse(result=InjectFlightResult.Rejected, notes='Invalid endurance')) if sum(1 if len(m) > 0 else 0 for m in flight_auth.connectivity_methods) == 0: return flask.jsonify( InjectFlightResponse(result=InjectFlightResult.Rejected, notes='Invalid connectivity methods')) if sum(1 if len(m) > 0 else 0 for m in flight_auth.identification_technologies) == 0: return flask.jsonify( InjectFlightResponse( result=InjectFlightResult.Rejected, notes='Invalid identification technologies')) try: urlparse(flight_auth.emergency_procedure_url) except ValueError: return flask.jsonify( InjectFlightResponse(result=InjectFlightResult.Rejected, notes='Invalid emergency procedure URL')) # Check for operational intents in the DSS start_time = scd.start_of(req_body.operational_intent.volumes) end_time = scd.end_of(req_body.operational_intent.volumes) area = scd.rect_bounds_of(req_body.operational_intent.volumes) alt_lo, alt_hi = scd.meter_altitude_bounds_of( req_body.operational_intent.volumes) vol4 = scd.make_vol4(start_time, end_time, alt_lo, alt_hi, polygon=scd.make_polygon(latlngrect=area)) try: op_intents = query_operational_intents(vol4) except (ValueError, scd_client.OperationError, requests.exceptions.ConnectionError, ConnectionError) as e: notes = 'Error querying operational intents: {}'.format(e) return flask.jsonify( InjectFlightResponse(result=InjectFlightResult.Failed, notes=notes)), 200 # Check for intersections v1 = req_body.operational_intent.volumes for op_intent in op_intents: if req_body.operational_intent.priority > op_intent.details.priority: continue if webapp.config[ config. KEY_BEHAVIOR_LOCALITY].allow_same_priority_intersections: continue v2a = op_intent.details.volumes v2b = op_intent.details.off_nominal_volumes if scd.vol4s_intersect(v1, v2a) or scd.vol4s_intersect(v1, v2b): notes = 'Requested flight intersected {}\'s operational intent {}'.format( op_intent.reference.manager, op_intent.reference.id) return flask.jsonify( InjectFlightResponse( result=InjectFlightResult.ConflictWithFlight, notes=notes)), 200 # Create operational intent in DSS base_url = '{}/mock/scd'.format(webapp.config[config.KEY_BASE_URL]) req = scd.PutOperationalIntentReferenceParameters( extents=req_body.operational_intent.volumes, key=[op.reference.ovn for op in op_intents], state=req_body.operational_intent.state, uss_base_url=base_url, new_subscription=scd.ImplicitSubscriptionParameters( uss_base_url=base_url)) id = str(uuid.uuid4()) try: result = scd_client.create_operational_intent_reference( resources.utm_client, id, req) except (ValueError, scd_client.OperationError, requests.exceptions.ConnectionError, ConnectionError) as e: notes = 'Error creating operational intent: {}'.format(e) return flask.jsonify( InjectFlightResponse(result=InjectFlightResult.Failed, notes=notes)), 200 scd_client.notify_subscribers( resources.utm_client, result.operational_intent_reference.id, scd.OperationalIntent(reference=result.operational_intent_reference, details=req_body.operational_intent), result.subscribers) # Store flight in database record = database.FlightRecord( op_intent_reference=result.operational_intent_reference, op_intent_injection=req_body.operational_intent, flight_authorisation=req_body.flight_authorisation) with db as tx: tx.flights[flight_id] = record return flask.jsonify( InjectFlightResponse(result=InjectFlightResult.Planned, operational_intent_id=id))
def clear_area() -> Tuple[str, int]: try: json = flask.request.json if json is None: raise ValueError('Request did not contain a JSON payload') req = ImplicitDict.parse(json, ClearAreaRequest) except ValueError as e: msg = 'Unable to parse ClearAreaRequest JSON request: {}'.format(e) return msg, 400 # Find operational intents in the DSS start_time = scd.start_of([req.extent]) end_time = scd.end_of([req.extent]) area = scd.rect_bounds_of([req.extent]) alt_lo, alt_hi = scd.meter_altitude_bounds_of([req.extent]) vol4 = scd.make_vol4(start_time, end_time, alt_lo, alt_hi, polygon=scd.make_polygon(latlngrect=area)) try: op_intent_refs = scd_client.query_operational_intent_references( resources.utm_client, vol4) except (ValueError, scd_client.OperationError, requests.exceptions.ConnectionError, ConnectionError) as e: msg = 'Error querying operational intents: {}'.format(e) return flask.jsonify( ClearAreaResponse(outcome=ClearAreaOutcome( success=False, message=msg, timestamp=StringBasedDateTime(datetime.utcnow())), request=req)), 200 # Try to delete every operational intent found dss_deletion_results = {} deleted = set() for op_intent_ref in op_intent_refs: try: scd_client.delete_operational_intent_reference( resources.utm_client, op_intent_ref.id, op_intent_ref.ovn) dss_deletion_results[op_intent_ref.id] = 'Deleted from DSS' deleted.add(op_intent_ref.id) except scd_client.OperationError as e: dss_deletion_results[op_intent_ref.id] = str(e) # Delete corresponding flight injections and cached operational intents with db as tx: flights_to_delete = [] for flight_id, record in tx.flights.items(): if record.op_intent_reference.id in deleted: flights_to_delete.append(flight_id) for flight_id in flights_to_delete: del tx.flights[flight_id] cache_deletions = [] for op_intent_id in deleted: if op_intent_id in tx.cached_operations: del tx.cached_operations[op_intent_id] cache_deletions.append(op_intent_id) msg = yaml.dump({ 'dss_deletions': dss_deletion_results, 'flight_deletions': flights_to_delete, 'cache_deletions': cache_deletions, }) return flask.jsonify( ClearAreaResponse(outcome=ClearAreaOutcome( success=True, message=msg, timestamp=StringBasedDateTime(datetime.utcnow())), request=req)), 200
def test_get_deleted_op_by_search(ids, scd_api, scd_session): resp = scd_session.post('/operational_intent_references/query', json={ 'area_of_interest': scd.make_vol4(None, None, 0, 5000, scd.make_circle(-56, 178, 300)) }) assert resp.status_code == 200, resp.content assert ids(OP_TYPE) not in [x['id'] for x in resp.json()['operational_intent_references']]