示例#1
0
def test_big_operation_search(scd_api, scd_session):
    with open('./scd/resources/op_big_operation.json', 'r') as f:
        req = json.load(f)
    dt = datetime.datetime.utcnow() - scd.start_of([req['area_of_interest']])
    req['area_of_interest'] = scd.offset_time([req['area_of_interest']], dt)[0]
    resp = scd_session.post('/operational_intent_references/query', json=req)
    assert resp.status_code == 400, resp.content
def test_big_operation_search(scd_session):
  """
  This test reproduces a case where a search resulted in 503 because the
  underlying gRPC backend had crashed.
  """
  with open('./scd/resources/op_big_operation.json', 'r') as f:
    req = json.load(f)
  dt = datetime.datetime.utcnow() - scd.start_of([req['area_of_interest']])
  req['area_of_interest'] = scd.offset_time([req['area_of_interest']], dt)[0]
  resp = scd_session.post('/operation_references/query', json=req)
  assert  resp.status_code == 400, resp.content
示例#3
0
def test_missing_conflicted_operation(ids, scd_api, scd_session):
    # Emplace the initial version of Operation 1
    with open('./scd/resources/op_missing_initial.yaml', 'r') as f:
        req = yaml.full_load(f)
    dt = datetime.datetime.utcnow() - scd.start_of(req['extents'])
    req['extents'] = scd.offset_time(req['extents'], dt)
    resp = scd_session.put('/operational_intent_references/{}'.format(
        ids(OP_TYPE)),
                           json=req)
    assert resp.status_code == 200, resp.content
    ovn1a = resp.json()['operational_intent_reference']['ovn']
    sub_id = resp.json()['operational_intent_reference']['subscription_id']

    # Emplace the pre-existing Operation that conflicted in the original observation
    with open('./scd/resources/op_missing_preexisting_unknown.yaml', 'r') as f:
        req = yaml.full_load(f)
    req['extents'] = scd.offset_time(req['extents'], dt)
    req['key'] = [ovn1a]
    resp = scd_session.put('/operational_intent_references/{}'.format(
        ids(OP_TYPE2)),
                           json=req)
    assert resp.status_code == 200, resp.content

    # Attempt to update Operation 1 without OVN for the pre-existing Operation
    with open('./scd/resources/op_missing_update.json', 'r') as f:
        req = json.load(f)
    req['extents'] = scd.offset_time(req['extents'], dt)
    req['key'] = [ovn1a]
    req['subscription_id'] = sub_id
    resp = scd_session.put('/operational_intent_references/{}/{}'.format(
        ids(OP_TYPE), ovn1a),
                           json=req)
    assert resp.status_code == 409, resp.content

    # checking entity conflicts
    conflicts = []
    data = resp.json()
    assert 'missing_operational_intents' in data
    assert ids(OP_TYPE2) in [
        intent['id'] for intent in data['missing_operational_intents']
    ], resp.content

    # Perform an area-based query on the area occupied by Operation 1
    with open('./scd/resources/op_missing_query.json', 'r') as f:
        req = json.load(f)
    req['area_of_interest'] = scd.offset_time([req['area_of_interest']], dt)[0]
    resp = scd_session.post('/operational_intent_references/query', json=req)
    assert resp.status_code == 200, resp.content
    ops = [op['id'] for op in resp.json()['operational_intent_references']]
    assert ids(OP_TYPE) in ops, resp.content

    # ids(OP_ID2) not expected here because its ceiling is <575m whereas query floor is
    # >591m.
    assert ids(OP_TYPE2) not in ops, resp.content
