Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[mock_uss] Add flight_planning API implementation to mock_uss #287

Merged

Conversation

BenjaminPelletier
Copy link
Member

This PR adds an implementation of the flight_planning automated testing API to mock_uss by simply calling the existing flight planning handlers for the legacy scd interface. The interface used in uss_qualifier's CI automated tests is switched to this new API for all but one of the mock_uss instances.

The overall state of mock_uss flight planning handling is still left in need of some attention after this PR, but I hope to follow up with some improvements.

@BenjaminPelletier BenjaminPelletier marked this pull request as ready for review October 25, 2023 01:48
Copy link
Contributor

@mickmis mickmis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM - comments are mostly questions.

kwargs["altitude_lower"] = self.altitude_lower.to_flight_planning_api()
if self.altitude_upper:
kwargs["altitude_upper"] = self.altitude_upper.to_flight_planning_api()
return fp_api.Volume3D(**kwargs)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm really not sure of myself here, but shouldn't this use ImplicitDict.parse in order for the fields to be correctly set?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When an ImplicitDict subclass is constructed with keyword arguments, those fields are set to the values specified without type checking (it's basically this). As long as the type for each field is known to be correct (and there's no need for a deep clone), constructing with keyword arguments is the most efficient way to produce an ImplicitDict-subclass instance. Parsing has a number of additional features such as type checking and type conversion (e.g., a string will be automatically wrapped into a StringBasedDateTime), so if the entire tree of fields in your input parameters may have one or more instances of incorrect types, parsing will fix that. But, parsing is much more expensive than direct construction. In case you didn't know, MyClass(foo=1, bar="baz") is equivalent to MyClass(**{"foo": 1, "bar": "baz"})

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the clarifications!

In case you didn't know, MyClass(foo=1, bar="baz") is equivalent to MyClass(**{"foo": 1, "bar": "baz"})

Indeed I thought those were different. Probably because my IDE performs type checks and highlight issues in the former but not the latter.

kwargs["time_start"] = fp_api.Time(value=self.time_start)
if self.time_end:
kwargs["time_end"] = fp_api.Time(value=self.time_end)
return fp_api.Volume4D(**kwargs)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same remark here about possible need to use ImplicitDict.parse

Comment on lines +366 to +369
if self.time_start:
kwargs["time_start"] = fp_api.Time(value=self.time_start)
if self.time_end:
kwargs["time_end"] = fp_api.Time(value=self.time_end)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it better here to set time_start and time_end to None?
I asked this question to myself a couple of times and I could not be sure.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The advantage of declaring a field as "Optional" but providing a default is that it is not necessary to check for field presence; e.g.:

class Foo1(ImplicitDict):
    bar: Optional[str] = None

def baz(f: Foo1):
    if f.bar is None:  # <-- this is ok
        [...]

class Foo2(ImplicitDict):
    bar: Optional[str]

def baz(f: Foo2):
    if "bar" in f and f.bar is None:  # <-- an additional check for field is necessary
        [...]

When already given an object, setting to None or not controls how the object renders into JSON:

class Foo(ImplicitDict):
    bar: Optional[int]
    baz: str

json.dumps(Foo(bar=None, baz="biz"))
 >> {"bar":null,"baz":"biz"}
json.dumps(Foo(baz="biz"))
 >> {"baz":"biz"}

The latter is almost always going to be better than the former. But, the former doesn't hurt unless you're trying to produce a pretty JSON/YAML output.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright - thanks for the explanations :)

@@ -27,7 +27,7 @@ def scdsc_get_operational_intent_details(entityid: str):
tx = db.value
flight = None
for f in tx.flights.values():
if f.op_intent.reference.id == entityid:
if f and f.op_intent.reference.id == entityid:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: If f does not exist, that means a null object was inserted in the DB, doesn't that mean that we have a bug somewhere?

edit: actually I assume it is for cases where it's a "placeholder for a new flight"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct -- None is a placeholder for new flight

Comment on lines 224 to 230
# FlightRecord was a true existing flight
log(f"Releasing placeholder for flight_id {flight_id}")
tx.flights[flight_id].locked = False
else:
# FlightRecord was just a placeholder for a new flight
log(f"Releasing lock on existing flight_id {flight_id}")
del tx.flights[flight_id]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# FlightRecord was a true existing flight
log(f"Releasing placeholder for flight_id {flight_id}")
tx.flights[flight_id].locked = False
else:
# FlightRecord was just a placeholder for a new flight
log(f"Releasing lock on existing flight_id {flight_id}")
del tx.flights[flight_id]
# FlightRecord was a true existing flight
log(f"Releasing lock on existing flight_id {flight_id}")
tx.flights[flight_id].locked = False
else:
# FlightRecord was just a placeholder for a new flight
log(f"Releasing placeholder for flight_id {flight_id}")
del tx.flights[flight_id]

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, thanks


def injection_status() -> Tuple[dict, int]:
return (
api.StatusResponse(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

flask.jsonify(...) isn't needed here? (I'm not too familiar with the framework)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

flask.jsonify turns a dict into a Flask Response. This function is just computing the content, and the handler (view function) itself turns it into a response on line 43.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is just computing the content, and the handler (view function) itself turns it into a response on line 43.

Missed that - OK

def flight_planning_v1_delete_flight(flight_plan_id: str) -> Tuple[str, int]:
"""Implements flight deletion in SCD automated testing injection API."""
scd_resp, code = delete_flight(flight_plan_id)
if code != 200:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why raising an error and not forwarding the error code? E.g. if we get a 404 I assume that we want to respond the same.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this particular handler, a missing flight is reflected as a Failure to perform the requested operation rather than indicating that a RESTful resource is missing, so 200 is returned (indicating successful attempt of the request accompanied by a nominal indication of the Failure outcome). That's probably not ideal API construction, but I think the code should work for what we currently have. I'll take the note on API construction.

@BenjaminPelletier BenjaminPelletier merged commit 1199553 into interuss:main Oct 26, 2023
8 checks passed
@BenjaminPelletier BenjaminPelletier deleted the flight_planner-impl branch October 26, 2023 18:03
github-actions bot added a commit that referenced this pull request Oct 26, 2023
* Add flight_planning API implementation to mock_uss

* Address comments 1199553
github-actions bot added a commit to openskies-sh/monitoring that referenced this pull request Oct 27, 2023
…ss#287)

* Add flight_planning API implementation to mock_uss

* Address comments 1199553
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants