예제 #1
0
def call_test_executor(user_config_json: str, auth_spec: str, flight_record_jsons: List[str], debug=False):
    user_config: RIDQualifierTestConfiguration = ImplicitDict.parse(
        json.loads(user_config_json)['rid'], RIDQualifierTestConfiguration)
    flight_records: List[FullFlightRecord] = [
        ImplicitDict.parse(json.loads(j), FullFlightRecord)
        for j in flight_record_jsons]
    if debug:
        report = test_report.test_data
    else:
        report = test_executor.run_rid_tests(user_config, auth_spec, flight_records)
    return json.dumps(report)
예제 #2
0
def main() -> int:
    # Import all submodules from the `actions` module so we can find all actions
    _import_submodules(actions)

    # Parse arguments
    args = _parse_args()

    # Retrieve action function
    action_method = infrastructure.actions.get(args.action, None)
    if action_method is None:
        raise ValueError('Could not find definition for action `{}`'.format(
            args.action))

    # Parse deployment spec
    with open(args.deployment_spec, 'r') as f:
        spec = ImplicitDict.parse(json.load(f), DeploymentSpec)
    original_spec = json.dumps(spec)
    context = make_context(spec)

    # Execute action
    context.log.msg('Executing action',
                    action=args.action,
                    spec_file=args.deployment_spec)
    action_method(context)

    # Check if the deployment spec was updated
    new_spec = json.dumps(context.spec)
    if new_spec != original_spec:
        context.log.msg(
            'Deployment spec updated; writing changes to {}'.format(
                args.deployment_spec))
        with open(args.deployment_spec, 'w') as f:
            json.dump(context.spec, f, indent=2)

    return os.EX_OK
예제 #3
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))
예제 #4
0
파일: tasks.py 프로젝트: OneSkySystems/dss
def call_test_executor(user_config_json, auth_spec, input_files, debug=False):
    user_config: RIDQualifierTestConfiguration = ImplicitDict.parse(
        json.loads(user_config_json), RIDQualifierTestConfiguration)
    if debug:
        report = test_report.test_data
    else:
        report = test_executor.main(user_config, auth_spec, input_files)
    return json.dumps(report)
예제 #5
0
 def get_nodes(self) -> List[Node]:
     resp = self._session.get('{}/nodes/'.format(self._base_url),
                              headers=self._get_headers())
     resp.raise_for_status()
     nodes = resp.json().get('nodes', None)
     if nodes is None:
         raise ValueError(
             'Invalid CockroachDB cluster response: `nodes` not specified: {}'
             .format(resp.content.decode('utf-8')))
     return [ImplicitDict.parse(n, Node) for n in nodes]
예제 #6
0
def delete_operational_intent_reference(
        utm_client: DSSTestSession, id: str,
        ovn: str) -> scd.ChangeOperationalIntentReferenceResponse:
    resp = utm_client.delete(
        '/dss/v1/operational_intent_references/{}/{}'.format(id, ovn),
        scope=scd.SCOPE_SC)
    if resp.status_code != 200:
        raise OperationError(
            'deleteOperationalIntentReference failed {}:\n{}'.format(
                resp.status_code, resp.content.decode('utf-8')))
    return ImplicitDict.parse(resp.json(),
                              scd.ChangeOperationalIntentReferenceResponse)
예제 #7
0
 def observe_flight_details(
     self, flight_id: str
 ) -> Tuple[Optional[observation_api.GetDetailsResponse], fetch.Query]:
     initiated_at = datetime.datetime.utcnow()
     resp = self.session.get('/display_data/{}'.format(flight_id))
     try:
         result = ImplicitDict.parse(resp.json(),
                                     observation_api.GetDetailsResponse
                                     ) if resp.status_code == 200 else None
     except ValueError:
         result = None
     return (result, fetch.describe_query(resp, initiated_at))
예제 #8
0
def get_operational_intent_details(utm_client: DSSTestSession,
                                   uss_base_url: str,
                                   id: str) -> scd.OperationalIntent:
    resp = utm_client.get('{}/uss/v1/operational_intents/{}'.format(
        uss_base_url, id),
                          scope=scd.SCOPE_SC)
    if resp.status_code != 200:
        raise OperationError(
            'getOperationalIntentDetails failed {}:\n{}'.format(
                resp.status_code, resp.content.decode('utf-8')))
    resp_body = ImplicitDict.parse(resp.json(),
                                   scd.GetOperationalIntentDetailsResponse)
    return resp_body.operational_intent
