Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions end-to-end-tests/src/bin/commtest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ async fn rack_prepare(
.body(ProjectCreate {
description: "A project for probes".into(),
name: "classone".parse().unwrap(),
defaults: None,
})
.send()
.await?;
Expand Down
1 change: 1 addition & 0 deletions end-to-end-tests/src/helpers/ctx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ impl Context {
.body(ProjectCreate {
name: generate_name("proj")?,
description: String::new(),
defaults: None,
})
.send()
.await?
Expand Down
1 change: 1 addition & 0 deletions nexus/db-fixed-data/src/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub static SERVICES_PROJECT: LazyLock<model::Project> = LazyLock::new(|| {
name: SERVICES_DB_NAME.parse().unwrap(),
description: "Built-in project for Oxide Services".to_string(),
},
defaults: None,
},
)
});
1 change: 1 addition & 0 deletions nexus/db-fixed-data/src/vpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ pub static SERVICES_VPC: LazyLock<model::IncompleteVpc> = LazyLock::new(|| {
},
ipv6_prefix: Some(*SERVICE_VPC_IPV6_SUBNET),
dns_name: SERVICES_DB_NAME.parse().unwrap(),
defaults: None,
},
)
// `IncompleteVpc::new` only fails if given an invalid `ipv6_prefix`
Expand Down
1 change: 1 addition & 0 deletions nexus/db-queries/src/db/datastore/disk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2121,6 +2121,7 @@ mod tests {
name: "testpost".parse().unwrap(),
description: "please ignore".to_string(),
},
defaults: None,
},
),
)
Expand Down
1 change: 1 addition & 0 deletions nexus/db-queries/src/db/datastore/external_ip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1818,6 +1818,7 @@ mod tests {
},
ipv6_prefix: Some("fd00::/48".parse().unwrap()),
dns_name: "foo".parse().unwrap(),
defaults: None,
},
)
.unwrap(),
Expand Down
4 changes: 4 additions & 0 deletions nexus/db-queries/src/db/datastore/external_subnet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2383,6 +2383,7 @@ mod tests {
name: "my-project".parse().unwrap(),
description: String::new(),
},
defaults: None,
},
),
)
Expand Down Expand Up @@ -4172,6 +4173,7 @@ mod tests {
name: "my-project".parse().unwrap(),
description: String::new(),
},
defaults: None,
},
),
)
Expand Down Expand Up @@ -4642,6 +4644,7 @@ mod tests {
name: "my-project".parse().unwrap(),
description: String::new(),
},
defaults: None,
},
),
)
Expand Down Expand Up @@ -4767,6 +4770,7 @@ mod tests {
name: "my-project".parse().unwrap(),
description: String::new(),
},
defaults: None,
},
),
)
Expand Down
1 change: 1 addition & 0 deletions nexus/db-queries/src/db/datastore/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2286,6 +2286,7 @@ mod tests {
name: "stuff".parse().unwrap(),
description: "Where I keep my stuff".into(),
},
defaults: None,
},
),
)
Expand Down
2 changes: 2 additions & 0 deletions nexus/db-queries/src/db/datastore/ip_pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2885,6 +2885,7 @@ mod test {
name: "my-project".parse().unwrap(),
description: "".to_string(),
},
defaults: None,
},
);
let (.., project) =
Expand Down Expand Up @@ -2998,6 +2999,7 @@ mod test {
name: "my-project".parse().unwrap(),
description: "".to_string(),
},
defaults: None,
},
);
let (.., project) =
Expand Down
1 change: 1 addition & 0 deletions nexus/db-queries/src/db/datastore/migration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ mod tests {
name: "stuff".parse().unwrap(),
description: "Where I keep my stuff".into(),
},
defaults: None,
},
),
)
Expand Down
1 change: 1 addition & 0 deletions nexus/db-queries/src/db/datastore/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,7 @@ mod test {
name: "project".parse().unwrap(),
description: "desc".to_string(),
},
defaults: None,
},
);
datastore.project_create(&opctx, project).await.unwrap();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,7 @@ mod test {
name: "myproject".parse().unwrap(),
description: "It's a project".into(),
},
defaults: None,
},
),
)
Expand Down
8 changes: 8 additions & 0 deletions nexus/db-queries/src/db/datastore/vpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3020,6 +3020,7 @@ mod tests {
name: "project".parse().unwrap(),
description: String::from("test project"),
},
defaults: None,
};
let project = Project::new(Uuid::new_v4(), project_params);
let (authz_project, _) = datastore
Expand All @@ -3044,6 +3045,7 @@ mod tests {
},
ipv6_prefix: None,
dns_name: name.clone(),
defaults: None,
},
)
.expect("failed to create incomplete VPC");
Expand Down Expand Up @@ -3085,6 +3087,7 @@ mod tests {
},
ipv6_prefix: None,
dns_name: name.clone(),
defaults: None,
},
)
.expect("failed to create incomplete VPC");
Expand Down Expand Up @@ -3125,6 +3128,7 @@ mod tests {
name: "project".parse().unwrap(),
description: String::from("test project"),
},
defaults: None,
};
let project = Project::new(Uuid::new_v4(), project_params);
let (authz_project, _) = datastore
Expand All @@ -3149,6 +3153,7 @@ mod tests {
},
ipv6_prefix: None,
dns_name: name.clone(),
defaults: None,
},
)
.expect("failed to create incomplete VPC");
Expand Down Expand Up @@ -3191,6 +3196,7 @@ mod tests {
},
ipv6_prefix: None,
dns_name: name.clone(),
defaults: None,
},
)
.expect("failed to create incomplete VPC");
Expand Down Expand Up @@ -3550,6 +3556,7 @@ mod tests {
name: "project".parse().unwrap(),
description: String::from("test project"),
},
defaults: None,
};
let project = Project::new(DEFAULT_SILO.id(), project_params);
let (authz_project, _) = datastore
Expand All @@ -3570,6 +3577,7 @@ mod tests {
},
ipv6_prefix: None,
dns_name: vpc_name.clone(),
defaults: None,
},
)
.expect("failed to create incomplete VPC");
Expand Down
1 change: 1 addition & 0 deletions nexus/db-queries/src/db/pub_test_utils/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ pub async fn create_project(
name: name.parse().unwrap(),
description: "desc".to_string(),
},
defaults: None,
},
);
datastore.project_create(&opctx, project).await.unwrap()
Expand Down
1 change: 1 addition & 0 deletions nexus/db-queries/src/db/pub_test_utils/multicast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ pub async fn create_test_setup_with_range(
},
ipv6_prefix: None,
dns_name: format!("{}-vpc", project_name).parse().unwrap(),
defaults: None,
};

