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)
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
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))
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)
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]
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)
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))
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
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)
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
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)
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)
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
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)
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))
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
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
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
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)
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
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)
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
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
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)
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 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))
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))