예제 #9
0
def create_operational_intent_reference(
    utm_client: DSSTestSession, id: str,
    req: scd.PutOperationalIntentReferenceParameters
) -> scd.ChangeOperationalIntentReferenceResponse:
    resp = utm_client.put(
        '/dss/v1/operational_intent_references/{}'.format(id),
        json=req,
        scope=scd.SCOPE_SC)
    if resp.status_code != 200 and resp.status_code != 201:
        raise OperationError(
            'createOperationalIntentReference failed {}:\n{}'.format(
                resp.status_code, resp.content.decode('utf-8')))
    return ImplicitDict.parse(resp.json(),
                              scd.ChangeOperationalIntentReferenceResponse)
예제 #10
0
def query_operational_intent_references(
        utm_client: DSSTestSession, area_of_interest: scd.Volume4D
) -> List[scd.OperationalIntentReference]:
    req = scd.QueryOperationalIntentReferenceParameters(
        area_of_interest=area_of_interest)
    resp = utm_client.post('/dss/v1/operational_intent_references/query',
                           json=req,
                           scope=scd.SCOPE_SC)
    if resp.status_code != 200:
        raise OperationError(
            'queryOperationalIntentReferences failed {}:\n{}'.format(
                resp.status_code, resp.content.decode('utf-8')))
    resp_body = ImplicitDict.parse(resp.json(),
                                   scd.QueryOperationalIntentReferenceResponse)
    return resp_body.operational_intent_references
예제 #11
0
def delete_flight(utm_client: DSSTestSession, uss_base_url: str,
                  flight_id: str) -> Tuple[DeleteFlightResponse, fetch.Query]:
    url = '{}/v1/flights/{}'.format(uss_base_url, flight_id)
    print("[SCD] DELETE {}".format(url))

    initiated_at = datetime.utcnow()
    resp = utm_client.delete(url, scope=SCOPE_SCD_QUALIFIER_INJECT)
    if resp.status_code != 200:
        raise QueryError(
            'Unexpected response code for deleteFlight {}. Response: {}'.
            format(resp.status_code, resp.content.decode('utf-8')),
            fetch.describe_query(resp, initiated_at))

    return ImplicitDict.parse(resp.json(),
                              DeleteFlightResponse), fetch.describe_query(
                                  resp, initiated_at)
예제 #12
0
def get_version(utm_client: DSSTestSession,
                uss_base_url: str) -> Tuple[StatusResponse, fetch.Query]:
    url = '{}/v1/status'.format(uss_base_url)
    print("[SCD] GET {}".format(url))

    initiated_at = datetime.utcnow()
    resp = utm_client.get(url, scope=SCOPE_SCD_QUALIFIER_INJECT)
    if resp.status_code != 200:
        raise QueryError(
            'Unexpected response code for get_version {}. Response: {}'.format(
                resp.status_code, resp.content.decode('utf-8')),
            fetch.describe_query(resp, initiated_at))

    return ImplicitDict.parse(resp.json(),
                              StatusResponse), fetch.describe_query(
                                  resp, initiated_at)
예제 #13
0
def main() -> int:
    args = parseArgs()

    auth_spec = args.auth

    # Load/parse configuration
    config_input = args.config
    if config_input.lower().endswith('.json'):
        with open(config_input, 'r') as f:
            config_json = json.load(f)
    else:
        config_json = json.loads(config_input)
    config: USSQualifierTestConfiguration = ImplicitDict.parse(
        config_json, USSQualifierTestConfiguration)

    if "rid" in config:
        print(
            f"[RID] Configuration provided with {len(config.rid.injection_targets)} injection targets."
        )
        rid_test_executor.validate_configuration(config.rid)
        rid_flight_records = rid_test_executor.load_rid_test_definitions(
            config.locale)
        rid_test_executor.run_rid_tests(test_configuration=config.rid,
                                        auth_spec=auth_spec,
                                        flight_records=rid_flight_records)
    else:
        print("[RID] No configuration provided.")

    if "scd" in config:
        print(
            f"[SCD] Configuration provided with {len(config.scd.injection_targets)} injection targets."
        )
        scd_test_executor.validate_configuration(config.scd)

        locale = Locality(config.locale.upper())
        print(
            f"[SCD] Locale: {locale.value} (is_uspace_applicable:{locale.is_uspace_applicable}, allow_same_priority_intersections:{locale.allow_same_priority_intersections})"
        )

        if not scd_test_executor.run_scd_tests(locale=locale,
                                               test_configuration=config.scd,
                                               auth_spec=auth_spec):
            return os.EX_SOFTWARE
    else:
        print("[SCD] No configuration provided.")

    return os.EX_OK
