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', [])
        ]
Exemple #2
0
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))
Exemple #3
0
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
Exemple #4
0
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))
Exemple #5
0
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
Exemple #6
0
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']]