Beispiel #1
0
    def post():
        """Upload a new sector definition"""

        req_args = utils.parse_args(_PARSER)

        sector_name = req_args["name"]

        if not sector_name:
            return responses.bad_request_resp("Sector name must be provided")

        sector_json = req_args["content"]

        if sector_json:
            sector_element = validate_geojson_sector(sector_json)
            if not isinstance(sector_element, SectorElement):
                return responses.bad_request_resp(
                    f"Invalid sector content: {sector_element}"
                )
        else:
            sector_element = None

        sector = SectorWrapper(sector_name, sector_element)
        err = utils.sim_proxy().simulation.load_sector(sector)

        return responses.checked_resp(err, HTTPStatus.CREATED)
Beispiel #2
0
    def post():
        """Logic for POST events"""

        req_args = utils.parse_args(_PARSER)

        if not utils.sim_proxy().simulation.sector:
            return responses.bad_request_resp(
                "A sector definition is required before uploading a scenario")

        # TODO(rkm 2020-01-12) Should never include the file extension
        name = req_args["name"]
        if not name:
            return responses.bad_request_resp("Scenario name must be provided")

        content = req_args["content"]

        if content:
            err = validate_json_scenario(content)
            if err:
                return responses.bad_request_resp(
                    f"Invalid scenario content: {err}")

        scenario = ScenarioWrapper(name, content)
        err = utils.sim_proxy().simulation.load_scenario(scenario)

        return responses.checked_resp(err, HTTPStatus.CREATED)
    def get():
        """Logic for GET events. Returns the current episode ID and log content"""

        req_args = utils.parse_args(_PARSER)
        close_ep = req_args.get("close_ep", False)

        if not in_agent_mode():
            return responses.bad_request_resp(
                "Episode data only recorded when in Agent mode")

        ep_file_path = bb_logging.EP_FILE

        if not ep_file_path:
            return responses.bad_request_resp("No episode being recorded")

        if close_ep:
            err = utils.sim_proxy().simulation.reset()
            if err:
                return responses.internal_err_resp(
                    f"Couldn't reset simulation: {err}")

        if not ep_file_path.exists():
            return responses.internal_err_resp("Could not find episode file")

        lines = list(line.rstrip("\n") for line in open(ep_file_path))

        data = {
            "cur_ep_id": bb_logging.EP_ID,
            "cur_ep_file": str(ep_file_path.absolute()),
            "log": lines,
        }

        return responses.ok_resp(data)
Beispiel #4
0
def check_exists(sim_proxy: SimProxy,
                 callsign: types.Callsign,
                 negate: bool = False) -> Optional[Response]:
    """Checks if an aircraft exists, and returns an appropriate response if not"""

    exists = sim_proxy.aircraft.exists(callsign)
    if not isinstance(exists, bool):
        return responses.internal_err_resp(
            f"Could not check if the aircraft exists: {exists}")
    if not exists and not negate:
        return responses.bad_request_resp(
            f'Aircraft "{callsign}" does not exist')
    if exists and negate:
        return responses.bad_request_resp(
            f'Aircraft "{callsign}" already exists')
    return None