예제 #14
0
def set_sp_behavior(sp_id: str) -> Tuple[str, int]:
    """Set the behavior of the specified virtual Service Provider."""
    try:
        json = flask.request.json
        if json is None:
            raise ValueError('Request did not contain a JSON payload')
        sp_behavior = ImplicitDict.parse(json,
                                         behavior.ServiceProviderBehavior)
    except ValueError as e:
        msg = 'Change behavior for Service Provider {} unable to parse JSON: {}'.format(
            sp_id, e)
        return msg, 400

    sp = db.get_sp(sp_id)
    sp.behavior = sp_behavior

    return flask.jsonify(sp.behavior)
예제 #15
0
 def observe_system(
     self, rect: s2sphere.LatLngRect
 ) -> Tuple[Optional[observation_api.GetDisplayDataResponse], fetch.Query]:
     initiated_at = datetime.datetime.utcnow()
     resp = self.session.get('/display_data?view={},{},{},{}'.format(
         rect.lo().lat().degrees,
         rect.lo().lng().degrees,
         rect.hi().lat().degrees,
         rect.hi().lng().degrees),
                             scope=rid.SCOPE_READ)
     try:
         result = (ImplicitDict.parse(
             resp.json(), observation_api.GetDisplayDataResponse)
                   if resp.status_code == 200 else None)
     except ValueError as e:
         result = None
     return (result, fetch.describe_query(resp, initiated_at))
예제 #16
0
def get_full_flight_records(aircraft_states_directory: Path) -> List[FullFlightRecord]:
    """Gets full flight records from the specified directory if they exist"""

    if not os.path.exists(aircraft_states_directory):
        raise ValueError('The aircraft states directory does not exist: {}'.format(aircraft_states_directory))

    all_files = os.listdir(aircraft_states_directory)
    files = [os.path.join(aircraft_states_directory,f) for f in all_files if os.path.isfile(os.path.join(aircraft_states_directory, f))]

    if not files:
        raise ValueError('There are no states in the states directory, create states first using the simulator/flight_state module.')

    flight_records: List[FullFlightRecord] = []
    for file in files:
        with open(file, 'r') as f:
            flight_records.append(ImplicitDict.parse(json.load(f), FullFlightRecord))

    return flight_records
예제 #17
0
파일: executor.py 프로젝트: interuss/dss
def get_automated_tests(automated_tests_dir: Path, prefix: str) -> Dict[str, AutomatedTest]:
    """Gets automated tests from the specified directory"""

    # Read all JSON files in this directory
    automated_tests: Dict[str, AutomatedTest] = {}
    for file in automated_tests_dir.glob('*.json'):
        test_id = prefix + os.path.splitext(os.path.basename(file))[0]
        with open(file, 'r') as f:
            automated_tests[test_id] = ImplicitDict.parse(json.load(f), AutomatedTest)

    # Read subdirectories
    for subdir in automated_tests_dir.iterdir():
        if subdir.is_dir():
            new_tests = get_automated_tests(subdir, prefix + subdir.name + '/')
            for k, v in new_tests.items():
                automated_tests[k] = v

    return automated_tests
예제 #18
0
def create_test(sp_id: str, test_id: str) -> Tuple[str, int]:
  """Implements test creation in RID automated testing injection API."""

  # TODO: Validate token signature & scope

  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)
    if sp_id not in db.sps:
      db.sps[sp_id] = database.RIDSP()
    db.sps[sp_id].tests[test_id] = record

    return flask.jsonify(injection_api.ChangeTestResponse(version=record.version, injected_flights=record.flights))
  except ValueError as e:
    msg = 'Create test {} for Service Provider {} unable to parse JSON: {}'.format(test_id, sp_id, e)
    return msg, 400
예제 #19
0
def create_flight(
    utm_client: DSSTestSession, uss_base_url: str,
    flight_request: InjectFlightRequest
) -> Tuple[str, InjectFlightResponse, fetch.Query]:
    flight_id = str(uuid.uuid4())
    url = '{}/v1/flights/{}'.format(uss_base_url, flight_id)
    print("[SCD] PUT {}".format(url))

    initiated_at = datetime.utcnow()
    resp = utm_client.put(url,
                          json=flight_request,
                          scope=SCOPE_SCD_QUALIFIER_INJECT)
    if resp.status_code != 200:
        raise QueryError(
            'Unexpected response code for createFlight {}. Response: {}'.
            format(resp.status_code, resp.content.decode('utf-8')),
            fetch.describe_query(resp, initiated_at))
    return flight_id, ImplicitDict.parse(
        resp.json(),
        InjectFlightResponse), fetch.describe_query(resp, initiated_at)
