Skip to content

Commit 4f9ea44

Browse files
committed
very rough VPC post endpoint
1 parent 9898d9e commit 4f9ea44

File tree

7 files changed

+157
-0
lines changed

7 files changed

+157
-0
lines changed

omicron-common/src/api/model.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1341,6 +1341,16 @@ impl Object for VPC {
13411341
}
13421342
}
13431343

1344+
/**
1345+
* Create-time parameters for a [`VPC`]
1346+
*/
1347+
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
1348+
#[serde(rename_all = "camelCase")]
1349+
pub struct VPCCreateParams {
1350+
#[serde(flatten)]
1351+
pub identity: IdentityMetadataCreateParams,
1352+
}
1353+
13441354
/// An `Ipv4Net` represents a IPv4 subnetwork, including the address and network mask.
13451355
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
13461356
pub struct Ipv4Net(pub ipnet::Ipv4Net);

omicron-nexus/src/db/conversions.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use omicron_common::api::OximeterAssignment;
1717
use omicron_common::api::OximeterInfo;
1818
use omicron_common::api::ProducerEndpoint;
1919
use omicron_common::api::ProjectCreateParams;
20+
use omicron_common::api::VPCCreateParams;
2021

2122
use super::sql::SqlSerialize;
2223
use super::sql::SqlValueSet;
@@ -83,6 +84,12 @@ impl SqlSerialize for DiskState {
8384
}
8485
}
8586

87+
impl SqlSerialize for VPCCreateParams {
88+
fn sql_serialize(&self, output: &mut SqlValueSet) {
89+
self.identity.sql_serialize(output);
90+
}
91+
}
92+
8693
impl SqlSerialize for OximeterInfo {
8794
fn sql_serialize(&self, output: &mut SqlValueSet) {
8895
output.set("id", &self.collector_id);

omicron-nexus/src/db/datastore.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -736,4 +736,32 @@ impl DataStore {
736736
>(&client, (project_id,), pagparams, VPC::ALL_COLUMNS)
737737
.await
738738
}
739+
740+
pub async fn project_create_vpc(
741+
&self,
742+
vpc_id: &Uuid,
743+
project_id: &Uuid,
744+
params: &api::VPCCreateParams,
745+
) -> Result<api::VPC, Error> {
746+
let client = self.pool.acquire().await?;
747+
let now = Utc::now();
748+
let mut values = SqlValueSet::new();
749+
values.set("id", vpc_id);
750+
values.set("time_created", &now);
751+
values.set("time_modified", &now);
752+
values.set("project_id", project_id);
753+
params.sql_serialize(&mut values);
754+
755+
let vpc =
756+
sql_insert_unique_idempotent_and_fetch::<VPC, LookupByUniqueId>(
757+
&client,
758+
&values,
759+
params.identity.name.as_str(),
760+
"id",
761+
(),
762+
&vpc_id
763+
)
764+
.await?;
765+
Ok(vpc)
766+
}
739767
}

omicron-nexus/src/http_entrypoints_external.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ use omicron_common::api::ProjectView;
4444
use omicron_common::api::RackView;
4545
use omicron_common::api::SagaView;
4646
use omicron_common::api::SledView;
47+
use omicron_common::api::VPCCreateParams;
4748
use omicron_common::api::VPCView;
4849
use schemars::JsonSchema;
4950
use serde::Deserialize;
@@ -83,6 +84,7 @@ pub fn external_api() -> NexusApiDescription {
8384
api.register(instance_disks_delete_disk)?;
8485

8586
api.register(project_vpcs_get)?;
87+
api.register(project_vpcs_post)?;
8688

8789
api.register(hardware_racks_get)?;
8890
api.register(hardware_racks_get_rack)?;
@@ -681,6 +683,27 @@ async fn project_vpcs_get(
681683
Ok(HttpResponseOk(ScanById::results_page(&query, view_list)?))
682684
}
683685

686+
/**
687+
* Create a VPC in a project.
688+
*/
689+
#[endpoint {
690+
method = POST,
691+
path = "/projects/{project_name}/vpcs",
692+
}]
693+
async fn project_vpcs_post(
694+
rqctx: Arc<RequestContext<Arc<ServerContext>>>,
695+
path_params: Path<ProjectPathParam>,
696+
new_vpc: TypedBody<VPCCreateParams>,
697+
) -> Result<HttpResponseCreated<VPCView>, HttpError> {
698+
let apictx = rqctx.context();
699+
let nexus = &apictx.nexus;
700+
let path = path_params.into_inner();
701+
let project_name = &path.project_name;
702+
let new_vpc_params = &new_vpc.into_inner();
703+
let vpc = nexus.project_create_vpc(&project_name, &new_vpc_params).await?;
704+
Ok(HttpResponseCreated(vpc.to_view()))
705+
}
706+
684707
/*
685708
* Racks
686709
*/

omicron-nexus/src/nexus.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ use omicron_common::api::ResourceType;
4242
use omicron_common::api::SagaView;
4343
use omicron_common::api::Sled;
4444
use omicron_common::api::UpdateResult;
45+
use omicron_common::api::VPCCreateParams;
4546
use omicron_common::api::VPC;
4647
use omicron_common::bail_unless;
4748
use omicron_common::collection::collection_page;
@@ -1011,6 +1012,18 @@ impl Nexus {
10111012
self.db_datastore.project_list_vpcs(&project_id, pagparams).await
10121013
}
10131014

1015+
pub async fn project_create_vpc(
1016+
&self,
1017+
project_name: &Name,
1018+
params: &VPCCreateParams,
1019+
) -> CreateResult<VPC> {
1020+
let project_id =
1021+
self.db_datastore.project_lookup_id_by_name(project_name).await?;
1022+
let id = Uuid::new_v4();
1023+
let vpc = self.db_datastore.project_create_vpc(&id, &project_id, params).await?;
1024+
Ok(vpc)
1025+
}
1026+
10141027
/*
10151028
* Racks. We simulate just one for now.
10161029
*/

omicron-nexus/tests/test_vpcs.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
1+
use http::method::Method;
2+
use http::StatusCode;
13
use omicron_common::api::IdentityMetadataCreateParams;
24
use omicron_common::api::Name;
35
use omicron_common::api::ProjectCreateParams;
46
use omicron_common::api::ProjectView;
7+
use omicron_common::api::VPCCreateParams;
58
use omicron_common::api::VPCView;
69
use std::convert::TryFrom;
710

11+
use dropshot::test_util::object_get;
812
use dropshot::test_util::objects_list_page;
913
use dropshot::test_util::objects_post;
1014
use dropshot::test_util::ClientTestContext;
1115

1216
pub mod common;
17+
use common::identity_eq;
1318
use common::test_setup;
1419

1520
#[macro_use]
@@ -40,8 +45,72 @@ async fn test_vpcs() {
4045
/* List vpcs. There aren't any yet. */
4146
let vpcs = vpcs_list(&client, &vpcs_url).await;
4247
assert_eq!(vpcs.len(), 0);
48+
49+
// /* Make sure we get a 404 if we fetch one. */
50+
let vpc_url = format!("{}/just-rainsticks", vpcs_url);
51+
52+
// let error = client
53+
// .make_request_error(Method::GET, &vpc_url, StatusCode::NOT_FOUND)
54+
// .await;
55+
// assert_eq!(
56+
// error.message,
57+
// "not found: vpc with name \"just-rainsticks\""
58+
// );
59+
60+
// /* Ditto if we try to delete one. */
61+
// let error = client
62+
// .make_request_error(
63+
// Method::DELETE,
64+
// &vpc_url,
65+
// StatusCode::NOT_FOUND,
66+
// )
67+
// .await;
68+
// assert_eq!(
69+
// error.message,
70+
// "not found: vpc with name \"just-rainsticks\""
71+
// );
72+
73+
/* Create a VPC. */
74+
let new_vpc = VPCCreateParams {
75+
identity: IdentityMetadataCreateParams {
76+
name: Name::try_from("just-rainsticks").unwrap(),
77+
description: String::from("sells rainsticks"),
78+
},
79+
};
80+
let vpc: VPCView = objects_post(&client, &vpcs_url, new_vpc.clone()).await;
81+
assert_eq!(vpc.identity.name, "just-rainsticks");
82+
assert_eq!(vpc.identity.description, "sells rainsticks");
83+
84+
/* Attempt to create a second VPC with a conflicting name. */
85+
// let error = client
86+
// .make_request_error_body(
87+
// Method::POST,
88+
// &vpcs_url,
89+
// new_vpc,
90+
// StatusCode::BAD_REQUEST,
91+
// )
92+
// .await;
93+
// assert_eq!(error.message, "already exists: instance \"just-rainsticks\"");
94+
95+
/* List VPCs again and expect to find the one we just created. */
96+
let vpcs = vpcs_list(&client, &vpcs_url).await;
97+
assert_eq!(vpcs.len(), 1);
98+
vpcs_eq(&vpcs[0], &vpc);
99+
100+
/* Fetch the VPC and expect it to match. */
101+
// let vpc = vpc_get(&client, &vpc_url).await;
102+
// vpcs_eq(&vpcs[0], &vpc);
43103
}
44104

45105
async fn vpcs_list(client: &ClientTestContext, vpcs_url: &str) -> Vec<VPCView> {
46106
objects_list_page::<VPCView>(client, vpcs_url).await.items
47107
}
108+
109+
async fn vpc_get(client: &ClientTestContext, vpc_url: &str) -> VPCView {
110+
object_get::<VPCView>(client, vpc_url).await
111+
}
112+
113+
fn vpcs_eq(vpc1: &VPCView, vpc2: &VPCView) {
114+
identity_eq(&vpc1.identity, &vpc2.identity);
115+
assert_eq!(vpc1.project_id, vpc2.project_id);
116+
}

tools/oxapi_demo

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,13 @@ function cmd_disk_delete
245245
do_curl "/projects/$1/disks/$2" -X DELETE
246246
}
247247

248+
function cmd_vpc_create_demo
249+
{
250+
[[ $# != 2 ]] && usage "expected PROJECT_NAME VPC_NAME"
251+
mkjson name="$2" description="a disk called $2" |
252+
do_curl "/projects/$1/vpcs" -X POST -T -
253+
}
254+
248255
function cmd_racks_list
249256
{
250257
[[ $# != 0 ]] && usage "expected no arguments"

0 commit comments

Comments
 (0)