From 9898d9edcf76556353fe27b67f0e6505f56c84ea Mon Sep 17 00:00:00 2001 From: David Crespo Date: Wed, 28 Jul 2021 12:19:08 -0500 Subject: [PATCH 01/12] list VPCs endpoint --- omicron-common/src/api/model.rs | 21 +++- omicron-nexus/src/db/datastore.rs | 16 +++ omicron-nexus/src/db/schema.rs | 23 ++++ .../src/http_entrypoints_external.rs | 34 +++++ omicron-nexus/src/nexus.rs | 11 ++ omicron-nexus/tests/output/nexus-openapi.json | 119 ++++++++++++++++++ omicron-nexus/tests/test_vpcs.rs | 47 +++++++ tools/oxapi_demo | 6 + 8 files changed, 276 insertions(+), 1 deletion(-) create mode 100644 omicron-nexus/tests/test_vpcs.rs diff --git a/omicron-common/src/api/model.rs b/omicron-common/src/api/model.rs index 874d6c692a2..49e5c24cdc4 100644 --- a/omicron-common/src/api/model.rs +++ b/omicron-common/src/api/model.rs @@ -433,6 +433,7 @@ pub enum ResourceType { Rack, Sled, SagaDbg, + VPC, } impl Display for ResourceType { @@ -448,6 +449,7 @@ impl Display for ResourceType { ResourceType::Rack => "rack", ResourceType::Sled => "sled", ResourceType::SagaDbg => "saga_dbg", + ResourceType::VPC => "vpc", } ) } @@ -1318,10 +1320,27 @@ impl From for SagaStateView { pub struct VPC { /** common identifying metadata */ pub identity: IdentityMetadata, - /** id for the project containing this Instance */ + /** id for the project containing this VPC */ + pub project_id: Uuid, +} + +#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct VPCView { + #[serde(flatten)] + pub identity: IdentityMetadata, + + /** id for the project containing this VPC */ pub project_id: Uuid, } +impl Object for VPC { + type View = VPCView; + fn to_view(&self) -> VPCView { + VPCView { identity: self.identity.clone(), project_id: self.project_id } + } +} + /// An `Ipv4Net` represents a IPv4 subnetwork, including the address and network mask. #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)] pub struct Ipv4Net(pub ipnet::Ipv4Net); diff --git a/omicron-nexus/src/db/datastore.rs b/omicron-nexus/src/db/datastore.rs index df66f45cb4c..09b18fed4ac 100644 --- a/omicron-nexus/src/db/datastore.rs +++ b/omicron-nexus/src/db/datastore.rs @@ -48,9 +48,11 @@ use super::schema::Disk; use super::schema::Instance; use super::schema::LookupByAttachedInstance; use super::schema::LookupByUniqueId; +use super::schema::LookupByUniqueIdInProject; use super::schema::LookupByUniqueName; use super::schema::LookupByUniqueNameInProject; use super::schema::Project; +use super::schema::VPC; use super::sql::SqlSerialize; use super::sql::SqlString; use super::sql::SqlValueSet; @@ -720,4 +722,18 @@ impl DataStore { ); Ok(()) } + + pub async fn project_list_vpcs( + &self, + project_id: &Uuid, + pagparams: &DataPageParams<'_, Uuid>, + ) -> ListResult { + let client = self.pool.acquire().await?; + sql_fetch_page_by::< + LookupByUniqueIdInProject, + VPC, + ::ModelType, + >(&client, (project_id,), pagparams, VPC::ALL_COLUMNS) + .await + } } diff --git a/omicron-nexus/src/db/schema.rs b/omicron-nexus/src/db/schema.rs index 79fba192cac..f5c2db58795 100644 --- a/omicron-nexus/src/db/schema.rs +++ b/omicron-nexus/src/db/schema.rs @@ -168,6 +168,10 @@ impl Table for VPC { ]; } +impl ResourceTable for VPC { + const RESOURCE_TYPE: ResourceType = ResourceType::VPC; +} + /** Describes the "VPCSubnet" table */ pub struct VPCSubnet; impl Table for VPCSubnet { @@ -395,6 +399,25 @@ impl<'a, R: ResourceTable> LookupKey<'a, R> for LookupByUniqueNameInProject { } } +/** + * Implementation of [`LookupKey`] for looking up objects within a project by + * the project_id and the object's id + */ +pub struct LookupByUniqueIdInProject; +impl<'a, R: ResourceTable> LookupKey<'a, R> for LookupByUniqueIdInProject { + type ScopeKey = (&'a Uuid,); + const SCOPE_KEY_COLUMN_NAMES: &'static [&'static str] = &["project_id"]; + type ItemKey = Uuid; + const ITEM_KEY_COLUMN_NAME: &'static str = "id"; + + fn where_select_error( + _scope_key: Self::ScopeKey, + item_key: &Self::ItemKey, + ) -> Error { + Error::not_found_by_id(R::RESOURCE_TYPE, item_key) + } +} + /** * Implementation of [`LookupKey`] for looking up objects by name within the * scope of an instance (SQL column "attach_instance_id"). This is really just diff --git a/omicron-nexus/src/http_entrypoints_external.rs b/omicron-nexus/src/http_entrypoints_external.rs index 92cf7ff733d..0057ebfbb62 100644 --- a/omicron-nexus/src/http_entrypoints_external.rs +++ b/omicron-nexus/src/http_entrypoints_external.rs @@ -44,6 +44,7 @@ use omicron_common::api::ProjectView; use omicron_common::api::RackView; use omicron_common::api::SagaView; use omicron_common::api::SledView; +use omicron_common::api::VPCView; use schemars::JsonSchema; use serde::Deserialize; use std::num::NonZeroU32; @@ -81,6 +82,8 @@ pub fn external_api() -> NexusApiDescription { api.register(instance_disks_put_disk)?; api.register(instance_disks_delete_disk)?; + api.register(project_vpcs_get)?; + api.register(hardware_racks_get)?; api.register(hardware_racks_get_rack)?; api.register(hardware_sleds_get)?; @@ -647,6 +650,37 @@ async fn instance_disks_delete_disk( Ok(HttpResponseDeleted()) } +/* + * VPCs + */ + +/** + * List instances in a project. + */ +#[endpoint { + method = GET, + path = "/projects/{project_name}/vpcs", + }] +async fn project_vpcs_get( + rqctx: Arc>>, + query_params: Query, + path_params: Path, +) -> Result>, HttpError> { + let apictx = rqctx.context(); + let nexus = &apictx.nexus; + let query = query_params.into_inner(); + let path = path_params.into_inner(); + let project_name = &path.project_name; + let vpc_stream = nexus + .project_list_vpcs( + &project_name, + &data_page_params_for(&rqctx, &query)?, + ) + .await?; + let view_list = to_view_list(vpc_stream).await; + Ok(HttpResponseOk(ScanById::results_page(&query, view_list)?)) +} + /* * Racks */ diff --git a/omicron-nexus/src/nexus.rs b/omicron-nexus/src/nexus.rs index f2816ed5619..396394ed766 100644 --- a/omicron-nexus/src/nexus.rs +++ b/omicron-nexus/src/nexus.rs @@ -42,6 +42,7 @@ use omicron_common::api::ResourceType; use omicron_common::api::SagaView; use omicron_common::api::Sled; use omicron_common::api::UpdateResult; +use omicron_common::api::VPC; use omicron_common::bail_unless; use omicron_common::collection::collection_page; use omicron_common::OximeterClient; @@ -1000,6 +1001,16 @@ impl Nexus { .map(|_| ()) } + pub async fn project_list_vpcs( + &self, + project_name: &Name, + pagparams: &DataPageParams<'_, Uuid>, + ) -> ListResult { + let project_id = + self.db_datastore.project_lookup_id_by_name(project_name).await?; + self.db_datastore.project_list_vpcs(&project_id, pagparams).await + } + /* * Racks. We simulate just one for now. */ diff --git a/omicron-nexus/tests/output/nexus-openapi.json b/omicron-nexus/tests/output/nexus-openapi.json index 4846a2ae14d..76712b945a5 100644 --- a/omicron-nexus/tests/output/nexus-openapi.json +++ b/omicron-nexus/tests/output/nexus-openapi.json @@ -941,6 +941,64 @@ } } }, + "/projects/{project_name}/vpcs": { + "get": { + "description": "List instances in a project.", + "operationId": "project_vpcs_get", + "parameters": [ + { + "in": "query", + "name": "limit", + "schema": { + "description": "Maximum number of items returned by a single call", + "type": "integer", + "format": "uint32", + "minimum": 1 + }, + "style": "form" + }, + { + "in": "query", + "name": "page_token", + "schema": { + "description": "Token returned by previous call to retreive the subsequent page", + "type": "string" + }, + "style": "form" + }, + { + "in": "query", + "name": "sort_by", + "schema": { + "$ref": "#/components/schemas/IdSortMode" + }, + "style": "form" + }, + { + "in": "path", + "name": "project_name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VPCViewResultsPage" + } + } + } + } + }, + "x-dropshot-pagination": true + } + }, "/sagas": { "get": { "description": "List all sagas (for debugging)", @@ -1725,6 +1783,67 @@ "items" ] }, + "VPCView": { + "description": "Identity-related metadata that's included in nearly all public API objects", + "type": "object", + "properties": { + "description": { + "description": "human-readable free-form text about a resource", + "type": "string" + }, + "id": { + "description": "unique, immutable, system-controlled identifier for each resource", + "type": "string", + "format": "uuid" + }, + "name": { + "$ref": "#/components/schemas/Name" + }, + "projectId": { + "description": "id for the project containing this VPC", + "type": "string", + "format": "uuid" + }, + "timeCreated": { + "description": "timestamp when this resource was created", + "type": "string", + "format": "date-time" + }, + "timeModified": { + "description": "timestamp when this resource was last modified", + "type": "string", + "format": "date-time" + } + }, + "required": [ + "description", + "id", + "name", + "projectId", + "timeCreated", + "timeModified" + ] + }, + "VPCViewResultsPage": { + "description": "A single page of results", + "type": "object", + "properties": { + "items": { + "description": "list of items on this page of results", + "type": "array", + "items": { + "$ref": "#/components/schemas/VPCView" + } + }, + "next_page": { + "description": "token used to fetch the next page of results (if any)", + "type": "string" + } + }, + "required": [ + "items" + ] + }, "IdSortMode": { "description": "Supported set of sort modes for scanning by id only.\n\nCurrently, we only support scanning in ascending order.", "type": "string", diff --git a/omicron-nexus/tests/test_vpcs.rs b/omicron-nexus/tests/test_vpcs.rs new file mode 100644 index 00000000000..81ae17cc407 --- /dev/null +++ b/omicron-nexus/tests/test_vpcs.rs @@ -0,0 +1,47 @@ +use omicron_common::api::IdentityMetadataCreateParams; +use omicron_common::api::Name; +use omicron_common::api::ProjectCreateParams; +use omicron_common::api::ProjectView; +use omicron_common::api::VPCView; +use std::convert::TryFrom; + +use dropshot::test_util::objects_list_page; +use dropshot::test_util::objects_post; +use dropshot::test_util::ClientTestContext; + +pub mod common; +use common::test_setup; + +#[macro_use] +extern crate slog; + +#[tokio::test] +async fn test_vpcs() { + let cptestctx = test_setup("test_vpcs").await; + let client = &cptestctx.external_client; + // let apictx = &cptestctx.server.apictx; + // let nexus = &apictx.nexus; + + /* Create a project that we'll use for testing. */ + let project_name = "springfield-squidport"; + let vpcs_url = format!("/projects/{}/vpcs", project_name); + let _: ProjectView = objects_post( + &client, + "/projects", + ProjectCreateParams { + identity: IdentityMetadataCreateParams { + name: Name::try_from(project_name).unwrap(), + description: "a pier".to_string(), + }, + }, + ) + .await; + + /* List vpcs. There aren't any yet. */ + let vpcs = vpcs_list(&client, &vpcs_url).await; + assert_eq!(vpcs.len(), 0); +} + +async fn vpcs_list(client: &ClientTestContext, vpcs_url: &str) -> Vec { + objects_list_page::(client, vpcs_url).await.items +} diff --git a/tools/oxapi_demo b/tools/oxapi_demo index 46a03005e5b..dbfc0f34860 100755 --- a/tools/oxapi_demo +++ b/tools/oxapi_demo @@ -157,6 +157,12 @@ function cmd_project_list_disks do_curl "/projects/$1/disks" } +function cmd_project_list_vpcs +{ + [[ $# != 1 ]] && usage "expected PROJECT_NAME" + do_curl "/projects/$1/vpcs" +} + function cmd_instance_create_demo { # memory is 1024 * 1024 * 256 From 8f7ddce3dfae5a7ad891d66e6315ffd78fa7497f Mon Sep 17 00:00:00 2001 From: David Crespo Date: Wed, 28 Jul 2021 14:15:34 -0500 Subject: [PATCH 02/12] very rough VPC post endpoint --- omicron-common/src/api/model.rs | 10 +++ omicron-nexus/src/db/conversions.rs | 7 ++ omicron-nexus/src/db/datastore.rs | 28 ++++++++ .../src/http_entrypoints_external.rs | 23 ++++++ omicron-nexus/src/nexus.rs | 16 +++++ omicron-nexus/tests/output/nexus-openapi.json | 53 ++++++++++++++ omicron-nexus/tests/test_vpcs.rs | 71 +++++++++++++++++++ tools/oxapi_demo | 7 ++ 8 files changed, 215 insertions(+) diff --git a/omicron-common/src/api/model.rs b/omicron-common/src/api/model.rs index 49e5c24cdc4..a29f5633bf8 100644 --- a/omicron-common/src/api/model.rs +++ b/omicron-common/src/api/model.rs @@ -1341,6 +1341,16 @@ impl Object for VPC { } } +/** + * Create-time parameters for a [`VPC`] + */ +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct VPCCreateParams { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, +} + /// An `Ipv4Net` represents a IPv4 subnetwork, including the address and network mask. #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)] pub struct Ipv4Net(pub ipnet::Ipv4Net); diff --git a/omicron-nexus/src/db/conversions.rs b/omicron-nexus/src/db/conversions.rs index 4f1759df478..7829725060c 100644 --- a/omicron-nexus/src/db/conversions.rs +++ b/omicron-nexus/src/db/conversions.rs @@ -17,6 +17,7 @@ use omicron_common::api::OximeterAssignment; use omicron_common::api::OximeterInfo; use omicron_common::api::ProducerEndpoint; use omicron_common::api::ProjectCreateParams; +use omicron_common::api::VPCCreateParams; use super::sql::SqlSerialize; use super::sql::SqlValueSet; @@ -83,6 +84,12 @@ impl SqlSerialize for DiskState { } } +impl SqlSerialize for VPCCreateParams { + fn sql_serialize(&self, output: &mut SqlValueSet) { + self.identity.sql_serialize(output); + } +} + impl SqlSerialize for OximeterInfo { fn sql_serialize(&self, output: &mut SqlValueSet) { output.set("id", &self.collector_id); diff --git a/omicron-nexus/src/db/datastore.rs b/omicron-nexus/src/db/datastore.rs index 09b18fed4ac..fc8f4e41a17 100644 --- a/omicron-nexus/src/db/datastore.rs +++ b/omicron-nexus/src/db/datastore.rs @@ -736,4 +736,32 @@ impl DataStore { >(&client, (project_id,), pagparams, VPC::ALL_COLUMNS) .await } + + pub async fn project_create_vpc( + &self, + vpc_id: &Uuid, + project_id: &Uuid, + params: &api::VPCCreateParams, + ) -> Result { + let client = self.pool.acquire().await?; + let now = Utc::now(); + let mut values = SqlValueSet::new(); + values.set("id", vpc_id); + values.set("time_created", &now); + values.set("time_modified", &now); + values.set("project_id", project_id); + params.sql_serialize(&mut values); + + let vpc = + sql_insert_unique_idempotent_and_fetch::( + &client, + &values, + params.identity.name.as_str(), + "id", + (), + &vpc_id, + ) + .await?; + Ok(vpc) + } } diff --git a/omicron-nexus/src/http_entrypoints_external.rs b/omicron-nexus/src/http_entrypoints_external.rs index 0057ebfbb62..375c3d09411 100644 --- a/omicron-nexus/src/http_entrypoints_external.rs +++ b/omicron-nexus/src/http_entrypoints_external.rs @@ -44,6 +44,7 @@ use omicron_common::api::ProjectView; use omicron_common::api::RackView; use omicron_common::api::SagaView; use omicron_common::api::SledView; +use omicron_common::api::VPCCreateParams; use omicron_common::api::VPCView; use schemars::JsonSchema; use serde::Deserialize; @@ -83,6 +84,7 @@ pub fn external_api() -> NexusApiDescription { api.register(instance_disks_delete_disk)?; api.register(project_vpcs_get)?; + api.register(project_vpcs_post)?; api.register(hardware_racks_get)?; api.register(hardware_racks_get_rack)?; @@ -681,6 +683,27 @@ async fn project_vpcs_get( Ok(HttpResponseOk(ScanById::results_page(&query, view_list)?)) } +/** + * Create a VPC in a project. + */ +#[endpoint { + method = POST, + path = "/projects/{project_name}/vpcs", + }] +async fn project_vpcs_post( + rqctx: Arc>>, + path_params: Path, + new_vpc: TypedBody, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let project_name = &path.project_name; + let new_vpc_params = &new_vpc.into_inner(); + let vpc = nexus.project_create_vpc(&project_name, &new_vpc_params).await?; + Ok(HttpResponseCreated(vpc.to_view())) +} + /* * Racks */ diff --git a/omicron-nexus/src/nexus.rs b/omicron-nexus/src/nexus.rs index 396394ed766..43944fa999e 100644 --- a/omicron-nexus/src/nexus.rs +++ b/omicron-nexus/src/nexus.rs @@ -42,6 +42,7 @@ use omicron_common::api::ResourceType; use omicron_common::api::SagaView; use omicron_common::api::Sled; use omicron_common::api::UpdateResult; +use omicron_common::api::VPCCreateParams; use omicron_common::api::VPC; use omicron_common::bail_unless; use omicron_common::collection::collection_page; @@ -1011,6 +1012,21 @@ impl Nexus { self.db_datastore.project_list_vpcs(&project_id, pagparams).await } + pub async fn project_create_vpc( + &self, + project_name: &Name, + params: &VPCCreateParams, + ) -> CreateResult { + let project_id = + self.db_datastore.project_lookup_id_by_name(project_name).await?; + let id = Uuid::new_v4(); + let vpc = self + .db_datastore + .project_create_vpc(&id, &project_id, params) + .await?; + Ok(vpc) + } + /* * Racks. We simulate just one for now. */ diff --git a/omicron-nexus/tests/output/nexus-openapi.json b/omicron-nexus/tests/output/nexus-openapi.json index 76712b945a5..fe1d2a388c9 100644 --- a/omicron-nexus/tests/output/nexus-openapi.json +++ b/omicron-nexus/tests/output/nexus-openapi.json @@ -997,6 +997,43 @@ } }, "x-dropshot-pagination": true + }, + "post": { + "description": "Create a VPC in a project.", + "operationId": "project_vpcs_post", + "parameters": [ + { + "in": "path", + "name": "project_name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VPCCreateParams" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "successful creation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VPCView" + } + } + } + } + } } }, "/sagas": { @@ -1783,6 +1820,22 @@ "items" ] }, + "VPCCreateParams": { + "description": "Create-time parameters for a [`VPC`]", + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "name": { + "$ref": "#/components/schemas/Name" + } + }, + "required": [ + "description", + "name" + ] + }, "VPCView": { "description": "Identity-related metadata that's included in nearly all public API objects", "type": "object", diff --git a/omicron-nexus/tests/test_vpcs.rs b/omicron-nexus/tests/test_vpcs.rs index 81ae17cc407..11fb0e72f28 100644 --- a/omicron-nexus/tests/test_vpcs.rs +++ b/omicron-nexus/tests/test_vpcs.rs @@ -1,15 +1,20 @@ +// use http::method::Method; +// use http::StatusCode; use omicron_common::api::IdentityMetadataCreateParams; use omicron_common::api::Name; use omicron_common::api::ProjectCreateParams; use omicron_common::api::ProjectView; +use omicron_common::api::VPCCreateParams; use omicron_common::api::VPCView; use std::convert::TryFrom; +use dropshot::test_util::object_get; use dropshot::test_util::objects_list_page; use dropshot::test_util::objects_post; use dropshot::test_util::ClientTestContext; pub mod common; +use common::identity_eq; use common::test_setup; #[macro_use] @@ -40,8 +45,74 @@ async fn test_vpcs() { /* List vpcs. There aren't any yet. */ let vpcs = vpcs_list(&client, &vpcs_url).await; assert_eq!(vpcs.len(), 0); + + // /* Make sure we get a 404 if we fetch one. */ + let vpc_url = format!("{}/just-rainsticks", vpcs_url); + + // let error = client + // .make_request_error(Method::GET, &vpc_url, StatusCode::NOT_FOUND) + // .await; + // assert_eq!( + // error.message, + // "not found: vpc with name \"just-rainsticks\"" + // ); + + // /* Ditto if we try to delete one. */ + // let error = client + // .make_request_error( + // Method::DELETE, + // &vpc_url, + // StatusCode::NOT_FOUND, + // ) + // .await; + // assert_eq!( + // error.message, + // "not found: vpc with name \"just-rainsticks\"" + // ); + + /* Create a VPC. */ + let new_vpc = VPCCreateParams { + identity: IdentityMetadataCreateParams { + name: Name::try_from("just-rainsticks").unwrap(), + description: String::from("sells rainsticks"), + }, + }; + let vpc: VPCView = objects_post(&client, &vpcs_url, new_vpc.clone()).await; + assert_eq!(vpc.identity.name, "just-rainsticks"); + assert_eq!(vpc.identity.description, "sells rainsticks"); + + /* Attempt to create a second VPC with a conflicting name. */ + // let error = client + // .make_request_error_body( + // Method::POST, + // &vpcs_url, + // new_vpc, + // StatusCode::BAD_REQUEST, + // ) + // .await; + // assert_eq!(error.message, "already exists: instance \"just-rainsticks\""); + + /* List VPCs again and expect to find the one we just created. */ + let vpcs = vpcs_list(&client, &vpcs_url).await; + assert_eq!(vpcs.len(), 1); + vpcs_eq(&vpcs[0], &vpc); + + /* Fetch the VPC and expect it to match. */ + // let vpc = vpc_get(&client, &vpc_url).await; + // vpcs_eq(&vpcs[0], &vpc); + + cptestctx.teardown().await; } async fn vpcs_list(client: &ClientTestContext, vpcs_url: &str) -> Vec { objects_list_page::(client, vpcs_url).await.items } + +async fn vpc_get(client: &ClientTestContext, vpc_url: &str) -> VPCView { + object_get::(client, vpc_url).await +} + +fn vpcs_eq(vpc1: &VPCView, vpc2: &VPCView) { + identity_eq(&vpc1.identity, &vpc2.identity); + assert_eq!(vpc1.project_id, vpc2.project_id); +} diff --git a/tools/oxapi_demo b/tools/oxapi_demo index dbfc0f34860..76371736940 100755 --- a/tools/oxapi_demo +++ b/tools/oxapi_demo @@ -245,6 +245,13 @@ function cmd_disk_delete do_curl "/projects/$1/disks/$2" -X DELETE } +function cmd_vpc_create_demo +{ + [[ $# != 2 ]] && usage "expected PROJECT_NAME VPC_NAME" + mkjson name="$2" description="a disk called $2" | + do_curl "/projects/$1/vpcs" -X POST -T - +} + function cmd_racks_list { [[ $# != 0 ]] && usage "expected no arguments" From 293a11a7bb31cb54ad3dc29b067d62000d0406ff Mon Sep 17 00:00:00 2001 From: David Crespo Date: Wed, 28 Jul 2021 15:31:58 -0500 Subject: [PATCH 03/12] VPC GET endpoint --- omicron-nexus/src/db/datastore.rs | 14 +++++++ .../src/http_entrypoints_external.rs | 32 +++++++++++++++- omicron-nexus/src/nexus.rs | 10 +++++ omicron-nexus/tests/test_vpcs.rs | 37 +++++++++---------- tools/oxapi_demo | 6 +++ 5 files changed, 78 insertions(+), 21 deletions(-) diff --git a/omicron-nexus/src/db/datastore.rs b/omicron-nexus/src/db/datastore.rs index fc8f4e41a17..f94366d40a8 100644 --- a/omicron-nexus/src/db/datastore.rs +++ b/omicron-nexus/src/db/datastore.rs @@ -764,4 +764,18 @@ impl DataStore { .await?; Ok(vpc) } + + pub async fn vpc_fetch_by_name( + &self, + project_id: &Uuid, + vpc_name: &Name, + ) -> LookupResult { + let client = self.pool.acquire().await?; + sql_fetch_row_by::( + &client, + (project_id,), + vpc_name, + ) + .await + } } diff --git a/omicron-nexus/src/http_entrypoints_external.rs b/omicron-nexus/src/http_entrypoints_external.rs index 375c3d09411..5cdc63ffd4c 100644 --- a/omicron-nexus/src/http_entrypoints_external.rs +++ b/omicron-nexus/src/http_entrypoints_external.rs @@ -85,6 +85,7 @@ pub fn external_api() -> NexusApiDescription { api.register(project_vpcs_get)?; api.register(project_vpcs_post)?; + api.register(project_vpcs_get_vpc)?; api.register(hardware_racks_get)?; api.register(hardware_racks_get_rack)?; @@ -657,7 +658,7 @@ async fn instance_disks_delete_disk( */ /** - * List instances in a project. + * List VPCs in a project. */ #[endpoint { method = GET, @@ -683,6 +684,35 @@ async fn project_vpcs_get( Ok(HttpResponseOk(ScanById::results_page(&query, view_list)?)) } +/** + * Path parameters for VPC requests + */ +#[derive(Deserialize, JsonSchema)] +struct VPCPathParam { + project_name: Name, + vpc_name: Name, +} + +/** + * Get a VPC in a project. + */ +#[endpoint { + method = GET, + path = "/projects/{project_name}/vpcs/{vpc_name}", + }] +async fn project_vpcs_get_vpc( + rqctx: Arc>>, + path_params: Path, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let project_name = &path.project_name; + let vpc_name = &path.vpc_name; + let vpc = nexus.project_lookup_vpc(&project_name, &vpc_name).await?; + Ok(HttpResponseOk(vpc.to_view())) +} + /** * Create a VPC in a project. */ diff --git a/omicron-nexus/src/nexus.rs b/omicron-nexus/src/nexus.rs index 43944fa999e..ee82fd05e4b 100644 --- a/omicron-nexus/src/nexus.rs +++ b/omicron-nexus/src/nexus.rs @@ -1027,6 +1027,16 @@ impl Nexus { Ok(vpc) } + pub async fn project_lookup_vpc( + &self, + project_name: &Name, + vpc_name: &Name, + ) -> LookupResult { + let project_id = + self.db_datastore.project_lookup_id_by_name(project_name).await?; + self.db_datastore.vpc_fetch_by_name(&project_id, vpc_name).await + } + /* * Racks. We simulate just one for now. */ diff --git a/omicron-nexus/tests/test_vpcs.rs b/omicron-nexus/tests/test_vpcs.rs index 11fb0e72f28..b70cead9ee5 100644 --- a/omicron-nexus/tests/test_vpcs.rs +++ b/omicron-nexus/tests/test_vpcs.rs @@ -1,5 +1,5 @@ -// use http::method::Method; -// use http::StatusCode; +use http::method::Method; +use http::StatusCode; use omicron_common::api::IdentityMetadataCreateParams; use omicron_common::api::Name; use omicron_common::api::ProjectCreateParams; @@ -49,13 +49,10 @@ async fn test_vpcs() { // /* Make sure we get a 404 if we fetch one. */ let vpc_url = format!("{}/just-rainsticks", vpcs_url); - // let error = client - // .make_request_error(Method::GET, &vpc_url, StatusCode::NOT_FOUND) - // .await; - // assert_eq!( - // error.message, - // "not found: vpc with name \"just-rainsticks\"" - // ); + let error = client + .make_request_error(Method::GET, &vpc_url, StatusCode::NOT_FOUND) + .await; + assert_eq!(error.message, "not found: vpc with name \"just-rainsticks\""); // /* Ditto if we try to delete one. */ // let error = client @@ -82,15 +79,15 @@ async fn test_vpcs() { assert_eq!(vpc.identity.description, "sells rainsticks"); /* Attempt to create a second VPC with a conflicting name. */ - // let error = client - // .make_request_error_body( - // Method::POST, - // &vpcs_url, - // new_vpc, - // StatusCode::BAD_REQUEST, - // ) - // .await; - // assert_eq!(error.message, "already exists: instance \"just-rainsticks\""); + let error = client + .make_request_error_body( + Method::POST, + &vpcs_url, + new_vpc, + StatusCode::BAD_REQUEST, + ) + .await; + assert_eq!(error.message, "already exists: vpc \"just-rainsticks\""); /* List VPCs again and expect to find the one we just created. */ let vpcs = vpcs_list(&client, &vpcs_url).await; @@ -98,8 +95,8 @@ async fn test_vpcs() { vpcs_eq(&vpcs[0], &vpc); /* Fetch the VPC and expect it to match. */ - // let vpc = vpc_get(&client, &vpc_url).await; - // vpcs_eq(&vpcs[0], &vpc); + let vpc = vpc_get(&client, &vpc_url).await; + vpcs_eq(&vpcs[0], &vpc); cptestctx.teardown().await; } diff --git a/tools/oxapi_demo b/tools/oxapi_demo index 76371736940..07d5d36edcf 100755 --- a/tools/oxapi_demo +++ b/tools/oxapi_demo @@ -252,6 +252,12 @@ function cmd_vpc_create_demo do_curl "/projects/$1/vpcs" -X POST -T - } +function cmd_vpc_get +{ + [[ $# != 2 ]] && usage "expected PROJECT_NAME VPC_NAME" + do_curl "/projects/$1/vpcs/$2" +} + function cmd_racks_list { [[ $# != 0 ]] && usage "expected no arguments" From 343fe84ca83de83be900a2ca994f9291f755a345 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Wed, 28 Jul 2021 16:03:31 -0500 Subject: [PATCH 04/12] VPC DELETE endpoint --- omicron-nexus/src/db/datastore.rs | 19 +++++ .../src/http_entrypoints_external.rs | 21 ++++++ omicron-nexus/src/nexus.rs | 9 +++ omicron-nexus/tests/output/nexus-openapi.json | 69 ++++++++++++++++++- omicron-nexus/tests/test_vpcs.rs | 35 ++++++---- tools/oxapi_demo | 6 ++ 6 files changed, 145 insertions(+), 14 deletions(-) diff --git a/omicron-nexus/src/db/datastore.rs b/omicron-nexus/src/db/datastore.rs index f94366d40a8..68514bf27c6 100644 --- a/omicron-nexus/src/db/datastore.rs +++ b/omicron-nexus/src/db/datastore.rs @@ -778,4 +778,23 @@ impl DataStore { ) .await } + + pub async fn project_delete_vpc(&self, vpc_id: &Uuid) -> DeleteResult { + let client = self.pool.acquire().await?; + let now = Utc::now(); + sql_execute_maybe_one( + &client, + format!( + "UPDATE {} SET time_deleted = $1 WHERE \ + time_deleted IS NULL AND id = $2 LIMIT 2 \ + RETURNING {}", + VPC::TABLE_NAME, + VPC::ALL_COLUMNS.join(", ") + ) + .as_str(), + &[&now, &vpc_id], + || Error::not_found_by_id(ResourceType::VPC, vpc_id), + ) + .await + } } diff --git a/omicron-nexus/src/http_entrypoints_external.rs b/omicron-nexus/src/http_entrypoints_external.rs index 5cdc63ffd4c..7747be66fef 100644 --- a/omicron-nexus/src/http_entrypoints_external.rs +++ b/omicron-nexus/src/http_entrypoints_external.rs @@ -86,6 +86,7 @@ pub fn external_api() -> NexusApiDescription { api.register(project_vpcs_get)?; api.register(project_vpcs_post)?; api.register(project_vpcs_get_vpc)?; + api.register(project_vpcs_delete_vpc)?; api.register(hardware_racks_get)?; api.register(hardware_racks_get_rack)?; @@ -734,6 +735,26 @@ async fn project_vpcs_post( Ok(HttpResponseCreated(vpc.to_view())) } +/** + * Delete a vpc from a project. + */ +#[endpoint { + method = DELETE, + path = "/projects/{project_name}/vpcs/{vpc_name}", + }] +async fn project_vpcs_delete_vpc( + rqctx: Arc>>, + path_params: Path, +) -> Result { + let apictx = rqctx.context(); + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let project_name = &path.project_name; + let vpc_name = &path.vpc_name; + nexus.project_delete_vpc(&project_name, &vpc_name).await?; + Ok(HttpResponseDeleted()) +} + /* * Racks */ diff --git a/omicron-nexus/src/nexus.rs b/omicron-nexus/src/nexus.rs index ee82fd05e4b..6077f18abdd 100644 --- a/omicron-nexus/src/nexus.rs +++ b/omicron-nexus/src/nexus.rs @@ -1037,6 +1037,15 @@ impl Nexus { self.db_datastore.vpc_fetch_by_name(&project_id, vpc_name).await } + pub async fn project_delete_vpc( + &self, + project_name: &Name, + vpc_name: &Name, + ) -> DeleteResult { + let vpc = self.project_lookup_vpc(project_name, vpc_name).await?; + self.db_datastore.project_delete_vpc(&vpc.identity.id).await + } + /* * Racks. We simulate just one for now. */ diff --git a/omicron-nexus/tests/output/nexus-openapi.json b/omicron-nexus/tests/output/nexus-openapi.json index fe1d2a388c9..27bc56f7672 100644 --- a/omicron-nexus/tests/output/nexus-openapi.json +++ b/omicron-nexus/tests/output/nexus-openapi.json @@ -943,7 +943,7 @@ }, "/projects/{project_name}/vpcs": { "get": { - "description": "List instances in a project.", + "description": "List VPCs in a project.", "operationId": "project_vpcs_get", "parameters": [ { @@ -1036,6 +1036,73 @@ } } }, + "/projects/{project_name}/vpcs/{vpc_name}": { + "get": { + "description": "Get a VPC in a project.", + "operationId": "project_vpcs_get_vpc", + "parameters": [ + { + "in": "path", + "name": "project_name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "simple" + }, + { + "in": "path", + "name": "vpc_name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VPCView" + } + } + } + } + } + }, + "delete": { + "description": "Delete a vpc from a project.", + "operationId": "project_vpcs_delete_vpc", + "parameters": [ + { + "in": "path", + "name": "project_name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "simple" + }, + { + "in": "path", + "name": "vpc_name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "simple" + } + ], + "responses": { + "204": { + "description": "successful deletion" + } + } + } + }, "/sagas": { "get": { "description": "List all sagas (for debugging)", diff --git a/omicron-nexus/tests/test_vpcs.rs b/omicron-nexus/tests/test_vpcs.rs index b70cead9ee5..3a8c9dd1934 100644 --- a/omicron-nexus/tests/test_vpcs.rs +++ b/omicron-nexus/tests/test_vpcs.rs @@ -46,7 +46,7 @@ async fn test_vpcs() { let vpcs = vpcs_list(&client, &vpcs_url).await; assert_eq!(vpcs.len(), 0); - // /* Make sure we get a 404 if we fetch one. */ + /* Make sure we get a 404 if we fetch one. */ let vpc_url = format!("{}/just-rainsticks", vpcs_url); let error = client @@ -54,18 +54,11 @@ async fn test_vpcs() { .await; assert_eq!(error.message, "not found: vpc with name \"just-rainsticks\""); - // /* Ditto if we try to delete one. */ - // let error = client - // .make_request_error( - // Method::DELETE, - // &vpc_url, - // StatusCode::NOT_FOUND, - // ) - // .await; - // assert_eq!( - // error.message, - // "not found: vpc with name \"just-rainsticks\"" - // ); + /* Ditto if we try to delete one. */ + let error = client + .make_request_error(Method::DELETE, &vpc_url, StatusCode::NOT_FOUND) + .await; + assert_eq!(error.message, "not found: vpc with name \"just-rainsticks\""); /* Create a VPC. */ let new_vpc = VPCCreateParams { @@ -98,6 +91,22 @@ async fn test_vpcs() { let vpc = vpc_get(&client, &vpc_url).await; vpcs_eq(&vpcs[0], &vpc); + /* Delete the VPC. */ + client + .make_request_no_body(Method::DELETE, &vpc_url, StatusCode::NO_CONTENT) + .await + .unwrap(); + + /* Now we expect a 404 on fetch */ + let error = client + .make_request_error(Method::GET, &vpc_url, StatusCode::NOT_FOUND) + .await; + assert_eq!(error.message, "not found: vpc with name \"just-rainsticks\""); + + /* And the list should be empty again */ + let vpcs = vpcs_list(&client, &vpcs_url).await; + assert_eq!(vpcs.len(), 0); + cptestctx.teardown().await; } diff --git a/tools/oxapi_demo b/tools/oxapi_demo index 07d5d36edcf..581b222d48a 100755 --- a/tools/oxapi_demo +++ b/tools/oxapi_demo @@ -258,6 +258,12 @@ function cmd_vpc_get do_curl "/projects/$1/vpcs/$2" } +function cmd_vpc_delete +{ + [[ $# != 2 ]] && usage "expected PROJECT_NAME VPC_NAME" + do_curl "/projects/$1/vpcs/$2" -X DELETE +} + function cmd_racks_list { [[ $# != 0 ]] && usage "expected no arguments" From 67eccbc89c4580346961160980072c95ec3ed1d4 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Wed, 28 Jul 2021 16:28:48 -0500 Subject: [PATCH 05/12] scan VPCs by name, not ID (didn't realize they had names when I started) --- omicron-nexus/src/db/datastore.rs | 5 ++--- omicron-nexus/src/db/schema.rs | 19 ------------------- .../src/http_entrypoints_external.rs | 4 ++-- omicron-nexus/src/nexus.rs | 2 +- omicron-nexus/tests/output/nexus-openapi.json | 2 +- 5 files changed, 6 insertions(+), 26 deletions(-) diff --git a/omicron-nexus/src/db/datastore.rs b/omicron-nexus/src/db/datastore.rs index 68514bf27c6..89e651bfa35 100644 --- a/omicron-nexus/src/db/datastore.rs +++ b/omicron-nexus/src/db/datastore.rs @@ -48,7 +48,6 @@ use super::schema::Disk; use super::schema::Instance; use super::schema::LookupByAttachedInstance; use super::schema::LookupByUniqueId; -use super::schema::LookupByUniqueIdInProject; use super::schema::LookupByUniqueName; use super::schema::LookupByUniqueNameInProject; use super::schema::Project; @@ -726,11 +725,11 @@ impl DataStore { pub async fn project_list_vpcs( &self, project_id: &Uuid, - pagparams: &DataPageParams<'_, Uuid>, + pagparams: &DataPageParams<'_, Name>, ) -> ListResult { let client = self.pool.acquire().await?; sql_fetch_page_by::< - LookupByUniqueIdInProject, + LookupByUniqueNameInProject, VPC, ::ModelType, >(&client, (project_id,), pagparams, VPC::ALL_COLUMNS) diff --git a/omicron-nexus/src/db/schema.rs b/omicron-nexus/src/db/schema.rs index f5c2db58795..eb6d9ebead7 100644 --- a/omicron-nexus/src/db/schema.rs +++ b/omicron-nexus/src/db/schema.rs @@ -399,25 +399,6 @@ impl<'a, R: ResourceTable> LookupKey<'a, R> for LookupByUniqueNameInProject { } } -/** - * Implementation of [`LookupKey`] for looking up objects within a project by - * the project_id and the object's id - */ -pub struct LookupByUniqueIdInProject; -impl<'a, R: ResourceTable> LookupKey<'a, R> for LookupByUniqueIdInProject { - type ScopeKey = (&'a Uuid,); - const SCOPE_KEY_COLUMN_NAMES: &'static [&'static str] = &["project_id"]; - type ItemKey = Uuid; - const ITEM_KEY_COLUMN_NAME: &'static str = "id"; - - fn where_select_error( - _scope_key: Self::ScopeKey, - item_key: &Self::ItemKey, - ) -> Error { - Error::not_found_by_id(R::RESOURCE_TYPE, item_key) - } -} - /** * Implementation of [`LookupKey`] for looking up objects by name within the * scope of an instance (SQL column "attach_instance_id"). This is really just diff --git a/omicron-nexus/src/http_entrypoints_external.rs b/omicron-nexus/src/http_entrypoints_external.rs index 7747be66fef..66473178e93 100644 --- a/omicron-nexus/src/http_entrypoints_external.rs +++ b/omicron-nexus/src/http_entrypoints_external.rs @@ -667,7 +667,7 @@ async fn instance_disks_delete_disk( }] async fn project_vpcs_get( rqctx: Arc>>, - query_params: Query, + query_params: Query, path_params: Path, ) -> Result>, HttpError> { let apictx = rqctx.context(); @@ -682,7 +682,7 @@ async fn project_vpcs_get( ) .await?; let view_list = to_view_list(vpc_stream).await; - Ok(HttpResponseOk(ScanById::results_page(&query, view_list)?)) + Ok(HttpResponseOk(ScanByName::results_page(&query, view_list)?)) } /** diff --git a/omicron-nexus/src/nexus.rs b/omicron-nexus/src/nexus.rs index 6077f18abdd..06398a7fa21 100644 --- a/omicron-nexus/src/nexus.rs +++ b/omicron-nexus/src/nexus.rs @@ -1005,7 +1005,7 @@ impl Nexus { pub async fn project_list_vpcs( &self, project_name: &Name, - pagparams: &DataPageParams<'_, Uuid>, + pagparams: &DataPageParams<'_, Name>, ) -> ListResult { let project_id = self.db_datastore.project_lookup_id_by_name(project_name).await?; diff --git a/omicron-nexus/tests/output/nexus-openapi.json b/omicron-nexus/tests/output/nexus-openapi.json index 27bc56f7672..c1b6ae2f468 100644 --- a/omicron-nexus/tests/output/nexus-openapi.json +++ b/omicron-nexus/tests/output/nexus-openapi.json @@ -970,7 +970,7 @@ "in": "query", "name": "sort_by", "schema": { - "$ref": "#/components/schemas/IdSortMode" + "$ref": "#/components/schemas/NameSortMode" }, "style": "form" }, From 6d482e547b365c807117ecd86860dd1faaf03684 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Wed, 28 Jul 2021 17:51:07 -0500 Subject: [PATCH 06/12] convert VPC/VPCView to new internal/external thing --- omicron-common/src/api/external/mod.rs | 18 ++------ omicron-common/src/api/internal/nexus.rs | 8 ++++ omicron-common/src/model_db.rs | 2 +- omicron-nexus/src/db/datastore.rs | 8 ++-- omicron-nexus/src/db/schema.rs | 2 +- .../src/http_entrypoints_external.rs | 14 +++--- omicron-nexus/src/nexus.rs | 2 +- omicron-nexus/tests/output/nexus-openapi.json | 44 +++++++++---------- omicron-nexus/tests/test_vpcs.rs | 24 +++++----- 9 files changed, 60 insertions(+), 62 deletions(-) diff --git a/omicron-common/src/api/external/mod.rs b/omicron-common/src/api/external/mod.rs index e07cbbeee0a..8fec292474d 100644 --- a/omicron-common/src/api/external/mod.rs +++ b/omicron-common/src/api/external/mod.rs @@ -1065,18 +1065,9 @@ impl From for SagaStateView { } } -/// A Virtual Private Cloud (VPC) object. -#[derive(Clone, Debug)] -pub struct VPC { - /** common identifying metadata */ - pub identity: IdentityMetadata, - /** id for the project containing this VPC */ - pub project_id: Uuid, -} - #[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "camelCase")] -pub struct VPCView { +pub struct VPC { #[serde(flatten)] pub identity: IdentityMetadata, @@ -1084,10 +1075,9 @@ pub struct VPCView { pub project_id: Uuid, } -impl Object for VPC { - type View = VPCView; - fn to_view(&self) -> VPCView { - VPCView { identity: self.identity.clone(), project_id: self.project_id } +impl From for VPC { + fn from(vpc: crate::api::internal::nexus::VPC) -> Self { + VPC { identity: vpc.identity, project_id: vpc.project_id } } } diff --git a/omicron-common/src/api/internal/nexus.rs b/omicron-common/src/api/internal/nexus.rs index 5da89257985..b669940cd6d 100644 --- a/omicron-common/src/api/internal/nexus.rs +++ b/omicron-common/src/api/internal/nexus.rs @@ -92,6 +92,14 @@ pub struct InstanceRuntimeState { pub time_updated: DateTime, } +#[derive(Clone, Debug)] +pub struct VPC { + /** common identifying metadata */ + pub identity: IdentityMetadata, + /** id for the project containing this VPC */ + pub project_id: Uuid, +} + /// Sent by a sled agent on startup to Nexus to request further instruction #[derive(Serialize, Deserialize, JsonSchema)] pub struct SledAgentStartupInfo { diff --git a/omicron-common/src/model_db.rs b/omicron-common/src/model_db.rs index 398f63b317f..ac285ca6601 100644 --- a/omicron-common/src/model_db.rs +++ b/omicron-common/src/model_db.rs @@ -65,7 +65,6 @@ use crate::api::external::MacAddr; use crate::api::external::Name; use crate::api::external::NetworkInterface; use crate::api::external::VPCSubnet; -use crate::api::external::VPC; use crate::api::external::{Ipv4Net, Ipv6Net}; use crate::api::internal::nexus::Disk; use crate::api::internal::nexus::DiskRuntimeState; @@ -75,6 +74,7 @@ use crate::api::internal::nexus::OximeterAssignment; use crate::api::internal::nexus::OximeterInfo; use crate::api::internal::nexus::ProducerEndpoint; use crate::api::internal::nexus::Project; +use crate::api::internal::nexus::VPC; use crate::bail_unless; use chrono::DateTime; use chrono::Utc; diff --git a/omicron-nexus/src/db/datastore.rs b/omicron-nexus/src/db/datastore.rs index 255ef838eb5..c00741c46ba 100644 --- a/omicron-nexus/src/db/datastore.rs +++ b/omicron-nexus/src/db/datastore.rs @@ -737,7 +737,7 @@ impl DataStore { &self, project_id: &Uuid, pagparams: &DataPageParams<'_, Name>, - ) -> ListResult { + ) -> ListResult { let client = self.pool.acquire().await?; sql_fetch_page_by::< LookupByUniqueNameInProject, @@ -751,8 +751,8 @@ impl DataStore { &self, vpc_id: &Uuid, project_id: &Uuid, - params: &api::VPCCreateParams, - ) -> Result { + params: &api::external::VPCCreateParams, + ) -> Result { let client = self.pool.acquire().await?; let now = Utc::now(); let mut values = SqlValueSet::new(); @@ -779,7 +779,7 @@ impl DataStore { &self, project_id: &Uuid, vpc_name: &Name, - ) -> LookupResult { + ) -> LookupResult { let client = self.pool.acquire().await?; sql_fetch_row_by::( &client, diff --git a/omicron-nexus/src/db/schema.rs b/omicron-nexus/src/db/schema.rs index e6fc093c719..3d480f5592f 100644 --- a/omicron-nexus/src/db/schema.rs +++ b/omicron-nexus/src/db/schema.rs @@ -154,7 +154,7 @@ impl Table for OximeterAssignment { /** Describes the "VPC" table */ pub struct VPC; impl Table for VPC { - type ModelType = api::external::VPC; + type ModelType = api::internal::nexus::VPC; const TABLE_NAME: &'static str = "VPC"; const ALL_COLUMNS: &'static [&'static str] = &[ "id", diff --git a/omicron-nexus/src/http_entrypoints_external.rs b/omicron-nexus/src/http_entrypoints_external.rs index e92635b7bb2..9c5a0f28481 100644 --- a/omicron-nexus/src/http_entrypoints_external.rs +++ b/omicron-nexus/src/http_entrypoints_external.rs @@ -45,7 +45,7 @@ use omicron_common::api::external::RackView; use omicron_common::api::external::SagaView; use omicron_common::api::external::SledView; use omicron_common::api::external::VPCCreateParams; -use omicron_common::api::external::VPCView; +use omicron_common::api::external::VPC; use schemars::JsonSchema; use serde::Deserialize; use std::num::NonZeroU32; @@ -676,7 +676,7 @@ async fn project_vpcs_get( rqctx: Arc>>, query_params: Query, path_params: Path, -) -> Result>, HttpError> { +) -> Result>, HttpError> { let apictx = rqctx.context(); let nexus = &apictx.nexus; let query = query_params.into_inner(); @@ -688,7 +688,7 @@ async fn project_vpcs_get( &data_page_params_for(&rqctx, &query)?, ) .await?; - let view_list = to_view_list(vpc_stream).await; + let view_list = to_list(vpc_stream).await; Ok(HttpResponseOk(ScanByName::results_page(&query, view_list)?)) } @@ -711,14 +711,14 @@ struct VPCPathParam { async fn project_vpcs_get_vpc( rqctx: Arc>>, path_params: Path, -) -> Result, HttpError> { +) -> Result, HttpError> { let apictx = rqctx.context(); let nexus = &apictx.nexus; let path = path_params.into_inner(); let project_name = &path.project_name; let vpc_name = &path.vpc_name; let vpc = nexus.project_lookup_vpc(&project_name, &vpc_name).await?; - Ok(HttpResponseOk(vpc.to_view())) + Ok(HttpResponseOk(vpc.into())) } /** @@ -732,14 +732,14 @@ async fn project_vpcs_post( rqctx: Arc>>, path_params: Path, new_vpc: TypedBody, -) -> Result, HttpError> { +) -> Result, HttpError> { let apictx = rqctx.context(); let nexus = &apictx.nexus; let path = path_params.into_inner(); let project_name = &path.project_name; let new_vpc_params = &new_vpc.into_inner(); let vpc = nexus.project_create_vpc(&project_name, &new_vpc_params).await?; - Ok(HttpResponseCreated(vpc.to_view())) + Ok(HttpResponseCreated(vpc.into())) } /** diff --git a/omicron-nexus/src/nexus.rs b/omicron-nexus/src/nexus.rs index c34cadd1a7e..67f2ca023cc 100644 --- a/omicron-nexus/src/nexus.rs +++ b/omicron-nexus/src/nexus.rs @@ -30,7 +30,6 @@ use omicron_common::api::external::ProjectUpdateParams; use omicron_common::api::external::ResourceType; use omicron_common::api::external::SagaView; use omicron_common::api::external::UpdateResult; -use omicron_common::api::external::VPC; use omicron_common::api::external::VPCCreateParams; use omicron_common::api::internal::nexus::Disk; use omicron_common::api::internal::nexus::DiskRuntimeState; @@ -41,6 +40,7 @@ use omicron_common::api::internal::nexus::ProducerEndpoint; use omicron_common::api::internal::nexus::Project; use omicron_common::api::internal::nexus::Rack; use omicron_common::api::internal::nexus::Sled; +use omicron_common::api::internal::nexus::VPC; use omicron_common::api::internal::sled_agent::DiskStateRequested; use omicron_common::api::internal::sled_agent::InstanceRuntimeStateRequested; use omicron_common::api::internal::sled_agent::InstanceStateRequested; diff --git a/omicron-nexus/tests/output/nexus-openapi.json b/omicron-nexus/tests/output/nexus-openapi.json index c1b6ae2f468..5467cf67d48 100644 --- a/omicron-nexus/tests/output/nexus-openapi.json +++ b/omicron-nexus/tests/output/nexus-openapi.json @@ -990,7 +990,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/VPCViewResultsPage" + "$ref": "#/components/schemas/VPCResultsPage" } } } @@ -1028,7 +1028,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/VPCView" + "$ref": "#/components/schemas/VPC" } } } @@ -1066,7 +1066,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/VPCView" + "$ref": "#/components/schemas/VPC" } } } @@ -1887,23 +1887,7 @@ "items" ] }, - "VPCCreateParams": { - "description": "Create-time parameters for a [`VPC`]", - "type": "object", - "properties": { - "description": { - "type": "string" - }, - "name": { - "$ref": "#/components/schemas/Name" - } - }, - "required": [ - "description", - "name" - ] - }, - "VPCView": { + "VPC": { "description": "Identity-related metadata that's included in nearly all public API objects", "type": "object", "properties": { @@ -1944,7 +1928,23 @@ "timeModified" ] }, - "VPCViewResultsPage": { + "VPCCreateParams": { + "description": "Create-time parameters for a [`VPC`]", + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "name": { + "$ref": "#/components/schemas/Name" + } + }, + "required": [ + "description", + "name" + ] + }, + "VPCResultsPage": { "description": "A single page of results", "type": "object", "properties": { @@ -1952,7 +1952,7 @@ "description": "list of items on this page of results", "type": "array", "items": { - "$ref": "#/components/schemas/VPCView" + "$ref": "#/components/schemas/VPC" } }, "next_page": { diff --git a/omicron-nexus/tests/test_vpcs.rs b/omicron-nexus/tests/test_vpcs.rs index 3a8c9dd1934..3010b561f14 100644 --- a/omicron-nexus/tests/test_vpcs.rs +++ b/omicron-nexus/tests/test_vpcs.rs @@ -1,11 +1,11 @@ use http::method::Method; use http::StatusCode; -use omicron_common::api::IdentityMetadataCreateParams; -use omicron_common::api::Name; -use omicron_common::api::ProjectCreateParams; -use omicron_common::api::ProjectView; -use omicron_common::api::VPCCreateParams; -use omicron_common::api::VPCView; +use omicron_common::api::external::IdentityMetadataCreateParams; +use omicron_common::api::external::Name; +use omicron_common::api::external::ProjectCreateParams; +use omicron_common::api::external::ProjectView; +use omicron_common::api::external::VPCCreateParams; +use omicron_common::api::external::VPC; use std::convert::TryFrom; use dropshot::test_util::object_get; @@ -67,7 +67,7 @@ async fn test_vpcs() { description: String::from("sells rainsticks"), }, }; - let vpc: VPCView = objects_post(&client, &vpcs_url, new_vpc.clone()).await; + let vpc: VPC = objects_post(&client, &vpcs_url, new_vpc.clone()).await; assert_eq!(vpc.identity.name, "just-rainsticks"); assert_eq!(vpc.identity.description, "sells rainsticks"); @@ -110,15 +110,15 @@ async fn test_vpcs() { cptestctx.teardown().await; } -async fn vpcs_list(client: &ClientTestContext, vpcs_url: &str) -> Vec { - objects_list_page::(client, vpcs_url).await.items +async fn vpcs_list(client: &ClientTestContext, vpcs_url: &str) -> Vec { + objects_list_page::(client, vpcs_url).await.items } -async fn vpc_get(client: &ClientTestContext, vpc_url: &str) -> VPCView { - object_get::(client, vpc_url).await +async fn vpc_get(client: &ClientTestContext, vpc_url: &str) -> VPC { + object_get::(client, vpc_url).await } -fn vpcs_eq(vpc1: &VPCView, vpc2: &VPCView) { +fn vpcs_eq(vpc1: &VPC, vpc2: &VPC) { identity_eq(&vpc1.identity, &vpc2.identity); assert_eq!(vpc1.project_id, vpc2.project_id); } From a3b5dc76f95a6d4b708af0d381be6dbe3971ab9c Mon Sep 17 00:00:00 2001 From: David Crespo Date: Wed, 28 Jul 2021 21:00:22 -0500 Subject: [PATCH 07/12] don't need to unwrap and rewrap sql result, don't need cols on delete --- omicron-nexus/src/db/datastore.rs | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/omicron-nexus/src/db/datastore.rs b/omicron-nexus/src/db/datastore.rs index c00741c46ba..b582a4bc474 100644 --- a/omicron-nexus/src/db/datastore.rs +++ b/omicron-nexus/src/db/datastore.rs @@ -762,17 +762,15 @@ impl DataStore { values.set("project_id", project_id); params.sql_serialize(&mut values); - let vpc = - sql_insert_unique_idempotent_and_fetch::( - &client, - &values, - params.identity.name.as_str(), - "id", - (), - &vpc_id, - ) - .await?; - Ok(vpc) + sql_insert_unique_idempotent_and_fetch::( + &client, + &values, + params.identity.name.as_str(), + "id", + (), + &vpc_id, + ) + .await } pub async fn vpc_fetch_by_name( @@ -796,10 +794,8 @@ impl DataStore { &client, format!( "UPDATE {} SET time_deleted = $1 WHERE \ - time_deleted IS NULL AND id = $2 LIMIT 2 \ - RETURNING {}", + time_deleted IS NULL AND id = $2", VPC::TABLE_NAME, - VPC::ALL_COLUMNS.join(", ") ) .as_str(), &[&now, &vpc_id], From 977962007ecd39462401916b2a7b74e3d96b09cb Mon Sep 17 00:00:00 2001 From: David Crespo Date: Thu, 29 Jul 2021 10:29:30 -0500 Subject: [PATCH 08/12] use external VPC model everywhere --- omicron-common/src/api/external/mod.rs | 6 ------ omicron-common/src/api/internal/nexus.rs | 8 -------- omicron-common/src/model_db.rs | 2 +- omicron-nexus/src/db/datastore.rs | 6 +++--- omicron-nexus/src/db/schema.rs | 2 +- omicron-nexus/src/http_entrypoints_external.rs | 4 ++-- omicron-nexus/src/nexus.rs | 2 +- omicron-nexus/tests/test_vpcs.rs | 2 -- 8 files changed, 8 insertions(+), 24 deletions(-) diff --git a/omicron-common/src/api/external/mod.rs b/omicron-common/src/api/external/mod.rs index 8fec292474d..957e16c584d 100644 --- a/omicron-common/src/api/external/mod.rs +++ b/omicron-common/src/api/external/mod.rs @@ -1075,12 +1075,6 @@ pub struct VPC { pub project_id: Uuid, } -impl From for VPC { - fn from(vpc: crate::api::internal::nexus::VPC) -> Self { - VPC { identity: vpc.identity, project_id: vpc.project_id } - } -} - /** * Create-time parameters for a [`VPC`] */ diff --git a/omicron-common/src/api/internal/nexus.rs b/omicron-common/src/api/internal/nexus.rs index b669940cd6d..5da89257985 100644 --- a/omicron-common/src/api/internal/nexus.rs +++ b/omicron-common/src/api/internal/nexus.rs @@ -92,14 +92,6 @@ pub struct InstanceRuntimeState { pub time_updated: DateTime, } -#[derive(Clone, Debug)] -pub struct VPC { - /** common identifying metadata */ - pub identity: IdentityMetadata, - /** id for the project containing this VPC */ - pub project_id: Uuid, -} - /// Sent by a sled agent on startup to Nexus to request further instruction #[derive(Serialize, Deserialize, JsonSchema)] pub struct SledAgentStartupInfo { diff --git a/omicron-common/src/model_db.rs b/omicron-common/src/model_db.rs index ac285ca6601..398f63b317f 100644 --- a/omicron-common/src/model_db.rs +++ b/omicron-common/src/model_db.rs @@ -65,6 +65,7 @@ use crate::api::external::MacAddr; use crate::api::external::Name; use crate::api::external::NetworkInterface; use crate::api::external::VPCSubnet; +use crate::api::external::VPC; use crate::api::external::{Ipv4Net, Ipv6Net}; use crate::api::internal::nexus::Disk; use crate::api::internal::nexus::DiskRuntimeState; @@ -74,7 +75,6 @@ use crate::api::internal::nexus::OximeterAssignment; use crate::api::internal::nexus::OximeterInfo; use crate::api::internal::nexus::ProducerEndpoint; use crate::api::internal::nexus::Project; -use crate::api::internal::nexus::VPC; use crate::bail_unless; use chrono::DateTime; use chrono::Utc; diff --git a/omicron-nexus/src/db/datastore.rs b/omicron-nexus/src/db/datastore.rs index b582a4bc474..66a3e375d7d 100644 --- a/omicron-nexus/src/db/datastore.rs +++ b/omicron-nexus/src/db/datastore.rs @@ -737,7 +737,7 @@ impl DataStore { &self, project_id: &Uuid, pagparams: &DataPageParams<'_, Name>, - ) -> ListResult { + ) -> ListResult { let client = self.pool.acquire().await?; sql_fetch_page_by::< LookupByUniqueNameInProject, @@ -752,7 +752,7 @@ impl DataStore { vpc_id: &Uuid, project_id: &Uuid, params: &api::external::VPCCreateParams, - ) -> Result { + ) -> Result { let client = self.pool.acquire().await?; let now = Utc::now(); let mut values = SqlValueSet::new(); @@ -777,7 +777,7 @@ impl DataStore { &self, project_id: &Uuid, vpc_name: &Name, - ) -> LookupResult { + ) -> LookupResult { let client = self.pool.acquire().await?; sql_fetch_row_by::( &client, diff --git a/omicron-nexus/src/db/schema.rs b/omicron-nexus/src/db/schema.rs index 3d480f5592f..e6fc093c719 100644 --- a/omicron-nexus/src/db/schema.rs +++ b/omicron-nexus/src/db/schema.rs @@ -154,7 +154,7 @@ impl Table for OximeterAssignment { /** Describes the "VPC" table */ pub struct VPC; impl Table for VPC { - type ModelType = api::internal::nexus::VPC; + type ModelType = api::external::VPC; const TABLE_NAME: &'static str = "VPC"; const ALL_COLUMNS: &'static [&'static str] = &[ "id", diff --git a/omicron-nexus/src/http_entrypoints_external.rs b/omicron-nexus/src/http_entrypoints_external.rs index 9c5a0f28481..7456c53bb96 100644 --- a/omicron-nexus/src/http_entrypoints_external.rs +++ b/omicron-nexus/src/http_entrypoints_external.rs @@ -718,7 +718,7 @@ async fn project_vpcs_get_vpc( let project_name = &path.project_name; let vpc_name = &path.vpc_name; let vpc = nexus.project_lookup_vpc(&project_name, &vpc_name).await?; - Ok(HttpResponseOk(vpc.into())) + Ok(HttpResponseOk(vpc)) } /** @@ -739,7 +739,7 @@ async fn project_vpcs_post( let project_name = &path.project_name; let new_vpc_params = &new_vpc.into_inner(); let vpc = nexus.project_create_vpc(&project_name, &new_vpc_params).await?; - Ok(HttpResponseCreated(vpc.into())) + Ok(HttpResponseCreated(vpc)) } /** diff --git a/omicron-nexus/src/nexus.rs b/omicron-nexus/src/nexus.rs index 67f2ca023cc..04287f30cc8 100644 --- a/omicron-nexus/src/nexus.rs +++ b/omicron-nexus/src/nexus.rs @@ -31,6 +31,7 @@ use omicron_common::api::external::ResourceType; use omicron_common::api::external::SagaView; use omicron_common::api::external::UpdateResult; use omicron_common::api::external::VPCCreateParams; +use omicron_common::api::external::VPC; use omicron_common::api::internal::nexus::Disk; use omicron_common::api::internal::nexus::DiskRuntimeState; use omicron_common::api::internal::nexus::Instance; @@ -40,7 +41,6 @@ use omicron_common::api::internal::nexus::ProducerEndpoint; use omicron_common::api::internal::nexus::Project; use omicron_common::api::internal::nexus::Rack; use omicron_common::api::internal::nexus::Sled; -use omicron_common::api::internal::nexus::VPC; use omicron_common::api::internal::sled_agent::DiskStateRequested; use omicron_common::api::internal::sled_agent::InstanceRuntimeStateRequested; use omicron_common::api::internal::sled_agent::InstanceStateRequested; diff --git a/omicron-nexus/tests/test_vpcs.rs b/omicron-nexus/tests/test_vpcs.rs index 3010b561f14..fb79fe48d51 100644 --- a/omicron-nexus/tests/test_vpcs.rs +++ b/omicron-nexus/tests/test_vpcs.rs @@ -24,8 +24,6 @@ extern crate slog; async fn test_vpcs() { let cptestctx = test_setup("test_vpcs").await; let client = &cptestctx.external_client; - // let apictx = &cptestctx.server.apictx; - // let nexus = &apictx.nexus; /* Create a project that we'll use for testing. */ let project_name = "springfield-squidport"; From 6cce1713e039401369eaaedbc1356d21c54b2122 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Thu, 29 Jul 2021 10:57:30 -0500 Subject: [PATCH 09/12] vpc names are unique per project, subnet and NI name unique per VPC --- omicron-common/src/sql/dbinit.sql | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/omicron-common/src/sql/dbinit.sql b/omicron-common/src/sql/dbinit.sql index 12f842fd603..ee176f0b5cf 100644 --- a/omicron-common/src/sql/dbinit.sql +++ b/omicron-common/src/sql/dbinit.sql @@ -261,9 +261,9 @@ CREATE TABLE omicron.public.VPC ( project_id UUID NOT NULL ); --- TODO: add project_id to index CREATE UNIQUE INDEX ON omicron.public.VPC ( - name + name, + project_id ) WHERE time_deleted IS NULL; @@ -281,9 +281,10 @@ CREATE TABLE omicron.public.VPCSubnet ( ipv6_block INET ); --- TODO: add project_id to index +/* Subnet and network interface names are unique per VPC, not project */ CREATE UNIQUE INDEX ON omicron.public.VPCSubnet ( - name + name, + vpc_id ) WHERE time_deleted IS NULL; @@ -312,9 +313,9 @@ CREATE TABLE omicron.public.NetworkInterface ( * as moving IPs between NICs on different instances, etc. */ --- TODO: add project_id to index CREATE UNIQUE INDEX ON omicron.public.NetworkInterface ( - name + name, + vpc_id ) WHERE time_deleted IS NULL; From 4938ba93bac98886611511875fc5dfe2ac1917ad Mon Sep 17 00:00:00 2001 From: David Crespo Date: Thu, 29 Jul 2021 11:01:49 -0500 Subject: [PATCH 10/12] create_project helper in test --- omicron-nexus/tests/test_vpcs.rs | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/omicron-nexus/tests/test_vpcs.rs b/omicron-nexus/tests/test_vpcs.rs index fb79fe48d51..d8ef18f1eb7 100644 --- a/omicron-nexus/tests/test_vpcs.rs +++ b/omicron-nexus/tests/test_vpcs.rs @@ -28,17 +28,7 @@ async fn test_vpcs() { /* Create a project that we'll use for testing. */ let project_name = "springfield-squidport"; let vpcs_url = format!("/projects/{}/vpcs", project_name); - let _: ProjectView = objects_post( - &client, - "/projects", - ProjectCreateParams { - identity: IdentityMetadataCreateParams { - name: Name::try_from(project_name).unwrap(), - description: "a pier".to_string(), - }, - }, - ) - .await; + let _ = create_project(&client, &project_name).await; /* List vpcs. There aren't any yet. */ let vpcs = vpcs_list(&client, &vpcs_url).await; @@ -120,3 +110,20 @@ fn vpcs_eq(vpc1: &VPC, vpc2: &VPC) { identity_eq(&vpc1.identity, &vpc2.identity); assert_eq!(vpc1.project_id, vpc2.project_id); } + +async fn create_project( + client: &ClientTestContext, + project_name: &str, +) -> ProjectView { + objects_post( + &client, + "/projects", + ProjectCreateParams { + identity: IdentityMetadataCreateParams { + name: Name::try_from(project_name).unwrap(), + description: "a pier".to_string(), + }, + }, + ) + .await +} From 50a9d8addc69cbd7d5e191745feb7538fd9b6a56 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Thu, 29 Jul 2021 11:26:24 -0500 Subject: [PATCH 11/12] test making sure vpc names are unique to the project but not globally --- omicron-nexus/tests/test_vpcs.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/omicron-nexus/tests/test_vpcs.rs b/omicron-nexus/tests/test_vpcs.rs index d8ef18f1eb7..23d592c7b32 100644 --- a/omicron-nexus/tests/test_vpcs.rs +++ b/omicron-nexus/tests/test_vpcs.rs @@ -30,6 +30,10 @@ async fn test_vpcs() { let vpcs_url = format!("/projects/{}/vpcs", project_name); let _ = create_project(&client, &project_name).await; + let project_name2 = "pokemon"; + let vpcs_url2 = format!("/projects/{}/vpcs", project_name2); + let _ = create_project(&client, &project_name2).await; + /* List vpcs. There aren't any yet. */ let vpcs = vpcs_list(&client, &vpcs_url).await; assert_eq!(vpcs.len(), 0); @@ -64,12 +68,17 @@ async fn test_vpcs() { .make_request_error_body( Method::POST, &vpcs_url, - new_vpc, + new_vpc.clone(), StatusCode::BAD_REQUEST, ) .await; assert_eq!(error.message, "already exists: vpc \"just-rainsticks\""); + /* creating a VPC with the same name in another project works, though */ + let vpc2: VPC = objects_post(&client, &vpcs_url2, new_vpc.clone()).await; + assert_eq!(vpc2.identity.name, "just-rainsticks"); + assert_eq!(vpc2.identity.description, "sells rainsticks"); + /* List VPCs again and expect to find the one we just created. */ let vpcs = vpcs_list(&client, &vpcs_url).await; assert_eq!(vpcs.len(), 1); From 07f6fcf1e2b1d42d22a0ae0e795d702af76807ce Mon Sep 17 00:00:00 2001 From: David Crespo Date: Thu, 29 Jul 2021 11:39:33 -0500 Subject: [PATCH 12/12] VPC -> Vpc --- omicron-common/src/api/external/mod.rs | 12 ++++---- omicron-common/src/model_db.rs | 12 ++++---- omicron-common/src/sql/dbinit.sql | 8 ++--- omicron-nexus/src/db/conversions.rs | 4 +-- omicron-nexus/src/db/datastore.rs | 24 +++++++-------- omicron-nexus/src/db/schema.rs | 30 +++++++++---------- .../src/http_entrypoints_external.rs | 18 +++++------ omicron-nexus/src/nexus.rs | 12 ++++---- omicron-nexus/tests/output/nexus-openapi.json | 18 +++++------ omicron-nexus/tests/test_vpcs.rs | 20 ++++++------- 10 files changed, 79 insertions(+), 79 deletions(-) diff --git a/omicron-common/src/api/external/mod.rs b/omicron-common/src/api/external/mod.rs index 957e16c584d..5376f88b4f0 100644 --- a/omicron-common/src/api/external/mod.rs +++ b/omicron-common/src/api/external/mod.rs @@ -435,7 +435,7 @@ pub enum ResourceType { Rack, Sled, SagaDbg, - VPC, + Vpc, } impl Display for ResourceType { @@ -451,7 +451,7 @@ impl Display for ResourceType { ResourceType::Rack => "rack", ResourceType::Sled => "sled", ResourceType::SagaDbg => "saga_dbg", - ResourceType::VPC => "vpc", + ResourceType::Vpc => "vpc", } ) } @@ -1067,7 +1067,7 @@ impl From for SagaStateView { #[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "camelCase")] -pub struct VPC { +pub struct Vpc { #[serde(flatten)] pub identity: IdentityMetadata, @@ -1076,11 +1076,11 @@ pub struct VPC { } /** - * Create-time parameters for a [`VPC`] + * Create-time parameters for a [`Vpc`] */ #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "camelCase")] -pub struct VPCCreateParams { +pub struct VpcCreateParams { #[serde(flatten)] pub identity: IdentityMetadataCreateParams, } @@ -1196,7 +1196,7 @@ impl JsonSchema for Ipv6Net { /// A VPC subnet represents a logical grouping for instances that allows network traffic between /// them, within a IPv4 subnetwork or optionall an IPv6 subnetwork. #[derive(Clone, Debug)] -pub struct VPCSubnet { +pub struct VpcSubnet { /** common identifying metadata */ pub identity: IdentityMetadata, diff --git a/omicron-common/src/model_db.rs b/omicron-common/src/model_db.rs index 398f63b317f..8f1efef1791 100644 --- a/omicron-common/src/model_db.rs +++ b/omicron-common/src/model_db.rs @@ -64,8 +64,8 @@ use crate::api::external::InstanceState; use crate::api::external::MacAddr; use crate::api::external::Name; use crate::api::external::NetworkInterface; -use crate::api::external::VPCSubnet; -use crate::api::external::VPC; +use crate::api::external::Vpc; +use crate::api::external::VpcSubnet; use crate::api::external::{Ipv4Net, Ipv6Net}; use crate::api::internal::nexus::Disk; use crate::api::internal::nexus::DiskRuntimeState; @@ -434,8 +434,8 @@ impl TryFrom<&tokio_postgres::Row> for OximeterAssignment { } } -/// Load an [`VPC`] from a row in the `VPC` table. -impl TryFrom<&tokio_postgres::Row> for VPC { +/// Load an [`Vpc`] from a row in the `Vpc` table. +impl TryFrom<&tokio_postgres::Row> for Vpc { type Error = Error; fn try_from(value: &tokio_postgres::Row) -> Result { @@ -446,8 +446,8 @@ impl TryFrom<&tokio_postgres::Row> for VPC { } } -/// Load an [`VPCSubnet`] from a row in the `VPCSubnet` table. -impl TryFrom<&tokio_postgres::Row> for VPCSubnet { +/// Load a [`VpcSubnet`] from a row in the `VpcSubnet` table. +impl TryFrom<&tokio_postgres::Row> for VpcSubnet { type Error = Error; fn try_from(value: &tokio_postgres::Row) -> Result { diff --git a/omicron-common/src/sql/dbinit.sql b/omicron-common/src/sql/dbinit.sql index ee176f0b5cf..d305ef13dab 100644 --- a/omicron-common/src/sql/dbinit.sql +++ b/omicron-common/src/sql/dbinit.sql @@ -249,7 +249,7 @@ CREATE TABLE omicron.public.OximeterAssignment ( * VPCs and networking primitives */ -CREATE TABLE omicron.public.VPC ( +CREATE TABLE omicron.public.Vpc ( /* Identity metadata */ id UUID PRIMARY KEY, name STRING(63) NOT NULL, @@ -261,13 +261,13 @@ CREATE TABLE omicron.public.VPC ( project_id UUID NOT NULL ); -CREATE UNIQUE INDEX ON omicron.public.VPC ( +CREATE UNIQUE INDEX ON omicron.public.Vpc ( name, project_id ) WHERE time_deleted IS NULL; -CREATE TABLE omicron.public.VPCSubnet ( +CREATE TABLE omicron.public.VpcSubnet ( /* Identity metadata */ id UUID PRIMARY KEY, name STRING(63) NOT NULL, @@ -282,7 +282,7 @@ CREATE TABLE omicron.public.VPCSubnet ( ); /* Subnet and network interface names are unique per VPC, not project */ -CREATE UNIQUE INDEX ON omicron.public.VPCSubnet ( +CREATE UNIQUE INDEX ON omicron.public.VpcSubnet ( name, vpc_id ) WHERE diff --git a/omicron-nexus/src/db/conversions.rs b/omicron-nexus/src/db/conversions.rs index 3144cacbbeb..66ff075a367 100644 --- a/omicron-nexus/src/db/conversions.rs +++ b/omicron-nexus/src/db/conversions.rs @@ -12,7 +12,7 @@ use omicron_common::api::external::IdentityMetadataCreateParams; use omicron_common::api::external::InstanceCreateParams; use omicron_common::api::external::InstanceState; use omicron_common::api::external::ProjectCreateParams; -use omicron_common::api::external::VPCCreateParams; +use omicron_common::api::external::VpcCreateParams; use omicron_common::api::internal::nexus::DiskRuntimeState; use omicron_common::api::internal::nexus::InstanceRuntimeState; use omicron_common::api::internal::nexus::OximeterAssignment; @@ -84,7 +84,7 @@ impl SqlSerialize for DiskState { } } -impl SqlSerialize for VPCCreateParams { +impl SqlSerialize for VpcCreateParams { fn sql_serialize(&self, output: &mut SqlValueSet) { self.identity.sql_serialize(output); } diff --git a/omicron-nexus/src/db/datastore.rs b/omicron-nexus/src/db/datastore.rs index 66a3e375d7d..ac835de1cf2 100644 --- a/omicron-nexus/src/db/datastore.rs +++ b/omicron-nexus/src/db/datastore.rs @@ -51,7 +51,7 @@ use super::schema::LookupByUniqueId; use super::schema::LookupByUniqueName; use super::schema::LookupByUniqueNameInProject; use super::schema::Project; -use super::schema::VPC; +use super::schema::Vpc; use super::sql::SqlSerialize; use super::sql::SqlString; use super::sql::SqlValueSet; @@ -737,13 +737,13 @@ impl DataStore { &self, project_id: &Uuid, pagparams: &DataPageParams<'_, Name>, - ) -> ListResult { + ) -> ListResult { let client = self.pool.acquire().await?; sql_fetch_page_by::< LookupByUniqueNameInProject, - VPC, - ::ModelType, - >(&client, (project_id,), pagparams, VPC::ALL_COLUMNS) + Vpc, + ::ModelType, + >(&client, (project_id,), pagparams, Vpc::ALL_COLUMNS) .await } @@ -751,8 +751,8 @@ impl DataStore { &self, vpc_id: &Uuid, project_id: &Uuid, - params: &api::external::VPCCreateParams, - ) -> Result { + params: &api::external::VpcCreateParams, + ) -> Result { let client = self.pool.acquire().await?; let now = Utc::now(); let mut values = SqlValueSet::new(); @@ -762,7 +762,7 @@ impl DataStore { values.set("project_id", project_id); params.sql_serialize(&mut values); - sql_insert_unique_idempotent_and_fetch::( + sql_insert_unique_idempotent_and_fetch::( &client, &values, params.identity.name.as_str(), @@ -777,9 +777,9 @@ impl DataStore { &self, project_id: &Uuid, vpc_name: &Name, - ) -> LookupResult { + ) -> LookupResult { let client = self.pool.acquire().await?; - sql_fetch_row_by::( + sql_fetch_row_by::( &client, (project_id,), vpc_name, @@ -795,11 +795,11 @@ impl DataStore { format!( "UPDATE {} SET time_deleted = $1 WHERE \ time_deleted IS NULL AND id = $2", - VPC::TABLE_NAME, + Vpc::TABLE_NAME, ) .as_str(), &[&now, &vpc_id], - || Error::not_found_by_id(ResourceType::VPC, vpc_id), + || Error::not_found_by_id(ResourceType::Vpc, vpc_id), ) .await } diff --git a/omicron-nexus/src/db/schema.rs b/omicron-nexus/src/db/schema.rs index e6fc093c719..63818b25184 100644 --- a/omicron-nexus/src/db/schema.rs +++ b/omicron-nexus/src/db/schema.rs @@ -151,11 +151,11 @@ impl Table for OximeterAssignment { &["oximeter_id", "producer_id", "time_created"]; } -/** Describes the "VPC" table */ -pub struct VPC; -impl Table for VPC { - type ModelType = api::external::VPC; - const TABLE_NAME: &'static str = "VPC"; +/** Describes the "Vpc" table */ +pub struct Vpc; +impl Table for Vpc { + type ModelType = api::external::Vpc; + const TABLE_NAME: &'static str = "Vpc"; const ALL_COLUMNS: &'static [&'static str] = &[ "id", "name", @@ -167,15 +167,15 @@ impl Table for VPC { ]; } -impl ResourceTable for VPC { - const RESOURCE_TYPE: ResourceType = ResourceType::VPC; +impl ResourceTable for Vpc { + const RESOURCE_TYPE: ResourceType = ResourceType::Vpc; } -/** Describes the "VPCSubnet" table */ -pub struct VPCSubnet; -impl Table for VPCSubnet { - type ModelType = api::external::VPCSubnet; - const TABLE_NAME: &'static str = "VPCSubnet"; +/** Describes the "VpcSubnet" table */ +pub struct VpcSubnet; +impl Table for VpcSubnet { + type ModelType = api::external::VpcSubnet; + const TABLE_NAME: &'static str = "VpcSubnet"; const ALL_COLUMNS: &'static [&'static str] = &[ "id", "name", @@ -217,7 +217,7 @@ mod test { use super::SagaNodeEvent; use super::Table; use super::{MetricProducer, Oximeter, OximeterAssignment}; - use super::{NetworkInterface, VPCSubnet, VPC}; + use super::{NetworkInterface, Vpc, VpcSubnet}; use omicron_common::dev; use std::collections::BTreeSet; use tokio_postgres::types::ToSql; @@ -246,8 +246,8 @@ mod test { check_table_schema::(&client).await; check_table_schema::(&client).await; check_table_schema::(&client).await; - check_table_schema::(&client).await; - check_table_schema::(&client).await; + check_table_schema::(&client).await; + check_table_schema::(&client).await; check_table_schema::(&client).await; database.cleanup().await.expect("failed to clean up database"); diff --git a/omicron-nexus/src/http_entrypoints_external.rs b/omicron-nexus/src/http_entrypoints_external.rs index 7456c53bb96..54f92bc852b 100644 --- a/omicron-nexus/src/http_entrypoints_external.rs +++ b/omicron-nexus/src/http_entrypoints_external.rs @@ -44,8 +44,8 @@ use omicron_common::api::external::ProjectView; use omicron_common::api::external::RackView; use omicron_common::api::external::SagaView; use omicron_common::api::external::SledView; -use omicron_common::api::external::VPCCreateParams; -use omicron_common::api::external::VPC; +use omicron_common::api::external::Vpc; +use omicron_common::api::external::VpcCreateParams; use schemars::JsonSchema; use serde::Deserialize; use std::num::NonZeroU32; @@ -676,7 +676,7 @@ async fn project_vpcs_get( rqctx: Arc>>, query_params: Query, path_params: Path, -) -> Result>, HttpError> { +) -> Result>, HttpError> { let apictx = rqctx.context(); let nexus = &apictx.nexus; let query = query_params.into_inner(); @@ -696,7 +696,7 @@ async fn project_vpcs_get( * Path parameters for VPC requests */ #[derive(Deserialize, JsonSchema)] -struct VPCPathParam { +struct VpcPathParam { project_name: Name, vpc_name: Name, } @@ -710,8 +710,8 @@ struct VPCPathParam { }] async fn project_vpcs_get_vpc( rqctx: Arc>>, - path_params: Path, -) -> Result, HttpError> { + path_params: Path, +) -> Result, HttpError> { let apictx = rqctx.context(); let nexus = &apictx.nexus; let path = path_params.into_inner(); @@ -731,8 +731,8 @@ async fn project_vpcs_get_vpc( async fn project_vpcs_post( rqctx: Arc>>, path_params: Path, - new_vpc: TypedBody, -) -> Result, HttpError> { + new_vpc: TypedBody, +) -> Result, HttpError> { let apictx = rqctx.context(); let nexus = &apictx.nexus; let path = path_params.into_inner(); @@ -751,7 +751,7 @@ async fn project_vpcs_post( }] async fn project_vpcs_delete_vpc( rqctx: Arc>>, - path_params: Path, + path_params: Path, ) -> Result { let apictx = rqctx.context(); let nexus = &apictx.nexus; diff --git a/omicron-nexus/src/nexus.rs b/omicron-nexus/src/nexus.rs index 04287f30cc8..c8b08e52332 100644 --- a/omicron-nexus/src/nexus.rs +++ b/omicron-nexus/src/nexus.rs @@ -30,8 +30,8 @@ use omicron_common::api::external::ProjectUpdateParams; use omicron_common::api::external::ResourceType; use omicron_common::api::external::SagaView; use omicron_common::api::external::UpdateResult; -use omicron_common::api::external::VPCCreateParams; -use omicron_common::api::external::VPC; +use omicron_common::api::external::Vpc; +use omicron_common::api::external::VpcCreateParams; use omicron_common::api::internal::nexus::Disk; use omicron_common::api::internal::nexus::DiskRuntimeState; use omicron_common::api::internal::nexus::Instance; @@ -1006,7 +1006,7 @@ impl Nexus { &self, project_name: &Name, pagparams: &DataPageParams<'_, Name>, - ) -> ListResult { + ) -> ListResult { let project_id = self.db_datastore.project_lookup_id_by_name(project_name).await?; self.db_datastore.project_list_vpcs(&project_id, pagparams).await @@ -1015,8 +1015,8 @@ impl Nexus { pub async fn project_create_vpc( &self, project_name: &Name, - params: &VPCCreateParams, - ) -> CreateResult { + params: &VpcCreateParams, + ) -> CreateResult { let project_id = self.db_datastore.project_lookup_id_by_name(project_name).await?; let id = Uuid::new_v4(); @@ -1031,7 +1031,7 @@ impl Nexus { &self, project_name: &Name, vpc_name: &Name, - ) -> LookupResult { + ) -> LookupResult { let project_id = self.db_datastore.project_lookup_id_by_name(project_name).await?; self.db_datastore.vpc_fetch_by_name(&project_id, vpc_name).await diff --git a/omicron-nexus/tests/output/nexus-openapi.json b/omicron-nexus/tests/output/nexus-openapi.json index 5467cf67d48..98b04113763 100644 --- a/omicron-nexus/tests/output/nexus-openapi.json +++ b/omicron-nexus/tests/output/nexus-openapi.json @@ -990,7 +990,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/VPCResultsPage" + "$ref": "#/components/schemas/VpcResultsPage" } } } @@ -1016,7 +1016,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/VPCCreateParams" + "$ref": "#/components/schemas/VpcCreateParams" } } }, @@ -1028,7 +1028,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/VPC" + "$ref": "#/components/schemas/Vpc" } } } @@ -1066,7 +1066,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/VPC" + "$ref": "#/components/schemas/Vpc" } } } @@ -1887,7 +1887,7 @@ "items" ] }, - "VPC": { + "Vpc": { "description": "Identity-related metadata that's included in nearly all public API objects", "type": "object", "properties": { @@ -1928,8 +1928,8 @@ "timeModified" ] }, - "VPCCreateParams": { - "description": "Create-time parameters for a [`VPC`]", + "VpcCreateParams": { + "description": "Create-time parameters for a [`Vpc`]", "type": "object", "properties": { "description": { @@ -1944,7 +1944,7 @@ "name" ] }, - "VPCResultsPage": { + "VpcResultsPage": { "description": "A single page of results", "type": "object", "properties": { @@ -1952,7 +1952,7 @@ "description": "list of items on this page of results", "type": "array", "items": { - "$ref": "#/components/schemas/VPC" + "$ref": "#/components/schemas/Vpc" } }, "next_page": { diff --git a/omicron-nexus/tests/test_vpcs.rs b/omicron-nexus/tests/test_vpcs.rs index 23d592c7b32..29bbbed4179 100644 --- a/omicron-nexus/tests/test_vpcs.rs +++ b/omicron-nexus/tests/test_vpcs.rs @@ -4,8 +4,8 @@ use omicron_common::api::external::IdentityMetadataCreateParams; use omicron_common::api::external::Name; use omicron_common::api::external::ProjectCreateParams; use omicron_common::api::external::ProjectView; -use omicron_common::api::external::VPCCreateParams; -use omicron_common::api::external::VPC; +use omicron_common::api::external::Vpc; +use omicron_common::api::external::VpcCreateParams; use std::convert::TryFrom; use dropshot::test_util::object_get; @@ -53,13 +53,13 @@ async fn test_vpcs() { assert_eq!(error.message, "not found: vpc with name \"just-rainsticks\""); /* Create a VPC. */ - let new_vpc = VPCCreateParams { + let new_vpc = VpcCreateParams { identity: IdentityMetadataCreateParams { name: Name::try_from("just-rainsticks").unwrap(), description: String::from("sells rainsticks"), }, }; - let vpc: VPC = objects_post(&client, &vpcs_url, new_vpc.clone()).await; + let vpc: Vpc = objects_post(&client, &vpcs_url, new_vpc.clone()).await; assert_eq!(vpc.identity.name, "just-rainsticks"); assert_eq!(vpc.identity.description, "sells rainsticks"); @@ -75,7 +75,7 @@ async fn test_vpcs() { assert_eq!(error.message, "already exists: vpc \"just-rainsticks\""); /* creating a VPC with the same name in another project works, though */ - let vpc2: VPC = objects_post(&client, &vpcs_url2, new_vpc.clone()).await; + let vpc2: Vpc = objects_post(&client, &vpcs_url2, new_vpc.clone()).await; assert_eq!(vpc2.identity.name, "just-rainsticks"); assert_eq!(vpc2.identity.description, "sells rainsticks"); @@ -107,15 +107,15 @@ async fn test_vpcs() { cptestctx.teardown().await; } -async fn vpcs_list(client: &ClientTestContext, vpcs_url: &str) -> Vec { - objects_list_page::(client, vpcs_url).await.items +async fn vpcs_list(client: &ClientTestContext, vpcs_url: &str) -> Vec { + objects_list_page::(client, vpcs_url).await.items } -async fn vpc_get(client: &ClientTestContext, vpc_url: &str) -> VPC { - object_get::(client, vpc_url).await +async fn vpc_get(client: &ClientTestContext, vpc_url: &str) -> Vpc { + object_get::(client, vpc_url).await } -fn vpcs_eq(vpc1: &VPC, vpc2: &VPC) { +fn vpcs_eq(vpc1: &Vpc, vpc2: &Vpc) { identity_eq(&vpc1.identity, &vpc2.identity); assert_eq!(vpc1.project_id, vpc2.project_id); }