예제 #20
0
def main() -> int:
    args = parseArgs()

    auth_spec = args.auth

    # Load/parse configuration
    config_input = args.config
    if config_input.lower().endswith('.json'):
        with open(config_input, 'r') as f:
            config_json = json.load(f)
    else:
        config_json = json.loads(config_input)
    config: USSQualifierTestConfiguration = ImplicitDict.parse(
        config_json, USSQualifierTestConfiguration)

    if "rid" in config:
        print(
            f"[RID] Configuration provided with {len(config.rid.injection_targets)} injection targets."
        )
        rid_test_executor.validate_configuration(config.rid)
        rid_flight_records = rid_test_executor.load_rid_test_definitions(
            config.locale)
        rid_test_executor.run_rid_tests(test_configuration=config.rid,
                                        auth_spec=auth_spec,
                                        flight_records=rid_flight_records)
    else:
        print("[RID] No configuration provided.")

    if "scd" in config:
        print(
            f"[SCD] Configuration provided with {len(config.scd.injection_targets)} injection targets."
        )
        scd_test_executor.validate_configuration(config.scd)
        scd_test_executor.run_scd_tests(locale=config.locale,
                                        test_configuration=config.scd,
                                        auth_spec=auth_spec)
    else:
        print("[SCD] No configuration provided.")

    return os.EX_OK
예제 #21
0
    def __init__(self,
                 test_configuration: RIDQualifierTestConfiguration) -> None:
        self.test_configuration = test_configuration
        # Change directory to read the test_definitions folder appropriately
        p = pathlib.Path(__file__).parent.absolute()
        os.chdir(p)

        aircraft_states_directory = Path('test_definitions',
                                         test_configuration.locale,
                                         'aircraft_states')
        aircraft_state_files = self.get_aircraft_states(
            aircraft_states_directory)

        usses = self.test_configuration.injection_targets

        self.disk_flight_records: List[FullFlightRecord] = []
        for uss_index, uss in enumerate(usses):
            aircraft_states_path = Path(aircraft_state_files[uss_index])
            with open(aircraft_states_path) as generated_rid_state:
                disk_flight_record = ImplicitDict.parse(
                    json.load(generated_rid_state), FullFlightRecord)
                self.disk_flight_records.append(disk_flight_record)
예제 #22
0
def main() -> int:
    args = parseArgs()

    auth_spec = args.auth

    # Load/parse configuration
    config_input = args.config
    if config_input.lower().endswith('.json'):
        with open(config_input, 'r') as f:
          config_json = json.load(f)
    else:
        config_json = json.loads(config_input)
    config: RIDQualifierTestConfiguration = ImplicitDict.parse(config_json, RIDQualifierTestConfiguration)

    # Validate configuration
    for injection_target in config.injection_targets:
        is_url(injection_target.injection_base_url)

    # Run test
    test_executor.main(test_configuration=config, auth_spec=auth_spec)

    return os.EX_OK
예제 #23
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
예제 #24
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)
예제 #25
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
예제 #26
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))
예제 #27
0
from monitoring.monitorlib.multiprocessing import SynchronizedValue
from monitoring.monitorlib.rid_automated_testing import injection_api
from monitoring.monitorlib.typing import ImplicitDict


class TestRecord(ImplicitDict):
    """Representation of RID SP's record of a set of injected test flights"""
    version: str
    flights: List[injection_api.TestFlight]
    isa_version: Optional[str] = None

    def __init__(self, **kwargs):
        kwargs['flights'] = [
            injection_api.TestFlight(**flight) for flight in kwargs['flights']
        ]
        for flight in kwargs['flights']:
            flight.order_telemetry()

        super(TestRecord, self).__init__(**kwargs)


class Database(ImplicitDict):
    """Simple pseudo-database structure tracking the state of the mock system"""
    tests: Dict[str, TestRecord] = {}


db = SynchronizedValue(Database(),
                       decoder=lambda b: ImplicitDict.parse(
                           json.loads(b.decode('utf-8')), Database))