def test_pos_get_single(test_flask_client):
    """Tests the GET method with a single aircraft"""

    # Test arg parsing

    endpoint_str = f"{_ENDPOINT_PATH}?{utils.CALLSIGN_LABEL}"

    callsign = ""
    resp = test_flask_client.get(f"{endpoint_str}={callsign}")
    assert resp.status_code == HTTPStatus.BAD_REQUEST
    assert utils.CALLSIGN_LABEL in resp.json["message"]

    with mock.patch(patch_utils_path(_ENDPOINT), wraps=utils) as utils_patch:

        utils_patch.CALLSIGN_LABEL = utils.CALLSIGN_LABEL
        sim_proxy_mock = mock.Mock()
        utils_patch.sim_proxy.return_value = sim_proxy_mock

        # Test error from simulation properties

        sim_proxy_mock.simulation.properties = "Error"

        endpoint_str = f"{endpoint_str}=TEST"

        resp = test_flask_client.get(endpoint_str)
        assert resp.status_code == HTTPStatus.INTERNAL_SERVER_ERROR
        assert resp.data.decode() == "Error"

        # Test aircraft existence check

        sim_proxy_mock.simulation.properties = TEST_SIM_PROPS

        with api.FLASK_APP.test_request_context():
            utils_patch.check_exists.return_value = bad_request_resp(
                "Missing aircraft")

        resp = test_flask_client.get(endpoint_str)
        assert resp.status_code == HTTPStatus.BAD_REQUEST
        assert resp.data.decode() == "Missing aircraft"

        # Test error from aircraft properties

        utils_patch.check_exists.return_value = None
        sim_proxy_mock.aircraft.properties.return_value = "Missing properties"

        resp = test_flask_client.get(endpoint_str)
        assert resp.status_code == HTTPStatus.INTERNAL_SERVER_ERROR
        assert resp.data.decode() == "Missing properties"

        # Test valid response

        sim_proxy_mock.aircraft.properties.return_value = TEST_AIRCRAFT_PROPS

        resp = test_flask_client.get(endpoint_str)
        assert resp.status_code == HTTPStatus.OK
        assert resp.json == {
            **utils.convert_aircraft_props(TEST_AIRCRAFT_PROPS),
            "scenario_time":
            TEST_SIM_PROPS.scenario_time,
        }
Beispiel #6
0
    def post():
        """
        Logic for POST events. If the request contains valid aircraft information, then
        a request is sent to the simulator to create it
        """

        req_args = utils.parse_args(_PARSER)
        callsign = req_args[utils.CALLSIGN_LABEL]

        resp = utils.check_exists(utils.sim_proxy(), callsign, negate=True)
        if resp:
            return resp

        position_or_resp = utils.try_parse_lat_lon(req_args)
        if not isinstance(position_or_resp, LatLon):
            return position_or_resp

        if not req_args["type"]:
            return responses.bad_request_resp(
                "Aircraft type must be specified")

        err = utils.sim_proxy().aircraft.create(
            callsign,
            req_args["type"],
            position_or_resp,
            req_args["hdg"],
            req_args["alt"],
            req_args["gspd"],
        )

        return responses.checked_resp(err, HTTPStatus.CREATED)
Beispiel #7
0
    def get():
        """
        Logic for GET events. If the request contains an identifier to an existing
        aircraft, then information about its route (FMS flightplan) is returned
        """

        req_args = utils.parse_args(_PARSER)
        callsign = req_args[utils.CALLSIGN_LABEL]

        resp = utils.check_exists(utils.sim_proxy(), callsign)
        if resp:
            return resp

        route_info = utils.sim_proxy().aircraft.route(callsign)

        if not isinstance(route_info, tuple):
            if route_info == "Aircraft has no route":
                return responses.bad_request_resp(route_info)
            return responses.internal_err_resp(route_info)

        data = {
            utils.CALLSIGN_LABEL: str(callsign),
            "route_name": route_info[0],
            "next_waypoint": route_info[1],
            "route_waypoints": route_info[2],
        }
        return responses.ok_resp(data)
Beispiel #8
0
def try_parse_lat_lon(args: dict) -> Union[types.LatLon, Response]:
    """Attempts to parse a LatLon from an argument dict"""

    try:
        assert "lat" in args, "Expected args to contain 'lat'"
        assert "lon" in args, "Expected args to contain 'lon'"
        return types.LatLon(args["lat"], args["lon"])
    except AssertionError as exc:
        return bad_request_resp(f"Invalid LatLon: {exc}")
Beispiel #9
0
    def post():
        """Logic for POST events"""

        if Settings.SIM_MODE != SimMode.Agent:
            return responses.bad_request_resp(
                "Must be in agent mode to use step")

        err = utils.sim_proxy().simulation.step()

        return responses.checked_resp(err)
Beispiel #10
0
    def post():
        """Logic for post events"""

        if Settings.SIM_MODE != SimMode.Sandbox:
            return responses.bad_request_resp(
                f"Can't resume sim from mode {Settings.SIM_MODE.name}")

        err = utils.sim_proxy().simulation.resume()

        return responses.checked_resp(err)