def test_missing_conflicted_operation(scd_session):
  """
  This test reproduces a case where a conflicting Operation did not appear in a
  follow-up area-based query for Operations.
  """
  # Emplace the initial version of Operation 1
  with open('./scd/resources/op_missing_initial.yaml', 'r') as f:
    req = yaml.full_load(f)
  dt = datetime.datetime.utcnow() - scd.start_of(req['extents'])
  req['extents'] = scd.offset_time(req['extents'], dt)
  resp = scd_session.put('/operation_references/{}'.format(OP_ID), json=req)
  assert resp.status_code == 200, resp.content
  ovn1a = resp.json()['operation_reference']['ovn']
  sub_id = resp.json()['operation_reference']['subscription_id']

  # Emplace the pre-existing Operation that conflicted in the original observation
  with open('./scd/resources/op_missing_preexisting_unknown.yaml', 'r') as f:
    req = yaml.full_load(f)
  req['extents'] = scd.offset_time(req['extents'], dt)
  req['key'] = [ovn1a]
  resp = scd_session.put('/operation_references/{}'.format(OP_ID2), json=req)
  assert resp.status_code == 200, resp.content

  # Attempt to update Operation 1 without OVN for the pre-existing Operation
  with open('./scd/resources/op_missing_update.json', 'r') as f:
    req = json.load(f)
  req['extents'] = scd.offset_time(req['extents'], dt)
  req['key'] = [ovn1a]
  req['subscription_id'] = sub_id
  resp = scd_session.put('/operation_references/{}'.format(OP_ID), json=req)
  assert resp.status_code == 409, resp.content
  conflicts = []
  for conflict in resp.json()['entity_conflicts']:
    if conflict.get('operation_reference', None):
      conflicts.append(conflict['operation_reference']['id'])
  assert OP_ID2 in conflicts, resp.content

  # Perform an area-based query on the area occupied by Operation 1
  with open('./scd/resources/op_missing_query.json', 'r') as f:
    req = json.load(f)
  req['area_of_interest'] = scd.offset_time([req['area_of_interest']], dt)[0]
  resp = scd_session.post('/operation_references/query', json=req)
  assert  resp.status_code == 200, resp.content
  ops = [op['id'] for op in resp.json()['operation_references']]
  assert OP_ID in ops, resp.content

  # OP_ID2 not expected here because its ceiling is <575m whereas query floor is
  # >591m.
  assert OP_ID2 not in ops, resp.content