let vpc = IncompleteVpc::new(
Expand Down
1 change: 1 addition & 0 deletions nexus/db-queries/src/db/queries/network_interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2147,6 +2147,7 @@ mod tests {
name: "project".parse().unwrap(),
description: "desc".to_string(),
},
defaults: None,
},
);
let (.., project) =
Expand Down
35 changes: 35 additions & 0 deletions nexus/external-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ api_versions!([
// | date-based version should be at the top of the list.
// v
// (next_yyyy_mm_dd_nn, IDENT),
(2026_08_19_02, PROJECT_AND_VPC_CREATE_DEFAULTS),
(2026_08_19_01, BGP_PEER_SRC_ADDR),
(2026_08_17_00, SUPPORT_BUNDLES_STABLE),
(2026_08_14_00, ALERT_LIST),
Expand Down Expand Up @@ -1158,12 +1159,29 @@ pub trait NexusExternalApi {
method = POST,
path = "/v1/projects",
tags = ["projects"],
versions = VERSION_PROJECT_AND_VPC_CREATE_DEFAULTS..,
}]
async fn project_create(
rqctx: RequestContext<Self::Context>,
new_project: TypedBody<latest::project::ProjectCreate>,
) -> Result<HttpResponseCreated<latest::project::Project>, HttpError>;

/// Create project
#[endpoint {
operation_id = "project_create",
method = POST,
path = "/v1/projects",
tags = ["projects"],
versions = ..VERSION_PROJECT_AND_VPC_CREATE_DEFAULTS,
}]
async fn project_create_v2025_11_20_00(
rqctx: RequestContext<Self::Context>,
new_project: TypedBody<v2025_11_20_00::project::ProjectCreate>,
) -> Result<HttpResponseCreated<v2025_11_20_00::project::Project>, HttpError>
{
Self::project_create(rqctx, new_project.map(Into::into)).await
}

/// Fetch project
#[endpoint {
method = GET,
Expand Down Expand Up @@ -6992,13 +7010,30 @@ pub trait NexusExternalApi {
method = POST,
path = "/v1/vpcs",
tags = ["vpcs"],
versions = VERSION_PROJECT_AND_VPC_CREATE_DEFAULTS..,
}]
async fn vpc_create(
rqctx: RequestContext<Self::Context>,
query_params: Query<latest::project::ProjectSelector>,
body: TypedBody<latest::vpc::VpcCreate>,
) -> Result<HttpResponseCreated<latest::vpc::Vpc>, HttpError>;

/// Create VPC
#[endpoint {
operation_id = "vpc_create",
method = POST,
path = "/v1/vpcs",
tags = ["vpcs"],
versions = ..VERSION_PROJECT_AND_VPC_CREATE_DEFAULTS,
}]
async fn vpc_create_v2025_11_20_00(
rqctx: RequestContext<Self::Context>,
query_params: Query<v2025_11_20_00::project::ProjectSelector>,
body: TypedBody<v2025_11_20_00::vpc::VpcCreate>,
) -> Result<HttpResponseCreated<v2025_11_20_00::vpc::Vpc>, HttpError> {
Self::vpc_create(rqctx, query_params, body.map(Into::into)).await
}

/// Fetch VPC
#[endpoint {
method = GET,
Expand Down
8 changes: 8 additions & 0 deletions nexus/src/app/sagas/instance_create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -772,6 +772,14 @@ async fn create_default_primary_network_interface(
.vpc_subnet_name(&internal_default_name)
.fetch()
.await
.map_err(|error| match error {
Error::ObjectNotFound { .. } => Error::non_resourcetype_not_found(
"this project has no VPC or subnet named \"default\", so a \
default network interface cannot be created; pass explicit \
network interface parameters or create the VPC/subnet first",
),
error => error,
})
.map_err(saga_action_failed)?;
let interface = db::model::IncompleteNetworkInterface::new_instance(
*interface_id,
Expand Down
Loading
Loading