Beispiel #11
0
    def post():
        """Logic for POST events. Sets the seed of the simulator"""

        req_args = utils.parse_args(_PARSER)
        seed: int = req_args["value"]

        if not is_valid_seed(seed):
            return responses.bad_request_resp(
                "Invalid seed specified. Must be a positive integer less than 2^32"
            )

        err = utils.sim_proxy().simulation.set_seed(seed)
        return responses.checked_resp(err)
Beispiel #12
0
    def post():
        """Logic for POST events. Sets the speed multiplier for the simulation"""

        req_args = utils.parse_args(_PARSER)
        multiplier = round(req_args["multiplier"], 2)

        if multiplier <= 0:
            return responses.bad_request_resp(
                "Multiplier must be greater than 0")

        # TODO Check if we still need to keep track of step_dt in the client
        err = utils.sim_proxy().simulation.set_speed(multiplier)

        return responses.checked_resp(err)
Beispiel #13
0
    def get():
        """Returns the sector defined in the current simulation"""

        sector: SectorWrapper = utils.sim_proxy().simulation.sector

        if not sector:
            return responses.bad_request_resp("No sector has been set")

        # TODO (RKM 2019-12-20) Check what exceptions this can throw
        try:
            geojson_str = geojson.dumps(sector.element)
        except Exception as exc:
            return responses.internal_err_resp(f"Couldn't get sector geojson: {exc}")

        return responses.ok_resp({"name": sector.name, "content": geojson_str})
Beispiel #14
0
    def get():
        """Logic for GET events"""

        if not in_agent_mode():
            return responses.bad_request_resp(
                "Episode data only recorded when in Agent mode"
            )

        current_ep_file = bb_logging.EP_FILE
        if not current_ep_file:
            return responses.bad_request_resp("No episode being recorded")

        cwd = os.getcwd()
        full_log_dir = os.path.join(cwd, bb_logging.INST_LOG_DIR)
        full_ep_file = os.path.join(cwd, current_ep_file)

        data = {
            "inst_id": bb_logging.INSTANCE_ID,
            "cur_ep_id": bb_logging.EP_ID,
            "cur_ep_file": full_ep_file,
            "log_dir": full_log_dir,
        }

        return responses.ok_resp(data)
Beispiel #15
0
def test_hdg_post(test_flask_client):
    """Tests the POST method"""

    # Test arg parsing

    resp = test_flask_client.post(_ENDPOINT_PATH)
    assert resp.status_code == HTTPStatus.BAD_REQUEST

    data = {api_utils.CALLSIGN_LABEL: "FAKE"}
    resp = test_flask_client.post(_ENDPOINT_PATH, json=data)
    assert resp.status_code == HTTPStatus.BAD_REQUEST

    data["hdg"] = "aaa"
    resp = test_flask_client.post(_ENDPOINT_PATH, json=data)
    assert resp.status_code == HTTPStatus.BAD_REQUEST

    with mock.patch(patch_utils_path(_ENDPOINT)) as utils_patch:

        sim_proxy_mock = mock.Mock()
        utils_patch.sim_proxy.return_value = sim_proxy_mock

        # Test aircraft exists check

        with api.FLASK_APP.test_request_context():
            utils_patch.check_exists.return_value = bad_request_resp(
                "Missing aircraft")

        data["hdg"] = 123

        resp = test_flask_client.post(_ENDPOINT_PATH, json=data)
        assert resp.status_code == HTTPStatus.BAD_REQUEST
        assert resp.data.decode() == "Missing aircraft"

        # Test error from set_heading

        utils_patch.check_exists.return_value = None
        sim_proxy_mock.aircraft.set_heading.return_value = "Couldn't set heading"

        resp = test_flask_client.post(_ENDPOINT_PATH, json=data)
        assert resp.status_code == HTTPStatus.INTERNAL_SERVER_ERROR
        assert resp.data.decode() == "Couldn't set heading"

        # Test valid response

        sim_proxy_mock.aircraft.set_heading.return_value = None

        resp = test_flask_client.post(_ENDPOINT_PATH, json=data)
        assert resp.status_code == HTTPStatus.OK