示例#5
0
def _verify_operational_intent(runner, step: TestStep, step_ref: TestStepReference, target: TestTarget, resp: InjectFlightResponse, flight_id: str) -> None:
    # Verify that the operation actually does exist in the system

    # Check the DSS for a reference
    op_intent = step.inject_flight.test_injection.operational_intent
    vol4s = op_intent.volumes + op_intent.off_nominal_volumes
    rect = scd.rect_bounds_of(vol4s)
    t0 = scd.start_of(vol4s)
    t1 = scd.end_of(vol4s)
    alts = scd.meter_altitude_bounds_of(vol4s)
    op_refs = fetch_scd.operational_intent_references(runner.dss_target.client, rect, t0, t1, alts[0], alts[1])
    dss_interaction_id = runner.report_recorder.capture_interaction(step_ref, op_refs, 'Check if injected operational intent exists in DSS')
    if not op_refs.success:
        # The DSS call didn't even succeed
        issue = Issue(
            context=runner.context,
            check_code='CREATED_FLIGHT_EXISTS_IN_SYSTEM',
            uss_role=step.inject_flight.injection_target.uss_role,
            target=target.name,
            relevant_requirements=[],
            severity=Severity.Critical,
            subject=None,
            summary='Error querying operational intent references from DSS',
            details=op_refs.error,
            interactions=[dss_interaction_id])
        raise TestRunnerError(issue.summary, issue)
    try:
        ImplicitDict.parse(op_refs.json_result, scd.QueryOperationalIntentReferenceResponse)
    except ValueError as e:
        # The DSS returned an invalid result
        issue = Issue(
            context=runner.context,
            check_code='CREATED_FLIGHT_EXISTS_IN_SYSTEM',
            uss_role=step.inject_flight.injection_target.uss_role,
            target=target.name,
            relevant_requirements=[],
            severity=Severity.Critical,
            subject=None,
            summary='Error in operational intent reference data format from DSS',
            details=str(e),
            interactions=[dss_interaction_id])
        raise TestRunnerError(issue.summary, issue)
    if resp.operational_intent_id not in op_refs.references_by_id:
        # The expected operational intent reference wasn't present
        issue = Issue(
            context=runner.context,
            check_code='CREATED_FLIGHT_EXISTS_IN_SYSTEM',
            uss_role=step.inject_flight.injection_target.uss_role,
            target=target.name,
            relevant_requirements=['F3548-21 USS0005'],
            severity=Severity.High,
            subject=IssueSubject(subject_type=SubjectType.OperationalIntent, subject=resp.operational_intent_id),
            summary='Operational intent not present in DSS',
            details='When queried for the operational intent {} for flight {} in the DSS, it was not found in the Volume4D of interest'.format(resp.operational_intent_id, flight_id),
            interactions=[dss_interaction_id])
        raise TestRunnerError(issue.summary, issue)
    op_ref = op_refs.references_by_id[resp.operational_intent_id]

    # Check the USS for the operational intent details
    op = fetch_scd.operational_intent(op_ref['uss_base_url'], resp.operational_intent_id, runner.dss_target.client)
    uss_interaction_id = runner.report_recorder.capture_interaction(step_ref, op, 'Inspect operational intent details')
    if not op.success:
        # The USS call didn't succeed
        issue = Issue(
            context=runner.context,
            check_code='CREATED_FLIGHT_EXISTS_IN_SYSTEM',
            uss_role=step.inject_flight.injection_target.uss_role,
            target=target.name,
            relevant_requirements=['F3548-21 USS0105'],
            severity=Severity.High,
            subject=IssueSubject(subject_type=SubjectType.OperationalIntent, subject=resp.operational_intent_id),
            summary='Error querying operational intent details from USS',
            details=op.error,
            interactions=[uss_interaction_id])
        raise TestRunnerError(issue.summary, issue)
    try:
        op_resp: scd.GetOperationalIntentDetailsResponse = ImplicitDict.parse(op.json_result, scd.GetOperationalIntentDetailsResponse)
    except ValueError as e:
        # The USS returned an invalid result
        issue = Issue(
            context=runner.context,
            check_code='CREATED_FLIGHT_EXISTS_IN_SYSTEM',
            uss_role=step.inject_flight.injection_target.uss_role,
            target=target.name,
            relevant_requirements=['F3548-21 USS0105'],
            severity=Severity.High,
            subject=IssueSubject(subject_type=SubjectType.OperationalIntent, subject=resp.operational_intent_id),
            summary='Error in operational intent data format from USS',
            details=str(e),
            interactions=[uss_interaction_id])
        raise TestRunnerError(issue.summary, issue)

    # Check that the USS is providing reasonable details
    resp_vol4s = op_resp.operational_intent.details.volumes + op_resp.operational_intent.details.off_nominal_volumes
    resp_alts = scd.meter_altitude_bounds_of(resp_vol4s)
    resp_start = scd.start_of(resp_vol4s)
    resp_end = scd.end_of(resp_vol4s)
    error_text = None
    if resp_alts[0] > alts[0] + NUMERIC_PRECISION:
        error_text = 'Lower altitude specified by USS in operational intent details ({} m WGS84) is above the lower altitude in the injected flight ({} m WGS84)'.format(resp_alts[0], alts[0])
    elif resp_alts[1] < alts[1] - NUMERIC_PRECISION:
        error_text = 'Upper altitude specified by USS in operational intent details ({} m WGS84) is below the upper altitude in the injected flight ({} m WGS84)'.format(resp_alts[1], alts[1])
    elif resp_start > t0 + datetime.timedelta(seconds=NUMERIC_PRECISION):
        error_text = 'Start time specified by USS in operational intent details ({}) is past the start time of the injected flight ({})'.format(resp_start.isoformat(), t0.isoformat())
    elif resp_end < t1 - datetime.timedelta(seconds=NUMERIC_PRECISION):
        error_text = 'End time specified by USS in operational intent details ({}) is prior to the end time of the injected flight ({})'.format(resp_end.isoformat(), t1.isoformat())
    if error_text:
        # The USS's flight details are incorrect
        issue = Issue(
            context=runner.context,
            check_code='CREATED_FLIGHT_EXISTS_IN_SYSTEM',
            uss_role=step.inject_flight.injection_target.uss_role,
            target=target.name,
            relevant_requirements=[],
            severity=Severity.High,
            subject=IssueSubject(subject_type=SubjectType.OperationalIntent, subject=resp.operational_intent_id),
            summary='Operational intent details does not match injected flight',
            details=error_text,
            interactions=[uss_interaction_id])
        raise TestRunnerError(issue.summary, issue)
示例#6
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))
示例#7
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