Skip to content

Conversation

@just-be-dev
Copy link
Contributor

@just-be-dev just-be-dev commented Dec 13, 2022

Changes

  • v1 endpoints added for orgs
  • v1 endpoints added for projects
  • Selectors updated to be combinatorial such that they don't need to be fully reconstructed every time
  • Path and query param definitions for new endpoints centralized to params module
  • Added Name and Uuid to NameOrId converters to reduce some of the translation noise

Comment on lines 514 to 593
pub struct ProjectPath {
pub struct OldProjectPath {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

An unfortunate naming conflict, but this'll go away completely once we do #2055

Comment on lines +2 to +18
organization_delete (delete "/organizations/{organization_name}")
project_delete (delete "/organizations/{organization_name}/projects/{project_name}")
instance_delete (delete "/organizations/{organization_name}/projects/{project_name}/instances/{instance_name}")
instance_view_by_id (get "/by-id/instances/{id}")
organization_view_by_id (get "/by-id/organizations/{id}")
project_view_by_id (get "/by-id/projects/{id}")
login_saml_begin (get "/login/{silo_name}/saml/{provider_name}")
organization_list (get "/organizations")
organization_view (get "/organizations/{organization_name}")
organization_policy_view (get "/organizations/{organization_name}/policy")
project_list (get "/organizations/{organization_name}/projects")
project_view (get "/organizations/{organization_name}/projects/{project_name}")
instance_list (get "/organizations/{organization_name}/projects/{project_name}/instances")
instance_view (get "/organizations/{organization_name}/projects/{project_name}/instances/{instance_name}")
instance_serial_console (get "/organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/serial-console")
instance_serial_console_stream (get "/organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/serial-console/stream")
project_policy_view (get "/organizations/{organization_name}/projects/{project_name}/policy")
Copy link
Contributor Author

Choose a reason for hiding this comment

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

There's work I could do here to get this test passing for all the old endpoints but I'm opting not to do that in favor of making process. The authz test is non-trivial and I'm trying to update it as little as possible until I can get a critical mass of APIs converted. I am going through and ensuring the new API endpoints are correctly tested though.

On that note, there is a slight issue in that you can only test and endpoint from the spec once. If you try to verify it more than once it freaks out on you. I don't have the exact error handy but it does this because it "consumes" routes from the spec and if it encounters something its already consumed then that's considered a failure. We actually need to be able to test a route at least twice (name path, id). Anyway, I'll add that to the top level issue to resolve.

@just-be-dev just-be-dev marked this pull request as ready for review December 14, 2022 21:40
Comment on lines +291 to +301
impl From<Name> for NameOrId {
fn from(name: Name) -> Self {
NameOrId::Name(name)
}
}

impl From<Uuid> for NameOrId {
fn from(id: Uuid) -> Self {
NameOrId::Id(id)
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

These greatly reduce a lot of conversion noise. We might not need them post v1 migration.

Comment on lines +40 to +44
impl From<Name> for external::NameOrId {
fn from(name: Name) -> Self {
Self::Name(name.0)
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same as above

Comment on lines -33 to -55
pub async fn organization_fetch(
&self,
opctx: &OpContext,
organization_name: &Name,
) -> LookupResult<db::model::Organization> {
let (.., db_organization) = LookupPath::new(opctx, &self.db_datastore)
.organization_name(organization_name)
.fetch()
.await?;
Ok(db_organization)
}

pub async fn organization_fetch_by_id(
&self,
opctx: &OpContext,
organization_id: &Uuid,
) -> LookupResult<db::model::Organization> {
let (.., db_organization) = LookupPath::new(opctx, &self.db_datastore)
.organization_id(*organization_id)
.fetch()
.await?;
Ok(db_organization)
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@davepacheco you mentioned in #1957 that we could probably just ditch the fetch endpoints now that we're passing around lookups. I've been doing that.

opctx: &'a OpContext,
instance_selector: &'a params::InstanceSelector,
) -> LookupResult<lookup::Instance<'a>> {
match instance_selector {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

You'll notice this selector logic is greatly reduced. It checks the name and ID case of the resource being selected against and delegates selection of sub resources to that resource's lookup.

Copy link
Contributor

Choose a reason for hiding this comment

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

That looks a lot cleaner. The error cases will likely need to be expanded depending on how that gets spec'd. Specifically the over-specification case will want a different error message.

For example:

InstanceSelector { project_selector: Some(project_selector), instance: NameOrId::Id(id) }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right, yeah. @david-crespo mentioned in the RFD how he thought that case shouldn't be an error and I'm still undecided. Should we just ignore that case?

Copy link
Contributor

Choose a reason for hiding this comment

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

The ideal would be to allow valid over-specifications, but blanket allowing it would allow parameter combinations that contained mismatched parent identifiers. i.e. /{instance}?project={p_id}&organization={o_id} where p_id is not a child of o_id

Solving that would require also solving the id -> id lookup path.

It certainly could be defined via documentation that we ignore excess parameters, though changing that decision would be a breaking change across the API.

I think allowing valid over-specification and disallowing invalid would be ideal, but I do not know what the effort in that is.

Copy link
Contributor Author

@just-be-dev just-be-dev Dec 15, 2022

Choose a reason for hiding this comment

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

Fair point. I think disallowing invalid combinations is the lowest common denominator. We could loosen that later and it would be non-breaking.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've updated this in 69ac008 to have expanded error checks. Essentially I just added an extra case for instance and project.

Comment on lines +48 to +52
#[derive(Deserialize, JsonSchema)]
pub struct OptionalOrganizationSelector {
#[serde(flatten)]
pub organization_selector: Option<OrganizationSelector>,
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The only reason this optional selector has to exist is because dropshot chokes on Query<Option<OrganizationalSelector>>. If that worked we could delete this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The types actually work out fine, but the EXPECTORATE update fails.

Example error
thread 'integration_tests::commands::test_nexus_openapi' panicked at 'expected normal process exit with code 0, got Signaled(6)

process stderr:thread 'main' panicked at 'invalid type Object(
    SchemaObject {
        metadata: Some(
            Metadata {
                id: None,
                title: Some(
                    "Nullable_ProjectSelector",
                ),
                description: None,
                default: None,
                deprecated: false,
                read_only: false,
                write_only: false,
                examples: [],
            },
        ),
        instance_type: None,
        format: None,
        enum_values: None,
        const_value: None,
        subschemas: Some(
            SubschemaValidation {
                all_of: Some(
                    [
                        Object(
                            SchemaObject {
                                metadata: None,
                                instance_type: None,
                                format: None,
                                enum_values: None,
                                const_value: None,
                                subschemas: None,
                                number: None,
                                string: None,
                                array: None,
                                object: None,
                                reference: Some(
                                    "#/components/schemas/ProjectSelector",
                                ),
                                extensions: {},
                            },
                        ),
                    ],
                ),
                any_of: None,
                one_of: None,
                not: None,
                if_schema: None,
                then_schema: None,
                else_schema: None,
            },
        ),
        number: None,
        string: None,
        array: None,
        object: None,
        reference: None,
        extensions: {
            "nullable": Bool(true),
        },
    },
)', /Users/just-be/.cargo/git/checkouts/dropshot-a4a923d29dccc492/2529126/dropshot/src/handler.rs:932:20
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
', nexus/tests/integration_tests/commands.rs:106:38
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Copy link
Contributor

@augustuswm augustuswm Dec 15, 2022

Choose a reason for hiding this comment

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

I haven't tried converting all of it but you might be able to represent this as:

#[derive(Deserialize, JsonSchema)]
pub struct OrganizationSelector {
    pub organization: Option<NameOrId>,
}

#[derive(Deserialize, JsonSchema)]
pub struct ProjectSelector {
    #[serde(flatten)]
    pub organization_selector: Option<OrganizationSelector>,
    pub project: Option<NameOrId>,
}

It would mean that the error message in _ => Err(Error::invalid_request(...) would need updating.

I'm also not positive on the open api spec that this will generate, it would need testing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's true. I made it more explicit because there are cases when we know the user must provide a path param (like the parent resource of a list endpoint) vs when it could be represented by the path. I think the extra code (which can be generated) is worth the trade off of a more correct spec.

Comment on lines +22 to +25
#[derive(Deserialize, JsonSchema)]
pub struct OrganizationPath {
pub organization: NameOrId,
}
Copy link
Contributor Author

@just-be-dev just-be-dev Dec 15, 2022

Choose a reason for hiding this comment

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

Important thing to note is that I've relocated all of the /v1/ params here. This'll grow to be a bit more noisy and I think will be a good opportunity for macaronization.

Copy link
Contributor

@karencfv karencfv left a comment

Choose a reason for hiding this comment

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

I'll leave the review of the internals for people who are more acquainted with the code base, but I played around with the progenitor generated client and it works beautifully!!!

Thanks for taking this on, the API is looking great! 🙇‍♀️

@just-be-dev just-be-dev requested a review from ahl December 15, 2022 21:17
pub pagination: PaginatedByNameOrId,
#[serde(flatten)]
pub organization: OrganizationSelector,
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd almost rather define this next to the endpoint handler since that's the only place it's called.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I just grouped them together because I'm hoping to macroize it at some point.

Copy link
Contributor

Choose a reason for hiding this comment

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

@david-crespo I thought you were the one who split these out by type rather than by use?

Copy link
Contributor

@david-crespo david-crespo left a comment

Choose a reason for hiding this comment

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

Looks great. A bit noisy, but the noise is extremely straightforward so it's not a big deal. We'll have to see whether macros or openapi-lint is the better approach to avoiding mistakes.

@just-be-dev
Copy link
Contributor Author

All right folks, I'm going to go ahead and merge. Your reviews are still welcome/greatly desired. I'll address any issues in a followup.

@just-be-dev just-be-dev merged commit 3e49d47 into main Dec 20, 2022
@just-be-dev just-be-dev deleted the rfd-322-orgs-n-projs branch December 20, 2022 18:06
Copy link
Contributor

@ahl ahl left a comment

Choose a reason for hiding this comment

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

adding these as new endpoints rather than modifying existing ones make errors much harder to spot. When are we going to get rid of the non-v1 operations? presumably we'll be changing the operation names to remove v1?

Comment on lines +138 to +147
// let expected_uncovered_endpoints =
// std::fs::read_to_string("tests/output/uncovered-authz-endpoints.txt")
// .expect("failed to load file of allowed uncovered endpoints");

// TODO: Update this to remove overwrite capabilities
// See https://github.com/oxidecomputer/expectorate/pull/12
assert_contents(
"tests/output/uncovered-authz-endpoints.txt",
uncovered_endpoints.as_str(),
);
Copy link
Contributor

Choose a reason for hiding this comment

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

what's the plan?

Comment on lines +50 to +51
#[serde(flatten)]
pub organization_selector: Option<OrganizationSelector>,
Copy link
Contributor

Choose a reason for hiding this comment

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

why not
pub organization: Option<NameOrId>,

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Consistency mostly. I'll flatten it down.

#[derive(Deserialize, JsonSchema)]
pub struct ProjectSelector {
#[serde(flatten)]
pub organization_selector: Option<OrganizationSelector>,
Copy link
Contributor

Choose a reason for hiding this comment

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

why not just organization: Option<NameOrId>

pub pagination: PaginatedByNameOrId,
#[serde(flatten)]
pub organization: OrganizationSelector,
}
Copy link
Contributor

Choose a reason for hiding this comment

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

@david-crespo I thought you were the one who split these out by type rather than by use?

@just-be-dev
Copy link
Contributor Author

adding these as new endpoints rather than modifying existing ones make errors much harder to spot. When are we going to get rid of the non-v1 operations?

That's true, but it's generally also less disruptive. My goal is to not block folks working on other things while the transition is in progress. The loose order of operations is

  1. Add the new endpoints
  2. Update the docs and tests
  3. Update the clients
  4. Remove the old endpoints

You can see (or add) more specifics in the parent issue: #2028

presumably we'll be changing the operation names to remove v1?

Tentatively, yes. That seems to be the popular opinion. I think that that because we can have /v1/ and /v2/ of an endpoint then we should likely have that mirrored via the operation names. Even though it's more noisy, it's externally consistent. I'm flexible though.

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.

6 participants