Beispiel #16
0
    def get():
        """Logic for GET events. Returns properties for the specified aircraft"""

        req_args = utils.parse_args(_PARSER)
        callsign = req_args[utils.CALLSIGN_LABEL]

        sim_props = utils.sim_proxy().simulation.properties
        if not isinstance(sim_props, SimProperties):
            return responses.internal_err_resp(sim_props)

        if callsign:
            resp = utils.check_exists(utils.sim_proxy(), callsign)
            if resp:
                return resp

            props = utils.sim_proxy().aircraft.properties(callsign)
            if not isinstance(props, AircraftProperties):
                return internal_err_resp(props)

            data = utils.convert_aircraft_props(props)
            data.update({"scenario_time": sim_props.scenario_time})

            return responses.ok_resp(data)

        # else: get_all_properties

        props = utils.sim_proxy().aircraft.all_properties
        if isinstance(props, str):
            return responses.internal_err_resp(
                f"Couldn't get the aircraft properties: {props}")
        if not props:
            return responses.bad_request_resp("No aircraft in the simulation")

        data = {}
        for prop in props.values():
            data.update(utils.convert_aircraft_props(prop))
        data["scenario_time"] = sim_props.scenario_time

        return responses.ok_resp(data)
Beispiel #17
0
    def post():
        """
        Requests that the specified aircraft proceeds immediately to the specified
        waypoint
        """

        req_args = utils.parse_args(_PARSER)
        waypoint_name = req_args["waypoint"]

        if not waypoint_name:
            return responses.bad_request_resp(
                "Waypoint name must be specified")

        callsign = req_args[utils.CALLSIGN_LABEL]

        resp = utils.check_exists(utils.sim_proxy(), callsign)
        if resp:
            return resp

        err = utils.sim_proxy().aircraft.direct_to_waypoint(
            callsign, waypoint_name)

        return responses.checked_resp(err)
