From 233dc6dafa30e811c1cfa4797473a9c7a1de6436 Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Thu, 17 Nov 2022 15:33:15 -0500 Subject: [PATCH 01/31] Test impl of new endpoint format --- common/src/api/external/mod.rs | 41 ++++++++++++++ nexus/src/external_api/http_entrypoints.rs | 62 +++++++++++++++++++++ nexus/tests/output/nexus_tags.txt | 1 + openapi/nexus.json | 64 ++++++++++++++++++++++ 4 files changed, 168 insertions(+) diff --git a/common/src/api/external/mod.rs b/common/src/api/external/mod.rs index 3d8ee960b29..e89e82e2225 100644 --- a/common/src/api/external/mod.rs +++ b/common/src/api/external/mod.rs @@ -268,6 +268,47 @@ impl Name { } } +#[derive(Serialize, Deserialize)] +#[serde(try_from = "String")] +pub enum ResourceIdentifier { + Name(Name), + Id(Uuid), +} + +impl TryFrom for ResourceIdentifier { + type Error = String; + + fn try_from(value: String) -> Result { + if let Ok(id) = Uuid::parse_str(&value) { + Ok(ResourceIdentifier::Id(id)) + } else { + Ok(ResourceIdentifier::Name(Name::try_from(value)?)) + } + } +} + +impl JsonSchema for ResourceIdentifier { + fn schema_name() -> String { + "ResourceIdentifier".to_string() + } + + fn json_schema( + gen: &mut schemars::gen::SchemaGenerator, + ) -> schemars::schema::Schema { + schemars::schema::SchemaObject { + subschemas: Some(Box::new(schemars::schema::SubschemaValidation { + one_of: Some(vec![ + gen.subschema_for::(), + gen.subschema_for::(), + ]), + ..Default::default() + })), + ..Default::default() + } + .into() + } +} + /// Name for a built-in role #[derive( Clone, diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index ee3a19ea565..4746e7827cd 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -61,6 +61,7 @@ use omicron_common::api::external::Error; use omicron_common::api::external::Instance; use omicron_common::api::external::InternalContext; use omicron_common::api::external::NetworkInterface; +use omicron_common::api::external::ResourceIdentifier; use omicron_common::api::external::RouterRoute; use omicron_common::api::external::RouterRouteCreateParams; use omicron_common::api::external::RouterRouteKind; @@ -137,6 +138,7 @@ pub fn external_api() -> NexusApiDescription { api.register(instance_list)?; api.register(instance_create)?; api.register(instance_view)?; + api.register(instance_lookup)?; api.register(instance_view_by_id)?; api.register(instance_delete)?; api.register(instance_migrate)?; @@ -2078,6 +2080,66 @@ async fn instance_create( apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await } +/// Path parameters for Instance requests. Name is temporary. +#[derive(Deserialize, JsonSchema)] +struct InstanceLookupPathParam { + instance: ResourceIdentifier, +} + +#[derive(Deserialize, JsonSchema)] +struct InstanceLookupQueryParam { + organization_name: Option, + project_name: Option, +} + +#[endpoint { + method = GET, + path = "/instances/{instance}", + tags = ["instances"], +}] +async fn instance_lookup( + rqctx: Arc>>, + query_params: Query, + path_params: Path, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let query = query_params.into_inner(); + let instance_ident = &path.instance; + let handler = async { + let opctx = OpContext::for_external_api(&rqctx).await?; + let instance = match instance_ident { + ResourceIdentifier::Name(name) => { + let organization_name = + query.organization_name.ok_or_else(|| { + Error::InvalidRequest { message: "organization_name is required when using instance name".to_string() } + })?; + let project_name = query.project_name.ok_or_else(|| { + Error::InvalidRequest { + message: + "project_name is required when using instance name" + .to_string(), + } + })?; + nexus + .instance_fetch( + &opctx, + &organization_name, + &project_name, + &Name(name.clone()), + ) + .await? + } + ResourceIdentifier::Id(id) => { + nexus.instance_fetch_by_id(&opctx, id).await? + } + }; + Ok(HttpResponseOk(instance.into())) + }; + apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await +} + /// Path parameters for Instance requests #[derive(Deserialize, JsonSchema)] struct InstancePathParam { diff --git a/nexus/tests/output/nexus_tags.txt b/nexus/tests/output/nexus_tags.txt index 86fc5e6b46f..4f6436ddb08 100644 --- a/nexus/tests/output/nexus_tags.txt +++ b/nexus/tests/output/nexus_tags.txt @@ -34,6 +34,7 @@ instance_disk_detach /organizations/{organization_name}/proj instance_disk_list /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/disks instance_external_ip_list /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/external-ips instance_list /organizations/{organization_name}/projects/{project_name}/instances +instance_lookup /instances/{instance} instance_migrate /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/migrate instance_network_interface_create /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/network-interfaces instance_network_interface_delete /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/network-interfaces/{interface_name} diff --git a/openapi/nexus.json b/openapi/nexus.json index ab1eb0e0b06..68ecd327437 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -590,6 +590,59 @@ "x-dropshot-pagination": true } }, + "/instances/{instance}": { + "get": { + "tags": [ + "instances" + ], + "operationId": "instance_lookup", + "parameters": [ + { + "in": "query", + "name": "organization_name", + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "form" + }, + { + "in": "query", + "name": "project_name", + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "form" + }, + { + "in": "path", + "name": "instance", + "required": true, + "schema": { + "$ref": "#/components/schemas/ResourceIdentifier" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Instance" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, "/login": { "post": { "tags": [ @@ -12775,6 +12828,17 @@ } ] }, + "ResourceIdentifier": { + "oneOf": [ + { + "$ref": "#/components/schemas/Name" + }, + { + "type": "string", + "format": "uuid" + } + ] + }, "NameOrIdSortMode": { "description": "Supported set of sort modes for scanning by name or id", "oneOf": [ From 0be1778be7e52cc540bfcdb2effc6bacd60e894b Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Thu, 17 Nov 2022 15:54:59 -0500 Subject: [PATCH 02/31] ResourceIdentifier -> NameOrId --- common/src/api/external/mod.rs | 10 ++++----- nexus/src/external_api/http_entrypoints.rs | 25 ++++++++++++++++------ 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/common/src/api/external/mod.rs b/common/src/api/external/mod.rs index e89e82e2225..fd708c717ad 100644 --- a/common/src/api/external/mod.rs +++ b/common/src/api/external/mod.rs @@ -270,24 +270,24 @@ impl Name { #[derive(Serialize, Deserialize)] #[serde(try_from = "String")] -pub enum ResourceIdentifier { +pub enum NameOrId { Name(Name), Id(Uuid), } -impl TryFrom for ResourceIdentifier { +impl TryFrom for NameOrId { type Error = String; fn try_from(value: String) -> Result { if let Ok(id) = Uuid::parse_str(&value) { - Ok(ResourceIdentifier::Id(id)) + Ok(NameOrId::Id(id)) } else { - Ok(ResourceIdentifier::Name(Name::try_from(value)?)) + Ok(NameOrId::Name(Name::try_from(value)?)) } } } -impl JsonSchema for ResourceIdentifier { +impl JsonSchema for NameOrId { fn schema_name() -> String { "ResourceIdentifier".to_string() } diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index 4746e7827cd..ff75859b3d3 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -60,8 +60,8 @@ use omicron_common::api::external::Disk; use omicron_common::api::external::Error; use omicron_common::api::external::Instance; use omicron_common::api::external::InternalContext; +use omicron_common::api::external::NameOrId; use omicron_common::api::external::NetworkInterface; -use omicron_common::api::external::ResourceIdentifier; use omicron_common::api::external::RouterRoute; use omicron_common::api::external::RouterRouteCreateParams; use omicron_common::api::external::RouterRouteKind; @@ -2083,7 +2083,7 @@ async fn instance_create( /// Path parameters for Instance requests. Name is temporary. #[derive(Deserialize, JsonSchema)] struct InstanceLookupPathParam { - instance: ResourceIdentifier, + instance: NameOrId, } #[derive(Deserialize, JsonSchema)] @@ -2107,10 +2107,25 @@ async fn instance_lookup( let path = path_params.into_inner(); let query = query_params.into_inner(); let instance_ident = &path.instance; + let instance_id = match &path.instance { + NameOrId::Id(id) => {} + NameOrId::Name(name) => { + let opctx = OpContext::for_external_api(&rqctx).await?; + let instance = nexus + .instance_lookup( + &opctx, + &query.organization_name, + &query.project_name, + &name, + ) + .await?; + instance.id() + } + }; let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; let instance = match instance_ident { - ResourceIdentifier::Name(name) => { + NameOrId::Name(name) => { let organization_name = query.organization_name.ok_or_else(|| { Error::InvalidRequest { message: "organization_name is required when using instance name".to_string() } @@ -2131,9 +2146,7 @@ async fn instance_lookup( ) .await? } - ResourceIdentifier::Id(id) => { - nexus.instance_fetch_by_id(&opctx, id).await? - } + NameOrId::Id(id) => nexus.instance_fetch_by_id(&opctx, id).await?, }; Ok(HttpResponseOk(instance.into())) }; From 6e3210f4df2fdda09e12eb7a463eb6d3799b62d7 Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Thu, 17 Nov 2022 16:08:08 -0500 Subject: [PATCH 03/31] Add docs, remove unneeded chunk --- nexus/src/external_api/http_entrypoints.rs | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index ff75859b3d3..2fd9962ef1d 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -2083,12 +2083,16 @@ async fn instance_create( /// Path parameters for Instance requests. Name is temporary. #[derive(Deserialize, JsonSchema)] struct InstanceLookupPathParam { + /// If Name is provided `organization_name` and `project_name` query parameters must also be present. + /// Otherwise they should be omitted. instance: NameOrId, } #[derive(Deserialize, JsonSchema)] struct InstanceLookupQueryParam { + /// Should only be specified if `instance` path param is a name organization_name: Option, + /// Should only be specified if `instance` path param is a name project_name: Option, } @@ -2107,21 +2111,6 @@ async fn instance_lookup( let path = path_params.into_inner(); let query = query_params.into_inner(); let instance_ident = &path.instance; - let instance_id = match &path.instance { - NameOrId::Id(id) => {} - NameOrId::Name(name) => { - let opctx = OpContext::for_external_api(&rqctx).await?; - let instance = nexus - .instance_lookup( - &opctx, - &query.organization_name, - &query.project_name, - &name, - ) - .await?; - instance.id() - } - }; let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; let instance = match instance_ident { From a2681b9d9bcffcaddfe56475f10637bdcba06feb Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Thu, 17 Nov 2022 17:07:09 -0500 Subject: [PATCH 04/31] Use v1 prefix, convert instance delete --- nexus/src/app/instance.rs | 31 +++++++---- nexus/src/external_api/http_entrypoints.rs | 65 +++++++++++++++++++--- 2 files changed, 78 insertions(+), 18 deletions(-) diff --git a/nexus/src/app/instance.rs b/nexus/src/app/instance.rs index be8d83e1dac..057a9906bf9 100644 --- a/nexus/src/app/instance.rs +++ b/nexus/src/app/instance.rs @@ -53,6 +53,22 @@ use uuid::Uuid; const MAX_KEYS_PER_INSTANCE: u32 = 8; impl super::Nexus { + pub async fn instance_lookup_id( + &self, + opctx: &OpContext, + organization_name: &Name, + project_name: &Name, + instance_name: &Name, + ) -> LookupResult { + let (.., authz_instance) = LookupPath::new(opctx, &self.db_datastore) + .organization_name(organization_name) + .project_name(project_name) + .instance_name(instance_name) + .lookup_for(authz::Action::Read) + .await?; + return Ok(authz_instance.id()); + } + pub async fn project_create_instance( self: &Arc, opctx: &OpContext, @@ -243,20 +259,15 @@ impl super::Nexus { pub async fn project_destroy_instance( &self, opctx: &OpContext, - organization_name: &Name, - project_name: &Name, - instance_name: &Name, + instance_id: &Uuid, ) -> DeleteResult { // TODO-robustness We need to figure out what to do with Destroyed // instances? Presumably we need to clean them up at some point, but // not right away so that callers can see that they've been destroyed. - let (.., authz_instance, _) = - LookupPath::new(opctx, &self.db_datastore) - .organization_name(organization_name) - .project_name(project_name) - .instance_name(instance_name) - .fetch() - .await?; + let (.., authz_instance) = LookupPath::new(opctx, &self.db_datastore) + .instance_id(*instance_id) + .lookup_for(authz::Action::Delete) + .await?; self.db_datastore .project_delete_instance(opctx, &authz_instance) diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index 2fd9962ef1d..e8af77a592d 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -138,7 +138,7 @@ pub fn external_api() -> NexusApiDescription { api.register(instance_list)?; api.register(instance_create)?; api.register(instance_view)?; - api.register(instance_lookup)?; + api.register(instance_view_v1)?; api.register(instance_view_by_id)?; api.register(instance_delete)?; api.register(instance_migrate)?; @@ -2098,10 +2098,10 @@ struct InstanceLookupQueryParam { #[endpoint { method = GET, - path = "/instances/{instance}", + path = "/v1/instances/{instance}", tags = ["instances"], }] -async fn instance_lookup( +async fn instance_view_v1( rqctx: Arc>>, query_params: Query, path_params: Path, @@ -2113,7 +2113,7 @@ async fn instance_lookup( let instance_ident = &path.instance; let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; - let instance = match instance_ident { + let instance_id = match instance_ident { NameOrId::Name(name) => { let organization_name = query.organization_name.ok_or_else(|| { @@ -2127,7 +2127,7 @@ async fn instance_lookup( } })?; nexus - .instance_fetch( + .instance_lookup_id( &opctx, &organization_name, &project_name, @@ -2135,8 +2135,9 @@ async fn instance_lookup( ) .await? } - NameOrId::Id(id) => nexus.instance_fetch_by_id(&opctx, id).await?, + NameOrId::Id(id) => *id, }; + let instance = nexus.instance_fetch_by_id(&opctx, &instance_id).await?; Ok(HttpResponseOk(instance.into())) }; apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await @@ -2203,6 +2204,53 @@ async fn instance_view_by_id( apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await } +#[endpoint { + method = DELETE, + path = "/v1/instances/{instance}", + tags = ["instances"], +}] +async fn instance_delete_v1( + rqctx: Arc>>, + query_params: Query, + path_params: Path, +) -> Result { + let apictx = rqctx.context(); + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let query = query_params.into_inner(); + let instance_ident = &path.instance; + let handler = async { + let opctx = OpContext::for_external_api(&rqctx).await?; + let instance_id = match instance_ident { + NameOrId::Name(name) => { + let organization_name = + query.organization_name.ok_or_else(|| { + Error::InvalidRequest { message: "organization_name is required when using instance name".to_string() } + })?; + let project_name = query.project_name.ok_or_else(|| { + Error::InvalidRequest { + message: + "project_name is required when using instance name" + .to_string(), + } + })?; + nexus + .instance_lookup_id( + &opctx, + &organization_name, + &project_name, + &Name(name.clone()), + ) + .await? + } + NameOrId::Id(id) => *id, + }; + nexus.project_destroy_instance(&opctx, &instance_id).await?; + Ok(HttpResponseDeleted()) + }; + apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await +} + /// Delete an instance #[endpoint { method = DELETE, @@ -2221,14 +2269,15 @@ async fn instance_delete( let instance_name = &path.instance_name; let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; - nexus - .project_destroy_instance( + let instance_id = nexus + .instance_lookup_id( &opctx, &organization_name, &project_name, &instance_name, ) .await?; + nexus.project_destroy_instance(&opctx, &instance_id).await?; Ok(HttpResponseDeleted()) }; apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await From 18bc9cf9980432df97ffcdb423e86730f7beae0c Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Thu, 17 Nov 2022 17:58:41 -0500 Subject: [PATCH 05/31] Push up diplay error --- common/src/api/external/mod.rs | 6 +++--- nexus/src/external_api/http_entrypoints.rs | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/common/src/api/external/mod.rs b/common/src/api/external/mod.rs index fd708c717ad..93ccab8ab61 100644 --- a/common/src/api/external/mod.rs +++ b/common/src/api/external/mod.rs @@ -268,8 +268,8 @@ impl Name { } } -#[derive(Serialize, Deserialize)] -#[serde(try_from = "String")] +#[derive(Serialize, Deserialize, Display, Debug)] +#[display("{0}")] pub enum NameOrId { Name(Name), Id(Uuid), @@ -289,7 +289,7 @@ impl TryFrom for NameOrId { impl JsonSchema for NameOrId { fn schema_name() -> String { - "ResourceIdentifier".to_string() + "NameOrId".to_string() } fn json_schema( diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index e8af77a592d..d5c2435f175 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -141,6 +141,7 @@ pub fn external_api() -> NexusApiDescription { api.register(instance_view_v1)?; api.register(instance_view_by_id)?; api.register(instance_delete)?; + api.register(instance_delete_v1)?; api.register(instance_migrate)?; api.register(instance_reboot)?; api.register(instance_start)?; From 07ec450199d759b1bf7219f1ad493c88cbaa46db Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Thu, 17 Nov 2022 18:03:11 -0500 Subject: [PATCH 06/31] ReferenceIdentifer -> NameOrId in nexus.json --- openapi/nexus.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openapi/nexus.json b/openapi/nexus.json index 68ecd327437..6e73e910be1 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -618,7 +618,7 @@ "name": "instance", "required": true, "schema": { - "$ref": "#/components/schemas/ResourceIdentifier" + "$ref": "#/components/schemas/NameOrId" }, "style": "simple" } @@ -12828,7 +12828,7 @@ } ] }, - "ResourceIdentifier": { + "NameOrId": { "oneOf": [ { "$ref": "#/components/schemas/Name" From 5b2f2177d2ac94ea3f57630ed322e963a31a0ff8 Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Thu, 17 Nov 2022 18:19:40 -0500 Subject: [PATCH 07/31] Update nexus.json to fix build issues --- common/src/api/external/mod.rs | 5 +- nexus/tests/output/nexus_tags.txt | 3 +- openapi/nexus.json | 178 +++++++++++++++++++----------- 3 files changed, 119 insertions(+), 67 deletions(-) diff --git a/common/src/api/external/mod.rs b/common/src/api/external/mod.rs index 93ccab8ab61..a490cba1eae 100644 --- a/common/src/api/external/mod.rs +++ b/common/src/api/external/mod.rs @@ -268,8 +268,9 @@ impl Name { } } -#[derive(Serialize, Deserialize, Display, Debug)] -#[display("{0}")] +#[derive(Serialize, Deserialize)] +#[serde(try_from = "String")] + pub enum NameOrId { Name(Name), Id(Uuid), diff --git a/nexus/tests/output/nexus_tags.txt b/nexus/tests/output/nexus_tags.txt index 4f6436ddb08..ba32c977480 100644 --- a/nexus/tests/output/nexus_tags.txt +++ b/nexus/tests/output/nexus_tags.txt @@ -29,12 +29,12 @@ API operations found with tag "instances" OPERATION ID URL PATH instance_create /organizations/{organization_name}/projects/{project_name}/instances instance_delete /organizations/{organization_name}/projects/{project_name}/instances/{instance_name} +instance_delete_v1 /v1/instances/{instance} instance_disk_attach /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/disks/attach instance_disk_detach /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/disks/detach instance_disk_list /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/disks instance_external_ip_list /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/external-ips instance_list /organizations/{organization_name}/projects/{project_name}/instances -instance_lookup /instances/{instance} instance_migrate /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/migrate instance_network_interface_create /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/network-interfaces instance_network_interface_delete /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/network-interfaces/{interface_name} @@ -49,6 +49,7 @@ instance_start /organizations/{organization_name}/proj instance_stop /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/stop instance_view /organizations/{organization_name}/projects/{project_name}/instances/{instance_name} instance_view_by_id /by-id/instances/{id} +instance_view_v1 /v1/instances/{instance} API operations found with tag "login" OPERATION ID URL PATH diff --git a/openapi/nexus.json b/openapi/nexus.json index 6e73e910be1..2caceb03fff 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -590,59 +590,6 @@ "x-dropshot-pagination": true } }, - "/instances/{instance}": { - "get": { - "tags": [ - "instances" - ], - "operationId": "instance_lookup", - "parameters": [ - { - "in": "query", - "name": "organization_name", - "schema": { - "$ref": "#/components/schemas/Name" - }, - "style": "form" - }, - { - "in": "query", - "name": "project_name", - "schema": { - "$ref": "#/components/schemas/Name" - }, - "style": "form" - }, - { - "in": "path", - "name": "instance", - "required": true, - "schema": { - "$ref": "#/components/schemas/NameOrId" - }, - "style": "simple" - } - ], - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Instance" - } - } - } - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - } - }, "/login": { "post": { "tags": [ @@ -7706,6 +7653,109 @@ }, "x-dropshot-pagination": true } + }, + "/v1/instances/{instance}": { + "get": { + "tags": [ + "instances" + ], + "operationId": "instance_view_v1", + "parameters": [ + { + "in": "query", + "name": "organization_name", + "description": "Should only be specified if `instance` path param is a name", + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "form" + }, + { + "in": "query", + "name": "project_name", + "description": "Should only be specified if `instance` path param is a name", + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "form" + }, + { + "in": "path", + "name": "instance", + "description": "If Name is provided `organization_name` and `project_name` query parameters must also be present. Otherwise they should be omitted.", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Instance" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "tags": [ + "instances" + ], + "operationId": "instance_delete_v1", + "parameters": [ + { + "in": "query", + "name": "organization_name", + "description": "Should only be specified if `instance` path param is a name", + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "form" + }, + { + "in": "query", + "name": "project_name", + "description": "Should only be specified if `instance` path param is a name", + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "form" + }, + { + "in": "path", + "name": "instance", + "description": "If Name is provided `organization_name` and `project_name` query parameters must also be present. Otherwise they should be omitted.", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + }, + "style": "simple" + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } } }, "components": { @@ -12828,17 +12878,6 @@ } ] }, - "NameOrId": { - "oneOf": [ - { - "$ref": "#/components/schemas/Name" - }, - { - "type": "string", - "format": "uuid" - } - ] - }, "NameOrIdSortMode": { "description": "Supported set of sort modes for scanning by name or id", "oneOf": [ @@ -12887,6 +12926,17 @@ "write", "write_bytes" ] + }, + "NameOrId": { + "oneOf": [ + { + "$ref": "#/components/schemas/Name" + }, + { + "type": "string", + "format": "uuid" + } + ] } } }, From 8631e9f3f3e248f9b42ee282db37d30b73e5e120 Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Thu, 17 Nov 2022 18:23:10 -0500 Subject: [PATCH 08/31] Change v1 to a prefix instead of postfix --- nexus/src/external_api/http_entrypoints.rs | 8 ++++---- nexus/tests/output/nexus_tags.txt | 4 ++-- openapi/nexus.json | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index d5c2435f175..ee57230a57a 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -138,16 +138,16 @@ pub fn external_api() -> NexusApiDescription { api.register(instance_list)?; api.register(instance_create)?; api.register(instance_view)?; - api.register(instance_view_v1)?; api.register(instance_view_by_id)?; api.register(instance_delete)?; - api.register(instance_delete_v1)?; api.register(instance_migrate)?; api.register(instance_reboot)?; api.register(instance_start)?; api.register(instance_stop)?; api.register(instance_serial_console)?; api.register(instance_serial_console_stream)?; + api.register(v1_instance_view)?; + api.register(v1_instance_delete)?; // Project-scoped images API api.register(image_list)?; @@ -2102,7 +2102,7 @@ struct InstanceLookupQueryParam { path = "/v1/instances/{instance}", tags = ["instances"], }] -async fn instance_view_v1( +async fn v1_instance_view( rqctx: Arc>>, query_params: Query, path_params: Path, @@ -2210,7 +2210,7 @@ async fn instance_view_by_id( path = "/v1/instances/{instance}", tags = ["instances"], }] -async fn instance_delete_v1( +async fn v1_instance_delete( rqctx: Arc>>, query_params: Query, path_params: Path, diff --git a/nexus/tests/output/nexus_tags.txt b/nexus/tests/output/nexus_tags.txt index ba32c977480..94a463a26fe 100644 --- a/nexus/tests/output/nexus_tags.txt +++ b/nexus/tests/output/nexus_tags.txt @@ -29,7 +29,6 @@ API operations found with tag "instances" OPERATION ID URL PATH instance_create /organizations/{organization_name}/projects/{project_name}/instances instance_delete /organizations/{organization_name}/projects/{project_name}/instances/{instance_name} -instance_delete_v1 /v1/instances/{instance} instance_disk_attach /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/disks/attach instance_disk_detach /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/disks/detach instance_disk_list /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/disks @@ -49,7 +48,8 @@ instance_start /organizations/{organization_name}/proj instance_stop /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/stop instance_view /organizations/{organization_name}/projects/{project_name}/instances/{instance_name} instance_view_by_id /by-id/instances/{id} -instance_view_v1 /v1/instances/{instance} +v1_instance_delete /v1/instances/{instance} +v1_instance_view /v1/instances/{instance} API operations found with tag "login" OPERATION ID URL PATH diff --git a/openapi/nexus.json b/openapi/nexus.json index 2caceb03fff..9820214853c 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -7659,7 +7659,7 @@ "tags": [ "instances" ], - "operationId": "instance_view_v1", + "operationId": "v1_instance_view", "parameters": [ { "in": "query", @@ -7713,7 +7713,7 @@ "tags": [ "instances" ], - "operationId": "instance_delete_v1", + "operationId": "v1_instance_delete", "parameters": [ { "in": "query", From 41672aa710a5d429681eeef11ef3635116dd5e10 Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Sat, 19 Nov 2022 12:42:55 -0500 Subject: [PATCH 09/31] Normalize id lookups and path selection --- nexus/db-model/src/name.rs | 10 +++ nexus/src/app/instance.rs | 58 ++++++++++--- nexus/src/external_api/http_entrypoints.rs | 98 ++++++++-------------- nexus/tests/integration_tests/instances.rs | 59 +++++++++++++ nexus/types/src/external_api/params.rs | 17 ++++ openapi/nexus.json | 60 +++++++++++-- 6 files changed, 221 insertions(+), 81 deletions(-) diff --git a/nexus/db-model/src/name.rs b/nexus/db-model/src/name.rs index 96603530333..10d0819b5fb 100644 --- a/nexus/db-model/src/name.rs +++ b/nexus/db-model/src/name.rs @@ -63,3 +63,13 @@ where String::from_sql(bytes)?.parse().map(Name).map_err(|e| e.into()) } } + +/// Newtype wrapper around [external::NameOrId]. This type isn't actually +/// stored in the database, but exists as a convenience for the API. +#[derive(JsonSchema, Serialize, Deserialize, RefCast)] +#[serde(transparent)] +#[repr(transparent)] +pub struct NameOrId(pub external::NameOrId); + +NewtypeFrom! { () pub struct NameOrId(external::NameOrId); } +NewtypeDeref! { () pub struct NameOrId(external::NameOrId); } diff --git a/nexus/src/app/instance.rs b/nexus/src/app/instance.rs index 057a9906bf9..39681995fb4 100644 --- a/nexus/src/app/instance.rs +++ b/nexus/src/app/instance.rs @@ -32,6 +32,7 @@ use omicron_common::api::external::InstanceState; use omicron_common::api::external::InternalContext; use omicron_common::api::external::ListResultVec; use omicron_common::api::external::LookupResult; +use omicron_common::api::external::NameOrId; use omicron_common::api::external::UpdateResult; use omicron_common::api::external::Vni; use omicron_common::api::internal::nexus; @@ -56,17 +57,54 @@ impl super::Nexus { pub async fn instance_lookup_id( &self, opctx: &OpContext, - organization_name: &Name, - project_name: &Name, - instance_name: &Name, + instance: NameOrId, + project_selector: params::ProjectSelector, ) -> LookupResult { - let (.., authz_instance) = LookupPath::new(opctx, &self.db_datastore) - .organization_name(organization_name) - .project_name(project_name) - .instance_name(instance_name) - .lookup_for(authz::Action::Read) - .await?; - return Ok(authz_instance.id()); + match instance { + NameOrId::Id(id) => Ok(id), + NameOrId::Name(name) => match project_selector { + params::ProjectSelector::ProjectId { project_id } => { + let (.., authz_instance) = + LookupPath::new(opctx, &self.db_datastore) + .project_id(project_id) + .instance_name(&Name(name.clone())) + .lookup_for(authz::Action::Read) + .await?; + Ok(authz_instance.id()) + } + params::ProjectSelector::ProjectAndOrgId { + project_name, + organization_id, + } => { + let (.., authz_instance) = + LookupPath::new(opctx, &self.db_datastore) + .organization_id(organization_id) + .project_name(&Name(project_name)) + .instance_name(&Name(name.clone())) + .lookup_for(authz::Action::Read) + .await?; + Ok(authz_instance.id()) + } + params::ProjectSelector::ProjectAndOrg { + project_name, + organization_name, + } => { + let (.., authz_instance) = + LookupPath::new(opctx, &self.db_datastore) + .organization_name(&Name(organization_name)) + .project_name(&Name(project_name)) + .instance_name(&Name(name.clone())) + .lookup_for(authz::Action::Read) + .await?; + Ok(authz_instance.id()) + } + params::ProjectSelector::None {} => { + Err(Error::InvalidRequest { + message: "When looking up an instance by name, the path to the instance must be provided via query paramaters".to_string() + }) + } + }, + } } pub async fn project_create_instance( diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index ee57230a57a..41f60c074da 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -18,6 +18,7 @@ use crate::authz; use crate::context::OpContext; use crate::db; use crate::db::model::Name; +use crate::db::model::NameOrId; use crate::external_api::shared; use crate::ServerContext; use dropshot::ApiDescription; @@ -60,7 +61,6 @@ use omicron_common::api::external::Disk; use omicron_common::api::external::Error; use omicron_common::api::external::Instance; use omicron_common::api::external::InternalContext; -use omicron_common::api::external::NameOrId; use omicron_common::api::external::NetworkInterface; use omicron_common::api::external::RouterRoute; use omicron_common::api::external::RouterRouteCreateParams; @@ -2042,6 +2042,7 @@ async fn instance_list( apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await } + /// Create an instance // TODO-correctness This is supposed to be async. Is that right? We can create // the instance immediately -- it's just not booted yet. Maybe the boot @@ -2090,11 +2091,9 @@ struct InstanceLookupPathParam { } #[derive(Deserialize, JsonSchema)] -struct InstanceLookupQueryParam { - /// Should only be specified if `instance` path param is a name - organization_name: Option, - /// Should only be specified if `instance` path param is a name - project_name: Option, +struct InstanceQueryParams { + #[serde(flatten)] + selector: params::ProjectSelector, } #[endpoint { @@ -2104,40 +2103,18 @@ struct InstanceLookupQueryParam { }] async fn v1_instance_view( rqctx: Arc>>, - query_params: Query, + query_params: Query, path_params: Path, ) -> Result, HttpError> { let apictx = rqctx.context(); let nexus = &apictx.nexus; let path = path_params.into_inner(); let query = query_params.into_inner(); - let instance_ident = &path.instance; - let handler = async { - let opctx = OpContext::for_external_api(&rqctx).await?; - let instance_id = match instance_ident { - NameOrId::Name(name) => { - let organization_name = - query.organization_name.ok_or_else(|| { - Error::InvalidRequest { message: "organization_name is required when using instance name".to_string() } - })?; - let project_name = query.project_name.ok_or_else(|| { - Error::InvalidRequest { - message: - "project_name is required when using instance name" - .to_string(), - } - })?; - nexus - .instance_lookup_id( - &opctx, - &organization_name, - &project_name, - &Name(name.clone()), - ) - .await? - } - NameOrId::Id(id) => *id, - }; + let handler = async { + let opctx = OpContext::for_external_api(&rqctx).await?; + let instance_id = nexus + .instance_lookup_id(&opctx, path.instance.into(), query.selector) + .await?; let instance = nexus.instance_fetch_by_id(&opctx, &instance_id).await?; Ok(HttpResponseOk(instance.into())) }; @@ -2212,40 +2189,18 @@ async fn instance_view_by_id( }] async fn v1_instance_delete( rqctx: Arc>>, - query_params: Query, + query_params: Query, path_params: Path, ) -> Result { let apictx = rqctx.context(); let nexus = &apictx.nexus; let path = path_params.into_inner(); let query = query_params.into_inner(); - let instance_ident = &path.instance; - let handler = async { - let opctx = OpContext::for_external_api(&rqctx).await?; - let instance_id = match instance_ident { - NameOrId::Name(name) => { - let organization_name = - query.organization_name.ok_or_else(|| { - Error::InvalidRequest { message: "organization_name is required when using instance name".to_string() } - })?; - let project_name = query.project_name.ok_or_else(|| { - Error::InvalidRequest { - message: - "project_name is required when using instance name" - .to_string(), - } - })?; - nexus - .instance_lookup_id( - &opctx, - &organization_name, - &project_name, - &Name(name.clone()), - ) - .await? - } - NameOrId::Id(id) => *id, - }; + let handler = async { + let opctx = OpContext::for_external_api(&rqctx).await?; + let instance_id = nexus + .instance_lookup_id(&opctx, path.instance.into(), query.selector) + .await?; nexus.project_destroy_instance(&opctx, &instance_id).await?; Ok(HttpResponseDeleted()) }; @@ -2270,12 +2225,25 @@ async fn instance_delete( let instance_name = &path.instance_name; let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; + + // TODO: Clean this up let instance_id = nexus .instance_lookup_id( &opctx, - &organization_name, - &project_name, - &instance_name, + omicron_common::api::external::NameOrId::Name( + omicron_common::api::external::Name::from( + instance_name.clone(), + ), + ), + params::ProjectSelector::ProjectAndOrg { + project_name: omicron_common::api::external::Name::from( + project_name.clone(), + ), + organization_name: + omicron_common::api::external::Name::from( + organization_name.clone(), + ), + }, ) .await?; nexus.project_destroy_instance(&opctx, &instance_id).await?; diff --git a/nexus/tests/integration_tests/instances.rs b/nexus/tests/integration_tests/instances.rs index 29bc92416d8..5dec931188b 100644 --- a/nexus/tests/integration_tests/instances.rs +++ b/nexus/tests/integration_tests/instances.rs @@ -123,6 +123,65 @@ async fn test_instances_access_before_create_returns_not_found( ); } +#[nexus_test] +async fn test_v1_instance_access(cptestctx: &ControlPlaneTestContext) { + let client = &cptestctx.external_client; + + create_ip_pool(&client, "p0", None, None).await; + let org = create_organization(&client, ORGANIZATION_NAME).await; + let project = create_project(client, ORGANIZATION_NAME, PROJECT_NAME).await; + + // Create an instance. + let instance_name = "test-instance"; + let instance = + create_instance(client, ORGANIZATION_NAME, PROJECT_NAME, instance_name) + .await; + + // Fetch instance by id + let fetched_instance = instance_get( + &client, + format!("/v1/instances/{}", instance.identity.id).as_str(), + ) + .await; + assert_eq!(fetched_instance.identity.id, instance.identity.id); + + // Fetch instance by name and project_id + let fetched_instance = instance_get( + &client, + format!( + "/v1/instances/{}?project_id={}", + instance.identity.name, project.identity.id + ) + .as_str(), + ) + .await; + assert_eq!(fetched_instance.identity.id, instance.identity.id); + + // Fetch instance by name, project_name, and organization_id + let fetched_instance = instance_get( + &client, + format!( + "/v1/instances/{}?project_name={}&organization_id={}", + instance.identity.name, project.identity.name, org.identity.id + ) + .as_str(), + ) + .await; + assert_eq!(fetched_instance.identity.id, instance.identity.id); + + // Fetch instance by name, project_name, and organization_name + let fetched_instance = instance_get( + &client, + format!( + "/v1/instances/{}?project_name={}&organization_name={}", + instance.identity.name, project.identity.name, org.identity.name + ) + .as_str(), + ) + .await; + assert_eq!(fetched_instance.identity.id, instance.identity.id); +} + #[nexus_test] async fn test_instances_create_reboot_halt( cptestctx: &ControlPlaneTestContext, diff --git a/nexus/types/src/external_api/params.rs b/nexus/types/src/external_api/params.rs index 765219752b6..4eb2b4fd214 100644 --- a/nexus/types/src/external_api/params.rs +++ b/nexus/types/src/external_api/params.rs @@ -18,6 +18,23 @@ use serde::{ use std::{net::IpAddr, str::FromStr}; use uuid::Uuid; +#[derive(Deserialize, JsonSchema)] +#[serde(untagged)] +pub enum ProjectSelector { + ProjectId { project_id: Uuid }, + ProjectAndOrgId { project_name: Name, organization_id: Uuid }, + ProjectAndOrg { project_name: Name, organization_name: Name }, + None {}, +} + +#[derive(Deserialize, JsonSchema)] +pub struct ProjectQuery { + /// Should only be specified if `instance` path param is a name + pub organization_name: Option, + /// Should only be specified if `instance` path param is a name + pub project_name: Option, +} + // Silos /// Create-time parameters for a [`Silo`](crate::external_api::views::Silo) diff --git a/openapi/nexus.json b/openapi/nexus.json index 9820214853c..1cbe48363d0 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -7661,10 +7661,35 @@ ], "operationId": "v1_instance_view", "parameters": [ + { + "in": "query", + "name": "project_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "style": "form" + }, + { + "in": "query", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "style": "form" + }, + { + "in": "query", + "name": "project_name", + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "form" + }, { "in": "query", "name": "organization_name", - "description": "Should only be specified if `instance` path param is a name", "schema": { "$ref": "#/components/schemas/Name" }, @@ -7673,7 +7698,6 @@ { "in": "query", "name": "project_name", - "description": "Should only be specified if `instance` path param is a name", "schema": { "$ref": "#/components/schemas/Name" }, @@ -7682,7 +7706,7 @@ { "in": "path", "name": "instance", - "description": "If Name is provided `organization_name` and `project_name` query parameters must also be present. Otherwise they should be omitted.", + "description": "If Name is used to reference the instance you must also include one of the following qualifiers as query parameters: - `project_id` - `project_name`, `organization_id` - `project_name`, `organization_name`\n\nIf Id is used the above qualifiers are will be ignored", "required": true, "schema": { "$ref": "#/components/schemas/NameOrId" @@ -7715,10 +7739,35 @@ ], "operationId": "v1_instance_delete", "parameters": [ + { + "in": "query", + "name": "project_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "style": "form" + }, + { + "in": "query", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "style": "form" + }, + { + "in": "query", + "name": "project_name", + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "form" + }, { "in": "query", "name": "organization_name", - "description": "Should only be specified if `instance` path param is a name", "schema": { "$ref": "#/components/schemas/Name" }, @@ -7727,7 +7776,6 @@ { "in": "query", "name": "project_name", - "description": "Should only be specified if `instance` path param is a name", "schema": { "$ref": "#/components/schemas/Name" }, @@ -7736,7 +7784,7 @@ { "in": "path", "name": "instance", - "description": "If Name is provided `organization_name` and `project_name` query parameters must also be present. Otherwise they should be omitted.", + "description": "If Name is used to reference the instance you must also include one of the following qualifiers as query parameters: - `project_id` - `project_name`, `organization_id` - `project_name`, `organization_name`\n\nIf Id is used the above qualifiers are will be ignored", "required": true, "schema": { "$ref": "#/components/schemas/NameOrId" From 954ffdb64f63be67f200b7b849b740f79c710404 Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Sun, 20 Nov 2022 18:36:01 -0500 Subject: [PATCH 10/31] Add v1 create instance api --- nexus/src/app/instance.rs | 110 +++++++++++---------- nexus/src/app/project.rs | 42 ++++++++ nexus/src/app/sagas/instance_create.rs | 4 - nexus/src/external_api/http_entrypoints.rs | 67 ++++++++++--- nexus/tests/output/nexus_tags.txt | 1 + nexus/types/src/external_api/params.rs | 62 ++++++++++-- openapi/nexus.json | 80 +++++++++++++++ 7 files changed, 288 insertions(+), 78 deletions(-) diff --git a/nexus/src/app/instance.rs b/nexus/src/app/instance.rs index 39681995fb4..03db6467fa6 100644 --- a/nexus/src/app/instance.rs +++ b/nexus/src/app/instance.rs @@ -32,7 +32,6 @@ use omicron_common::api::external::InstanceState; use omicron_common::api::external::InternalContext; use omicron_common::api::external::ListResultVec; use omicron_common::api::external::LookupResult; -use omicron_common::api::external::NameOrId; use omicron_common::api::external::UpdateResult; use omicron_common::api::external::Vni; use omicron_common::api::internal::nexus; @@ -57,66 +56,73 @@ impl super::Nexus { pub async fn instance_lookup_id( &self, opctx: &OpContext, - instance: NameOrId, - project_selector: params::ProjectSelector, + instance_selector: params::InstanceSelector, ) -> LookupResult { - match instance { - NameOrId::Id(id) => Ok(id), - NameOrId::Name(name) => match project_selector { - params::ProjectSelector::ProjectId { project_id } => { - let (.., authz_instance) = - LookupPath::new(opctx, &self.db_datastore) - .project_id(project_id) - .instance_name(&Name(name.clone())) - .lookup_for(authz::Action::Read) - .await?; - Ok(authz_instance.id()) - } - params::ProjectSelector::ProjectAndOrgId { - project_name, - organization_id, - } => { - let (.., authz_instance) = - LookupPath::new(opctx, &self.db_datastore) - .organization_id(organization_id) - .project_name(&Name(project_name)) - .instance_name(&Name(name.clone())) - .lookup_for(authz::Action::Read) - .await?; - Ok(authz_instance.id()) - } - params::ProjectSelector::ProjectAndOrg { - project_name, - organization_name, - } => { - let (.., authz_instance) = - LookupPath::new(opctx, &self.db_datastore) - .organization_name(&Name(organization_name)) - .project_name(&Name(project_name)) - .instance_name(&Name(name.clone())) - .lookup_for(authz::Action::Read) - .await?; - Ok(authz_instance.id()) - } - params::ProjectSelector::None {} => { - Err(Error::InvalidRequest { - message: "When looking up an instance by name, the path to the instance must be provided via query paramaters".to_string() - }) - } - }, + match instance_selector { + params::InstanceSelector::InstanceId { instance_id } => { + Ok(instance_id) + } + params::InstanceSelector::InstanceAndProjectId { + instance_name, + project_id, + } => { + let (.., authz_instance) = + LookupPath::new(opctx, &self.db_datastore) + .project_id(project_id) + .instance_name(&Name(instance_name.clone())) + .lookup_for(authz::Action::Read) + .await?; + Ok(authz_instance.id()) + } + params::InstanceSelector::InstanceProjectAndOrgId { + instance_name, + project_name, + organization_id, + } => { + let (.., authz_instance) = + LookupPath::new(opctx, &self.db_datastore) + .organization_id(organization_id) + .project_name(&Name(project_name)) + .instance_name(&Name(instance_name.clone())) + .lookup_for(authz::Action::Read) + .await?; + Ok(authz_instance.id()) + } + params::InstanceSelector::InstanceProjectAndOrg { + instance_name, + project_name, + organization_name, + } => { + let (.., authz_instance) = + LookupPath::new(opctx, &self.db_datastore) + .organization_name(&Name(organization_name)) + .project_name(&Name(project_name)) + .instance_name(&Name(instance_name.clone())) + .lookup_for(authz::Action::Read) + .await?; + Ok(authz_instance.id()) + } + params::InstanceSelector::None {} => Err(Error::InvalidRequest { + message: " + Unable to resolve instance. Expected one of + - instance_id + - instance_name, project_id + - instance_name, project_name, organization_id + - instance_name, project_name, organization_name + " + .to_string(), + }), } } pub async fn project_create_instance( self: &Arc, opctx: &OpContext, - organization_name: &Name, - project_name: &Name, + project_id: &Uuid, params: ¶ms::InstanceCreate, ) -> CreateResult { let (.., authz_project) = LookupPath::new(opctx, &self.db_datastore) - .organization_name(organization_name) - .project_name(project_name) + .project_id(*project_id) .lookup_for(authz::Action::CreateChild) .await?; @@ -187,8 +193,6 @@ impl super::Nexus { let saga_params = sagas::instance_create::Params { serialized_authn: authn::saga::Serialized::for_opctx(opctx), - organization_name: organization_name.clone().into(), - project_name: project_name.clone().into(), project_id: authz_project.id(), create_params: params.clone(), }; diff --git a/nexus/src/app/project.rs b/nexus/src/app/project.rs index a76262ca219..c0eeb4c95dd 100644 --- a/nexus/src/app/project.rs +++ b/nexus/src/app/project.rs @@ -24,6 +24,48 @@ use omicron_common::api::external::UpdateResult; use uuid::Uuid; impl super::Nexus { + pub async fn project_lookup_id( + &self, + opctx: &OpContext, + project_selector: params::ProjectSelector, + ) -> LookupResult { + match project_selector { + params::ProjectSelector::ProjectId { project_id } => Ok(project_id), + params::ProjectSelector::ProjectAndOrgId { + project_name, + organization_id, + } => { + let (.., authz_project) = + LookupPath::new(opctx, &self.db_datastore) + .organization_id(organization_id) + .project_name(&Name(project_name)) + .lookup_for(authz::Action::Read) + .await?; + Ok(authz_project.id()) + } + params::ProjectSelector::ProjectAndOrg { + project_name, + organization_name, + } => { + let (.., authz_project) = + LookupPath::new(opctx, &self.db_datastore) + .organization_name(&Name(organization_name)) + .project_name(&Name(project_name)) + .lookup_for(authz::Action::Read) + .await?; + Ok(authz_project.id()) + } + params::ProjectSelector::None {} => Err(Error::InvalidRequest { + message: " + Unable to resolve project. Expected one of + - project_id + - project_name, organization_id + - project_name, organization_name + " + .to_string(), + }), + } + } pub async fn project_create( &self, opctx: &OpContext, diff --git a/nexus/src/app/sagas/instance_create.rs b/nexus/src/app/sagas/instance_create.rs index 8f6e79662a3..0a65eba18fb 100644 --- a/nexus/src/app/sagas/instance_create.rs +++ b/nexus/src/app/sagas/instance_create.rs @@ -46,8 +46,6 @@ use uuid::Uuid; #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Params { pub serialized_authn: authn::saga::Serialized, - pub organization_name: Name, - pub project_name: Name, pub project_id: Uuid, pub create_params: params::InstanceCreate, } @@ -1024,8 +1022,6 @@ mod test { fn new_test_params(opctx: &OpContext, project_id: Uuid) -> Params { Params { serialized_authn: Serialized::for_opctx(opctx), - organization_name: ORG_NAME.parse().unwrap(), - project_name: PROJECT_NAME.parse().unwrap(), project_id, create_params: params::InstanceCreate { identity: IdentityMetadataCreateParams { diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index 41f60c074da..e19930ac8ab 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -147,6 +147,7 @@ pub fn external_api() -> NexusApiDescription { api.register(instance_serial_console)?; api.register(instance_serial_console_stream)?; api.register(v1_instance_view)?; + api.register(v1_instance_create)?; api.register(v1_instance_delete)?; // Project-scoped images API @@ -2042,6 +2043,31 @@ async fn instance_list( apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await } +#[endpoint { + method = POST, + path = "/v1/instances", + tags = ["instances"], +}] +async fn v1_instance_create( + rqctx: Arc>>, + query_params: Query, + new_instance: TypedBody, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let nexus = &apictx.nexus; + let query = query_params.into_inner(); + let new_instance_params = &new_instance.into_inner(); + let handler = async { + let opctx = OpContext::for_external_api(&rqctx).await?; + let project_id = + nexus.project_lookup_id(&opctx, query.selector).await?; + let instance = nexus + .project_create_instance(&opctx, &project_id, &new_instance_params) + .await?; + Ok(HttpResponseCreated(instance.into())) + }; + apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await +} /// Create an instance // TODO-correctness This is supposed to be async. Is that right? We can create @@ -2069,24 +2095,32 @@ async fn instance_create( let new_instance_params = &new_instance.into_inner(); let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; - let instance = nexus - .project_create_instance( + let project_id = nexus + .project_lookup_id( &opctx, - &organization_name, - &project_name, - &new_instance_params, + params::ProjectSelector::ProjectAndOrg { + project_name: project_name.clone().into(), + organization_name: organization_name.clone().into(), + }, ) .await?; + let instance = nexus + .project_create_instance(&opctx, &project_id, &new_instance_params) + .await?; Ok(HttpResponseCreated(instance.into())) }; apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await } -/// Path parameters for Instance requests. Name is temporary. +/// Path parameters for Instance requests #[derive(Deserialize, JsonSchema)] struct InstanceLookupPathParam { - /// If Name is provided `organization_name` and `project_name` query parameters must also be present. - /// Otherwise they should be omitted. + /// If Name is used to reference the instance you must also include one of the following qualifiers as query parameters: + /// - `project_id` + /// - `project_name`, `organization_id` + /// - `project_name`, `organization_name` + /// + /// If Id is used the above qualifiers are will be ignored instance: NameOrId, } @@ -2113,7 +2147,10 @@ async fn v1_instance_view( let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; let instance_id = nexus - .instance_lookup_id(&opctx, path.instance.into(), query.selector) + .instance_lookup_id( + &opctx, + query.selector.to_instance_selector(path.instance.into()), + ) .await?; let instance = nexus.instance_fetch_by_id(&opctx, &instance_id).await?; Ok(HttpResponseOk(instance.into())) @@ -2199,7 +2236,10 @@ async fn v1_instance_delete( let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; let instance_id = nexus - .instance_lookup_id(&opctx, path.instance.into(), query.selector) + .instance_lookup_id( + &opctx, + query.selector.to_instance_selector(path.instance.into()), + ) .await?; nexus.project_destroy_instance(&opctx, &instance_id).await?; Ok(HttpResponseDeleted()) @@ -2226,16 +2266,13 @@ async fn instance_delete( let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; - // TODO: Clean this up let instance_id = nexus .instance_lookup_id( &opctx, - omicron_common::api::external::NameOrId::Name( - omicron_common::api::external::Name::from( + params::InstanceSelector::InstanceProjectAndOrg { + instance_name: omicron_common::api::external::Name::from( instance_name.clone(), ), - ), - params::ProjectSelector::ProjectAndOrg { project_name: omicron_common::api::external::Name::from( project_name.clone(), ), diff --git a/nexus/tests/output/nexus_tags.txt b/nexus/tests/output/nexus_tags.txt index 94a463a26fe..683d4d3779c 100644 --- a/nexus/tests/output/nexus_tags.txt +++ b/nexus/tests/output/nexus_tags.txt @@ -48,6 +48,7 @@ instance_start /organizations/{organization_name}/proj instance_stop /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/stop instance_view /organizations/{organization_name}/projects/{project_name}/instances/{instance_name} instance_view_by_id /by-id/instances/{id} +v1_instance_create /v1/instances v1_instance_delete /v1/instances/{instance} v1_instance_view /v1/instances/{instance} diff --git a/nexus/types/src/external_api/params.rs b/nexus/types/src/external_api/params.rs index 4eb2b4fd214..b6c4b232b7f 100644 --- a/nexus/types/src/external_api/params.rs +++ b/nexus/types/src/external_api/params.rs @@ -8,7 +8,7 @@ use crate::external_api::shared; use chrono::{DateTime, Utc}; use omicron_common::api::external::{ ByteCount, IdentityMetadataCreateParams, IdentityMetadataUpdateParams, - InstanceCpuCount, Ipv4Net, Ipv6Net, Name, + InstanceCpuCount, Ipv4Net, Ipv6Net, Name, NameOrId, }; use schemars::JsonSchema; use serde::{ @@ -27,12 +27,62 @@ pub enum ProjectSelector { None {}, } +impl ProjectSelector { + pub fn to_instance_selector(self, instance: NameOrId) -> InstanceSelector { + match instance { + NameOrId::Id(instance_id) => { + InstanceSelector::InstanceId { instance_id } + } + NameOrId::Name(instance_name) => match self { + ProjectSelector::ProjectId { project_id } => { + InstanceSelector::InstanceAndProjectId { + instance_name, + project_id, + } + } + ProjectSelector::ProjectAndOrgId { + project_name, + organization_id, + } => InstanceSelector::InstanceProjectAndOrgId { + instance_name, + project_name, + organization_id, + }, + ProjectSelector::ProjectAndOrg { + project_name, + organization_name, + } => InstanceSelector::InstanceProjectAndOrg { + instance_name, + project_name, + organization_name, + }, + ProjectSelector::None {} => InstanceSelector::None {}, + }, + } + } +} + #[derive(Deserialize, JsonSchema)] -pub struct ProjectQuery { - /// Should only be specified if `instance` path param is a name - pub organization_name: Option, - /// Should only be specified if `instance` path param is a name - pub project_name: Option, +#[serde(untagged)] +pub enum InstanceSelector { + InstanceId { + instance_id: Uuid, + }, + InstanceAndProjectId { + instance_name: Name, + project_id: Uuid, + }, + InstanceProjectAndOrgId { + instance_name: Name, + project_name: Name, + organization_id: Uuid, + }, + InstanceProjectAndOrg { + instance_name: Name, + project_name: Name, + organization_name: Name, + }, + None {}, } // Silos diff --git a/openapi/nexus.json b/openapi/nexus.json index 1cbe48363d0..dec4ca65bff 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -7654,6 +7654,86 @@ "x-dropshot-pagination": true } }, + "/v1/instances": { + "post": { + "tags": [ + "instances" + ], + "operationId": "v1_instance_create", + "parameters": [ + { + "in": "query", + "name": "project_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "style": "form" + }, + { + "in": "query", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "style": "form" + }, + { + "in": "query", + "name": "project_name", + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "form" + }, + { + "in": "query", + "name": "organization_name", + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "form" + }, + { + "in": "query", + "name": "project_name", + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "form" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InstanceCreate" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "successful creation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Instance" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, "/v1/instances/{instance}": { "get": { "tags": [ From c8d191bf346e9c032a611ea650d4e65244306e2d Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Mon, 21 Nov 2022 12:02:58 -0500 Subject: [PATCH 11/31] Add instance list endpoint --- nexus/src/app/instance.rs | 6 +- nexus/src/external_api/http_entrypoints.rs | 57 ++++++++++++- nexus/tests/output/nexus_tags.txt | 1 + openapi/nexus.json | 99 ++++++++++++++++++++++ 4 files changed, 157 insertions(+), 6 deletions(-) diff --git a/nexus/src/app/instance.rs b/nexus/src/app/instance.rs index 03db6467fa6..74d5f9e558d 100644 --- a/nexus/src/app/instance.rs +++ b/nexus/src/app/instance.rs @@ -253,13 +253,11 @@ impl super::Nexus { pub async fn project_list_instances( &self, opctx: &OpContext, - organization_name: &Name, - project_name: &Name, + project_id: Uuid, pagparams: &DataPageParams<'_, Name>, ) -> ListResultVec { let (.., authz_project) = LookupPath::new(opctx, &self.db_datastore) - .organization_name(organization_name) - .project_name(project_name) + .project_id(project_id) .lookup_for(authz::Action::ListChildren) .await?; self.db_datastore diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index e19930ac8ab..807c6523cf0 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -146,6 +146,7 @@ pub fn external_api() -> NexusApiDescription { api.register(instance_stop)?; api.register(instance_serial_console)?; api.register(instance_serial_console_stream)?; + api.register(v1_instance_list)?; api.register(v1_instance_view)?; api.register(v1_instance_create)?; api.register(v1_instance_delete)?; @@ -2003,6 +2004,50 @@ async fn disk_metrics_list( // Instances +#[derive(Deserialize, JsonSchema)] +struct InstanceListQueryParams { + #[serde(flatten)] + pagination: PaginatedByName, + #[serde(flatten)] + selector: params::ProjectSelector, +} + +#[endpoint { + method = GET, + path = "/v1/instances", + tags = ["instances"], +}] +async fn v1_instance_list( + rqctx: Arc>>, + query_params: Query, +) -> Result>, HttpError> { + let apictx = rqctx.context(); + let nexus = &apictx.nexus; + let query = query_params.into_inner(); + let handler = async { + let opctx = OpContext::for_external_api(&rqctx).await?; + let project_id = + nexus.project_lookup_id(&opctx, query.selector).await?; + let instances = nexus + .project_list_instances( + &opctx, + project_id, + &data_page_params_for(&rqctx, &query.pagination)? + .map_name(|n| Name::ref_cast(n)), + ) + .await? + .into_iter() + .map(|i| i.into()) + .collect(); + Ok(HttpResponseOk(ScanByName::results_page( + &query.pagination, + instances, + &marker_for_name, + )?)) + }; + apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await +} + /// List instances #[endpoint { method = GET, @@ -2022,11 +2067,19 @@ async fn instance_list( let project_name = &path.project_name; let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; + let project_id = nexus + .project_lookup_id( + &opctx, + params::ProjectSelector::ProjectAndOrg { + project_name: project_name.clone().into(), + organization_name: organization_name.clone().into(), + }, + ) + .await?; let instances = nexus .project_list_instances( &opctx, - &organization_name, - &project_name, + project_id, &data_page_params_for(&rqctx, &query)? .map_name(|n| Name::ref_cast(n)), ) diff --git a/nexus/tests/output/nexus_tags.txt b/nexus/tests/output/nexus_tags.txt index 683d4d3779c..fe9eb1c8898 100644 --- a/nexus/tests/output/nexus_tags.txt +++ b/nexus/tests/output/nexus_tags.txt @@ -50,6 +50,7 @@ instance_view /organizations/{organization_name}/proj instance_view_by_id /by-id/instances/{id} v1_instance_create /v1/instances v1_instance_delete /v1/instances/{instance} +v1_instance_list /v1/instances v1_instance_view /v1/instances/{instance} API operations found with tag "login" diff --git a/openapi/nexus.json b/openapi/nexus.json index dec4ca65bff..009e01513e2 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -7655,6 +7655,105 @@ } }, "/v1/instances": { + "get": { + "tags": [ + "instances" + ], + "operationId": "v1_instance_list", + "parameters": [ + { + "in": "query", + "name": "limit", + "description": "Maximum number of items returned by a single call", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 1 + }, + "style": "form" + }, + { + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", + "schema": { + "nullable": true, + "type": "string" + }, + "style": "form" + }, + { + "in": "query", + "name": "sort_by", + "schema": { + "$ref": "#/components/schemas/NameSortMode" + }, + "style": "form" + }, + { + "in": "query", + "name": "project_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "style": "form" + }, + { + "in": "query", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "style": "form" + }, + { + "in": "query", + "name": "project_name", + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "form" + }, + { + "in": "query", + "name": "organization_name", + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "form" + }, + { + "in": "query", + "name": "project_name", + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "form" + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InstanceResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + }, + "x-dropshot-pagination": true + }, "post": { "tags": [ "instances" From 6708bacf2eb0a7416e98d822828ec78465d5c9df Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Tue, 22 Nov 2022 14:09:29 -0500 Subject: [PATCH 12/31] Add instance migrate endpoint --- nexus/src/app/instance.rs | 8 +- nexus/src/external_api/http_entrypoints.rs | 59 +++++++++++++- nexus/tests/output/nexus_tags.txt | 1 + openapi/nexus.json | 90 ++++++++++++++++++++++ 4 files changed, 149 insertions(+), 9 deletions(-) diff --git a/nexus/src/app/instance.rs b/nexus/src/app/instance.rs index 74d5f9e558d..6139be7ab57 100644 --- a/nexus/src/app/instance.rs +++ b/nexus/src/app/instance.rs @@ -325,15 +325,11 @@ impl super::Nexus { pub async fn project_instance_migrate( self: &Arc, opctx: &OpContext, - organization_name: &Name, - project_name: &Name, - instance_name: &Name, + instance_id: Uuid, params: params::InstanceMigrate, ) -> UpdateResult { let (.., authz_instance) = LookupPath::new(opctx, &self.db_datastore) - .organization_name(organization_name) - .project_name(project_name) - .instance_name(instance_name) + .instance_id(instance_id) .lookup_for(authz::Action::Modify) .await?; diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index 807c6523cf0..2f2f5c1188f 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -150,6 +150,7 @@ pub fn external_api() -> NexusApiDescription { api.register(v1_instance_view)?; api.register(v1_instance_create)?; api.register(v1_instance_delete)?; + api.register(v1_instance_migrate)?; // Project-scoped images API api.register(image_list)?; @@ -2342,6 +2343,43 @@ async fn instance_delete( apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await } +// TODO should this be in the public API? +#[endpoint { + method = POST, + path = "/v1/instances/{instance}/migrate", + tags = ["instances"], +}] +async fn v1_instance_migrate( + rqctx: Arc>>, + query_params: Query, + path_params: Path, + migrate_params: TypedBody, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let query = query_params.into_inner(); + let migrate_instance_params = migrate_params.into_inner(); + let handler = async { + let opctx = OpContext::for_external_api(&rqctx).await?; + let instance_id = nexus + .instance_lookup_id( + &opctx, + query.selector.to_instance_selector(path.instance.into()), + ) + .await?; + let instance = nexus + .project_instance_migrate( + &opctx, + instance_id, + migrate_instance_params, + ) + .await?; + Ok(HttpResponseOk(instance.into())) + }; + apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await +} + // TODO should this be in the public API? /// Migrate an instance #[endpoint { @@ -2363,12 +2401,27 @@ async fn instance_migrate( let migrate_instance_params = migrate_params.into_inner(); let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; + let instance_id = nexus + .instance_lookup_id( + &opctx, + params::InstanceSelector::InstanceProjectAndOrg { + instance_name: omicron_common::api::external::Name::from( + instance_name.clone(), + ), + project_name: omicron_common::api::external::Name::from( + project_name.clone(), + ), + organization_name: + omicron_common::api::external::Name::from( + organization_name.clone(), + ), + }, + ) + .await?; let instance = nexus .project_instance_migrate( &opctx, - &organization_name, - &project_name, - &instance_name, + instance_id, migrate_instance_params, ) .await?; diff --git a/nexus/tests/output/nexus_tags.txt b/nexus/tests/output/nexus_tags.txt index fe9eb1c8898..362486f492e 100644 --- a/nexus/tests/output/nexus_tags.txt +++ b/nexus/tests/output/nexus_tags.txt @@ -51,6 +51,7 @@ instance_view_by_id /by-id/instances/{id} v1_instance_create /v1/instances v1_instance_delete /v1/instances/{instance} v1_instance_list /v1/instances +v1_instance_migrate /v1/instances/{instance}/migrate v1_instance_view /v1/instances/{instance} API operations found with tag "login" diff --git a/openapi/nexus.json b/openapi/nexus.json index 009e01513e2..2f8b3953ee8 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -7983,6 +7983,96 @@ } } } + }, + "/v1/instances/{instance}/migrate": { + "post": { + "tags": [ + "instances" + ], + "operationId": "v1_instance_migrate", + "parameters": [ + { + "in": "query", + "name": "project_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "style": "form" + }, + { + "in": "query", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "style": "form" + }, + { + "in": "query", + "name": "project_name", + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "form" + }, + { + "in": "query", + "name": "organization_name", + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "form" + }, + { + "in": "query", + "name": "project_name", + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "form" + }, + { + "in": "path", + "name": "instance", + "description": "If Name is used to reference the instance you must also include one of the following qualifiers as query parameters: - `project_id` - `project_name`, `organization_id` - `project_name`, `organization_name`\n\nIf Id is used the above qualifiers are will be ignored", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InstanceMigrate" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Instance" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } } }, "components": { From 26b5651bcd87ec6a46858df3c58213d3759c4c80 Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Tue, 22 Nov 2022 14:26:25 -0500 Subject: [PATCH 13/31] Add instance reboot endpoint --- nexus/src/app/instance.rs | 8 +-- nexus/src/external_api/http_entrypoints.rs | 49 +++++++++++-- nexus/tests/output/nexus_tags.txt | 1 + openapi/nexus.json | 80 ++++++++++++++++++++++ 4 files changed, 127 insertions(+), 11 deletions(-) diff --git a/nexus/src/app/instance.rs b/nexus/src/app/instance.rs index 6139be7ab57..4e7cb03075c 100644 --- a/nexus/src/app/instance.rs +++ b/nexus/src/app/instance.rs @@ -385,9 +385,7 @@ impl super::Nexus { pub async fn instance_reboot( &self, opctx: &OpContext, - organization_name: &Name, - project_name: &Name, - instance_name: &Name, + instance_id: Uuid, ) -> UpdateResult { // To implement reboot, we issue a call to the sled agent to set a // runtime state of "reboot". We cannot simply stop the Instance and @@ -402,9 +400,7 @@ impl super::Nexus { // running. let (.., authz_instance, db_instance) = LookupPath::new(opctx, &self.db_datastore) - .organization_name(organization_name) - .project_name(project_name) - .instance_name(instance_name) + .instance_id(instance_id) .fetch() .await?; let requested = InstanceRuntimeStateRequested { diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index 2f2f5c1188f..e1d5000229c 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -151,6 +151,7 @@ pub fn external_api() -> NexusApiDescription { api.register(v1_instance_create)?; api.register(v1_instance_delete)?; api.register(v1_instance_migrate)?; + api.register(v1_instance_reboot)?; // Project-scoped images API api.register(image_list)?; @@ -2430,6 +2431,34 @@ async fn instance_migrate( apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await } +#[endpoint { + method = POST, + path = "/v1/instances/{instance}/reboot", + tags = ["instances"], +}] +async fn v1_instance_reboot( + rqctx: Arc>>, + query_params: Query, + path_params: Path, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let query = query_params.into_inner(); + let handler = async { + let opctx = OpContext::for_external_api(&rqctx).await?; + let instance_id = nexus + .instance_lookup_id( + &opctx, + query.selector.to_instance_selector(path.instance.into()), + ) + .await?; + let instance = nexus.instance_reboot(&opctx, instance_id).await?; + Ok(HttpResponseOk(instance.into())) + }; + apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await +} + /// Reboot an instance #[endpoint { method = POST, @@ -2448,14 +2477,24 @@ async fn instance_reboot( let instance_name = &path.instance_name; let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; - let instance = nexus - .instance_reboot( + let instance_id = nexus + .instance_lookup_id( &opctx, - &organization_name, - &project_name, - &instance_name, + params::InstanceSelector::InstanceProjectAndOrg { + instance_name: omicron_common::api::external::Name::from( + instance_name.clone(), + ), + project_name: omicron_common::api::external::Name::from( + project_name.clone(), + ), + organization_name: + omicron_common::api::external::Name::from( + organization_name.clone(), + ), + }, ) .await?; + let instance = nexus.instance_reboot(&opctx, instance_id).await?; Ok(HttpResponseAccepted(instance.into())) }; apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await diff --git a/nexus/tests/output/nexus_tags.txt b/nexus/tests/output/nexus_tags.txt index 362486f492e..0cd178d68de 100644 --- a/nexus/tests/output/nexus_tags.txt +++ b/nexus/tests/output/nexus_tags.txt @@ -52,6 +52,7 @@ v1_instance_create /v1/instances v1_instance_delete /v1/instances/{instance} v1_instance_list /v1/instances v1_instance_migrate /v1/instances/{instance}/migrate +v1_instance_reboot /v1/instances/{instance}/reboot v1_instance_view /v1/instances/{instance} API operations found with tag "login" diff --git a/openapi/nexus.json b/openapi/nexus.json index 2f8b3953ee8..3c748a805c5 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -8073,6 +8073,86 @@ } } } + }, + "/v1/instances/{instance}/reboot": { + "post": { + "tags": [ + "instances" + ], + "operationId": "v1_instance_reboot", + "parameters": [ + { + "in": "query", + "name": "project_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "style": "form" + }, + { + "in": "query", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "style": "form" + }, + { + "in": "query", + "name": "project_name", + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "form" + }, + { + "in": "query", + "name": "organization_name", + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "form" + }, + { + "in": "query", + "name": "project_name", + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "form" + }, + { + "in": "path", + "name": "instance", + "description": "If Name is used to reference the instance you must also include one of the following qualifiers as query parameters: - `project_id` - `project_name`, `organization_id` - `project_name`, `organization_name`\n\nIf Id is used the above qualifiers are will be ignored", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Instance" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } } }, "components": { From f70f84fc00b1fad2a684cbcb9dde46971824f1f8 Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Tue, 22 Nov 2022 14:34:05 -0500 Subject: [PATCH 14/31] Add instance start endpoint --- nexus/src/app/instance.rs | 8 +-- nexus/src/external_api/http_entrypoints.rs | 51 ++++++++++++-- nexus/tests/output/nexus_tags.txt | 1 + openapi/nexus.json | 81 ++++++++++++++++++++++ 4 files changed, 129 insertions(+), 12 deletions(-) diff --git a/nexus/src/app/instance.rs b/nexus/src/app/instance.rs index 4e7cb03075c..daefe1f8f73 100644 --- a/nexus/src/app/instance.rs +++ b/nexus/src/app/instance.rs @@ -421,15 +421,11 @@ impl super::Nexus { pub async fn instance_start( &self, opctx: &OpContext, - organization_name: &Name, - project_name: &Name, - instance_name: &Name, + instance_id: Uuid, ) -> UpdateResult { let (.., authz_instance, db_instance) = LookupPath::new(opctx, &self.db_datastore) - .organization_name(organization_name) - .project_name(project_name) - .instance_name(instance_name) + .instance_id(instance_id) .fetch() .await?; let requested = InstanceRuntimeStateRequested { diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index e1d5000229c..d27a96f8533 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -152,6 +152,7 @@ pub fn external_api() -> NexusApiDescription { api.register(v1_instance_delete)?; api.register(v1_instance_migrate)?; api.register(v1_instance_reboot)?; + api.register(v1_instance_start)?; // Project-scoped images API api.register(image_list)?; @@ -2320,7 +2321,6 @@ async fn instance_delete( let instance_name = &path.instance_name; let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; - let instance_id = nexus .instance_lookup_id( &opctx, @@ -2500,6 +2500,35 @@ async fn instance_reboot( apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await } +/// Boot an instance +#[endpoint { + method = POST, + path = "/v1/instances/{instance}/start", + tags = ["instances"], +}] +async fn v1_instance_start( + rqctx: Arc>>, + query_params: Query, + path_params: Path, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let query = query_params.into_inner(); + let handler = async { + let opctx = OpContext::for_external_api(&rqctx).await?; + let instance_id = nexus + .instance_lookup_id( + &opctx, + query.selector.to_instance_selector(path.instance.into()), + ) + .await?; + let instance = nexus.instance_start(&opctx, instance_id).await?; + Ok(HttpResponseOk(instance.into())) + }; + apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await +} + /// Boot an instance #[endpoint { method = POST, @@ -2518,14 +2547,24 @@ async fn instance_start( let instance_name = &path.instance_name; let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; - let instance = nexus - .instance_start( + let instance_id = nexus + .instance_lookup_id( &opctx, - &organization_name, - &project_name, - &instance_name, + params::InstanceSelector::InstanceProjectAndOrg { + instance_name: omicron_common::api::external::Name::from( + instance_name.clone(), + ), + project_name: omicron_common::api::external::Name::from( + project_name.clone(), + ), + organization_name: + omicron_common::api::external::Name::from( + organization_name.clone(), + ), + }, ) .await?; + let instance = nexus.instance_start(&opctx, instance_id).await?; Ok(HttpResponseAccepted(instance.into())) }; apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await diff --git a/nexus/tests/output/nexus_tags.txt b/nexus/tests/output/nexus_tags.txt index 0cd178d68de..41927a2723a 100644 --- a/nexus/tests/output/nexus_tags.txt +++ b/nexus/tests/output/nexus_tags.txt @@ -53,6 +53,7 @@ v1_instance_delete /v1/instances/{instance} v1_instance_list /v1/instances v1_instance_migrate /v1/instances/{instance}/migrate v1_instance_reboot /v1/instances/{instance}/reboot +v1_instance_start /v1/instances/{instance}/start v1_instance_view /v1/instances/{instance} API operations found with tag "login" diff --git a/openapi/nexus.json b/openapi/nexus.json index 3c748a805c5..fc80986d960 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -8153,6 +8153,87 @@ } } } + }, + "/v1/instances/{instance}/start": { + "post": { + "tags": [ + "instances" + ], + "summary": "Boot an instance", + "operationId": "v1_instance_start", + "parameters": [ + { + "in": "query", + "name": "project_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "style": "form" + }, + { + "in": "query", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "style": "form" + }, + { + "in": "query", + "name": "project_name", + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "form" + }, + { + "in": "query", + "name": "organization_name", + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "form" + }, + { + "in": "query", + "name": "project_name", + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "form" + }, + { + "in": "path", + "name": "instance", + "description": "If Name is used to reference the instance you must also include one of the following qualifiers as query parameters: - `project_id` - `project_name`, `organization_id` - `project_name`, `organization_name`\n\nIf Id is used the above qualifiers are will be ignored", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Instance" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } } }, "components": { From e6374fe618a0fe1fa1e899778414f41c7335b0cf Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Tue, 22 Nov 2022 14:42:37 -0500 Subject: [PATCH 15/31] Add instance stop endpoint --- nexus/src/app/instance.rs | 8 +-- nexus/src/external_api/http_entrypoints.rs | 49 +++++++++++-- nexus/tests/output/nexus_tags.txt | 1 + openapi/nexus.json | 80 ++++++++++++++++++++++ 4 files changed, 127 insertions(+), 11 deletions(-) diff --git a/nexus/src/app/instance.rs b/nexus/src/app/instance.rs index daefe1f8f73..8c448cffc05 100644 --- a/nexus/src/app/instance.rs +++ b/nexus/src/app/instance.rs @@ -446,15 +446,11 @@ impl super::Nexus { pub async fn instance_stop( &self, opctx: &OpContext, - organization_name: &Name, - project_name: &Name, - instance_name: &Name, + instance_id: Uuid, ) -> UpdateResult { let (.., authz_instance, db_instance) = LookupPath::new(opctx, &self.db_datastore) - .organization_name(organization_name) - .project_name(project_name) - .instance_name(instance_name) + .instance_id(instance_id) .fetch() .await?; let requested = InstanceRuntimeStateRequested { diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index d27a96f8533..17426cae88c 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -153,6 +153,7 @@ pub fn external_api() -> NexusApiDescription { api.register(v1_instance_migrate)?; api.register(v1_instance_reboot)?; api.register(v1_instance_start)?; + api.register(v1_instance_stop)?; // Project-scoped images API api.register(image_list)?; @@ -2570,6 +2571,34 @@ async fn instance_start( apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await } +#[endpoint { + method = POST, + path = "/v1/instances/{instance}/stop", + tags = ["instances"], +}] +async fn v1_instance_stop( + rqctx: Arc>>, + query_params: Query, + path_params: Path, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let query = query_params.into_inner(); + let handler = async { + let opctx = OpContext::for_external_api(&rqctx).await?; + let instance_id = nexus + .instance_lookup_id( + &opctx, + query.selector.to_instance_selector(path.instance.into()), + ) + .await?; + let instance = nexus.instance_stop(&opctx, instance_id).await?; + Ok(HttpResponseOk(instance.into())) + }; + apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await +} + /// Halt an instance #[endpoint { method = POST, @@ -2588,14 +2617,24 @@ async fn instance_stop( let instance_name = &path.instance_name; let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; - let instance = nexus - .instance_stop( + let instance_id = nexus + .instance_lookup_id( &opctx, - &organization_name, - &project_name, - &instance_name, + params::InstanceSelector::InstanceProjectAndOrg { + instance_name: omicron_common::api::external::Name::from( + instance_name.clone(), + ), + project_name: omicron_common::api::external::Name::from( + project_name.clone(), + ), + organization_name: + omicron_common::api::external::Name::from( + organization_name.clone(), + ), + }, ) .await?; + let instance = nexus.instance_stop(&opctx, instance_id).await?; Ok(HttpResponseAccepted(instance.into())) }; apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await diff --git a/nexus/tests/output/nexus_tags.txt b/nexus/tests/output/nexus_tags.txt index 41927a2723a..4a4f649be66 100644 --- a/nexus/tests/output/nexus_tags.txt +++ b/nexus/tests/output/nexus_tags.txt @@ -54,6 +54,7 @@ v1_instance_list /v1/instances v1_instance_migrate /v1/instances/{instance}/migrate v1_instance_reboot /v1/instances/{instance}/reboot v1_instance_start /v1/instances/{instance}/start +v1_instance_stop /v1/instances/{instance}/stop v1_instance_view /v1/instances/{instance} API operations found with tag "login" diff --git a/openapi/nexus.json b/openapi/nexus.json index fc80986d960..1304dc01cc0 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -8234,6 +8234,86 @@ } } } + }, + "/v1/instances/{instance}/stop": { + "post": { + "tags": [ + "instances" + ], + "operationId": "v1_instance_stop", + "parameters": [ + { + "in": "query", + "name": "project_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "style": "form" + }, + { + "in": "query", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "style": "form" + }, + { + "in": "query", + "name": "project_name", + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "form" + }, + { + "in": "query", + "name": "organization_name", + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "form" + }, + { + "in": "query", + "name": "project_name", + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "form" + }, + { + "in": "path", + "name": "instance", + "description": "If Name is used to reference the instance you must also include one of the following qualifiers as query parameters: - `project_id` - `project_name`, `organization_id` - `project_name`, `organization_name`\n\nIf Id is used the above qualifiers are will be ignored", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Instance" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } } }, "components": { From cdaa97744f067db524802386c31eb7b92c285703 Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Tue, 22 Nov 2022 15:27:34 -0500 Subject: [PATCH 16/31] Add serial console endpoints --- nexus/src/app/instance.rs | 42 +---- nexus/src/external_api/http_entrypoints.rs | 136 +++++++++++++-- nexus/tests/output/nexus_tags.txt | 2 + openapi/nexus.json | 189 +++++++++++++++++++++ 4 files changed, 315 insertions(+), 54 deletions(-) diff --git a/nexus/src/app/instance.rs b/nexus/src/app/instance.rs index 8c448cffc05..c8b933d58f0 100644 --- a/nexus/src/app/instance.rs +++ b/nexus/src/app/instance.rs @@ -266,22 +266,6 @@ impl super::Nexus { } pub async fn instance_fetch( - &self, - opctx: &OpContext, - organization_name: &Name, - project_name: &Name, - instance_name: &Name, - ) -> LookupResult { - let (.., db_instance) = LookupPath::new(opctx, &self.db_datastore) - .organization_name(organization_name) - .project_name(project_name) - .instance_name(instance_name) - .fetch() - .await?; - Ok(db_instance) - } - - pub async fn instance_fetch_by_id( &self, opctx: &OpContext, instance_id: &Uuid, @@ -1147,19 +1131,10 @@ impl super::Nexus { pub(crate) async fn instance_serial_console_data( &self, opctx: &OpContext, - organization_name: &Name, - project_name: &Name, - instance_name: &Name, + instance_id: &Uuid, params: ¶ms::InstanceSerialConsoleRequest, ) -> Result { - let db_instance = self - .instance_fetch( - opctx, - organization_name, - project_name, - instance_name, - ) - .await?; + let db_instance = self.instance_fetch(opctx, instance_id).await?; let sa = self.instance_sled(&db_instance).await?; let data = sa @@ -1183,18 +1158,9 @@ impl super::Nexus { &self, opctx: &OpContext, conn: dropshot::WebsocketConnection, - organization_name: &Name, - project_name: &Name, - instance_name: &Name, + instance_id: &Uuid, ) -> Result<(), Error> { - let instance = self - .instance_fetch( - opctx, - organization_name, - project_name, - instance_name, - ) - .await?; + let instance = self.instance_fetch(opctx, instance_id).await?; let ip_addr = instance .runtime_state .propolis_ip diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index 17426cae88c..3199914d6f9 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -154,6 +154,8 @@ pub fn external_api() -> NexusApiDescription { api.register(v1_instance_reboot)?; api.register(v1_instance_start)?; api.register(v1_instance_stop)?; + api.register(v1_instance_serial_console)?; + api.register(v1_instance_serial_console_stream)?; // Project-scoped images API api.register(image_list)?; @@ -2209,7 +2211,7 @@ async fn v1_instance_view( query.selector.to_instance_selector(path.instance.into()), ) .await?; - let instance = nexus.instance_fetch_by_id(&opctx, &instance_id).await?; + let instance = nexus.instance_fetch(&opctx, &instance_id).await?; Ok(HttpResponseOk(instance.into())) }; apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await @@ -2241,14 +2243,24 @@ async fn instance_view( let instance_name = &path.instance_name; let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; - let instance = nexus - .instance_fetch( + let instance_id = nexus + .instance_lookup_id( &opctx, - &organization_name, - &project_name, - &instance_name, + params::InstanceSelector::InstanceProjectAndOrg { + instance_name: omicron_common::api::external::Name::from( + instance_name.clone(), + ), + project_name: omicron_common::api::external::Name::from( + project_name.clone(), + ), + organization_name: + omicron_common::api::external::Name::from( + organization_name.clone(), + ), + }, ) .await?; + let instance = nexus.instance_fetch(&opctx, &instance_id).await?; Ok(HttpResponseOk(instance.into())) }; apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await @@ -2270,7 +2282,7 @@ async fn instance_view_by_id( let id = &path.id; let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; - let instance = nexus.instance_fetch_by_id(&opctx, id).await?; + let instance = nexus.instance_fetch(&opctx, id).await?; Ok(HttpResponseOk(instance.into())) }; apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await @@ -2640,6 +2652,49 @@ async fn instance_stop( apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await } +#[derive(Deserialize, JsonSchema)] +pub struct InstanceSerialConsoleParams { + #[serde(flatten)] + selector: params::ProjectSelector, + + #[serde(flatten)] + pub console_params: params::InstanceSerialConsoleRequest, +} + +#[endpoint { + method = POST, + path = "/v1/instances/{instance}/serial-console", + tags = ["instances"], +}] +async fn v1_instance_serial_console( + rqctx: Arc>>, + path_params: Path, + query_params: Query, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let query = query_params.into_inner(); + let handler = async { + let opctx = OpContext::for_external_api(&rqctx).await?; + let instance_id = nexus + .instance_lookup_id( + &opctx, + query.selector.to_instance_selector(path.instance.into()), + ) + .await?; + let console = nexus + .instance_serial_console_data( + &opctx, + &instance_id, + &query.console_params, + ) + .await?; + Ok(HttpResponseOk(console.into())) + }; + apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await +} + /// Fetch an instance's serial console #[endpoint { method = GET, @@ -2659,12 +2714,27 @@ async fn instance_serial_console( let instance_name = &path.instance_name; let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; + let instance_id = nexus + .instance_lookup_id( + &opctx, + params::InstanceSelector::InstanceProjectAndOrg { + instance_name: omicron_common::api::external::Name::from( + instance_name.clone(), + ), + project_name: omicron_common::api::external::Name::from( + project_name.clone(), + ), + organization_name: + omicron_common::api::external::Name::from( + organization_name.clone(), + ), + }, + ) + .await?; let data = nexus .instance_serial_console_data( &opctx, - &organization_name, - &project_name, - &instance_name, + &instance_id, &query_params.into_inner(), ) .await?; @@ -2673,6 +2743,32 @@ async fn instance_serial_console( apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await } +#[channel { + protocol = WEBSOCKETS, + path = "/v1/instances/{instance}/serial-console/stream", + tags = ["instances"], +}] +async fn v1_instance_serial_console_stream( + rqctx: Arc>>, + conn: WebsocketConnection, + path_params: Path, + query_params: Query, +) -> WebsocketChannelResult { + let apictx = rqctx.context(); + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let query = query_params.into_inner(); + let opctx = OpContext::for_external_api(&rqctx).await?; + let instance_id = nexus + .instance_lookup_id( + &opctx, + query.selector.to_instance_selector(path.instance.into()), + ) + .await?; + nexus.instance_serial_console_stream(&opctx, conn, &instance_id).await?; + Ok(()) +} + /// Connect to an instance's serial console #[channel { protocol = WEBSOCKETS, @@ -2691,15 +2787,23 @@ async fn instance_serial_console_stream( let project_name = &path.project_name; let instance_name = &path.instance_name; let opctx = OpContext::for_external_api(&rqctx).await?; - nexus - .instance_serial_console_stream( + let instance_id = nexus + .instance_lookup_id( &opctx, - conn, - organization_name, - project_name, - instance_name, + params::InstanceSelector::InstanceProjectAndOrg { + instance_name: omicron_common::api::external::Name::from( + instance_name.clone(), + ), + project_name: omicron_common::api::external::Name::from( + project_name.clone(), + ), + organization_name: omicron_common::api::external::Name::from( + organization_name.clone(), + ), + }, ) .await?; + nexus.instance_serial_console_stream(&opctx, conn, &instance_id).await?; Ok(()) } diff --git a/nexus/tests/output/nexus_tags.txt b/nexus/tests/output/nexus_tags.txt index 4a4f649be66..a631e59da38 100644 --- a/nexus/tests/output/nexus_tags.txt +++ b/nexus/tests/output/nexus_tags.txt @@ -53,6 +53,8 @@ v1_instance_delete /v1/instances/{instance} v1_instance_list /v1/instances v1_instance_migrate /v1/instances/{instance}/migrate v1_instance_reboot /v1/instances/{instance}/reboot +v1_instance_serial_console /v1/instances/{instance}/serial-console +v1_instance_serial_console_stream /v1/instances/{instance}/serial-console/stream v1_instance_start /v1/instances/{instance}/start v1_instance_stop /v1/instances/{instance}/stop v1_instance_view /v1/instances/{instance} diff --git a/openapi/nexus.json b/openapi/nexus.json index 1304dc01cc0..1e692676033 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -8154,6 +8154,195 @@ } } }, + "/v1/instances/{instance}/serial-console": { + "post": { + "tags": [ + "instances" + ], + "operationId": "v1_instance_serial_console", + "parameters": [ + { + "in": "path", + "name": "instance", + "description": "If Name is used to reference the instance you must also include one of the following qualifiers as query parameters: - `project_id` - `project_name`, `organization_id` - `project_name`, `organization_name`\n\nIf Id is used the above qualifiers are will be ignored", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + }, + "style": "simple" + }, + { + "in": "query", + "name": "from_start", + "description": "Character index in the serial buffer from which to read, counting the bytes output since instance start. If this is not provided, `most_recent` must be provided, and if this *is* provided, `most_recent` must *not* be provided.", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "style": "form" + }, + { + "in": "query", + "name": "max_bytes", + "description": "Maximum number of bytes of buffered serial console contents to return. If the requested range runs to the end of the available buffer, the data returned will be shorter than `max_bytes`.", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "style": "form" + }, + { + "in": "query", + "name": "most_recent", + "description": "Character index in the serial buffer from which to read, counting *backward* from the most recently buffered data retrieved from the instance. (See note on `from_start` about mutual exclusivity)", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "style": "form" + }, + { + "in": "query", + "name": "project_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "style": "form" + }, + { + "in": "query", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "style": "form" + }, + { + "in": "query", + "name": "project_name", + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "form" + }, + { + "in": "query", + "name": "organization_name", + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "form" + }, + { + "in": "query", + "name": "project_name", + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "form" + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InstanceSerialConsoleData" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/v1/instances/{instance}/serial-console/stream": { + "get": { + "tags": [ + "instances" + ], + "operationId": "v1_instance_serial_console_stream", + "parameters": [ + { + "in": "path", + "name": "instance", + "description": "If Name is used to reference the instance you must also include one of the following qualifiers as query parameters: - `project_id` - `project_name`, `organization_id` - `project_name`, `organization_name`\n\nIf Id is used the above qualifiers are will be ignored", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + }, + "style": "simple" + }, + { + "in": "query", + "name": "project_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "style": "form" + }, + { + "in": "query", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "style": "form" + }, + { + "in": "query", + "name": "project_name", + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "form" + }, + { + "in": "query", + "name": "organization_name", + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "form" + }, + { + "in": "query", + "name": "project_name", + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "form" + } + ], + "responses": { + "default": { + "description": "", + "content": { + "*/*": { + "schema": {} + } + } + } + }, + "x-dropshot-websocket": {} + } + }, "/v1/instances/{instance}/start": { "post": { "tags": [ From d0a19c6669e6427c24989a4d79fe84ac687a9bf4 Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Tue, 22 Nov 2022 17:03:40 -0500 Subject: [PATCH 17/31] Workaround for multiple emits --- nexus/types/src/external_api/params.rs | 21 ++++++- openapi/nexus.json | 80 -------------------------- 2 files changed, 18 insertions(+), 83 deletions(-) diff --git a/nexus/types/src/external_api/params.rs b/nexus/types/src/external_api/params.rs index b6c4b232b7f..151a71f0a5a 100644 --- a/nexus/types/src/external_api/params.rs +++ b/nexus/types/src/external_api/params.rs @@ -21,9 +21,19 @@ use uuid::Uuid; #[derive(Deserialize, JsonSchema)] #[serde(untagged)] pub enum ProjectSelector { - ProjectId { project_id: Uuid }, - ProjectAndOrgId { project_name: Name, organization_id: Uuid }, - ProjectAndOrg { project_name: Name, organization_name: Name }, + ProjectId { + project_id: Uuid, + }, + ProjectAndOrgId { + project_name: Name, + organization_id: Uuid, + }, + ProjectAndOrg { + // FIXME: There's a bug in schemars or serde which causes project_name to be emitted twice + #[schemars(skip)] + project_name: Name, + organization_name: Name, + }, None {}, } @@ -73,12 +83,17 @@ pub enum InstanceSelector { project_id: Uuid, }, InstanceProjectAndOrgId { + // FIXME: There's a bug in schemars or serde which causes instance_name to be emitted multiple times + #[schemars(skip)] instance_name: Name, project_name: Name, organization_id: Uuid, }, InstanceProjectAndOrg { + #[schemars(skip)] instance_name: Name, + // FIXME: There's a bug in schemars or serde which causes project_name to be emitted multiple times + #[schemars(skip)] project_name: Name, organization_name: Name, }, diff --git a/openapi/nexus.json b/openapi/nexus.json index 1e692676033..aae6eea4267 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -7724,14 +7724,6 @@ "$ref": "#/components/schemas/Name" }, "style": "form" - }, - { - "in": "query", - "name": "project_name", - "schema": { - "$ref": "#/components/schemas/Name" - }, - "style": "form" } ], "responses": { @@ -7793,14 +7785,6 @@ "$ref": "#/components/schemas/Name" }, "style": "form" - }, - { - "in": "query", - "name": "project_name", - "schema": { - "$ref": "#/components/schemas/Name" - }, - "style": "form" } ], "requestBody": { @@ -7874,14 +7858,6 @@ }, "style": "form" }, - { - "in": "query", - "name": "project_name", - "schema": { - "$ref": "#/components/schemas/Name" - }, - "style": "form" - }, { "in": "path", "name": "instance", @@ -7952,14 +7928,6 @@ }, "style": "form" }, - { - "in": "query", - "name": "project_name", - "schema": { - "$ref": "#/components/schemas/Name" - }, - "style": "form" - }, { "in": "path", "name": "instance", @@ -8025,14 +7993,6 @@ }, "style": "form" }, - { - "in": "query", - "name": "project_name", - "schema": { - "$ref": "#/components/schemas/Name" - }, - "style": "form" - }, { "in": "path", "name": "instance", @@ -8115,14 +8075,6 @@ }, "style": "form" }, - { - "in": "query", - "name": "project_name", - "schema": { - "$ref": "#/components/schemas/Name" - }, - "style": "form" - }, { "in": "path", "name": "instance", @@ -8240,14 +8192,6 @@ "$ref": "#/components/schemas/Name" }, "style": "form" - }, - { - "in": "query", - "name": "project_name", - "schema": { - "$ref": "#/components/schemas/Name" - }, - "style": "form" } ], "responses": { @@ -8320,14 +8264,6 @@ "$ref": "#/components/schemas/Name" }, "style": "form" - }, - { - "in": "query", - "name": "project_name", - "schema": { - "$ref": "#/components/schemas/Name" - }, - "style": "form" } ], "responses": { @@ -8385,14 +8321,6 @@ }, "style": "form" }, - { - "in": "query", - "name": "project_name", - "schema": { - "$ref": "#/components/schemas/Name" - }, - "style": "form" - }, { "in": "path", "name": "instance", @@ -8465,14 +8393,6 @@ }, "style": "form" }, - { - "in": "query", - "name": "project_name", - "schema": { - "$ref": "#/components/schemas/Name" - }, - "style": "form" - }, { "in": "path", "name": "instance", From f439f592f8abe46dcd33fd15f2ff8d2e7d82f1b8 Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Tue, 22 Nov 2022 17:22:19 -0500 Subject: [PATCH 18/31] Implement NameOrId for display to try resolve dropshot error --- common/src/api/external/mod.rs | 5 ++--- nexus/db-model/src/name.rs | 3 ++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/common/src/api/external/mod.rs b/common/src/api/external/mod.rs index a490cba1eae..6a95f85cb4d 100644 --- a/common/src/api/external/mod.rs +++ b/common/src/api/external/mod.rs @@ -268,9 +268,8 @@ impl Name { } } -#[derive(Serialize, Deserialize)] -#[serde(try_from = "String")] - +#[derive(Serialize, Deserialize, Display)] +#[display("{0}")] pub enum NameOrId { Name(Name), Id(Uuid), diff --git a/nexus/db-model/src/name.rs b/nexus/db-model/src/name.rs index 10d0819b5fb..57235bdf011 100644 --- a/nexus/db-model/src/name.rs +++ b/nexus/db-model/src/name.rs @@ -66,9 +66,10 @@ where /// Newtype wrapper around [external::NameOrId]. This type isn't actually /// stored in the database, but exists as a convenience for the API. -#[derive(JsonSchema, Serialize, Deserialize, RefCast)] +#[derive(JsonSchema, Serialize, Deserialize, RefCast, Display)] #[serde(transparent)] #[repr(transparent)] +#[display("{0}")] pub struct NameOrId(pub external::NameOrId); NewtypeFrom! { () pub struct NameOrId(external::NameOrId); } From b4e818a8d52642bf1522f645112d65856ec36fce Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Tue, 22 Nov 2022 19:02:00 -0500 Subject: [PATCH 19/31] Flip the order of id/name in the NameOrId enum --- common/src/api/external/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/api/external/mod.rs b/common/src/api/external/mod.rs index 6a95f85cb4d..84870942582 100644 --- a/common/src/api/external/mod.rs +++ b/common/src/api/external/mod.rs @@ -271,8 +271,8 @@ impl Name { #[derive(Serialize, Deserialize, Display)] #[display("{0}")] pub enum NameOrId { - Name(Name), Id(Uuid), + Name(Name), } impl TryFrom for NameOrId { From 7bdfeb09f31a2d9046612baad0c0092608d2de84 Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Fri, 25 Nov 2022 11:08:49 -0500 Subject: [PATCH 20/31] Add untagged to NameOrId --- common/src/api/external/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/common/src/api/external/mod.rs b/common/src/api/external/mod.rs index 84870942582..0a8212b4272 100644 --- a/common/src/api/external/mod.rs +++ b/common/src/api/external/mod.rs @@ -270,6 +270,7 @@ impl Name { #[derive(Serialize, Deserialize, Display)] #[display("{0}")] +#[serde(untagged)] pub enum NameOrId { Id(Uuid), Name(Name), From cf5f54076c66c8a1798f68d6a2256dca0fcddd18 Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Tue, 29 Nov 2022 18:45:41 -0500 Subject: [PATCH 21/31] Update NameOrId to be labeled --- common/src/api/external/mod.rs | 4 ++-- openapi/nexus.json | 16 +++++++++++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/common/src/api/external/mod.rs b/common/src/api/external/mod.rs index 0a8212b4272..bb2d0b2e788 100644 --- a/common/src/api/external/mod.rs +++ b/common/src/api/external/mod.rs @@ -299,8 +299,8 @@ impl JsonSchema for NameOrId { schemars::schema::SchemaObject { subschemas: Some(Box::new(schemars::schema::SubschemaValidation { one_of: Some(vec![ - gen.subschema_for::(), - gen.subschema_for::(), + label_schema("id", gen.subschema_for::()), + label_schema("name", gen.subschema_for::()), ]), ..Default::default() })), diff --git a/openapi/nexus.json b/openapi/nexus.json index aae6eea4267..3f6ad4d8e31 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -13597,11 +13597,21 @@ "NameOrId": { "oneOf": [ { - "$ref": "#/components/schemas/Name" + "title": "id", + "allOf": [ + { + "type": "string", + "format": "uuid" + } + ] }, { - "type": "string", - "format": "uuid" + "title": "name", + "allOf": [ + { + "$ref": "#/components/schemas/Name" + } + ] } ] } From 8c7ce212c7c5b8d94d320ccf6b89e37a50e87447 Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Wed, 30 Nov 2022 10:38:57 -0500 Subject: [PATCH 22/31] RFD-322 PoC Revision 2 (#1995) --- common/src/api/external/mod.rs | 2 +- nexus/db-model/src/name.rs | 11 - nexus/src/app/instance.rs | 45 ++-- nexus/src/app/project.rs | 26 +- nexus/src/external_api/http_entrypoints.rs | 181 ++++++-------- nexus/tests/integration_tests/instances.rs | 6 +- nexus/types/src/external_api/params.rs | 96 ++------ openapi/nexus.json | 274 ++++----------------- 8 files changed, 189 insertions(+), 452 deletions(-) diff --git a/common/src/api/external/mod.rs b/common/src/api/external/mod.rs index bb2d0b2e788..7ae77794494 100644 --- a/common/src/api/external/mod.rs +++ b/common/src/api/external/mod.rs @@ -268,7 +268,7 @@ impl Name { } } -#[derive(Serialize, Deserialize, Display)] +#[derive(Serialize, Deserialize, Display, Clone)] #[display("{0}")] #[serde(untagged)] pub enum NameOrId { diff --git a/nexus/db-model/src/name.rs b/nexus/db-model/src/name.rs index 57235bdf011..96603530333 100644 --- a/nexus/db-model/src/name.rs +++ b/nexus/db-model/src/name.rs @@ -63,14 +63,3 @@ where String::from_sql(bytes)?.parse().map(Name).map_err(|e| e.into()) } } - -/// Newtype wrapper around [external::NameOrId]. This type isn't actually -/// stored in the database, but exists as a convenience for the API. -#[derive(JsonSchema, Serialize, Deserialize, RefCast, Display)] -#[serde(transparent)] -#[repr(transparent)] -#[display("{0}")] -pub struct NameOrId(pub external::NameOrId); - -NewtypeFrom! { () pub struct NameOrId(external::NameOrId); } -NewtypeDeref! { () pub struct NameOrId(external::NameOrId); } diff --git a/nexus/src/app/instance.rs b/nexus/src/app/instance.rs index c8b933d58f0..1289f9183c4 100644 --- a/nexus/src/app/instance.rs +++ b/nexus/src/app/instance.rs @@ -32,6 +32,7 @@ use omicron_common::api::external::InstanceState; use omicron_common::api::external::InternalContext; use omicron_common::api::external::ListResultVec; use omicron_common::api::external::LookupResult; +use omicron_common::api::external::NameOrId; use omicron_common::api::external::UpdateResult; use omicron_common::api::external::Vni; use omicron_common::api::internal::nexus; @@ -59,13 +60,16 @@ impl super::Nexus { instance_selector: params::InstanceSelector, ) -> LookupResult { match instance_selector { - params::InstanceSelector::InstanceId { instance_id } => { - Ok(instance_id) + params::InstanceSelector { instance: NameOrId::Id(id), .. } => { + // TODO: 400 if project or organization are present + Ok(id) } - params::InstanceSelector::InstanceAndProjectId { - instance_name, - project_id, + params::InstanceSelector { + instance: NameOrId::Name(instance_name), + project: Some(NameOrId::Id(project_id)), + .. } => { + // TODO: 400 if organization is present let (.., authz_instance) = LookupPath::new(opctx, &self.db_datastore) .project_id(project_id) @@ -74,10 +78,10 @@ impl super::Nexus { .await?; Ok(authz_instance.id()) } - params::InstanceSelector::InstanceProjectAndOrgId { - instance_name, - project_name, - organization_id, + params::InstanceSelector { + instance: NameOrId::Name(instance_name), + project: Some(NameOrId::Name(project_name)), + organization: Some(NameOrId::Id(organization_id)), } => { let (.., authz_instance) = LookupPath::new(opctx, &self.db_datastore) @@ -88,10 +92,10 @@ impl super::Nexus { .await?; Ok(authz_instance.id()) } - params::InstanceSelector::InstanceProjectAndOrg { - instance_name, - project_name, - organization_name, + params::InstanceSelector { + instance: NameOrId::Name(instance_name), + project: Some(NameOrId::Name(project_name)), + organization: Some(NameOrId::Name(organization_name)), } => { let (.., authz_instance) = LookupPath::new(opctx, &self.db_datastore) @@ -102,14 +106,15 @@ impl super::Nexus { .await?; Ok(authz_instance.id()) } - params::InstanceSelector::None {} => Err(Error::InvalidRequest { + // TODO: Add a better error message + _ => Err(Error::InvalidRequest { message: " - Unable to resolve instance. Expected one of - - instance_id - - instance_name, project_id - - instance_name, project_name, organization_id - - instance_name, project_name, organization_name - " + Unable to resolve instance. Expected one of + - instance: Uuid + - instance: Name, project: Uuid + - instance: Name, project: Name, organization: Uuid + - instance: Name, project: Name, organization: Name + " .to_string(), }), } diff --git a/nexus/src/app/project.rs b/nexus/src/app/project.rs index c0eeb4c95dd..e5178a6537e 100644 --- a/nexus/src/app/project.rs +++ b/nexus/src/app/project.rs @@ -20,6 +20,7 @@ use omicron_common::api::external::Error; use omicron_common::api::external::IdentityMetadataCreateParams; use omicron_common::api::external::ListResultVec; use omicron_common::api::external::LookupResult; +use omicron_common::api::external::NameOrId; use omicron_common::api::external::UpdateResult; use uuid::Uuid; @@ -30,10 +31,13 @@ impl super::Nexus { project_selector: params::ProjectSelector, ) -> LookupResult { match project_selector { - params::ProjectSelector::ProjectId { project_id } => Ok(project_id), - params::ProjectSelector::ProjectAndOrgId { - project_name, - organization_id, + params::ProjectSelector { project: NameOrId::Id(id), .. } => { + // TODO: 400 if organization is present + Ok(id) + } + params::ProjectSelector { + project: NameOrId::Name(project_name), + organization: Some(NameOrId::Id(organization_id)), } => { let (.., authz_project) = LookupPath::new(opctx, &self.db_datastore) @@ -43,9 +47,9 @@ impl super::Nexus { .await?; Ok(authz_project.id()) } - params::ProjectSelector::ProjectAndOrg { - project_name, - organization_name, + params::ProjectSelector { + project: NameOrId::Name(project_name), + organization: Some(NameOrId::Name(organization_name)), } => { let (.., authz_project) = LookupPath::new(opctx, &self.db_datastore) @@ -55,12 +59,12 @@ impl super::Nexus { .await?; Ok(authz_project.id()) } - params::ProjectSelector::None {} => Err(Error::InvalidRequest { + _ => Err(Error::InvalidRequest { message: " Unable to resolve project. Expected one of - - project_id - - project_name, organization_id - - project_name, organization_name + - project: Uuid + - project: Name, organization: Uuid + - project: Name, organization: Name " .to_string(), }), diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index 3199914d6f9..4cbc23ad709 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -18,7 +18,6 @@ use crate::authz; use crate::context::OpContext; use crate::db; use crate::db::model::Name; -use crate::db::model::NameOrId; use crate::external_api::shared; use crate::ServerContext; use dropshot::ApiDescription; @@ -61,6 +60,7 @@ use omicron_common::api::external::Disk; use omicron_common::api::external::Error; use omicron_common::api::external::Instance; use omicron_common::api::external::InternalContext; +use omicron_common::api::external::NameOrId; use omicron_common::api::external::NetworkInterface; use omicron_common::api::external::RouterRoute; use omicron_common::api::external::RouterRouteCreateParams; @@ -2076,9 +2076,11 @@ async fn instance_list( let project_id = nexus .project_lookup_id( &opctx, - params::ProjectSelector::ProjectAndOrg { - project_name: project_name.clone().into(), - organization_name: organization_name.clone().into(), + params::ProjectSelector { + project: NameOrId::Name(project_name.clone().into()), + organization: Some(NameOrId::Name( + organization_name.clone().into(), + )), }, ) .await?; @@ -2102,6 +2104,12 @@ async fn instance_list( apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await } +#[derive(Deserialize, JsonSchema)] +struct InstanceCreateParams { + #[serde(flatten)] + selector: params::ProjectSelector, +} + #[endpoint { method = POST, path = "/v1/instances", @@ -2109,7 +2117,7 @@ async fn instance_list( }] async fn v1_instance_create( rqctx: Arc>>, - query_params: Query, + query_params: Query, new_instance: TypedBody, ) -> Result, HttpError> { let apictx = rqctx.context(); @@ -2157,9 +2165,11 @@ async fn instance_create( let project_id = nexus .project_lookup_id( &opctx, - params::ProjectSelector::ProjectAndOrg { - project_name: project_name.clone().into(), - organization_name: organization_name.clone().into(), + params::ProjectSelector { + project: NameOrId::Name(project_name.clone().into()), + organization: Some(NameOrId::Name( + organization_name.clone().into(), + )), }, ) .await?; @@ -2186,7 +2196,7 @@ struct InstanceLookupPathParam { #[derive(Deserialize, JsonSchema)] struct InstanceQueryParams { #[serde(flatten)] - selector: params::ProjectSelector, + selector: Option, } #[endpoint { @@ -2208,7 +2218,7 @@ async fn v1_instance_view( let instance_id = nexus .instance_lookup_id( &opctx, - query.selector.to_instance_selector(path.instance.into()), + params::InstanceSelector::new(path.instance, &query.selector), ) .await?; let instance = nexus.instance_fetch(&opctx, &instance_id).await?; @@ -2246,17 +2256,12 @@ async fn instance_view( let instance_id = nexus .instance_lookup_id( &opctx, - params::InstanceSelector::InstanceProjectAndOrg { - instance_name: omicron_common::api::external::Name::from( - instance_name.clone(), - ), - project_name: omicron_common::api::external::Name::from( - project_name.clone(), - ), - organization_name: - omicron_common::api::external::Name::from( - organization_name.clone(), - ), + params::InstanceSelector { + instance: NameOrId::Name(instance_name.clone().into()), + project: Some(NameOrId::Name(project_name.clone().into())), + organization: Some(NameOrId::Name( + organization_name.clone().into(), + )), }, ) .await?; @@ -2307,7 +2312,7 @@ async fn v1_instance_delete( let instance_id = nexus .instance_lookup_id( &opctx, - query.selector.to_instance_selector(path.instance.into()), + params::InstanceSelector::new(path.instance, &query.selector), ) .await?; nexus.project_destroy_instance(&opctx, &instance_id).await?; @@ -2337,17 +2342,12 @@ async fn instance_delete( let instance_id = nexus .instance_lookup_id( &opctx, - params::InstanceSelector::InstanceProjectAndOrg { - instance_name: omicron_common::api::external::Name::from( - instance_name.clone(), - ), - project_name: omicron_common::api::external::Name::from( - project_name.clone(), - ), - organization_name: - omicron_common::api::external::Name::from( - organization_name.clone(), - ), + params::InstanceSelector { + instance: NameOrId::Name(instance_name.clone().into()), + project: Some(NameOrId::Name(project_name.clone().into())), + organization: Some(NameOrId::Name( + organization_name.clone().into(), + )), }, ) .await?; @@ -2379,7 +2379,7 @@ async fn v1_instance_migrate( let instance_id = nexus .instance_lookup_id( &opctx, - query.selector.to_instance_selector(path.instance.into()), + params::InstanceSelector::new(path.instance, &query.selector), ) .await?; let instance = nexus @@ -2418,17 +2418,12 @@ async fn instance_migrate( let instance_id = nexus .instance_lookup_id( &opctx, - params::InstanceSelector::InstanceProjectAndOrg { - instance_name: omicron_common::api::external::Name::from( - instance_name.clone(), - ), - project_name: omicron_common::api::external::Name::from( - project_name.clone(), - ), - organization_name: - omicron_common::api::external::Name::from( - organization_name.clone(), - ), + params::InstanceSelector { + instance: NameOrId::Name(instance_name.clone().into()), + project: Some(NameOrId::Name(project_name.clone().into())), + organization: Some(NameOrId::Name( + organization_name.clone().into(), + )), }, ) .await?; @@ -2463,7 +2458,7 @@ async fn v1_instance_reboot( let instance_id = nexus .instance_lookup_id( &opctx, - query.selector.to_instance_selector(path.instance.into()), + params::InstanceSelector::new(path.instance, &query.selector), ) .await?; let instance = nexus.instance_reboot(&opctx, instance_id).await?; @@ -2493,17 +2488,12 @@ async fn instance_reboot( let instance_id = nexus .instance_lookup_id( &opctx, - params::InstanceSelector::InstanceProjectAndOrg { - instance_name: omicron_common::api::external::Name::from( - instance_name.clone(), - ), - project_name: omicron_common::api::external::Name::from( - project_name.clone(), - ), - organization_name: - omicron_common::api::external::Name::from( - organization_name.clone(), - ), + params::InstanceSelector { + instance: NameOrId::Name(instance_name.clone().into()), + project: Some(NameOrId::Name(project_name.clone().into())), + organization: Some(NameOrId::Name( + organization_name.clone().into(), + )), }, ) .await?; @@ -2533,7 +2523,7 @@ async fn v1_instance_start( let instance_id = nexus .instance_lookup_id( &opctx, - query.selector.to_instance_selector(path.instance.into()), + params::InstanceSelector::new(path.instance, &query.selector), ) .await?; let instance = nexus.instance_start(&opctx, instance_id).await?; @@ -2563,17 +2553,12 @@ async fn instance_start( let instance_id = nexus .instance_lookup_id( &opctx, - params::InstanceSelector::InstanceProjectAndOrg { - instance_name: omicron_common::api::external::Name::from( - instance_name.clone(), - ), - project_name: omicron_common::api::external::Name::from( - project_name.clone(), - ), - organization_name: - omicron_common::api::external::Name::from( - organization_name.clone(), - ), + params::InstanceSelector { + instance: NameOrId::Name(instance_name.clone().into()), + project: Some(NameOrId::Name(project_name.clone().into())), + organization: Some(NameOrId::Name( + organization_name.clone().into(), + )), }, ) .await?; @@ -2602,7 +2587,7 @@ async fn v1_instance_stop( let instance_id = nexus .instance_lookup_id( &opctx, - query.selector.to_instance_selector(path.instance.into()), + params::InstanceSelector::new(path.instance, &query.selector), ) .await?; let instance = nexus.instance_stop(&opctx, instance_id).await?; @@ -2632,17 +2617,12 @@ async fn instance_stop( let instance_id = nexus .instance_lookup_id( &opctx, - params::InstanceSelector::InstanceProjectAndOrg { - instance_name: omicron_common::api::external::Name::from( - instance_name.clone(), - ), - project_name: omicron_common::api::external::Name::from( - project_name.clone(), - ), - organization_name: - omicron_common::api::external::Name::from( - organization_name.clone(), - ), + params::InstanceSelector { + instance: NameOrId::Name(instance_name.clone().into()), + project: Some(NameOrId::Name(project_name.clone().into())), + organization: Some(NameOrId::Name( + organization_name.clone().into(), + )), }, ) .await?; @@ -2655,7 +2635,7 @@ async fn instance_stop( #[derive(Deserialize, JsonSchema)] pub struct InstanceSerialConsoleParams { #[serde(flatten)] - selector: params::ProjectSelector, + selector: Option, #[serde(flatten)] pub console_params: params::InstanceSerialConsoleRequest, @@ -2680,7 +2660,7 @@ async fn v1_instance_serial_console( let instance_id = nexus .instance_lookup_id( &opctx, - query.selector.to_instance_selector(path.instance.into()), + params::InstanceSelector::new(path.instance, &query.selector), ) .await?; let console = nexus @@ -2717,17 +2697,12 @@ async fn instance_serial_console( let instance_id = nexus .instance_lookup_id( &opctx, - params::InstanceSelector::InstanceProjectAndOrg { - instance_name: omicron_common::api::external::Name::from( - instance_name.clone(), - ), - project_name: omicron_common::api::external::Name::from( - project_name.clone(), - ), - organization_name: - omicron_common::api::external::Name::from( - organization_name.clone(), - ), + params::InstanceSelector { + instance: NameOrId::Name(instance_name.clone().into()), + project: Some(NameOrId::Name(project_name.clone().into())), + organization: Some(NameOrId::Name( + organization_name.clone().into(), + )), }, ) .await?; @@ -2762,7 +2737,7 @@ async fn v1_instance_serial_console_stream( let instance_id = nexus .instance_lookup_id( &opctx, - query.selector.to_instance_selector(path.instance.into()), + params::InstanceSelector::new(path.instance, &query.selector), ) .await?; nexus.instance_serial_console_stream(&opctx, conn, &instance_id).await?; @@ -2790,16 +2765,12 @@ async fn instance_serial_console_stream( let instance_id = nexus .instance_lookup_id( &opctx, - params::InstanceSelector::InstanceProjectAndOrg { - instance_name: omicron_common::api::external::Name::from( - instance_name.clone(), - ), - project_name: omicron_common::api::external::Name::from( - project_name.clone(), - ), - organization_name: omicron_common::api::external::Name::from( - organization_name.clone(), - ), + params::InstanceSelector { + instance: NameOrId::Name(instance_name.clone().into()), + project: Some(NameOrId::Name(project_name.clone().into())), + organization: Some(NameOrId::Name( + organization_name.clone().into(), + )), }, ) .await?; diff --git a/nexus/tests/integration_tests/instances.rs b/nexus/tests/integration_tests/instances.rs index 5dec931188b..594c9573640 100644 --- a/nexus/tests/integration_tests/instances.rs +++ b/nexus/tests/integration_tests/instances.rs @@ -149,7 +149,7 @@ async fn test_v1_instance_access(cptestctx: &ControlPlaneTestContext) { let fetched_instance = instance_get( &client, format!( - "/v1/instances/{}?project_id={}", + "/v1/instances/{}?project={}", instance.identity.name, project.identity.id ) .as_str(), @@ -161,7 +161,7 @@ async fn test_v1_instance_access(cptestctx: &ControlPlaneTestContext) { let fetched_instance = instance_get( &client, format!( - "/v1/instances/{}?project_name={}&organization_id={}", + "/v1/instances/{}?project={}&organization={}", instance.identity.name, project.identity.name, org.identity.id ) .as_str(), @@ -173,7 +173,7 @@ async fn test_v1_instance_access(cptestctx: &ControlPlaneTestContext) { let fetched_instance = instance_get( &client, format!( - "/v1/instances/{}?project_name={}&organization_name={}", + "/v1/instances/{}?project={}&organization={}", instance.identity.name, project.identity.name, org.identity.name ) .as_str(), diff --git a/nexus/types/src/external_api/params.rs b/nexus/types/src/external_api/params.rs index 151a71f0a5a..dd57a3935dc 100644 --- a/nexus/types/src/external_api/params.rs +++ b/nexus/types/src/external_api/params.rs @@ -19,87 +19,33 @@ use std::{net::IpAddr, str::FromStr}; use uuid::Uuid; #[derive(Deserialize, JsonSchema)] -#[serde(untagged)] -pub enum ProjectSelector { - ProjectId { - project_id: Uuid, - }, - ProjectAndOrgId { - project_name: Name, - organization_id: Uuid, - }, - ProjectAndOrg { - // FIXME: There's a bug in schemars or serde which causes project_name to be emitted twice - #[schemars(skip)] - project_name: Name, - organization_name: Name, - }, - None {}, +pub struct ProjectSelector { + pub project: NameOrId, + pub organization: Option, } -impl ProjectSelector { - pub fn to_instance_selector(self, instance: NameOrId) -> InstanceSelector { - match instance { - NameOrId::Id(instance_id) => { - InstanceSelector::InstanceId { instance_id } - } - NameOrId::Name(instance_name) => match self { - ProjectSelector::ProjectId { project_id } => { - InstanceSelector::InstanceAndProjectId { - instance_name, - project_id, - } - } - ProjectSelector::ProjectAndOrgId { - project_name, - organization_id, - } => InstanceSelector::InstanceProjectAndOrgId { - instance_name, - project_name, - organization_id, - }, - ProjectSelector::ProjectAndOrg { - project_name, - organization_name, - } => InstanceSelector::InstanceProjectAndOrg { - instance_name, - project_name, - organization_name, - }, - ProjectSelector::None {} => InstanceSelector::None {}, - }, +#[derive(Deserialize, JsonSchema)] +pub struct InstanceSelector { + pub instance: NameOrId, + pub project: Option, + pub organization: Option, +} + +impl InstanceSelector { + pub fn new( + instance: NameOrId, + project_selector: &Option, + ) -> InstanceSelector { + InstanceSelector { + instance, + organization: project_selector + .as_ref() + .and_then(|s| s.organization.clone()), + project: project_selector.as_ref().map(|s| s.project.clone()), } } } -#[derive(Deserialize, JsonSchema)] -#[serde(untagged)] -pub enum InstanceSelector { - InstanceId { - instance_id: Uuid, - }, - InstanceAndProjectId { - instance_name: Name, - project_id: Uuid, - }, - InstanceProjectAndOrgId { - // FIXME: There's a bug in schemars or serde which causes instance_name to be emitted multiple times - #[schemars(skip)] - instance_name: Name, - project_name: Name, - organization_id: Uuid, - }, - InstanceProjectAndOrg { - #[schemars(skip)] - instance_name: Name, - // FIXME: There's a bug in schemars or serde which causes project_name to be emitted multiple times - #[schemars(skip)] - project_name: Name, - organization_name: Name, - }, - None {}, -} - // Silos /// Create-time parameters for a [`Silo`](crate::external_api::views::Silo) diff --git a/openapi/nexus.json b/openapi/nexus.json index 3f6ad4d8e31..8e730cb17be 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -7675,53 +7675,36 @@ }, { "in": "query", - "name": "page_token", - "description": "Token returned by previous call to retrieve the subsequent page", - "schema": { - "nullable": true, - "type": "string" - }, - "style": "form" - }, - { - "in": "query", - "name": "sort_by", - "schema": { - "$ref": "#/components/schemas/NameSortMode" - }, - "style": "form" - }, - { - "in": "query", - "name": "project_id", + "name": "organization", "schema": { - "type": "string", - "format": "uuid" + "$ref": "#/components/schemas/NameOrId" }, "style": "form" }, { "in": "query", - "name": "organization_id", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", "schema": { - "type": "string", - "format": "uuid" + "nullable": true, + "type": "string" }, "style": "form" }, { "in": "query", - "name": "project_name", + "name": "project", + "required": true, "schema": { - "$ref": "#/components/schemas/Name" + "$ref": "#/components/schemas/NameOrId" }, "style": "form" }, { "in": "query", - "name": "organization_name", + "name": "sort_by", "schema": { - "$ref": "#/components/schemas/Name" + "$ref": "#/components/schemas/NameSortMode" }, "style": "form" } @@ -7754,35 +7737,18 @@ "parameters": [ { "in": "query", - "name": "project_id", + "name": "organization", "schema": { - "type": "string", - "format": "uuid" - }, - "style": "form" - }, - { - "in": "query", - "name": "organization_id", - "schema": { - "type": "string", - "format": "uuid" - }, - "style": "form" - }, - { - "in": "query", - "name": "project_name", - "schema": { - "$ref": "#/components/schemas/Name" + "$ref": "#/components/schemas/NameOrId" }, "style": "form" }, { "in": "query", - "name": "organization_name", + "name": "project", + "required": true, "schema": { - "$ref": "#/components/schemas/Name" + "$ref": "#/components/schemas/NameOrId" }, "style": "form" } @@ -7826,35 +7792,17 @@ "parameters": [ { "in": "query", - "name": "project_id", + "name": "organization", "schema": { - "type": "string", - "format": "uuid" - }, - "style": "form" - }, - { - "in": "query", - "name": "organization_id", - "schema": { - "type": "string", - "format": "uuid" - }, - "style": "form" - }, - { - "in": "query", - "name": "project_name", - "schema": { - "$ref": "#/components/schemas/Name" + "$ref": "#/components/schemas/NameOrId" }, "style": "form" }, { "in": "query", - "name": "organization_name", + "name": "project", "schema": { - "$ref": "#/components/schemas/Name" + "$ref": "#/components/schemas/NameOrId" }, "style": "form" }, @@ -7896,35 +7844,17 @@ "parameters": [ { "in": "query", - "name": "project_id", - "schema": { - "type": "string", - "format": "uuid" - }, - "style": "form" - }, - { - "in": "query", - "name": "organization_id", + "name": "organization", "schema": { - "type": "string", - "format": "uuid" - }, - "style": "form" - }, - { - "in": "query", - "name": "project_name", - "schema": { - "$ref": "#/components/schemas/Name" + "$ref": "#/components/schemas/NameOrId" }, "style": "form" }, { "in": "query", - "name": "organization_name", + "name": "project", "schema": { - "$ref": "#/components/schemas/Name" + "$ref": "#/components/schemas/NameOrId" }, "style": "form" }, @@ -7961,35 +7891,17 @@ "parameters": [ { "in": "query", - "name": "project_id", - "schema": { - "type": "string", - "format": "uuid" - }, - "style": "form" - }, - { - "in": "query", - "name": "organization_id", - "schema": { - "type": "string", - "format": "uuid" - }, - "style": "form" - }, - { - "in": "query", - "name": "project_name", + "name": "organization", "schema": { - "$ref": "#/components/schemas/Name" + "$ref": "#/components/schemas/NameOrId" }, "style": "form" }, { "in": "query", - "name": "organization_name", + "name": "project", "schema": { - "$ref": "#/components/schemas/Name" + "$ref": "#/components/schemas/NameOrId" }, "style": "form" }, @@ -8043,35 +7955,17 @@ "parameters": [ { "in": "query", - "name": "project_id", - "schema": { - "type": "string", - "format": "uuid" - }, - "style": "form" - }, - { - "in": "query", - "name": "organization_id", + "name": "organization", "schema": { - "type": "string", - "format": "uuid" - }, - "style": "form" - }, - { - "in": "query", - "name": "project_name", - "schema": { - "$ref": "#/components/schemas/Name" + "$ref": "#/components/schemas/NameOrId" }, "style": "form" }, { "in": "query", - "name": "organization_name", + "name": "project", "schema": { - "$ref": "#/components/schemas/Name" + "$ref": "#/components/schemas/NameOrId" }, "style": "form" }, @@ -8161,35 +8055,17 @@ }, { "in": "query", - "name": "project_id", + "name": "organization", "schema": { - "type": "string", - "format": "uuid" - }, - "style": "form" - }, - { - "in": "query", - "name": "organization_id", - "schema": { - "type": "string", - "format": "uuid" - }, - "style": "form" - }, - { - "in": "query", - "name": "project_name", - "schema": { - "$ref": "#/components/schemas/Name" + "$ref": "#/components/schemas/NameOrId" }, "style": "form" }, { "in": "query", - "name": "organization_name", + "name": "project", "schema": { - "$ref": "#/components/schemas/Name" + "$ref": "#/components/schemas/NameOrId" }, "style": "form" } @@ -8233,35 +8109,17 @@ }, { "in": "query", - "name": "project_id", - "schema": { - "type": "string", - "format": "uuid" - }, - "style": "form" - }, - { - "in": "query", - "name": "organization_id", + "name": "organization", "schema": { - "type": "string", - "format": "uuid" - }, - "style": "form" - }, - { - "in": "query", - "name": "project_name", - "schema": { - "$ref": "#/components/schemas/Name" + "$ref": "#/components/schemas/NameOrId" }, "style": "form" }, { "in": "query", - "name": "organization_name", + "name": "project", "schema": { - "$ref": "#/components/schemas/Name" + "$ref": "#/components/schemas/NameOrId" }, "style": "form" } @@ -8289,35 +8147,17 @@ "parameters": [ { "in": "query", - "name": "project_id", - "schema": { - "type": "string", - "format": "uuid" - }, - "style": "form" - }, - { - "in": "query", - "name": "organization_id", - "schema": { - "type": "string", - "format": "uuid" - }, - "style": "form" - }, - { - "in": "query", - "name": "project_name", + "name": "organization", "schema": { - "$ref": "#/components/schemas/Name" + "$ref": "#/components/schemas/NameOrId" }, "style": "form" }, { "in": "query", - "name": "organization_name", + "name": "project", "schema": { - "$ref": "#/components/schemas/Name" + "$ref": "#/components/schemas/NameOrId" }, "style": "form" }, @@ -8361,35 +8201,17 @@ "parameters": [ { "in": "query", - "name": "project_id", - "schema": { - "type": "string", - "format": "uuid" - }, - "style": "form" - }, - { - "in": "query", - "name": "organization_id", + "name": "organization", "schema": { - "type": "string", - "format": "uuid" - }, - "style": "form" - }, - { - "in": "query", - "name": "project_name", - "schema": { - "$ref": "#/components/schemas/Name" + "$ref": "#/components/schemas/NameOrId" }, "style": "form" }, { "in": "query", - "name": "organization_name", + "name": "project", "schema": { - "$ref": "#/components/schemas/Name" + "$ref": "#/components/schemas/NameOrId" }, "style": "form" }, From 87f4d2522101ae2760da43d1d474de1b9b14e85a Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Wed, 30 Nov 2022 13:31:22 -0500 Subject: [PATCH 23/31] Move version to postfix --- nexus/src/external_api/http_entrypoints.rs | 40 +++++++++++----------- nexus/tests/output/nexus_tags.txt | 20 +++++------ openapi/nexus.json | 20 +++++------ 3 files changed, 40 insertions(+), 40 deletions(-) diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index 4cbc23ad709..9a73863bd1c 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -146,16 +146,16 @@ pub fn external_api() -> NexusApiDescription { api.register(instance_stop)?; api.register(instance_serial_console)?; api.register(instance_serial_console_stream)?; - api.register(v1_instance_list)?; - api.register(v1_instance_view)?; - api.register(v1_instance_create)?; - api.register(v1_instance_delete)?; - api.register(v1_instance_migrate)?; - api.register(v1_instance_reboot)?; - api.register(v1_instance_start)?; - api.register(v1_instance_stop)?; - api.register(v1_instance_serial_console)?; - api.register(v1_instance_serial_console_stream)?; + api.register(instance_list_v1)?; + api.register(instance_view_v1)?; + api.register(instance_create_v1)?; + api.register(instance_delete_v1)?; + api.register(instance_migrate_v1)?; + api.register(instance_reboot_v1)?; + api.register(instance_start_v1)?; + api.register(instance_stop_v1)?; + api.register(instance_serial_console_v1)?; + api.register(instance_serial_console_stream_v1)?; // Project-scoped images API api.register(image_list)?; @@ -2023,7 +2023,7 @@ struct InstanceListQueryParams { path = "/v1/instances", tags = ["instances"], }] -async fn v1_instance_list( +async fn instance_list_v1( rqctx: Arc>>, query_params: Query, ) -> Result>, HttpError> { @@ -2115,7 +2115,7 @@ struct InstanceCreateParams { path = "/v1/instances", tags = ["instances"], }] -async fn v1_instance_create( +async fn instance_create_v1( rqctx: Arc>>, query_params: Query, new_instance: TypedBody, @@ -2204,7 +2204,7 @@ struct InstanceQueryParams { path = "/v1/instances/{instance}", tags = ["instances"], }] -async fn v1_instance_view( +async fn instance_view_v1( rqctx: Arc>>, query_params: Query, path_params: Path, @@ -2298,7 +2298,7 @@ async fn instance_view_by_id( path = "/v1/instances/{instance}", tags = ["instances"], }] -async fn v1_instance_delete( +async fn instance_delete_v1( rqctx: Arc>>, query_params: Query, path_params: Path, @@ -2363,7 +2363,7 @@ async fn instance_delete( path = "/v1/instances/{instance}/migrate", tags = ["instances"], }] -async fn v1_instance_migrate( +async fn instance_migrate_v1( rqctx: Arc>>, query_params: Query, path_params: Path, @@ -2444,7 +2444,7 @@ async fn instance_migrate( path = "/v1/instances/{instance}/reboot", tags = ["instances"], }] -async fn v1_instance_reboot( +async fn instance_reboot_v1( rqctx: Arc>>, query_params: Query, path_params: Path, @@ -2509,7 +2509,7 @@ async fn instance_reboot( path = "/v1/instances/{instance}/start", tags = ["instances"], }] -async fn v1_instance_start( +async fn instance_start_v1( rqctx: Arc>>, query_params: Query, path_params: Path, @@ -2573,7 +2573,7 @@ async fn instance_start( path = "/v1/instances/{instance}/stop", tags = ["instances"], }] -async fn v1_instance_stop( +async fn instance_stop_v1( rqctx: Arc>>, query_params: Query, path_params: Path, @@ -2646,7 +2646,7 @@ pub struct InstanceSerialConsoleParams { path = "/v1/instances/{instance}/serial-console", tags = ["instances"], }] -async fn v1_instance_serial_console( +async fn instance_serial_console_v1( rqctx: Arc>>, path_params: Path, query_params: Query, @@ -2723,7 +2723,7 @@ async fn instance_serial_console( path = "/v1/instances/{instance}/serial-console/stream", tags = ["instances"], }] -async fn v1_instance_serial_console_stream( +async fn instance_serial_console_stream_v1( rqctx: Arc>>, conn: WebsocketConnection, path_params: Path, diff --git a/nexus/tests/output/nexus_tags.txt b/nexus/tests/output/nexus_tags.txt index a631e59da38..3828fb5bfb7 100644 --- a/nexus/tests/output/nexus_tags.txt +++ b/nexus/tests/output/nexus_tags.txt @@ -28,13 +28,17 @@ image_view_by_id /by-id/images/{id} API operations found with tag "instances" OPERATION ID URL PATH instance_create /organizations/{organization_name}/projects/{project_name}/instances +instance_create_v1 /v1/instances instance_delete /organizations/{organization_name}/projects/{project_name}/instances/{instance_name} +instance_delete_v1 /v1/instances/{instance} instance_disk_attach /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/disks/attach instance_disk_detach /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/disks/detach instance_disk_list /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/disks instance_external_ip_list /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/external-ips instance_list /organizations/{organization_name}/projects/{project_name}/instances +instance_list_v1 /v1/instances instance_migrate /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/migrate +instance_migrate_v1 /v1/instances/{instance}/migrate instance_network_interface_create /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/network-interfaces instance_network_interface_delete /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/network-interfaces/{interface_name} instance_network_interface_list /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/network-interfaces @@ -42,22 +46,18 @@ instance_network_interface_update /organizations/{organization_name}/proj instance_network_interface_view /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/network-interfaces/{interface_name} instance_network_interface_view_by_id /by-id/network-interfaces/{id} instance_reboot /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/reboot +instance_reboot_v1 /v1/instances/{instance}/reboot instance_serial_console /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/serial-console instance_serial_console_stream /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/serial-console/stream +instance_serial_console_stream_v1 /v1/instances/{instance}/serial-console/stream +instance_serial_console_v1 /v1/instances/{instance}/serial-console instance_start /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/start +instance_start_v1 /v1/instances/{instance}/start instance_stop /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/stop +instance_stop_v1 /v1/instances/{instance}/stop instance_view /organizations/{organization_name}/projects/{project_name}/instances/{instance_name} instance_view_by_id /by-id/instances/{id} -v1_instance_create /v1/instances -v1_instance_delete /v1/instances/{instance} -v1_instance_list /v1/instances -v1_instance_migrate /v1/instances/{instance}/migrate -v1_instance_reboot /v1/instances/{instance}/reboot -v1_instance_serial_console /v1/instances/{instance}/serial-console -v1_instance_serial_console_stream /v1/instances/{instance}/serial-console/stream -v1_instance_start /v1/instances/{instance}/start -v1_instance_stop /v1/instances/{instance}/stop -v1_instance_view /v1/instances/{instance} +instance_view_v1 /v1/instances/{instance} API operations found with tag "login" OPERATION ID URL PATH diff --git a/openapi/nexus.json b/openapi/nexus.json index 8e730cb17be..5f23b984097 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -7659,7 +7659,7 @@ "tags": [ "instances" ], - "operationId": "v1_instance_list", + "operationId": "instance_list_v1", "parameters": [ { "in": "query", @@ -7733,7 +7733,7 @@ "tags": [ "instances" ], - "operationId": "v1_instance_create", + "operationId": "instance_create_v1", "parameters": [ { "in": "query", @@ -7788,7 +7788,7 @@ "tags": [ "instances" ], - "operationId": "v1_instance_view", + "operationId": "instance_view_v1", "parameters": [ { "in": "query", @@ -7840,7 +7840,7 @@ "tags": [ "instances" ], - "operationId": "v1_instance_delete", + "operationId": "instance_delete_v1", "parameters": [ { "in": "query", @@ -7887,7 +7887,7 @@ "tags": [ "instances" ], - "operationId": "v1_instance_migrate", + "operationId": "instance_migrate_v1", "parameters": [ { "in": "query", @@ -7951,7 +7951,7 @@ "tags": [ "instances" ], - "operationId": "v1_instance_reboot", + "operationId": "instance_reboot_v1", "parameters": [ { "in": "query", @@ -8005,7 +8005,7 @@ "tags": [ "instances" ], - "operationId": "v1_instance_serial_console", + "operationId": "instance_serial_console_v1", "parameters": [ { "in": "path", @@ -8095,7 +8095,7 @@ "tags": [ "instances" ], - "operationId": "v1_instance_serial_console_stream", + "operationId": "instance_serial_console_stream_v1", "parameters": [ { "in": "path", @@ -8143,7 +8143,7 @@ "instances" ], "summary": "Boot an instance", - "operationId": "v1_instance_start", + "operationId": "instance_start_v1", "parameters": [ { "in": "query", @@ -8197,7 +8197,7 @@ "tags": [ "instances" ], - "operationId": "v1_instance_stop", + "operationId": "instance_stop_v1", "parameters": [ { "in": "query", From 393283d0a21d29b4498ee76ee0fd76ad9a13216c Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Thu, 1 Dec 2022 16:16:35 -0500 Subject: [PATCH 24/31] Merge main int rfd-322-poc --- Cargo.lock | 186 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 128 insertions(+), 58 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6f2cd138475..9d4bb3ef769 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -183,9 +183,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.58" +version = "0.1.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" +checksum = "31e6e93155431f3931513b243d371981bb2770112b370c82745a1d19d2f99364" dependencies = [ "proc-macro2", "quote", @@ -207,7 +207,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", "winapi", ] @@ -505,9 +505,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" +checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" dependencies = [ "serde", ] @@ -709,14 +709,14 @@ dependencies = [ [[package]] name = "clap" -version = "4.0.24" +version = "4.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60494cedb60cb47462c0ff7be53de32c0e42a6fc2c772184554fa12bd9489c03" +checksum = "4d63b9e9c07271b9957ad22c173bae2a4d9a81127680962039296abcd2f8251d" dependencies = [ - "atty", "bitflags", "clap_derive 4.0.21", "clap_lex 0.3.0", + "is-terminal", "once_cell", "strsim", "termcolor", @@ -1521,9 +1521,9 @@ dependencies = [ [[package]] name = "dtrace-parser" -version = "0.1.12" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bbb93fb1a0c517bf20f37caaf5d1f7d20f144c6c35a7d751ecad077b6c042e8" +checksum = "bed110893a7f9f4ceb072e166354a09f6cb4cc416eec5b5e5e8ee367442d434b" dependencies = [ "pest", "pest_derive", @@ -1675,6 +1675,27 @@ dependencies = [ "termcolor", ] +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "expectorate" version = "1.0.5" @@ -1948,7 +1969,7 @@ name = "gateway-cli" version = "0.1.0" dependencies = [ "anyhow", - "clap 4.0.24", + "clap 4.0.29", "futures", "gateway-client", "libc", @@ -2205,6 +2226,15 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + [[package]] name = "hex" version = "0.4.3" @@ -2530,7 +2560,7 @@ name = "internal-dns" version = "0.1.0" dependencies = [ "anyhow", - "clap 4.0.24", + "clap 4.0.29", "dropshot", "expectorate", "internal-dns-client", @@ -2579,6 +2609,16 @@ dependencies = [ "uuid", ] +[[package]] +name = "io-lifetimes" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c" +dependencies = [ + "libc", + "windows-sys 0.42.0", +] + [[package]] name = "ipconfig" version = "0.3.0" @@ -2606,6 +2646,18 @@ dependencies = [ "serde", ] +[[package]] +name = "is-terminal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927609f78c2913a6f6ac3c27a4fe87f43e2a35367c0c4b0f8265e8f49a104330" +dependencies = [ + "hermit-abi 0.2.6", + "io-lifetimes", + "rustix", + "windows-sys 0.42.0", +] + [[package]] name = "itertools" version = "0.10.5" @@ -2755,6 +2807,12 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +[[package]] +name = "linux-raw-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f9f08d8963a6c613f4b1a78f4f4a4dbfadf8e6545b2d72861731e4858b8b47f" + [[package]] name = "lock_api" version = "0.4.7" @@ -3164,7 +3222,7 @@ version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", ] @@ -3281,7 +3339,7 @@ name = "omicron-deploy" version = "0.1.0" dependencies = [ "anyhow", - "clap 4.0.24", + "clap 4.0.29", "crossbeam", "omicron-package", "omicron-sled-agent", @@ -3297,7 +3355,7 @@ dependencies = [ name = "omicron-gateway" version = "0.1.0" dependencies = [ - "clap 4.0.24", + "clap 4.0.29", "dropshot", "expectorate", "futures", @@ -3335,7 +3393,7 @@ dependencies = [ "base64", "bb8", "chrono", - "clap 4.0.24", + "clap 4.0.29", "cookie", "criterion", "crucible-agent-client", @@ -3419,7 +3477,7 @@ name = "omicron-package" version = "0.1.0" dependencies = [ "anyhow", - "clap 4.0.24", + "clap 4.0.29", "futures", "hex", "indicatif", @@ -3457,7 +3515,7 @@ dependencies = [ "bytes", "cfg-if 1.0.0", "chrono", - "clap 4.0.24", + "clap 4.0.29", "crucible-agent-client", "crucible-client-types", "ddm-admin-client", @@ -3520,7 +3578,7 @@ name = "omicron-test-utils" version = "0.1.0" dependencies = [ "anyhow", - "clap 4.0.24", + "clap 4.0.29", "dropshot", "expectorate", "futures", @@ -3780,7 +3838,7 @@ dependencies = [ name = "oximeter-collector" version = "0.1.0" dependencies = [ - "clap 4.0.24", + "clap 4.0.29", "dropshot", "expectorate", "internal-dns-client", @@ -3811,7 +3869,7 @@ dependencies = [ "async-trait", "bytes", "chrono", - "clap 4.0.24", + "clap 4.0.29", "dropshot", "itertools", "omicron-test-utils", @@ -4401,7 +4459,7 @@ version = "0.2.1-dev" source = "git+https://github.com/oxidecomputer/progenitor?branch=main#4b5cef4b105f351c74d14acae5e0b4d69313afb2" dependencies = [ "anyhow", - "clap 4.0.24", + "clap 4.0.29", "openapiv3", "progenitor-client 0.2.1-dev (git+https://github.com/oxidecomputer/progenitor?branch=main)", "progenitor-impl 0.2.1-dev (git+https://github.com/oxidecomputer/progenitor?branch=main)", @@ -4416,7 +4474,7 @@ version = "0.2.1-dev" source = "git+https://github.com/oxidecomputer/progenitor#4b5cef4b105f351c74d14acae5e0b4d69313afb2" dependencies = [ "anyhow", - "clap 4.0.24", + "clap 4.0.29", "openapiv3", "progenitor-client 0.2.1-dev (git+https://github.com/oxidecomputer/progenitor)", "progenitor-impl 0.2.1-dev (git+https://github.com/oxidecomputer/progenitor)", @@ -4744,11 +4802,10 @@ dependencies = [ [[package]] name = "rayon" -version = "1.5.3" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" +checksum = "1e060280438193c554f654141c9ea9417886713b7acd75974c85b18a69a88e0b" dependencies = [ - "autocfg", "crossbeam-deque", "either", "rayon-core", @@ -4756,9 +4813,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.9.3" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" +checksum = "cac410af5d00ab6884528b4ab69d1e8e146e8d471201800fa1b4524126de6ad3" dependencies = [ "crossbeam-channel", "crossbeam-deque", @@ -4858,9 +4915,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.12" +version = "0.11.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "431949c384f4e2ae07605ccaa56d1d9d2ecdb5cadd4f9577ccfab29f2e5149fc" +checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c" dependencies = [ "base64", "bytes", @@ -5089,6 +5146,20 @@ dependencies = [ "toolchain_find", ] +[[package]] +name = "rustix" +version = "0.36.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb93e85278e08bb5788653183213d3a60fc242b10cb9be96586f5a73dcb67c23" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.42.0", +] + [[package]] name = "rustls" version = "0.20.7" @@ -5398,9 +5469,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.87" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" +checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" dependencies = [ "itoa", "ryu", @@ -5452,9 +5523,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368f2d60d049ea019a84dcd6687b0d1e0030fe663ae105039bdf967ed5e6a9a7" +checksum = "25bf4a5a814902cd1014dbccfa4d4560fb8432c779471e96e035602519f82eef" dependencies = [ "base64", "chrono", @@ -5468,9 +5539,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ccadfacf6cf10faad22bbadf55986bdd0856edfb5d9210aa1dcf1f516e84e93" +checksum = "e3452b4c0f6c1e357f73fdb87cd1efabaa12acf328c7a528e252893baeb3f4aa" dependencies = [ "darling", "proc-macro2", @@ -5506,9 +5577,9 @@ dependencies = [ [[package]] name = "sha-1" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" dependencies = [ "cfg-if 1.0.0", "cpufeatures", @@ -5841,7 +5912,7 @@ version = "0.1.0" dependencies = [ "anyhow", "async-trait", - "clap 4.0.24", + "clap 4.0.29", "dropshot", "futures", "gateway-messages", @@ -6097,9 +6168,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.103" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" +checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" dependencies = [ "proc-macro2", "quote", @@ -6333,9 +6404,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.21.2" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" +checksum = "d76ce4a75fb488c605c54bf610f221cea8b0dafb53333c1a67e8ee199dcd2ae3" dependencies = [ "autocfg", "bytes", @@ -6627,9 +6698,9 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" [[package]] name = "trybuild" -version = "1.0.71" +version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea496675d71016e9bc76aa42d87f16aefd95447cc5818e671e12b2d7e269075d" +checksum = "db29f438342820400f2d9acfec0d363e987a38b2950bdb50a7069ed17b2148ee" dependencies = [ "glob", "once_cell", @@ -6813,11 +6884,10 @@ dependencies = [ [[package]] name = "usdt" -version = "0.3.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5409e16f35fdba646ff4290e6f96f0bb958b43e0bd09d790c3714456a76c11ac" +checksum = "39bf7190754941ac252f6fe9c1ff008c09c5fd0292b1732f319900c7fce365d0" dependencies = [ - "dof", "dtrace-parser", "serde", "usdt-attr-macro", @@ -6827,9 +6897,9 @@ dependencies = [ [[package]] name = "usdt-attr-macro" -version = "0.3.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d203cad87b8424e1fbd0fef29b6df2113e68be5dd75f4043427eb0ce6601041f" +checksum = "f6c80eed594ef75117f363ee2c109b45e13507bdc4729f9d7aea434604ad1777" dependencies = [ "dtrace-parser", "proc-macro2", @@ -6841,9 +6911,9 @@ dependencies = [ [[package]] name = "usdt-impl" -version = "0.3.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c146394183fcea90d93e2d4602e1a7eb6cab8e807b58f99d4f343720d4e9d69" +checksum = "f7c8b459b1b7997d655cf1bb142551a5b216a6b0e56e51ebd76ecbc0ff5fd1de" dependencies = [ "byteorder", "dof", @@ -6861,9 +6931,9 @@ dependencies = [ [[package]] name = "usdt-macro" -version = "0.3.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dddab3586eaf539e2daddc425261daef6a6d2f85131bac7345caa8e6a423b27" +checksum = "a13f229fd5cde35ccca2c0151c67a880f3cc13ba2992a05db55b47cc77a5ef3f" dependencies = [ "dtrace-parser", "proc-macro2", @@ -6881,9 +6951,9 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] name = "uuid" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feb41e78f93363bb2df8b0e86a2ca30eed7806ea16ea0c790d757cf93f79be83" +checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c" dependencies = [ "getrandom 0.2.8", "serde", @@ -7099,7 +7169,7 @@ name = "wicket" version = "0.1.0" dependencies = [ "anyhow", - "clap 4.0.24", + "clap 4.0.29", "crossterm", "futures", "hex", @@ -7125,7 +7195,7 @@ dependencies = [ name = "wicketd" version = "0.1.0" dependencies = [ - "clap 4.0.24", + "clap 4.0.29", "dropshot", "expectorate", "futures", From 2a87558bfdc64304a80d30e1dabf6027da68d337 Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Fri, 2 Dec 2022 13:31:05 -0500 Subject: [PATCH 25/31] Always do a lookup, even when only handling UUID --- nexus/src/app/instance.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/nexus/src/app/instance.rs b/nexus/src/app/instance.rs index 1289f9183c4..8477cabb924 100644 --- a/nexus/src/app/instance.rs +++ b/nexus/src/app/instance.rs @@ -62,7 +62,12 @@ impl super::Nexus { match instance_selector { params::InstanceSelector { instance: NameOrId::Id(id), .. } => { // TODO: 400 if project or organization are present - Ok(id) + let (.., authz_instance) = + LookupPath::new(opctx, &self.db_datastore) + .instance_id(id) + .lookup_for(authz::Action::Read) + .await?; + Ok(authz_instance.id()) } params::InstanceSelector { instance: NameOrId::Name(instance_name), From ce7da01f346ebb33c1988b8c9a9b740c0781ac42 Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Tue, 6 Dec 2022 11:08:22 -0500 Subject: [PATCH 26/31] Fix clippy failures --- nexus/src/external_api/http_entrypoints.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index 9a73863bd1c..24e1f784af8 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -2663,14 +2663,14 @@ async fn instance_serial_console_v1( params::InstanceSelector::new(path.instance, &query.selector), ) .await?; - let console = nexus + let data = nexus .instance_serial_console_data( &opctx, &instance_id, &query.console_params, ) .await?; - Ok(HttpResponseOk(console.into())) + Ok(HttpResponseOk(data)) }; apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await } From 96165f55a1deb1b3f19b48e333c2129cdd42a302 Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Tue, 6 Dec 2022 15:45:02 -0500 Subject: [PATCH 27/31] Update unauthz coverage --- nexus/src/external_api/http_entrypoints.rs | 2 +- nexus/tests/integration_tests/endpoints.rs | 30 +++++++++++-------- nexus/tests/integration_tests/unauthorized.rs | 2 +- .../unauthorized_coverage.rs | 17 +++++++++-- .../output/uncovered-authz-endpoints.txt | 12 ++++++++ openapi/nexus.json | 2 +- 6 files changed, 47 insertions(+), 18 deletions(-) diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index 24e1f784af8..d50195bcd6f 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -2642,7 +2642,7 @@ pub struct InstanceSerialConsoleParams { } #[endpoint { - method = POST, + method = GET, path = "/v1/instances/{instance}/serial-console", tags = ["instances"], }] diff --git a/nexus/tests/integration_tests/endpoints.rs b/nexus/tests/integration_tests/endpoints.rs index 6dfb59200d7..b11218c39c0 100644 --- a/nexus/tests/integration_tests/endpoints.rs +++ b/nexus/tests/integration_tests/endpoints.rs @@ -104,6 +104,8 @@ lazy_static! { pub static ref DEMO_PROJECT_NAME: Name = "demo-project".parse().unwrap(); pub static ref DEMO_PROJECT_URL: String = format!("{}/{}", *DEMO_ORG_PROJECTS_URL, *DEMO_PROJECT_NAME); + pub static ref DEMO_PROJECT_SELECTOR: String = + format!("?organization={}&project={}", *DEMO_ORG_NAME, *DEMO_PROJECT_NAME); pub static ref DEMO_PROJECT_POLICY_URL: String = format!("{}/policy", *DEMO_PROJECT_URL); pub static ref DEMO_PROJECT_URL_DISKS: String = @@ -111,7 +113,7 @@ lazy_static! { pub static ref DEMO_PROJECT_URL_IMAGES: String = format!("{}/images", *DEMO_PROJECT_URL); pub static ref DEMO_PROJECT_URL_INSTANCES: String = - format!("{}/instances", *DEMO_PROJECT_URL); + "/v1/instances".to_string(); pub static ref DEMO_PROJECT_URL_SNAPSHOTS: String = format!("{}/snapshots", *DEMO_PROJECT_URL); pub static ref DEMO_PROJECT_URL_VPCS: String = @@ -224,29 +226,31 @@ lazy_static! { // Instance used for testing pub static ref DEMO_INSTANCE_NAME: Name = "demo-instance".parse().unwrap(); pub static ref DEMO_INSTANCE_URL: String = - format!("{}/{}", *DEMO_PROJECT_URL_INSTANCES, *DEMO_INSTANCE_NAME); + format!("/v1/instances/{}?organization={}&project={}", *DEMO_INSTANCE_NAME, *DEMO_ORG_NAME, *DEMO_PROJECT_NAME); pub static ref DEMO_INSTANCE_START_URL: String = - format!("{}/start", *DEMO_INSTANCE_URL); + format!("/v1/instances/{}/start?organization={}&project={}", *DEMO_INSTANCE_NAME, *DEMO_ORG_NAME, *DEMO_PROJECT_NAME); pub static ref DEMO_INSTANCE_STOP_URL: String = - format!("{}/stop", *DEMO_INSTANCE_URL); + format!("/v1/instances/{}/stop?organization={}&project={}", *DEMO_INSTANCE_NAME, *DEMO_ORG_NAME, *DEMO_PROJECT_NAME); pub static ref DEMO_INSTANCE_REBOOT_URL: String = - format!("{}/reboot", *DEMO_INSTANCE_URL); + format!("/v1/instances/{}/reboot?organization={}&project={}", *DEMO_INSTANCE_NAME, *DEMO_ORG_NAME, *DEMO_PROJECT_NAME); pub static ref DEMO_INSTANCE_MIGRATE_URL: String = - format!("{}/migrate", *DEMO_INSTANCE_URL); + format!("/v1/instances/{}/migrate?organization={}&project={}", *DEMO_INSTANCE_NAME, *DEMO_ORG_NAME, *DEMO_PROJECT_NAME); + pub static ref DEMO_INSTANCE_SERIAL_URL: String = + format!("/v1/instances/{}/serial-console?organization={}&project={}", *DEMO_INSTANCE_NAME, *DEMO_ORG_NAME, *DEMO_PROJECT_NAME); + pub static ref DEMO_INSTANCE_SERIAL_STREAM_URL: String = + format!("/v1/instances/{}/serial-console/stream?organization={}&project={}", *DEMO_INSTANCE_NAME, *DEMO_ORG_NAME, *DEMO_PROJECT_NAME); + + // To be migrated... pub static ref DEMO_INSTANCE_DISKS_URL: String = - format!("{}/disks", *DEMO_INSTANCE_URL); + format!("/organizations/{}/projects/{}/instances/{}/disks", *DEMO_ORG_NAME, *DEMO_PROJECT_NAME, *DEMO_INSTANCE_NAME); pub static ref DEMO_INSTANCE_DISKS_ATTACH_URL: String = format!("{}/attach", *DEMO_INSTANCE_DISKS_URL); pub static ref DEMO_INSTANCE_DISKS_DETACH_URL: String = format!("{}/detach", *DEMO_INSTANCE_DISKS_URL); pub static ref DEMO_INSTANCE_NICS_URL: String = - format!("{}/network-interfaces", *DEMO_INSTANCE_URL); + format!("/organizations/{}/projects/{}/instances/{}/network-interfaces", *DEMO_ORG_NAME, *DEMO_PROJECT_NAME, *DEMO_INSTANCE_NAME); pub static ref DEMO_INSTANCE_EXTERNAL_IPS_URL: String = - format!("{}/external-ips", *DEMO_INSTANCE_URL); - pub static ref DEMO_INSTANCE_SERIAL_URL: String = - format!("{}/serial-console", *DEMO_INSTANCE_URL); - pub static ref DEMO_INSTANCE_SERIAL_STREAM_URL: String = - format!("{}/serial-console/stream", *DEMO_INSTANCE_URL); + format!("/organizations/{}/projects/{}/instances/{}/external-ips", *DEMO_ORG_NAME, *DEMO_PROJECT_NAME, *DEMO_INSTANCE_NAME); pub static ref DEMO_INSTANCE_CREATE: params::InstanceCreate = params::InstanceCreate { identity: IdentityMetadataCreateParams { diff --git a/nexus/tests/integration_tests/unauthorized.rs b/nexus/tests/integration_tests/unauthorized.rs index 134256a6ce9..7f6a6c300f1 100644 --- a/nexus/tests/integration_tests/unauthorized.rs +++ b/nexus/tests/integration_tests/unauthorized.rs @@ -258,7 +258,7 @@ lazy_static! { SetupReq::Post { url: &*DEMO_PROJECT_URL_INSTANCES, body: serde_json::to_value(&*DEMO_INSTANCE_CREATE).unwrap(), - id_routes: vec!["/by-id/instances/{id}"], + id_routes: vec!["/v1/instances/{id}"], }, // Lookup the previously created NIC SetupReq::Get { diff --git a/nexus/tests/integration_tests/unauthorized_coverage.rs b/nexus/tests/integration_tests/unauthorized_coverage.rs index bbaebe6f79d..64ce7eabc82 100644 --- a/nexus/tests/integration_tests/unauthorized_coverage.rs +++ b/nexus/tests/integration_tests/unauthorized_coverage.rs @@ -86,7 +86,7 @@ fn test_unauthorized_coverage() { let method_string = m.http_method().to_string().to_uppercase(); let found = spec_operations.iter().find(|(op, regex)| { op.method.to_uppercase() == method_string - && regex.is_match(v.url) + && regex.is_match(v.url.split('?').next().unwrap_or(v.url)) }); if let Some((op, _)) = found { println!( @@ -138,7 +138,20 @@ fn test_unauthorized_coverage() { let expected_uncovered_endpoints = std::fs::read_to_string("tests/output/uncovered-authz-endpoints.txt") .expect("failed to load file of allowed uncovered endpoints"); - assert_eq!(expected_uncovered_endpoints, uncovered_endpoints); + let mut unexpected_uncovered_endpoints = "These endpoints were expected to be covered by the unauthorized_coverage test but were not:\n".to_string(); + let mut has_uncovered_endpoints = false; + for endpoint in uncovered_endpoints.lines() { + if !expected_uncovered_endpoints.contains(endpoint) { + unexpected_uncovered_endpoints + .push_str(&format!("\t{}\n", endpoint)); + has_uncovered_endpoints = true; + } + } + assert_eq!( + has_uncovered_endpoints, false, + "{}\nMake sure you've added a test for this endpoint in unauthorized.rs.", + unexpected_uncovered_endpoints + ) } #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] diff --git a/nexus/tests/output/uncovered-authz-endpoints.txt b/nexus/tests/output/uncovered-authz-endpoints.txt index 3f161678dee..810d372e96d 100644 --- a/nexus/tests/output/uncovered-authz-endpoints.txt +++ b/nexus/tests/output/uncovered-authz-endpoints.txt @@ -7,3 +7,15 @@ login_spoof (post "/login") login_local (post "/login/{silo_name}/local") login_saml (post "/login/{silo_name}/saml/{provider_name}") logout (post "/logout") + +Deprecated API endpoints to be removed at the end of the V1 migration +instance_delete (delete "/organizations/{organization_name}/projects/{project_name}/instances/{instance_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") +instance_create (post "/organizations/{organization_name}/projects/{project_name}/instances") +instance_migrate (post "/organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/migrate") +instance_reboot (post "/organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/reboot") +instance_start (post "/organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/start") +instance_stop (post "/organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/stop") diff --git a/openapi/nexus.json b/openapi/nexus.json index 5f23b984097..e6a4b225d1d 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -8001,7 +8001,7 @@ } }, "/v1/instances/{instance}/serial-console": { - "post": { + "get": { "tags": [ "instances" ], From e616c0352d80263ac339629e52afae605ee67f65 Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Tue, 6 Dec 2022 16:34:31 -0500 Subject: [PATCH 28/31] Fix up some failed testcases --- nexus/tests/integration_tests/endpoints.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nexus/tests/integration_tests/endpoints.rs b/nexus/tests/integration_tests/endpoints.rs index b11218c39c0..340515d4f21 100644 --- a/nexus/tests/integration_tests/endpoints.rs +++ b/nexus/tests/integration_tests/endpoints.rs @@ -112,8 +112,7 @@ lazy_static! { format!("{}/disks", *DEMO_PROJECT_URL); pub static ref DEMO_PROJECT_URL_IMAGES: String = format!("{}/images", *DEMO_PROJECT_URL); - pub static ref DEMO_PROJECT_URL_INSTANCES: String = - "/v1/instances".to_string(); + pub static ref DEMO_PROJECT_URL_INSTANCES: String = format!("/v1/instances?organization={}&project={}", *DEMO_ORG_NAME, *DEMO_PROJECT_NAME); pub static ref DEMO_PROJECT_URL_SNAPSHOTS: String = format!("{}/snapshots", *DEMO_PROJECT_URL); pub static ref DEMO_PROJECT_URL_VPCS: String = @@ -1285,11 +1284,12 @@ lazy_static! { }, VerifyEndpoint { - url: "/by-id/instances/{id}", + url: "/v1/instances/{id}", visibility: Visibility::Protected, unprivileged_access: UnprivilegedAccess::None, allowed_methods: vec![ AllowedMethod::Get, + AllowedMethod::Delete, ], }, From 3c13edcfb53820d61c824d094996503e2ffdb46e Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Tue, 6 Dec 2022 21:20:09 -0500 Subject: [PATCH 29/31] Add view by id to skip list --- nexus/tests/integration_tests/endpoints.rs | 10 ---------- nexus/tests/output/uncovered-authz-endpoints.txt | 1 + 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/nexus/tests/integration_tests/endpoints.rs b/nexus/tests/integration_tests/endpoints.rs index 340515d4f21..6051eeaf7a0 100644 --- a/nexus/tests/integration_tests/endpoints.rs +++ b/nexus/tests/integration_tests/endpoints.rs @@ -1283,16 +1283,6 @@ lazy_static! { ], }, - VerifyEndpoint { - url: "/v1/instances/{id}", - visibility: Visibility::Protected, - unprivileged_access: UnprivilegedAccess::None, - allowed_methods: vec![ - AllowedMethod::Get, - AllowedMethod::Delete, - ], - }, - VerifyEndpoint { url: &*DEMO_INSTANCE_URL, visibility: Visibility::Protected, diff --git a/nexus/tests/output/uncovered-authz-endpoints.txt b/nexus/tests/output/uncovered-authz-endpoints.txt index 810d372e96d..70576c0b21c 100644 --- a/nexus/tests/output/uncovered-authz-endpoints.txt +++ b/nexus/tests/output/uncovered-authz-endpoints.txt @@ -19,3 +19,4 @@ instance_migrate (post "/organizations/{organization_n instance_reboot (post "/organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/reboot") instance_start (post "/organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/start") instance_stop (post "/organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/stop") +instance_view_by_id (get "/by-id/instances/{id}") \ No newline at end of file From 12bc36552c2cdf90b3751a0f6c28a55c62001df2 Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Wed, 7 Dec 2022 13:36:17 -0500 Subject: [PATCH 30/31] Update lookups to return authz refs instead of uuids --- nexus/src/app/instance.rs | 64 +++++------ nexus/src/app/project.rs | 15 ++- nexus/src/external_api/http_entrypoints.rs | 125 +++++++++++---------- 3 files changed, 103 insertions(+), 101 deletions(-) diff --git a/nexus/src/app/instance.rs b/nexus/src/app/instance.rs index 906855e5e2b..6a767408ddf 100644 --- a/nexus/src/app/instance.rs +++ b/nexus/src/app/instance.rs @@ -54,11 +54,11 @@ use uuid::Uuid; const MAX_KEYS_PER_INSTANCE: u32 = 8; impl super::Nexus { - pub async fn instance_lookup_id( + pub async fn instance_lookup( &self, opctx: &OpContext, instance_selector: params::InstanceSelector, - ) -> LookupResult { + ) -> LookupResult { match instance_selector { params::InstanceSelector { instance: NameOrId::Id(id), .. } => { // TODO: 400 if project or organization are present @@ -67,7 +67,7 @@ impl super::Nexus { .instance_id(id) .lookup_for(authz::Action::Read) .await?; - Ok(authz_instance.id()) + Ok(authz_instance) } params::InstanceSelector { instance: NameOrId::Name(instance_name), @@ -81,7 +81,7 @@ impl super::Nexus { .instance_name(&Name(instance_name.clone())) .lookup_for(authz::Action::Read) .await?; - Ok(authz_instance.id()) + Ok(authz_instance) } params::InstanceSelector { instance: NameOrId::Name(instance_name), @@ -95,7 +95,7 @@ impl super::Nexus { .instance_name(&Name(instance_name.clone())) .lookup_for(authz::Action::Read) .await?; - Ok(authz_instance.id()) + Ok(authz_instance) } params::InstanceSelector { instance: NameOrId::Name(instance_name), @@ -109,7 +109,7 @@ impl super::Nexus { .instance_name(&Name(instance_name.clone())) .lookup_for(authz::Action::Read) .await?; - Ok(authz_instance.id()) + Ok(authz_instance) } // TODO: Add a better error message _ => Err(Error::InvalidRequest { @@ -128,13 +128,10 @@ impl super::Nexus { pub async fn project_create_instance( self: &Arc, opctx: &OpContext, - project_id: &Uuid, + authz_project: &authz::Project, params: ¶ms::InstanceCreate, ) -> CreateResult { - let (.., authz_project) = LookupPath::new(opctx, &self.db_datastore) - .project_id(*project_id) - .lookup_for(authz::Action::CreateChild) - .await?; + opctx.authorize(authz::Action::CreateChild, authz_project).await?; // Validate parameters if params.disks.len() > MAX_DISKS_PER_INSTANCE as usize { @@ -263,13 +260,10 @@ impl super::Nexus { pub async fn project_list_instances( &self, opctx: &OpContext, - project_id: Uuid, + authz_project: &authz::Project, pagparams: &DataPageParams<'_, Name>, ) -> ListResultVec { - let (.., authz_project) = LookupPath::new(opctx, &self.db_datastore) - .project_id(project_id) - .lookup_for(authz::Action::ListChildren) - .await?; + opctx.authorize(authz::Action::ListChildren, authz_project).await?; self.db_datastore .project_list_instances(opctx, &authz_project, pagparams) .await @@ -278,10 +272,10 @@ impl super::Nexus { pub async fn instance_fetch( &self, opctx: &OpContext, - instance_id: &Uuid, + authz_instance: &authz::Instance, ) -> LookupResult { let (.., db_instance) = LookupPath::new(opctx, &self.db_datastore) - .instance_id(*instance_id) + .instance_id(authz_instance.id()) .fetch() .await?; Ok(db_instance) @@ -293,15 +287,12 @@ impl super::Nexus { pub async fn project_destroy_instance( &self, opctx: &OpContext, - instance_id: &Uuid, + authz_instance: &authz::Instance, ) -> DeleteResult { // TODO-robustness We need to figure out what to do with Destroyed // instances? Presumably we need to clean them up at some point, but // not right away so that callers can see that they've been destroyed. - let (.., authz_instance) = LookupPath::new(opctx, &self.db_datastore) - .instance_id(*instance_id) - .lookup_for(authz::Action::Delete) - .await?; + opctx.authorize(authz::Action::Delete, authz_instance).await?; self.db_datastore .project_delete_instance(opctx, &authz_instance) @@ -319,13 +310,10 @@ impl super::Nexus { pub async fn project_instance_migrate( self: &Arc, opctx: &OpContext, - instance_id: Uuid, + authz_instance: &authz::Instance, params: params::InstanceMigrate, ) -> UpdateResult { - let (.., authz_instance) = LookupPath::new(opctx, &self.db_datastore) - .instance_id(instance_id) - .lookup_for(authz::Action::Modify) - .await?; + opctx.authorize(authz::Action::Modify, authz_instance).await?; // Kick off the migration saga let saga_params = sagas::instance_migrate::Params { @@ -379,7 +367,7 @@ impl super::Nexus { pub async fn instance_reboot( &self, opctx: &OpContext, - instance_id: Uuid, + authz_instance: &authz::Instance, ) -> UpdateResult { // To implement reboot, we issue a call to the sled agent to set a // runtime state of "reboot". We cannot simply stop the Instance and @@ -394,7 +382,7 @@ impl super::Nexus { // running. let (.., authz_instance, db_instance) = LookupPath::new(opctx, &self.db_datastore) - .instance_id(instance_id) + .instance_id(authz_instance.id()) .fetch() .await?; let requested = InstanceRuntimeStateRequested { @@ -415,11 +403,11 @@ impl super::Nexus { pub async fn instance_start( &self, opctx: &OpContext, - instance_id: Uuid, + authz_instance: &authz::Instance, ) -> UpdateResult { let (.., authz_instance, db_instance) = LookupPath::new(opctx, &self.db_datastore) - .instance_id(instance_id) + .instance_id(authz_instance.id()) .fetch() .await?; let requested = InstanceRuntimeStateRequested { @@ -440,11 +428,11 @@ impl super::Nexus { pub async fn instance_stop( &self, opctx: &OpContext, - instance_id: Uuid, + authz_instance: &authz::Instance, ) -> UpdateResult { let (.., authz_instance, db_instance) = LookupPath::new(opctx, &self.db_datastore) - .instance_id(instance_id) + .instance_id(authz_instance.id()) .fetch() .await?; let requested = InstanceRuntimeStateRequested { @@ -1139,10 +1127,10 @@ impl super::Nexus { pub(crate) async fn instance_serial_console_data( &self, opctx: &OpContext, - instance_id: &Uuid, + authz_instance: &authz::Instance, params: ¶ms::InstanceSerialConsoleRequest, ) -> Result { - let db_instance = self.instance_fetch(opctx, instance_id).await?; + let db_instance = self.instance_fetch(opctx, authz_instance).await?; let sa = self.instance_sled(&db_instance).await?; let data = sa @@ -1166,9 +1154,9 @@ impl super::Nexus { &self, opctx: &OpContext, conn: dropshot::WebsocketConnection, - instance_id: &Uuid, + authz_instance: &authz::Instance, ) -> Result<(), Error> { - let instance = self.instance_fetch(opctx, instance_id).await?; + let instance = self.instance_fetch(opctx, authz_instance).await?; let ip_addr = instance .runtime_state .propolis_ip diff --git a/nexus/src/app/project.rs b/nexus/src/app/project.rs index e5178a6537e..917fc93dfb6 100644 --- a/nexus/src/app/project.rs +++ b/nexus/src/app/project.rs @@ -25,15 +25,20 @@ use omicron_common::api::external::UpdateResult; use uuid::Uuid; impl super::Nexus { - pub async fn project_lookup_id( + pub async fn project_lookup( &self, opctx: &OpContext, project_selector: params::ProjectSelector, - ) -> LookupResult { + ) -> LookupResult { match project_selector { params::ProjectSelector { project: NameOrId::Id(id), .. } => { // TODO: 400 if organization is present - Ok(id) + let (.., authz_project) = + LookupPath::new(opctx, &self.db_datastore) + .project_id(id) + .lookup_for(authz::Action::Read) + .await?; + Ok(authz_project) } params::ProjectSelector { project: NameOrId::Name(project_name), @@ -45,7 +50,7 @@ impl super::Nexus { .project_name(&Name(project_name)) .lookup_for(authz::Action::Read) .await?; - Ok(authz_project.id()) + Ok(authz_project) } params::ProjectSelector { project: NameOrId::Name(project_name), @@ -57,7 +62,7 @@ impl super::Nexus { .project_name(&Name(project_name)) .lookup_for(authz::Action::Read) .await?; - Ok(authz_project.id()) + Ok(authz_project) } _ => Err(Error::InvalidRequest { message: " diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index d50195bcd6f..8da76a36770 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -2032,12 +2032,12 @@ async fn instance_list_v1( let query = query_params.into_inner(); let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; - let project_id = - nexus.project_lookup_id(&opctx, query.selector).await?; + let authz_project = + nexus.project_lookup(&opctx, query.selector).await?; let instances = nexus .project_list_instances( &opctx, - project_id, + &authz_project, &data_page_params_for(&rqctx, &query.pagination)? .map_name(|n| Name::ref_cast(n)), ) @@ -2073,8 +2073,8 @@ async fn instance_list( let project_name = &path.project_name; let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; - let project_id = nexus - .project_lookup_id( + let authz_project = nexus + .project_lookup( &opctx, params::ProjectSelector { project: NameOrId::Name(project_name.clone().into()), @@ -2087,7 +2087,7 @@ async fn instance_list( let instances = nexus .project_list_instances( &opctx, - project_id, + &authz_project, &data_page_params_for(&rqctx, &query)? .map_name(|n| Name::ref_cast(n)), ) @@ -2126,8 +2126,7 @@ async fn instance_create_v1( let new_instance_params = &new_instance.into_inner(); let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; - let project_id = - nexus.project_lookup_id(&opctx, query.selector).await?; + let project_id = nexus.project_lookup(&opctx, query.selector).await?; let instance = nexus .project_create_instance(&opctx, &project_id, &new_instance_params) .await?; @@ -2163,7 +2162,7 @@ async fn instance_create( let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; let project_id = nexus - .project_lookup_id( + .project_lookup( &opctx, params::ProjectSelector { project: NameOrId::Name(project_name.clone().into()), @@ -2215,13 +2214,13 @@ async fn instance_view_v1( let query = query_params.into_inner(); let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; - let instance_id = nexus - .instance_lookup_id( + let authz_instance = nexus + .instance_lookup( &opctx, params::InstanceSelector::new(path.instance, &query.selector), ) .await?; - let instance = nexus.instance_fetch(&opctx, &instance_id).await?; + let instance = nexus.instance_fetch(&opctx, &authz_instance).await?; Ok(HttpResponseOk(instance.into())) }; apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await @@ -2253,8 +2252,8 @@ async fn instance_view( let instance_name = &path.instance_name; let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; - let instance_id = nexus - .instance_lookup_id( + let authz_instance = nexus + .instance_lookup( &opctx, params::InstanceSelector { instance: NameOrId::Name(instance_name.clone().into()), @@ -2265,7 +2264,7 @@ async fn instance_view( }, ) .await?; - let instance = nexus.instance_fetch(&opctx, &instance_id).await?; + let instance = nexus.instance_fetch(&opctx, &authz_instance).await?; Ok(HttpResponseOk(instance.into())) }; apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await @@ -2287,7 +2286,17 @@ async fn instance_view_by_id( let id = &path.id; let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; - let instance = nexus.instance_fetch(&opctx, id).await?; + let authz_instance = nexus + .instance_lookup( + &opctx, + params::InstanceSelector { + instance: NameOrId::Id(*id), + project: None, + organization: None, + }, + ) + .await?; + let instance = nexus.instance_fetch(&opctx, &authz_instance).await?; Ok(HttpResponseOk(instance.into())) }; apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await @@ -2309,13 +2318,13 @@ async fn instance_delete_v1( let query = query_params.into_inner(); let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; - let instance_id = nexus - .instance_lookup_id( + let authz_instance = nexus + .instance_lookup( &opctx, params::InstanceSelector::new(path.instance, &query.selector), ) .await?; - nexus.project_destroy_instance(&opctx, &instance_id).await?; + nexus.project_destroy_instance(&opctx, &authz_instance).await?; Ok(HttpResponseDeleted()) }; apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await @@ -2339,8 +2348,8 @@ async fn instance_delete( let instance_name = &path.instance_name; let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; - let instance_id = nexus - .instance_lookup_id( + let authz_instance = nexus + .instance_lookup( &opctx, params::InstanceSelector { instance: NameOrId::Name(instance_name.clone().into()), @@ -2351,7 +2360,7 @@ async fn instance_delete( }, ) .await?; - nexus.project_destroy_instance(&opctx, &instance_id).await?; + nexus.project_destroy_instance(&opctx, &authz_instance).await?; Ok(HttpResponseDeleted()) }; apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await @@ -2376,8 +2385,8 @@ async fn instance_migrate_v1( let migrate_instance_params = migrate_params.into_inner(); let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; - let instance_id = nexus - .instance_lookup_id( + let authz_instance = nexus + .instance_lookup( &opctx, params::InstanceSelector::new(path.instance, &query.selector), ) @@ -2385,7 +2394,7 @@ async fn instance_migrate_v1( let instance = nexus .project_instance_migrate( &opctx, - instance_id, + &authz_instance, migrate_instance_params, ) .await?; @@ -2415,8 +2424,8 @@ async fn instance_migrate( let migrate_instance_params = migrate_params.into_inner(); let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; - let instance_id = nexus - .instance_lookup_id( + let authz_instance = nexus + .instance_lookup( &opctx, params::InstanceSelector { instance: NameOrId::Name(instance_name.clone().into()), @@ -2430,7 +2439,7 @@ async fn instance_migrate( let instance = nexus .project_instance_migrate( &opctx, - instance_id, + &authz_instance, migrate_instance_params, ) .await?; @@ -2455,13 +2464,13 @@ async fn instance_reboot_v1( let query = query_params.into_inner(); let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; - let instance_id = nexus - .instance_lookup_id( + let authz_instance = nexus + .instance_lookup( &opctx, params::InstanceSelector::new(path.instance, &query.selector), ) .await?; - let instance = nexus.instance_reboot(&opctx, instance_id).await?; + let instance = nexus.instance_reboot(&opctx, &authz_instance).await?; Ok(HttpResponseOk(instance.into())) }; apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await @@ -2485,8 +2494,8 @@ async fn instance_reboot( let instance_name = &path.instance_name; let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; - let instance_id = nexus - .instance_lookup_id( + let authz_instance = nexus + .instance_lookup( &opctx, params::InstanceSelector { instance: NameOrId::Name(instance_name.clone().into()), @@ -2497,7 +2506,7 @@ async fn instance_reboot( }, ) .await?; - let instance = nexus.instance_reboot(&opctx, instance_id).await?; + let instance = nexus.instance_reboot(&opctx, &authz_instance).await?; Ok(HttpResponseAccepted(instance.into())) }; apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await @@ -2520,13 +2529,13 @@ async fn instance_start_v1( let query = query_params.into_inner(); let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; - let instance_id = nexus - .instance_lookup_id( + let authz_instance = nexus + .instance_lookup( &opctx, params::InstanceSelector::new(path.instance, &query.selector), ) .await?; - let instance = nexus.instance_start(&opctx, instance_id).await?; + let instance = nexus.instance_start(&opctx, &authz_instance).await?; Ok(HttpResponseOk(instance.into())) }; apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await @@ -2550,8 +2559,8 @@ async fn instance_start( let instance_name = &path.instance_name; let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; - let instance_id = nexus - .instance_lookup_id( + let authz_instance = nexus + .instance_lookup( &opctx, params::InstanceSelector { instance: NameOrId::Name(instance_name.clone().into()), @@ -2562,7 +2571,7 @@ async fn instance_start( }, ) .await?; - let instance = nexus.instance_start(&opctx, instance_id).await?; + let instance = nexus.instance_start(&opctx, &authz_instance).await?; Ok(HttpResponseAccepted(instance.into())) }; apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await @@ -2584,13 +2593,13 @@ async fn instance_stop_v1( let query = query_params.into_inner(); let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; - let instance_id = nexus - .instance_lookup_id( + let authz_instance = nexus + .instance_lookup( &opctx, params::InstanceSelector::new(path.instance, &query.selector), ) .await?; - let instance = nexus.instance_stop(&opctx, instance_id).await?; + let instance = nexus.instance_stop(&opctx, &authz_instance).await?; Ok(HttpResponseOk(instance.into())) }; apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await @@ -2614,8 +2623,8 @@ async fn instance_stop( let instance_name = &path.instance_name; let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; - let instance_id = nexus - .instance_lookup_id( + let authz_instance = nexus + .instance_lookup( &opctx, params::InstanceSelector { instance: NameOrId::Name(instance_name.clone().into()), @@ -2626,7 +2635,7 @@ async fn instance_stop( }, ) .await?; - let instance = nexus.instance_stop(&opctx, instance_id).await?; + let instance = nexus.instance_stop(&opctx, &authz_instance).await?; Ok(HttpResponseAccepted(instance.into())) }; apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await @@ -2657,8 +2666,8 @@ async fn instance_serial_console_v1( let query = query_params.into_inner(); let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; - let instance_id = nexus - .instance_lookup_id( + let authz_instance = nexus + .instance_lookup( &opctx, params::InstanceSelector::new(path.instance, &query.selector), ) @@ -2666,7 +2675,7 @@ async fn instance_serial_console_v1( let data = nexus .instance_serial_console_data( &opctx, - &instance_id, + &authz_instance, &query.console_params, ) .await?; @@ -2694,8 +2703,8 @@ async fn instance_serial_console( let instance_name = &path.instance_name; let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; - let instance_id = nexus - .instance_lookup_id( + let authz_instance = nexus + .instance_lookup( &opctx, params::InstanceSelector { instance: NameOrId::Name(instance_name.clone().into()), @@ -2709,7 +2718,7 @@ async fn instance_serial_console( let data = nexus .instance_serial_console_data( &opctx, - &instance_id, + &authz_instance, &query_params.into_inner(), ) .await?; @@ -2734,13 +2743,13 @@ async fn instance_serial_console_stream_v1( let path = path_params.into_inner(); let query = query_params.into_inner(); let opctx = OpContext::for_external_api(&rqctx).await?; - let instance_id = nexus - .instance_lookup_id( + let authz_instance = nexus + .instance_lookup( &opctx, params::InstanceSelector::new(path.instance, &query.selector), ) .await?; - nexus.instance_serial_console_stream(&opctx, conn, &instance_id).await?; + nexus.instance_serial_console_stream(&opctx, conn, &authz_instance).await?; Ok(()) } @@ -2762,8 +2771,8 @@ async fn instance_serial_console_stream( let project_name = &path.project_name; let instance_name = &path.instance_name; let opctx = OpContext::for_external_api(&rqctx).await?; - let instance_id = nexus - .instance_lookup_id( + let authz_instance = nexus + .instance_lookup( &opctx, params::InstanceSelector { instance: NameOrId::Name(instance_name.clone().into()), @@ -2774,7 +2783,7 @@ async fn instance_serial_console_stream( }, ) .await?; - nexus.instance_serial_console_stream(&opctx, conn, &instance_id).await?; + nexus.instance_serial_console_stream(&opctx, conn, &authz_instance).await?; Ok(()) } From fc517afe392bbe66b6731b40e8c4d36fe600b6f6 Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Wed, 7 Dec 2022 23:08:27 -0500 Subject: [PATCH 31/31] Address PR feedback --- nexus/src/app/instance.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nexus/src/app/instance.rs b/nexus/src/app/instance.rs index 6a767408ddf..23fbdebb3f3 100644 --- a/nexus/src/app/instance.rs +++ b/nexus/src/app/instance.rs @@ -78,7 +78,7 @@ impl super::Nexus { let (.., authz_instance) = LookupPath::new(opctx, &self.db_datastore) .project_id(project_id) - .instance_name(&Name(instance_name.clone())) + .instance_name(&Name(instance_name)) .lookup_for(authz::Action::Read) .await?; Ok(authz_instance)