Beispiel #18
0
    def post():
        """
        Logic for POST events. Returns the simulator to a previous state given a logfile
        :return:
        """

        if Settings.SIM_MODE != SimMode.Agent:
            return responses.bad_request_resp("Can only be used in agent mode")

        if Settings.SIM_TYPE != SimType.BlueSky:
            return responses.bad_request_resp(
                f"Method not supported for the {Settings.SIM_TYPE} simulator")

        req_args = parse_args(_PARSER)

        if bool(req_args["filename"]) == bool(req_args["lines"]):
            return responses.bad_request_resp(
                "Either filename or lines must be specified")

        target_time = req_args["time"]
        if target_time <= 0:
            return responses.bad_request_resp(
                "Target time must be greater than 0")

        props = sim_proxy().simulation.properties
        if isinstance(props, str):
            return responses.internal_err_resp(
                f"Could not get the sim properties: {props}")

        prev_dt = props.dt

        _LOGGER.debug("Starting log reload")

        # Reset now so the current episode log is closed
        err = sim_proxy().simulation.reset()

        if err:
            return responses.internal_err_resp(f"Simulation not reset: {err}")

        if req_args["filename"]:
            log_path = Path(os.getcwd(), req_args["filename"])
            if not log_path.exists():
                return responses.bad_request_resp(
                    f'Could not find episode file {req_args["filename"]}')
            with open(req_args["filename"]) as log_file:
                lines = list(log_file)
        else:
            lines = req_args["lines"]

        _LOGGER.debug("Parsing log content")
        parsed_scn = parse_lines(lines, target_time)

        if isinstance(parsed_scn, str):
            return responses.bad_request_resp(
                f"Could not parse episode content: {parsed_scn}")

        # TODO Move regex to outer scope and comment
        # Assert that the requested time is not past the end of the log
        last_data = next(x for x in reversed(lines)
                         if re.match(r".*A \[(\d+)\] (.*)$", x))
        last_time = int(re.search(r"\[(.*)]", last_data).group(1))

        if target_time > last_time:
            return responses.bad_request_resp(
                "Error: Target time was greater than the latest time in the log"
            )

        # err = validate_scenario(parsed_scn["lines"])

        if err:
            return responses.bad_request_resp(
                "Could not create a valid scenario from the given log")

        # All good - do the reload

        _LOGGER.debug("Setting the simulator seed")
        err = sim_proxy().simulation.set_seed(int(parsed_scn["seed"]))

        if err:
            return responses.internal_err_resp(f"Could not set seed {err}")

        scn_name = f"reloads/{str(uuid.uuid4())[:8]}.scn"

        _LOGGER.debug("Uploading the new scenario")
        # store_local_scn(scn_name, parsed_scn["lines"])
        err = sim_proxy().simulation.upload_new_scenario(
            scn_name, parsed_scn["lines"])

        if err:
            return responses.internal_err_resp(
                f"Error uploading scenario: {err}")

        _LOGGER.info("Starting the new scenario")
        err = sim_proxy().simulation.load_scenario(scn_name, start_paused=True)

        if err:
            return responses.internal_err_resp(
                f"Could not start scenario after upload {err}")

        props = sim_proxy().simulation.properties
        if isinstance(props, str):
            return responses.internal_err_resp(
                f"Could not get the sim properties: {props}")

        diff = target_time - props.scenario_time

        if diff:
            # Naive approach - set DTMULT to target, then STEP once...
            _LOGGER.debug(
                f"Time difference is {diff}. Stepping to {target_time}")
            err = sim_proxy().simulation.set_speed(diff)
            if err:
                return responses.internal_err_resp(
                    f"Could not change speed: {err}")

            _LOGGER.debug("Performing step")
            err = sim_proxy().simulation.step()

            if err:
                return responses.internal_err_resp(
                    f"Could not step simulations: {err}")

        else:
            _LOGGER.debug("Simulation already at required time")

        # Reset DTMULT to the previous value
        err = sim_proxy().simulation.set_speed(prev_dt)
        if err:
            return responses.internal_err_resp(
                "Episode reloaded, but could not reset DTMULT to previous value"
            )

        # TODO Do we want to check before/after positions here and check if the
        # differences are acceptable?

        return responses.ok_resp()
Beispiel #19
0
def test_cre_post(test_flask_client):
    """Tests the POST method"""

    # Test arg parsing

    data = {}
    resp = test_flask_client.post(_ENDPOINT_PATH, json=data)
    assert resp.status_code == HTTPStatus.BAD_REQUEST
    assert utils.CALLSIGN_LABEL in resp.json["message"]

    callsign = "T"
    data = {utils.CALLSIGN_LABEL: callsign}
    resp = test_flask_client.post(_ENDPOINT_PATH, json=data)
    assert resp.status_code == HTTPStatus.BAD_REQUEST
    assert utils.CALLSIGN_LABEL in resp.json["message"]

    callsign = "AAA"
    data = {utils.CALLSIGN_LABEL: callsign}
    resp = test_flask_client.post(_ENDPOINT_PATH, json=data)
    assert resp.status_code == HTTPStatus.BAD_REQUEST
    assert "type" in resp.json["message"]

    data["type"] = ""
    resp = test_flask_client.post(_ENDPOINT_PATH, json=data)
    assert resp.status_code == HTTPStatus.BAD_REQUEST
    assert "lat" in resp.json["message"]

    data["lat"] = 91
    resp = test_flask_client.post(_ENDPOINT_PATH, json=data)
    assert resp.status_code == HTTPStatus.BAD_REQUEST
    assert "lon" in resp.json["message"]

    data["lon"] = 181
    resp = test_flask_client.post(_ENDPOINT_PATH, json=data)
    assert resp.status_code == HTTPStatus.BAD_REQUEST
    assert "hdg" in resp.json["message"]

    data["hdg"] = "aaa"
    resp = test_flask_client.post(_ENDPOINT_PATH, json=data)
    assert resp.status_code == HTTPStatus.BAD_REQUEST
    assert resp.json["message"]["hdg"] == "Heading must be an int"

    data["hdg"] = 123
    resp = test_flask_client.post(_ENDPOINT_PATH, json=data)
    assert resp.status_code == HTTPStatus.BAD_REQUEST
    assert "alt" in resp.json["message"]

    data["alt"] = -1
    resp = test_flask_client.post(_ENDPOINT_PATH, json=data)
    assert resp.status_code == HTTPStatus.BAD_REQUEST
    assert resp.json["message"]["alt"] == "Altitude must be positive"

    data["alt"] = "FL100"
    resp = test_flask_client.post(_ENDPOINT_PATH, json=data)
    assert resp.status_code == HTTPStatus.BAD_REQUEST
    assert "gspd" in resp.json["message"]

    data["gspd"] = "..."
    resp = test_flask_client.post(_ENDPOINT_PATH, json=data)
    assert resp.status_code == HTTPStatus.BAD_REQUEST
    assert resp.json["message"]["gspd"] == "Ground speed must be numeric"

    with mock.patch(patch_utils_path(_ENDPOINT), wraps=utils) as utils_patch:

        utils_patch.CALLSIGN_LABEL = utils.CALLSIGN_LABEL

        # Test response when aircraft exists

        with api.FLASK_APP.test_request_context():
            response = responses.bad_request_resp("Missing aircraft")

        utils_patch.check_exists.return_value = response

        data = {
            utils.CALLSIGN_LABEL: "TEST1",
            "type": "",
            "lat": "1.23",
            "lon": "4.56",
            "hdg": 123,
            "alt": 18_500,
            "gspd": 50,
        }
Beispiel #20
0
def test_listroute_get(test_flask_client):
    """Tests the GET method"""

    endpoint_path = f"{_ENDPOINT_PATH}?{utils.CALLSIGN_LABEL}="

    # Test arg parsing

    resp = test_flask_client.get(_ENDPOINT_PATH)
    assert resp.status_code == HTTPStatus.BAD_REQUEST

    callsign_str = "A"
    resp = test_flask_client.get(f"{endpoint_path}{callsign_str}")
    assert resp.status_code == HTTPStatus.BAD_REQUEST
    assert utils.CALLSIGN_LABEL in resp.json["message"]

    with mock.patch(patch_utils_path(_ENDPOINT), wraps=utils) as utils_patch:

        utils_patch.CALLSIGN_LABEL = utils.CALLSIGN_LABEL
        sim_proxy_mock = mock.Mock()
        utils_patch.sim_proxy.return_value = sim_proxy_mock

        # Test aircraft exists check

        with api.FLASK_APP.test_request_context():
            utils_patch.check_exists.return_value = bad_request_resp(
                "Missing aircraft")

        callsign_str = "TEST"
        resp = test_flask_client.get(f"{endpoint_path}{callsign_str}")
        assert resp.status_code == HTTPStatus.BAD_REQUEST
        assert resp.data.decode() == "Missing aircraft"

        # Test response when no route defined

        utils_patch.check_exists.return_value = None
        sim_proxy_mock.aircraft.route.return_value = "Aircraft has no route"

        resp = test_flask_client.get(f"{endpoint_path}{callsign_str}")
        assert resp.status_code == HTTPStatus.BAD_REQUEST
        assert resp.data.decode() == "Aircraft has no route"

        # Test error from aircraft route

        sim_proxy_mock.aircraft.route.return_value = "Couldn't get route"

        resp = test_flask_client.get(f"{endpoint_path}{callsign_str}")
        assert resp.status_code == HTTPStatus.INTERNAL_SERVER_ERROR
        assert resp.data.decode() == "Couldn't get route"

        # Test valid response

        route_info = (
            "test_route",
            "FIRE",
            ["WATER", "FIRE", "EARTH"],
        )
        sim_proxy_mock.aircraft.route.return_value = route_info

        resp = test_flask_client.get(f"{endpoint_path}{callsign_str}")
        assert resp.status_code == HTTPStatus.OK
        assert resp.json == {
            utils.CALLSIGN_LABEL: "TEST",
            "route_name": route_info[0],
            "next_waypoint": route_info[1],
            "route_waypoints": route_info[2],
        }