From a0b2e896f4d44ac3a9578a7eb98fc4807a28f0af Mon Sep 17 00:00:00 2001 From: bnaecker Date: Tue, 15 Mar 2022 14:30:40 -0700 Subject: [PATCH 1/4] Distinguishes between IPv4 and v6 subnet overlap when creating VPC (#760) Subnets - Extends `SubnetError` variant for communicating overlapping address ranges to contain the actual subnet that caused the problem. This can be used to determine if it was a V4 or V6, client-provided or auto-generated subnet. - Reworks the SQL for checking this case. It now tries to insert NULL for a subnet if that's been found to overlap with an existing range, which violates a not-NULL constraint on the `vpc_subnet` table. By inspecting which constraint failed, we can learn which input caused the issue. - Updates tests to verify, ensuring we get the actual bad subnet back --- nexus/src/db/datastore.rs | 25 +- nexus/src/db/subnet_allocation.rs | 401 ++++++++++--------- nexus/src/nexus.rs | 43 +- nexus/tests/integration_tests/vpc_subnets.rs | 6 +- 4 files changed, 251 insertions(+), 224 deletions(-) diff --git a/nexus/src/db/datastore.rs b/nexus/src/db/datastore.rs index 1947f46a9a..dc7b684dfc 100644 --- a/nexus/src/db/datastore.rs +++ b/nexus/src/db/datastore.rs @@ -35,10 +35,7 @@ use crate::context::OpContext; use crate::db::fixed_data::role_assignment_builtin::BUILTIN_ROLE_ASSIGNMENTS; use crate::db::fixed_data::role_builtin::BUILTIN_ROLES; use crate::external_api::params; -use async_bb8_diesel::{ - AsyncConnection, AsyncRunQueryDsl, ConnectionError, ConnectionManager, - PoolError, -}; +use async_bb8_diesel::{AsyncConnection, AsyncRunQueryDsl, ConnectionManager}; use chrono::Utc; use diesel::pg::Pg; use diesel::prelude::*; @@ -2537,29 +2534,13 @@ impl DataStore { subnet: VpcSubnet, ) -> Result { use db::schema::vpc_subnet::dsl; - let name = subnet.name().clone(); - let values = FilterConflictingVpcSubnetRangesQuery(subnet); + let values = FilterConflictingVpcSubnetRangesQuery(subnet.clone()); diesel::insert_into(dsl::vpc_subnet) .values(values) .returning(VpcSubnet::as_returning()) .get_result_async(self.pool()) .await - .map_err(|err| { - if let PoolError::Connection(ConnectionError::Query( - diesel::result::Error::NotFound, - )) = err - { - SubnetError::OverlappingIpRange - } else { - SubnetError::External(public_error_from_diesel_pool( - err, - ErrorHandler::Conflict( - ResourceType::VpcSubnet, - name.to_string().as_str(), - ), - )) - } - }) + .map_err(|e| SubnetError::from_pool(e, &subnet)) } pub async fn vpc_delete_subnet( diff --git a/nexus/src/db/subnet_allocation.rs b/nexus/src/db/subnet_allocation.rs index 991dc24902..e6ecf5ec02 100644 --- a/nexus/src/db/subnet_allocation.rs +++ b/nexus/src/db/subnet_allocation.rs @@ -7,6 +7,7 @@ use crate::db; use crate::db::identity::Resource; use crate::db::model::IncompleteNetworkInterface; +use crate::db::model::VpcSubnet; use chrono::{DateTime, Utc}; use diesel::pg::Pg; use diesel::prelude::*; @@ -41,12 +42,166 @@ fn generate_last_address_offset(subnet: &ipnetwork::IpNetwork) -> i64 { } /// Errors related to allocating VPC Subnets. -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum SubnetError { - OverlappingIpRange, + /// An IPv4 or IPv6 subnet overlaps with an existing VPC Subnet + OverlappingIpRange(ipnetwork::IpNetwork), + /// An other error External(external::Error), } +impl SubnetError { + /// Construct a `SubnetError` from a Diesel error, catching the desired + /// cases and building useful errors. + pub fn from_pool( + e: async_bb8_diesel::PoolError, + subnet: &VpcSubnet, + ) -> Self { + use crate::db::error; + use async_bb8_diesel::ConnectionError; + use async_bb8_diesel::PoolError; + use diesel::result::DatabaseErrorKind; + use diesel::result::Error; + const IPV4_OVERLAP_ERROR_MESSAGE: &str = + r#"null value in column "ipv4_block" violates not-null constraint"#; + const IPV6_OVERLAP_ERROR_MESSAGE: &str = + r#"null value in column "ipv6_block" violates not-null constraint"#; + const NAME_CONFLICT_CONSTRAINT: &str = "vpc_subnet_vpc_id_name_key"; + match e { + // Attempt to insert overlapping IPv4 subnet + PoolError::Connection(ConnectionError::Query( + Error::DatabaseError( + DatabaseErrorKind::NotNullViolation, + ref info, + ), + )) if info.message() == IPV4_OVERLAP_ERROR_MESSAGE => { + SubnetError::OverlappingIpRange(subnet.ipv4_block.0 .0.into()) + } + + // Attempt to insert overlapping IPv6 subnet + PoolError::Connection(ConnectionError::Query( + Error::DatabaseError( + DatabaseErrorKind::NotNullViolation, + ref info, + ), + )) if info.message() == IPV6_OVERLAP_ERROR_MESSAGE => { + SubnetError::OverlappingIpRange(subnet.ipv6_block.0 .0.into()) + } + + // Conflicting name for the subnet within a VPC + PoolError::Connection(ConnectionError::Query( + Error::DatabaseError( + DatabaseErrorKind::UniqueViolation, + ref info, + ), + )) if info.constraint_name() == Some(NAME_CONFLICT_CONSTRAINT) => { + SubnetError::External(error::public_error_from_diesel_pool( + e, + error::ErrorHandler::Conflict( + external::ResourceType::VpcSubnet, + subnet.identity().name.as_str(), + ), + )) + } + + // Any other error at all is a bug + _ => SubnetError::External(error::public_error_from_diesel_pool( + e, + error::ErrorHandler::Server, + )), + } + } + + /// Convert into a public error + pub fn into_external(self) -> external::Error { + match self { + SubnetError::OverlappingIpRange(ip) => { + external::Error::invalid_request( + format!("IP address range '{}' conflicts with an existing subnet", ip).as_str() + ) + }, + SubnetError::External(e) => e, + } + } +} + +/// Generate a subquery that selects any overlapping address ranges of the same +/// type as the input IP subnet. +/// +/// This generates a query that, in full, looks like: +/// +/// ```sql +/// SELECT +/// +/// FROM +/// vpc_subnet +/// WHERE +/// vpc_id = AND +/// time_deleted IS NULL AND +/// inet_contains_or_equals(ipv*_block, ) +/// LIMIT 1 +/// ``` +/// +/// The input may be either an IPv4 or IPv6 subnet, and the corresponding column +/// is compared against. Note that the exact input IP range is returned on +/// purpose. +fn push_select_overlapping_ip_range( + mut out: AstPass, + vpc_id: &Uuid, + ip: &ipnetwork::IpNetwork, +) -> diesel::QueryResult<()> { + use crate::db::schema::vpc_subnet::dsl; + out.push_sql("SELECT "); + out.push_bind_param::(ip)?; + out.push_sql(" FROM "); + dsl::vpc_subnet.from_clause().walk_ast(out.reborrow())?; + out.push_sql(" WHERE "); + out.push_identifier(dsl::vpc_id::NAME)?; + out.push_sql(" = "); + out.push_bind_param::(vpc_id)?; + out.push_sql(" AND "); + out.push_identifier(dsl::time_deleted::NAME)?; + out.push_sql(" IS NULL AND inet_contains_or_equals("); + if ip.is_ipv4() { + out.push_identifier(dsl::ipv4_block::NAME)?; + } else { + out.push_identifier(dsl::ipv6_block::NAME)?; + } + out.push_sql(", "); + out.push_bind_param::(ip)?; + out.push_sql(")"); + Ok(()) +} + +/// Generate a subquery that returns NULL if there is an overlapping IP address +/// range of any type. +/// +/// This specifically generates a query that looks like: +/// +/// ```sql +/// SELECT NULLIF( +/// , +/// push_select_overlapping_ip_range(, ) +/// ) +/// ``` +/// +/// The `NULLIF` function returns NULL if those two expressions are equal, and +/// the first expression otherwise. That is, this returns NULL if there exists +/// an overlapping IP range already in the VPC Subnet table, and the requested +/// IP range if not. +fn push_null_if_overlapping_ip_range( + mut out: AstPass, + vpc_id: &Uuid, + ip: &ipnetwork::IpNetwork, +) -> diesel::QueryResult<()> { + out.push_sql("SELECT NULLIF("); + out.push_bind_param::(ip)?; + out.push_sql(", ("); + push_select_overlapping_ip_range(out.reborrow(), vpc_id, ip)?; + out.push_sql("))"); + Ok(()) +} + /// Generate a CTE that can be used to insert a VPC Subnet, only if the IP /// address ranges of that subnet don't overlap with existing Subnets in the /// same VPC. @@ -61,9 +216,7 @@ pub enum SubnetError { /// time_created, /// time_modified, /// time_deleted, -/// vpc_id, -/// ipv4_block, -/// ipv6_block +/// vpc_id /// ) AS (VALUES ( /// , /// , @@ -72,24 +225,32 @@ pub enum SubnetError { /// , /// NULL::TIMESTAMPTZ, /// , -/// , -/// -/// )) -/// SELECT * -/// FROM candidate -/// WHERE NOT EXISTS ( -/// SELECT ipv4_block, ipv6_block -/// FROM vpc_subnet -/// WHERE -/// vpc_id = AND -/// time_deleted IS NULL AND -/// ( -/// inet_contains_or_equals(ipv4_block, candidate.ipv4_block) OR -/// inet_contains_or_equals(ipv6_block, candidate.ipv6_block) -/// ) +/// )), +/// candidate_ipv4(ipv4_block) AS ( +/// SELECT( +/// NULLIF( +/// , +/// ( +/// SELECT +/// ipv4_block +/// FROM +/// vpc_subnet +/// WHERE +/// vpc_id = AND +/// time_deleted IS NULL AND +/// inet_contains_or_equals(, ipv4_block) +/// LIMIT 1 +/// ) +/// ) +/// ) +/// ), +/// candidate_ipv6(ipv6_block) AS ( +/// /// ) +/// SELECT * +/// FROM candidate, candidate_ipv4, candidate_ipv6 /// ``` -pub struct FilterConflictingVpcSubnetRangesQuery(pub db::model::VpcSubnet); +pub struct FilterConflictingVpcSubnetRangesQuery(pub VpcSubnet); impl QueryId for FilterConflictingVpcSubnetRangesQuery { type QueryId = (); @@ -100,151 +261,68 @@ impl QueryFragment for FilterConflictingVpcSubnetRangesQuery { fn walk_ast(&self, mut out: AstPass) -> diesel::QueryResult<()> { use db::schema::vpc_subnet::dsl; - // "SELECT * FROM (WITH candidate(" + // Create the base `candidate` from values provided that need no + // verificiation. out.push_sql("SELECT * FROM (WITH candidate("); - - // "id, " out.push_identifier(dsl::id::NAME)?; out.push_sql(", "); - - // "name, " out.push_identifier(dsl::name::NAME)?; out.push_sql(", "); - - // "description, " out.push_identifier(dsl::description::NAME)?; out.push_sql(", "); - - // "time_created, " out.push_identifier(dsl::time_created::NAME)?; out.push_sql(", "); - - // "time_modified, " out.push_identifier(dsl::time_modified::NAME)?; out.push_sql(", "); - - // "time_deleted, " out.push_identifier(dsl::time_deleted::NAME)?; out.push_sql(", "); - - // "vpc_id, " out.push_identifier(dsl::vpc_id::NAME)?; - out.push_sql(", "); - - // "ipv4_block, " - out.push_identifier(dsl::ipv4_block::NAME)?; - out.push_sql(", "); - - // "ipv6_block) AS (VALUES (" - out.push_identifier(dsl::ipv6_block::NAME)?; out.push_sql(") AS (VALUES ("); - - // ", " out.push_bind_param::(&self.0.id())?; out.push_sql(", "); - - // ", " out.push_bind_param::( &self.0.name().to_string(), )?; out.push_sql(", "); - - // ", " out.push_bind_param::(&self.0.description())?; out.push_sql(", "); - - // ", " out.push_bind_param::>( &self.0.time_created(), )?; out.push_sql(", "); - - // ", " out.push_bind_param::>( &self.0.time_modified(), )?; out.push_sql(", "); - - // "NULL::TIMESTAMPTZ, " out.push_sql("NULL::TIMESTAMPTZ, "); - - // ", " out.push_bind_param::(&self.0.vpc_id)?; - out.push_sql(", "); - - // , " - out.push_bind_param::( - &ipnetwork::IpNetwork::from(self.0.ipv4_block.0 .0), - )?; - out.push_sql(", "); + out.push_sql(")), "); - // ")" - out.push_bind_param::( - &ipnetwork::IpNetwork::from(self.0.ipv6_block.0 .0), - )?; - out.push_sql("))"); - - /* - * Possibly filter the candidate row. - * - * This selects everything in the `candidate` CTE, where there is no - * "overlapping" row in the `vpc_subnet` table. Specifically, we search - * that table for rows with: - * - * - The same `vpc_id` - * - Not soft-deleted - * - The IPv4 range overlaps _or_ the IPv6 range overlaps - * - * Those are removed from `candidate`. - */ - - // " SELECT * FROM candidate WHERE NOT EXISTS (" - out.push_sql(" SELECT * FROM candidate WHERE NOT EXISTS ("); - - // " SELECT " - out.push_sql("SELECT "); - - // "ipv4_block, " + // Push the candidate IPv4 and IPv6 selection subqueries, which return + // NULL if the corresponding address range overlaps. + out.push_sql("candidate_ipv4("); out.push_identifier(dsl::ipv4_block::NAME)?; - out.push_sql(", "); - - // "ipv6_block " - out.push_identifier(dsl::ipv6_block::NAME)?; - - // "FROM vpc_subnet" - out.push_sql(" FROM "); - dsl::vpc_subnet.from_clause().walk_ast(out.reborrow())?; - - // " WHERE " - out.push_sql(" WHERE "); - - // "vpc_id = " - out.push_identifier(dsl::vpc_id::NAME)?; - out.push_sql(" = "); - out.push_bind_param::(&self.0.vpc_id)?; - - // " AND time_deleted IS NULL AND ((" - out.push_sql(" AND "); - out.push_identifier(dsl::time_deleted::NAME)?; - out.push_sql(" IS NULL AND ("); - - // "inet_contains_or_equals(ipv4_block, - out.push_sql("inet_contains_or_equals("); - out.push_identifier(dsl::ipv4_block::NAME)?; - out.push_sql(", "); - out.push_bind_param::( + out.push_sql(") AS ("); + push_null_if_overlapping_ip_range( + out.reborrow(), + &self.0.vpc_id, &ipnetwork::IpNetwork::from(self.0.ipv4_block.0 .0), )?; - // ") OR inet_contains_or_equals(ipv6_block, ))" - out.push_sql(") OR inet_contains_or_equals("); + out.push_sql("), candidate_ipv6("); out.push_identifier(dsl::ipv6_block::NAME)?; - out.push_sql(", "); - out.push_bind_param::( + out.push_sql(") AS ("); + push_null_if_overlapping_ip_range( + out.reborrow(), + &self.0.vpc_id, &ipnetwork::IpNetwork::from(self.0.ipv6_block.0 .0), )?; - out.push_sql("))))"); + out.push_sql(") "); + // Select the entire set of candidate columns. + out.push_sql( + "SELECT * FROM candidate, candidate_ipv4, candidate_ipv6)", + ); Ok(()) } } @@ -1054,13 +1132,12 @@ impl QueryFragment for InsertNetworkInterfaceQueryValues { #[cfg(test)] mod test { - use super::FilterConflictingVpcSubnetRangesQuery; use super::NetworkInterfaceError; use super::SubnetError; use crate::db::model::{ self, IncompleteNetworkInterface, NetworkInterface, VpcSubnet, }; - use diesel::pg::Pg; + use ipnetwork::IpNetwork; use nexus_test_utils::db::test_setup_database; use omicron_common::api::external::{ Error, IdentityMetadataCreateParams, Ipv4Net, Ipv6Net, Name, @@ -1070,46 +1147,6 @@ mod test { use std::sync::Arc; use uuid::Uuid; - #[test] - fn test_filter_conflicting_vpc_subnet_ranges_query_string() { - use crate::db::identity::Resource; - let ipv4_block = Ipv4Net("172.30.0.0/22".parse().unwrap()); - let ipv6_block = Ipv6Net("fd12:3456:7890::/64".parse().unwrap()); - let name = "a-name".to_string().try_into().unwrap(); - let description = "some description".to_string(); - let identity = IdentityMetadataCreateParams { name, description }; - let vpc_id = Uuid::new_v4(); - let subnet_id = Uuid::new_v4(); - let row = - VpcSubnet::new(subnet_id, vpc_id, identity, ipv4_block, ipv6_block); - let query = FilterConflictingVpcSubnetRangesQuery(row.clone()); - let query_str = diesel::debug_query::(&query).to_string(); - let expected_query = format!( - concat!( - "SELECT * FROM (WITH candidate(", - r#""id", "name", "description", "time_created", "time_modified", "#, - r#""time_deleted", "vpc_id", "ipv4_block", "ipv6_block") AS "#, - "(VALUES ($1, $2, $3, $4, $5, NULL::TIMESTAMPTZ, $6, $7, $8)) ", - "SELECT * FROM candidate WHERE NOT EXISTS (", - r#"SELECT "ipv4_block", "ipv6_block" FROM "vpc_subnet" WHERE "#, - r#""vpc_id" = $9 AND "time_deleted" IS NULL AND ("#, - r#"inet_contains_or_equals("ipv4_block", $10) OR inet_contains_or_equals("ipv6_block", $11)))) "#, - r#"-- binds: [{subnet_id}, "{name}", "{description}", {time_created:?}, "#, - r#"{time_modified:?}, {vpc_id}, V4({ipv4_block:?}), V6({ipv6_block:?}), "#, - r#"{vpc_id}, V4({ipv4_block:?}), V6({ipv6_block:?})]"#, - ), - subnet_id = row.id(), - name = row.name(), - description = row.description(), - vpc_id = row.vpc_id, - ipv4_block = row.ipv4_block.0 .0, - ipv6_block = row.ipv6_block.0 .0, - time_created = row.time_created(), - time_modified = row.time_modified(), - ); - assert_eq!(query_str, expected_query); - } - #[tokio::test] async fn test_filter_conflicting_vpc_subnet_ranges_query() { let make_id = @@ -1162,9 +1199,9 @@ mod test { assert!( matches!( db_datastore.vpc_create_subnet_raw(new_row).await, - Err(SubnetError::OverlappingIpRange) + Err(SubnetError::OverlappingIpRange(IpNetwork::V4(_))) ), - "Should not be able to insert new VPC subnet with the same IP ranges" + "Should not be able to insert new VPC subnet with the same IPv4 and IPv6 ranges" ); // We should be able to insert data with the same ranges, if we change @@ -1190,12 +1227,14 @@ mod test { other_ipv4_block, ipv6_block, ); - assert!( - matches!( - db_datastore.vpc_create_subnet_raw(new_row).await, - Err(SubnetError::OverlappingIpRange), - ), - "Should not be able to insert VPC Subnet with overlapping IPv4 range" + let err = db_datastore + .vpc_create_subnet_raw(new_row) + .await + .expect_err("Should not be able to insert VPC Subnet with overlapping IPv6 range"); + assert_eq!( + err, + SubnetError::OverlappingIpRange(IpNetwork::from(ipv6_block.0)), + "SubnetError variant should include the exact IP range that overlaps" ); let new_row = VpcSubnet::new( other_subnet_id, @@ -1204,12 +1243,14 @@ mod test { ipv4_block, other_ipv6_block, ); - assert!( - matches!( - db_datastore.vpc_create_subnet_raw(new_row).await, - Err(SubnetError::OverlappingIpRange), - ), - "Should not be able to insert VPC Subnet with overlapping IPv6 range" + let err = db_datastore + .vpc_create_subnet_raw(new_row) + .await + .expect_err("Should not be able to insert VPC Subnet with overlapping IPv4 range"); + assert_eq!( + err, + SubnetError::OverlappingIpRange(IpNetwork::from(ipv4_block.0)), + "SubnetError variant should include the exact IP range that overlaps" ); // We should get an _external error_ if the IP address ranges are OK, diff --git a/nexus/src/nexus.rs b/nexus/src/nexus.rs index 232b64bccb..6b921cd066 100644 --- a/nexus/src/nexus.rs +++ b/nexus/src/nexus.rs @@ -1822,12 +1822,15 @@ impl Nexus { .vpc_create_subnet(opctx, &authz_vpc, subnet) .await .map_err(|err| match err { - SubnetError::OverlappingIpRange => { + SubnetError::OverlappingIpRange(ip) => { let ipv4_block = &defaults::DEFAULT_VPC_SUBNET_IPV4_BLOCK; - warn!( + error!( self.log, - "failed to create default VPC Subnet, \ - found overlapping IP address ranges"; + concat!( + "failed to create default VPC Subnet, IP address ", + "range '{}' overlaps with existing", + ), + ip; "vpc_id" => ?vpc_id, "subnet_id" => ?default_subnet_id, "ipv4_block" => ?**ipv4_block, @@ -2080,8 +2083,12 @@ impl Nexus { .await; match result { // Allow NUM_RETRIES retries, after the first attempt. - Err(SubnetError::OverlappingIpRange) - if retry <= NUM_RETRIES => + // + // Note that we only catch IPv6 overlaps. The client + // always specifies the IPv4 range, so we fail the + // request if that overlaps with an existing range. + Err(SubnetError::OverlappingIpRange(ip)) + if retry <= NUM_RETRIES && ip.is_ipv6() => { debug!( self.log, @@ -2096,7 +2103,9 @@ impl Nexus { } }; match result { - Err(SubnetError::OverlappingIpRange) => { + Err(SubnetError::OverlappingIpRange(ip)) + if ip.is_ipv6() => + { // TODO-monitoring TODO-debugging // // We should maintain a counter for this occurrence, and @@ -2105,7 +2114,7 @@ impl Nexus { // goal here is for us to notice that this is happening // before it becomes a major issue for customers. let vpc_id = authz_vpc.id(); - warn!( + error!( self.log, "failed to generate unique random IPv6 address \ range in {} retries", @@ -2118,6 +2127,10 @@ impl Nexus { for VPC Subnet", )) } + Err(SubnetError::OverlappingIpRange(_)) => { + // Overlapping IPv4 ranges, which is always a client error. + Err(result.unwrap_err().into_external()) + } Err(SubnetError::External(e)) => Err(e), Ok(subnet) => Ok(subnet), } @@ -2142,19 +2155,7 @@ impl Nexus { self.db_datastore .vpc_create_subnet(opctx, &authz_vpc, subnet) .await - .map_err(|err| match err { - SubnetError::OverlappingIpRange => { - external::Error::invalid_request(&format!( - concat!( - "IPv4 block '{}' and/or IPv6 block '{}' ", - "overlaps with existing VPC Subnet ", - "IP address ranges" - ), - params.ipv4_block, ipv6_block, - )) - } - SubnetError::External(e) => e, - }) + .map_err(SubnetError::into_external) } } } diff --git a/nexus/tests/integration_tests/vpc_subnets.rs b/nexus/tests/integration_tests/vpc_subnets.rs index ae507ee54c..e66192919b 100644 --- a/nexus/tests/integration_tests/vpc_subnets.rs +++ b/nexus/tests/integration_tests/vpc_subnets.rs @@ -157,6 +157,10 @@ async fn test_vpc_subnets(cptestctx: &ControlPlaneTestContext) { ipv4_block, ipv6_block, }; + let expected_error = format!( + "IP address range '{}' conflicts with an existing subnet", + ipv4_block, + ); let error: dropshot::HttpErrorResponseBody = NexusRequest::new( RequestBuilder::new(client, Method::POST, &subnets_url) .expect_status(Some(StatusCode::BAD_REQUEST)) @@ -168,7 +172,7 @@ async fn test_vpc_subnets(cptestctx: &ControlPlaneTestContext) { .unwrap() .parsed_body() .unwrap(); - assert!(error.message.starts_with("IPv4 block '")); + assert_eq!(error.message, expected_error); // creating another subnet in the same VPC with the same name, but different // IP address ranges also fails. From 6db44796e75939cbbbfaf3c1c3754cea8ee39149 Mon Sep 17 00:00:00 2001 From: Sean Klein Date: Tue, 15 Mar 2022 19:31:32 -0400 Subject: [PATCH 2/4] [nexus] Add stubs for snapshot APIs (#748) First part of #735 --- common/src/sql/dbinit.sql | 29 ++ nexus/src/db/model.rs | 37 ++- nexus/src/db/schema.rs | 16 + nexus/src/external_api/http_entrypoints.rs | 151 ++++++++- nexus/src/external_api/params.rs | 15 + nexus/src/external_api/tag-config.json | 8 +- nexus/src/external_api/views.rs | 18 +- nexus/src/nexus.rs | 40 +++ nexus/tests/output/nexus_tags.txt | 7 + openapi/nexus.json | 347 +++++++++++++++++++++ 10 files changed, 664 insertions(+), 4 deletions(-) diff --git a/common/src/sql/dbinit.sql b/common/src/sql/dbinit.sql index d332f60b03..bb4028ec2f 100644 --- a/common/src/sql/dbinit.sql +++ b/common/src/sql/dbinit.sql @@ -371,6 +371,35 @@ CREATE INDEX ON omicron.public.disk ( time_deleted IS NULL AND attach_instance_id IS NOT NULL; +CREATE TABLE omicron.public.snapshot ( + /* Identity metadata (resource) */ + id UUID PRIMARY KEY, + name STRING(63) NOT NULL, + description STRING(512) NOT NULL, + time_created TIMESTAMPTZ NOT NULL, + time_modified TIMESTAMPTZ NOT NULL, + /* Indicates that the object has been deleted */ + time_deleted TIMESTAMPTZ, + + /* Every Snapshot is in exactly one Project at a time. */ + project_id UUID NOT NULL, + + /* Every Snapshot originated from a single disk */ + disk_id UUID NOT NULL, + + /* Every Snapshot consists of a root volume */ + volume_id UUID NOT NULL, + + /* Disk configuration (from the time the snapshot was taken) */ + size_bytes INT NOT NULL +); + +CREATE UNIQUE INDEX ON omicron.public.snapshot ( + project_id, + name +) WHERE + time_deleted IS NULL; + /* * Oximeter collector servers. */ diff --git a/nexus/src/db/model.rs b/nexus/src/db/model.rs index e9615092fd..afea062982 100644 --- a/nexus/src/db/model.rs +++ b/nexus/src/db/model.rs @@ -9,12 +9,13 @@ use crate::db::identity::{Asset, Resource}; use crate::db::schema::{ console_session, dataset, disk, instance, metric_producer, network_interface, organization, oximeter, project, rack, region, - role_assignment_builtin, role_builtin, router_route, sled, + role_assignment_builtin, role_builtin, router_route, sled, snapshot, update_available_artifact, user_builtin, volume, vpc, vpc_firewall_rule, vpc_router, vpc_subnet, zpool, }; use crate::defaults; use crate::external_api::params; +use crate::external_api::views; use crate::internal_api; use chrono::{DateTime, Utc}; use db_macros::{Asset, Resource}; @@ -1260,6 +1261,40 @@ impl Into for DiskState { } } +#[derive( + Queryable, + Insertable, + Selectable, + Clone, + Debug, + Resource, + Serialize, + Deserialize, +)] +#[table_name = "snapshot"] +pub struct Snapshot { + #[diesel(embed)] + identity: SnapshotIdentity, + + project_id: Uuid, + disk_id: Uuid, + volume_id: Uuid, + + #[column_name = "size_bytes"] + pub size: ByteCount, +} + +impl From for views::Snapshot { + fn from(snapshot: Snapshot) -> Self { + Self { + identity: snapshot.identity(), + project_id: snapshot.project_id, + disk_id: snapshot.disk_id, + size: snapshot.size.into(), + } + } +} + /// Information announced by a metric server, used so that clients can contact it and collect /// available metric data from it. #[derive(Queryable, Insertable, Debug, Clone, Selectable, Asset)] diff --git a/nexus/src/db/schema.rs b/nexus/src/db/schema.rs index ba665e533f..1cab17dd90 100644 --- a/nexus/src/db/schema.rs +++ b/nexus/src/db/schema.rs @@ -26,6 +26,22 @@ table! { } } +table! { + snapshot (id) { + id -> Uuid, + name -> Text, + description -> Text, + time_created -> Timestamptz, + time_modified -> Timestamptz, + time_deleted -> Nullable, + + project_id -> Uuid, + disk_id -> Uuid, + volume_id -> Uuid, + size_bytes -> Int8, + } +} + table! { instance (id) { id -> Uuid, diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index 834df6eceb..70448a4990 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -12,7 +12,9 @@ use crate::ServerContext; use super::{ console_api, params, - views::{Organization, Project, Rack, Role, Sled, User, Vpc, VpcSubnet}, + views::{ + Organization, Project, Rack, Role, Sled, Snapshot, User, Vpc, VpcSubnet, + }, }; use crate::context::OpContext; use dropshot::endpoint; @@ -103,6 +105,11 @@ pub fn external_api() -> NexusApiDescription { api.register(instance_disks_attach)?; api.register(instance_disks_detach)?; + api.register(project_snapshots_get)?; + api.register(project_snapshots_post)?; + api.register(project_snapshots_get_snapshot)?; + api.register(project_snapshots_delete_snapshot)?; + api.register(project_vpcs_get)?; api.register(project_vpcs_post)?; api.register(project_vpcs_get_vpc)?; @@ -1261,6 +1268,148 @@ async fn instance_network_interfaces_get_interface( apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await } +/* + * Snapshots + */ + +/// List snapshots in a project. +#[endpoint { + method = GET, + path = "/organizations/{organization_name}/projects/{project_name}/snapshots", + tags = ["snapshots"], +}] +async fn project_snapshots_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 organization_name = &path.organization_name; + let project_name = &path.project_name; + let handler = async { + let opctx = OpContext::for_external_api(&rqctx).await?; + let snapshots = nexus + .project_list_snapshots( + &opctx, + organization_name, + project_name, + &data_page_params_for(&rqctx, &query)? + .map_name(|n| Name::ref_cast(n)), + ) + .await? + .into_iter() + .map(|d| d.into()) + .collect(); + Ok(HttpResponseOk(ScanByName::results_page(&query, snapshots)?)) + }; + apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await +} + +/// Create a snapshot of a disk. +#[endpoint { + method = POST, + path = "/organizations/{organization_name}/projects/{project_name}/snapshots", + tags = ["snapshots"], +}] +async fn project_snapshots_post( + rqctx: Arc>>, + path_params: Path, + new_snapshot: TypedBody, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let organization_name = &path.organization_name; + let project_name = &path.project_name; + let new_snapshot_params = &new_snapshot.into_inner(); + let handler = async { + let opctx = OpContext::for_external_api(&rqctx).await?; + let snapshot = nexus + .project_create_snapshot( + &opctx, + &organization_name, + &project_name, + &new_snapshot_params, + ) + .await?; + Ok(HttpResponseCreated(snapshot.into())) + }; + apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await +} + +/// Path parameters for Snapshot requests +#[derive(Deserialize, JsonSchema)] +struct SnapshotPathParam { + organization_name: Name, + project_name: Name, + snapshot_name: Name, +} + +/// Get a snapshot in a project. +#[endpoint { + method = GET, + path = "/organizations/{organization_name}/projects/{project_name}/snapshots/{snapshot_name}", + tags = ["snapshots"], +}] +async fn project_snapshots_get_snapshot( + rqctx: Arc>>, + path_params: Path, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let organization_name = &path.organization_name; + let project_name = &path.project_name; + let snapshot_name = &path.snapshot_name; + let handler = async { + let opctx = OpContext::for_external_api(&rqctx).await?; + let snapshot = nexus + .snapshot_fetch( + &opctx, + &organization_name, + &project_name, + &snapshot_name, + ) + .await?; + Ok(HttpResponseOk(snapshot.into())) + }; + apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await +} + +/// Delete a snapshot from a project. +#[endpoint { + method = DELETE, + path = "/organizations/{organization_name}/projects/{project_name}/snapshots/{snapshot_name}", + tags = ["snapshots"], +}] +async fn project_snapshots_delete_snapshot( + rqctx: Arc>>, + path_params: Path, +) -> Result { + let apictx = rqctx.context(); + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let organization_name = &path.organization_name; + let project_name = &path.project_name; + let snapshot_name = &path.snapshot_name; + let handler = async { + let opctx = OpContext::for_external_api(&rqctx).await?; + nexus + .project_delete_snapshot( + &opctx, + &organization_name, + &project_name, + &snapshot_name, + ) + .await?; + Ok(HttpResponseDeleted()) + }; + apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await +} + /* * VPCs */ diff --git a/nexus/src/external_api/params.rs b/nexus/src/external_api/params.rs index 6e9b48a727..3029623a80 100644 --- a/nexus/src/external_api/params.rs +++ b/nexus/src/external_api/params.rs @@ -291,6 +291,21 @@ pub struct NetworkInterfaceIdentifier { pub interface_name: Name, } +/* + * SNAPSHOTS + */ + +/// Create-time parameters for a [`Snapshot`](omicron_common::api::external::Snapshot) +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct SnapshotCreate { + /// common identifying metadata + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + + /// The name of the disk to be snapshotted + pub disk: Name, +} + /* * BUILT-IN USERS * diff --git a/nexus/src/external_api/tag-config.json b/nexus/src/external_api/tag-config.json index a84bd660e4..0a9cea9c55 100644 --- a/nexus/src/external_api/tag-config.json +++ b/nexus/src/external_api/tag-config.json @@ -80,6 +80,12 @@ "url": "http://oxide.computer/docs/#xxx" } }, + "snapshots": { + "description": "Snapshots of Virtual Disks at a particular point in time.", + "external_docs": { + "url": "http://oxide.computer/docs/#xxx" + } + }, "subnets": { "description": "This tag should be moved into a generic network tag", "external_docs": { @@ -105,4 +111,4 @@ } } } -} \ No newline at end of file +} diff --git a/nexus/src/external_api/views.rs b/nexus/src/external_api/views.rs index 3958a82508..7d4b6b1b92 100644 --- a/nexus/src/external_api/views.rs +++ b/nexus/src/external_api/views.rs @@ -11,7 +11,8 @@ use crate::db::identity::{Asset, Resource}; use crate::db::model; use api_identity::ObjectIdentity; use omicron_common::api::external::{ - IdentityMetadata, Ipv4Net, Ipv6Net, Name, ObjectIdentity, RoleName, + ByteCount, IdentityMetadata, Ipv4Net, Ipv6Net, Name, ObjectIdentity, + RoleName, }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -64,6 +65,21 @@ impl Into for model::Project { } } +/* + * SNAPSHOTS + */ + +/// Client view of a Snapshot +#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct Snapshot { + #[serde(flatten)] + pub identity: IdentityMetadata, + + pub project_id: Uuid, + pub disk_id: Uuid, + pub size: ByteCount, +} + /* * VPCs */ diff --git a/nexus/src/nexus.rs b/nexus/src/nexus.rs index 6b921cd066..2a185e147c 100644 --- a/nexus/src/nexus.rs +++ b/nexus/src/nexus.rs @@ -794,6 +794,46 @@ impl Nexus { Ok(()) } + pub async fn project_create_snapshot( + self: &Arc, + _opctx: &OpContext, + _organization_name: &Name, + _project_name: &Name, + _params: ¶ms::SnapshotCreate, + ) -> CreateResult { + unimplemented!(); + } + + pub async fn project_list_snapshots( + &self, + _opctx: &OpContext, + _organization_name: &Name, + _project_name: &Name, + _pagparams: &DataPageParams<'_, Name>, + ) -> ListResultVec { + unimplemented!(); + } + + pub async fn snapshot_fetch( + &self, + _opctx: &OpContext, + _organization_name: &Name, + _project_name: &Name, + _snapshot_name: &Name, + ) -> LookupResult { + unimplemented!(); + } + + pub async fn project_delete_snapshot( + self: &Arc, + _opctx: &OpContext, + _organization_name: &Name, + _project_name: &Name, + _snapshot_name: &Name, + ) -> DeleteResult { + unimplemented!(); + } + /* * Instances */ diff --git a/nexus/tests/output/nexus_tags.txt b/nexus/tests/output/nexus_tags.txt index 2eae202df1..2ddef25a4c 100644 --- a/nexus/tests/output/nexus_tags.txt +++ b/nexus/tests/output/nexus_tags.txt @@ -90,6 +90,13 @@ OPERATION ID URL PATH hardware_sleds_get /hardware/sleds hardware_sleds_get_sled /hardware/sleds/{sled_id} +API operations found with tag "snapshots" +OPERATION ID URL PATH +project_snapshots_delete_snapshot /organizations/{organization_name}/projects/{project_name}/snapshots/{snapshot_name} +project_snapshots_get /organizations/{organization_name}/projects/{project_name}/snapshots +project_snapshots_get_snapshot /organizations/{organization_name}/projects/{project_name}/snapshots/{snapshot_name} +project_snapshots_post /organizations/{organization_name}/projects/{project_name}/snapshots + API operations found with tag "subnets" OPERATION ID URL PATH subnet_network_interfaces_get /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/subnets/{subnet_name}/network-interfaces diff --git a/openapi/nexus.json b/openapi/nexus.json index 638c98fc7a..35f9c5f5f5 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -1923,6 +1923,246 @@ } } }, + "/organizations/{organization_name}/projects/{project_name}/snapshots": { + "get": { + "tags": [ + "snapshots" + ], + "summary": "List snapshots in a project.", + "operationId": "project_snapshots_get", + "parameters": [ + { + "in": "query", + "name": "limit", + "description": "Maximum number of items returned by a single call", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 1 + }, + "style": "form" + }, + { + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retreive the subsequent page", + "schema": { + "nullable": true, + "type": "string" + }, + "style": "form" + }, + { + "in": "query", + "name": "sort_by", + "schema": { + "$ref": "#/components/schemas/NameSortMode" + }, + "style": "form" + }, + { + "in": "path", + "name": "organization_name", + "description": "The organization's unique name.", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "simple" + }, + { + "in": "path", + "name": "project_name", + "description": "The project's unique name within the organization.", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SnapshotResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + }, + "x-dropshot-pagination": true + }, + "post": { + "tags": [ + "snapshots" + ], + "summary": "Create a snapshot of a disk.", + "operationId": "project_snapshots_post", + "parameters": [ + { + "in": "path", + "name": "organization_name", + "description": "The organization's unique name.", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "simple" + }, + { + "in": "path", + "name": "project_name", + "description": "The project's unique name within the organization.", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SnapshotCreate" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "successful creation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Snapshot" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/organizations/{organization_name}/projects/{project_name}/snapshots/{snapshot_name}": { + "get": { + "tags": [ + "snapshots" + ], + "summary": "Get a snapshot in a project.", + "operationId": "project_snapshots_get_snapshot", + "parameters": [ + { + "in": "path", + "name": "organization_name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "simple" + }, + { + "in": "path", + "name": "project_name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "simple" + }, + { + "in": "path", + "name": "snapshot_name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Snapshot" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "tags": [ + "snapshots" + ], + "summary": "Delete a snapshot from a project.", + "operationId": "project_snapshots_delete_snapshot", + "parameters": [ + { + "in": "path", + "name": "organization_name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "simple" + }, + { + "in": "path", + "name": "project_name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "simple" + }, + { + "in": "path", + "name": "snapshot_name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "simple" + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, "/organizations/{organization_name}/projects/{project_name}/vpcs": { "get": { "tags": [ @@ -5577,6 +5817,106 @@ "items" ] }, + "Snapshot": { + "description": "Client view of a Snapshot", + "type": "object", + "properties": { + "description": { + "description": "human-readable free-form text about a resource", + "type": "string" + }, + "disk_id": { + "type": "string", + "format": "uuid" + }, + "id": { + "description": "unique, immutable, system-controlled identifier for each resource", + "type": "string", + "format": "uuid" + }, + "name": { + "description": "unique, mutable, user-controlled identifier for each resource", + "allOf": [ + { + "$ref": "#/components/schemas/Name" + } + ] + }, + "project_id": { + "type": "string", + "format": "uuid" + }, + "size": { + "$ref": "#/components/schemas/ByteCount" + }, + "time_created": { + "description": "timestamp when this resource was created", + "type": "string", + "format": "date-time" + }, + "time_modified": { + "description": "timestamp when this resource was last modified", + "type": "string", + "format": "date-time" + } + }, + "required": [ + "description", + "disk_id", + "id", + "name", + "project_id", + "size", + "time_created", + "time_modified" + ] + }, + "SnapshotCreate": { + "description": "Create-time parameters for a [`Snapshot`](omicron_common::api::external::Snapshot)", + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "disk": { + "description": "The name of the disk to be snapshotted", + "allOf": [ + { + "$ref": "#/components/schemas/Name" + } + ] + }, + "name": { + "$ref": "#/components/schemas/Name" + } + }, + "required": [ + "description", + "disk", + "name" + ] + }, + "SnapshotResultsPage": { + "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/Snapshot" + } + }, + "next_page": { + "nullable": true, + "description": "token used to fetch the next page of results (if any)", + "type": "string" + } + }, + "required": [ + "items" + ] + }, "TimeseriesName": { "title": "The name of a timeseries", "description": "Names are constructed by concatenating the target and metric names with ':'. Target and metric names must be lowercase alphanumeric characters with '_' separating words.", @@ -6683,6 +7023,13 @@ "url": "http://oxide.computer/docs/#xxx" } }, + { + "name": "snapshots", + "description": "Snapshots of Virtual Disks at a particular point in time.", + "externalDocs": { + "url": "http://oxide.computer/docs/#xxx" + } + }, { "name": "subnets", "description": "This tag should be moved into a generic network tag", From 0b393bf1ff72febe1dd6ca1f7845604f6b3d147d Mon Sep 17 00:00:00 2001 From: Sean Klein Date: Tue, 15 Mar 2022 20:10:25 -0400 Subject: [PATCH 3/4] Remove 'sled-agent' API parameters from 'common' (#774) --- common/src/api/internal/mod.rs | 1 - common/src/api/internal/sled_agent.rs | 177 -------------------- nexus-client/src/lib.rs | 111 +++---------- nexus/src/db/model.rs | 59 +++++++ nexus/src/nexus.rs | 13 +- nexus/src/sagas.rs | 52 +++--- sled-agent-client/src/lib.rs | 212 +++++------------------- sled-agent/src/bootstrap/config.rs | 4 +- sled-agent/src/common/instance.rs | 21 ++- sled-agent/src/http_entrypoints.rs | 7 +- sled-agent/src/instance.rs | 14 +- sled-agent/src/instance_manager.rs | 11 +- sled-agent/src/params.rs | 215 ++++++++++++++++++++++++- sled-agent/src/services.rs | 4 +- sled-agent/src/sim/collection.rs | 7 +- sled-agent/src/sim/http_entrypoints.rs | 3 +- sled-agent/src/sim/instance.rs | 3 +- sled-agent/src/sim/sled_agent.rs | 6 +- sled-agent/src/sled_agent.rs | 11 +- sled-agent/src/storage_manager.rs | 2 +- 20 files changed, 406 insertions(+), 527 deletions(-) delete mode 100644 common/src/api/internal/sled_agent.rs diff --git a/common/src/api/internal/mod.rs b/common/src/api/internal/mod.rs index dee9229037..8b4ef670b9 100644 --- a/common/src/api/internal/mod.rs +++ b/common/src/api/internal/mod.rs @@ -5,4 +5,3 @@ //! Internally facing APIs. pub mod nexus; -pub mod sled_agent; diff --git a/common/src/api/internal/sled_agent.rs b/common/src/api/internal/sled_agent.rs deleted file mode 100644 index 58cf07b04f..0000000000 --- a/common/src/api/internal/sled_agent.rs +++ /dev/null @@ -1,177 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -//! APIs exposed by Sled Agent. - -use crate::api::{external, internal}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use std::fmt::{Debug, Display, Formatter, Result as FormatResult}; -use std::net::SocketAddr; -use uuid::Uuid; - -/// Describes the instance hardware. -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] -pub struct InstanceHardware { - pub runtime: internal::nexus::InstanceRuntimeState, - pub nics: Vec, -} - -/// Sent to a sled agent to establish the runtime state of an Instance -#[derive(Serialize, Deserialize, JsonSchema)] -pub struct InstanceEnsureBody { - /// Last runtime state of the Instance known to Nexus (used if the agent - /// has never seen this Instance before). - pub initial: InstanceHardware, - /// requested runtime state of the Instance - pub target: InstanceRuntimeStateRequested, - /// If we're migrating this instance, the details needed to drive the migration - pub migrate: Option, -} - -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct InstanceMigrateParams { - pub src_propolis_uuid: Uuid, - pub src_propolis_addr: SocketAddr, -} - -/// Requestable running state of an Instance. -/// -/// A subset of [`external::InstanceState`]. -#[derive( - Copy, - Clone, - Debug, - Deserialize, - Eq, - Ord, - PartialEq, - PartialOrd, - Serialize, - JsonSchema, -)] -#[serde(rename_all = "lowercase")] -pub enum InstanceStateRequested { - Running, - Stopped, - // Issues a reset command to the instance, such that it should - // stop and then immediately become running. - Reboot, - Migrating, - Destroyed, -} - -impl Display for InstanceStateRequested { - fn fmt(&self, f: &mut Formatter) -> FormatResult { - write!(f, "{}", self.label()) - } -} - -impl InstanceStateRequested { - fn label(&self) -> &str { - match self { - InstanceStateRequested::Running => "running", - InstanceStateRequested::Stopped => "stopped", - InstanceStateRequested::Reboot => "reboot", - InstanceStateRequested::Migrating => "migrating", - InstanceStateRequested::Destroyed => "destroyed", - } - } - - /// Returns true if the state represents a stopped Instance. - pub fn is_stopped(&self) -> bool { - match self { - InstanceStateRequested::Running => false, - InstanceStateRequested::Stopped => true, - InstanceStateRequested::Reboot => false, - InstanceStateRequested::Migrating => false, - InstanceStateRequested::Destroyed => true, - } - } -} - -/// Instance runtime state to update for a migration. -#[derive(Copy, Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct InstanceRuntimeStateMigrateParams { - pub migration_id: Uuid, - pub dst_propolis_id: Uuid, -} - -/// Used to request an Instance state change from a sled agent -/// -/// Right now, it's only the run state and migration id that can -/// be changed, though we might want to support changing properties -/// like "ncpus" here. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct InstanceRuntimeStateRequested { - pub run_state: InstanceStateRequested, - pub migration_params: Option, -} - -/// The type of a dataset, and an auxiliary information necessary -/// to successfully launch a zone managing the associated data. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum DatasetKind { - CockroachDb { - /// The addresses of all nodes within the cluster. - all_addresses: Vec, - }, - Crucible, - Clickhouse, -} - -impl std::fmt::Display for DatasetKind { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use DatasetKind::*; - let s = match self { - Crucible => "crucible", - CockroachDb { .. } => "cockroach", - Clickhouse => "clickhouse", - }; - write!(f, "{}", s) - } -} - -/// Used to request a new partition kind exists within a zpool. -/// -/// Many partition types are associated with services that will be -/// instantiated when the partition is detected. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] -pub struct DatasetEnsureBody { - // The name (and UUID) of the Zpool which we are inserting into. - pub zpool_uuid: Uuid, - // The type of the filesystem. - pub partition_kind: DatasetKind, - // The address on which the zone will listen for requests. - pub address: SocketAddr, - // NOTE: We could insert a UUID here, if we want that to be set by the - // caller explicitly? Currently, the lack of a UUID implies that - // "at most one partition type" exists within a zpool. - // - // It's unclear if this is actually necessary - making this change - // would also require the RSS to query existing datasets before - // requesting new ones (after all, we generally wouldn't want to - // create two CRDB datasets with different UUIDs on the same zpool). -} - -#[derive( - Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq, Eq, Hash, -)] -pub struct ServiceRequest { - // The name of the service to be created. - pub name: String, - // The addresses on which the service should listen for requests. - pub addresses: Vec, -} - -/// Used to request that the Sled initialize certain services on initialization. -/// -/// This may be used to record that certain sleds are responsible for -/// launching services which may not be associated with a partition, such -/// as Nexus. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] -pub struct ServiceEnsureBody { - pub services: Vec, -} diff --git a/nexus-client/src/lib.rs b/nexus-client/src/lib.rs index e8ce34da7e..35ec58288c 100644 --- a/nexus-client/src/lib.rs +++ b/nexus-client/src/lib.rs @@ -7,8 +7,6 @@ * from within the control plane */ -use std::convert::TryFrom; - use omicron_common::generate_logging_api; generate_logging_api!("../openapi/nexus-internal.json"); @@ -19,12 +17,6 @@ impl omicron_common::api::external::ClientError for types::Error { } } -impl From for omicron_common::api::external::Generation { - fn from(s: types::Generation) -> Self { - Self::try_from(s.0 as i64).unwrap() - } -} - impl From for types::ByteCount { fn from(s: omicron_common::api::external::ByteCount) -> Self { Self(s.to_bytes()) @@ -64,19 +56,6 @@ impl From } } -impl From - for types::DatasetKind -{ - fn from(d: omicron_common::api::internal::sled_agent::DatasetKind) -> Self { - use omicron_common::api::internal::sled_agent::DatasetKind::*; - match d { - CockroachDb { .. } => types::DatasetKind::Cockroach, - Crucible { .. } => types::DatasetKind::Crucible, - Clickhouse { .. } => types::DatasetKind::Clickhouse, - } - } -} - impl From for types::InstanceRuntimeState { @@ -99,63 +78,22 @@ impl From } } -impl From<&omicron_common::api::internal::nexus::InstanceRuntimeState> - for types::InstanceRuntimeState -{ - fn from( - s: &omicron_common::api::internal::nexus::InstanceRuntimeState, - ) -> Self { - Self { - run_state: s.run_state.into(), - sled_uuid: s.sled_uuid, - propolis_uuid: s.propolis_uuid, - dst_propolis_uuid: s.dst_propolis_uuid, - propolis_addr: s.propolis_addr.map(|addr| addr.to_string()), - migration_uuid: s.migration_uuid, - ncpus: s.ncpus.into(), - memory: s.memory.into(), - hostname: s.hostname.clone(), - gen: s.gen.into(), - time_updated: s.time_updated, - } - } -} - impl From for types::InstanceState { fn from(s: omicron_common::api::external::InstanceState) -> Self { + use omicron_common::api::external::InstanceState; match s { - omicron_common::api::external::InstanceState::Creating => { - Self::Creating - } - omicron_common::api::external::InstanceState::Starting => { - Self::Starting - } - omicron_common::api::external::InstanceState::Running => { - Self::Running - } - omicron_common::api::external::InstanceState::Stopping => { - Self::Stopping - } - omicron_common::api::external::InstanceState::Stopped => { - Self::Stopped - } - omicron_common::api::external::InstanceState::Rebooting => { - Self::Rebooting - } - omicron_common::api::external::InstanceState::Migrating => { - Self::Migrating - } - omicron_common::api::external::InstanceState::Repairing => { - Self::Repairing - } - omicron_common::api::external::InstanceState::Failed => { - Self::Failed - } - omicron_common::api::external::InstanceState::Destroyed => { - Self::Destroyed - } + InstanceState::Creating => Self::Creating, + InstanceState::Starting => Self::Starting, + InstanceState::Running => Self::Running, + InstanceState::Stopping => Self::Stopping, + InstanceState::Stopped => Self::Stopped, + InstanceState::Rebooting => Self::Rebooting, + InstanceState::Migrating => Self::Migrating, + InstanceState::Repairing => Self::Repairing, + InstanceState::Failed => Self::Failed, + InstanceState::Destroyed => Self::Destroyed, } } } @@ -188,26 +126,15 @@ impl From impl From for types::DiskState { fn from(s: omicron_common::api::external::DiskState) -> Self { + use omicron_common::api::external::DiskState; match s { - omicron_common::api::external::DiskState::Creating => { - Self::Creating - } - omicron_common::api::external::DiskState::Detached => { - Self::Detached - } - omicron_common::api::external::DiskState::Attaching(u) => { - Self::Attaching(u) - } - omicron_common::api::external::DiskState::Attached(u) => { - Self::Attached(u) - } - omicron_common::api::external::DiskState::Detaching(u) => { - Self::Detaching(u) - } - omicron_common::api::external::DiskState::Destroyed => { - Self::Destroyed - } - omicron_common::api::external::DiskState::Faulted => Self::Faulted, + DiskState::Creating => Self::Creating, + DiskState::Detached => Self::Detached, + DiskState::Attaching(u) => Self::Attaching(u), + DiskState::Attached(u) => Self::Attached(u), + DiskState::Detaching(u) => Self::Detaching(u), + DiskState::Destroyed => Self::Destroyed, + DiskState::Faulted => Self::Faulted, } } } diff --git a/nexus/src/db/model.rs b/nexus/src/db/model.rs index afea062982..5b5352c7f4 100644 --- a/nexus/src/db/model.rs +++ b/nexus/src/db/model.rs @@ -191,6 +191,12 @@ where } } +impl From for sled_agent_client::types::ByteCount { + fn from(b: ByteCount) -> Self { + Self(b.to_bytes()) + } +} + #[derive( Copy, Clone, @@ -242,6 +248,12 @@ where } } +impl From for sled_agent_client::types::Generation { + fn from(g: Generation) -> Self { + Self(i64::from(&g.0) as u64) + } +} + /// Representation of a [`u16`] in the database. /// We need this because the database does not support unsigned types. /// This handles converting from the database's INT4 to the actual u16. @@ -316,6 +328,12 @@ where } } +impl From for sled_agent_client::types::InstanceCpuCount { + fn from(i: InstanceCpuCount) -> Self { + Self(i.0 .0) + } +} + #[derive(Clone, Copy, Debug, PartialEq, AsExpression, FromSqlRow)] #[sql_type = "sql_types::Inet"] pub struct Ipv4Net(pub external::Ipv4Net); @@ -979,6 +997,28 @@ pub struct InstanceRuntimeState { pub hostname: String, } +impl From + for sled_agent_client::types::InstanceRuntimeState +{ + fn from(s: InstanceRuntimeState) -> Self { + Self { + run_state: s.state.into(), + sled_uuid: s.sled_uuid, + propolis_uuid: s.propolis_uuid, + dst_propolis_uuid: s.dst_propolis_uuid, + propolis_addr: s + .propolis_ip + .map(|ip| SocketAddr::new(ip.ip(), PROPOLIS_PORT).to_string()), + migration_uuid: s.migration_uuid, + ncpus: s.ncpus.into(), + memory: s.memory.into(), + hostname: s.hostname, + gen: s.gen.into(), + time_updated: s.time_updated, + } + } +} + /// Conversion to the external API type. impl Into for InstanceRuntimeState { fn into(self) -> external::InstanceRuntimeState { @@ -1061,6 +1101,25 @@ impl InstanceState { } } +impl From for sled_agent_client::types::InstanceState { + fn from(s: InstanceState) -> Self { + use external::InstanceState::*; + use sled_agent_client::types::InstanceState as Output; + match s.0 { + Creating => Output::Creating, + Starting => Output::Starting, + Running => Output::Running, + Stopping => Output::Stopping, + Stopped => Output::Stopped, + Rebooting => Output::Rebooting, + Migrating => Output::Migrating, + Repairing => Output::Repairing, + Failed => Output::Failed, + Destroyed => Output::Destroyed, + } + } +} + /// A Disk (network block device). #[derive( Queryable, diff --git a/nexus/src/nexus.rs b/nexus/src/nexus.rs index 2a185e147c..c9889a8589 100644 --- a/nexus/src/nexus.rs +++ b/nexus/src/nexus.rs @@ -54,9 +54,6 @@ use omicron_common::api::external::VpcRouterKind; use omicron_common::api::internal::nexus; use omicron_common::api::internal::nexus::DiskRuntimeState; use omicron_common::api::internal::nexus::UpdateArtifact; -use omicron_common::api::internal::sled_agent::InstanceRuntimeStateMigrateParams; -use omicron_common::api::internal::sled_agent::InstanceRuntimeStateRequested; -use omicron_common::api::internal::sled_agent::InstanceStateRequested; use omicron_common::backoff; use omicron_common::bail_unless; use oximeter_client::Client as OximeterClient; @@ -65,6 +62,9 @@ use oximeter_db::TimeseriesSchemaPaginationParams; use oximeter_producer::register; use rand::{rngs::StdRng, Rng, RngCore, SeedableRng}; use ring::digest; +use sled_agent_client::types::InstanceRuntimeStateMigrateParams; +use sled_agent_client::types::InstanceRuntimeStateRequested; +use sled_agent_client::types::InstanceStateRequested; use sled_agent_client::Client as SledAgentClient; use slog::Logger; use std::convert::{TryFrom, TryInto}; @@ -1291,15 +1291,12 @@ impl Nexus { * beat us to it. */ - let runtime: nexus::InstanceRuntimeState = - db_instance.runtime().clone().into(); - // TODO: Populate this with an appropriate NIC. // See also: sic_create_instance_record in sagas.rs for a similar // construction. let instance_hardware = sled_agent_client::types::InstanceHardware { runtime: sled_agent_client::types::InstanceRuntimeState::from( - runtime, + db_instance.runtime().clone(), ), nics: vec![], }; @@ -1309,7 +1306,7 @@ impl Nexus { &db_instance.id(), &sled_agent_client::types::InstanceEnsureBody { initial: instance_hardware, - target: requested.into(), + target: requested, migrate: None, }, ) diff --git a/nexus/src/sagas.rs b/nexus/src/sagas.rs index bf2a8d94e4..9838846efa 100644 --- a/nexus/src/sagas.rs +++ b/nexus/src/sagas.rs @@ -33,10 +33,15 @@ use omicron_common::api::external::InstanceState; use omicron_common::api::external::Name; use omicron_common::api::external::NetworkInterface; use omicron_common::api::internal::nexus::InstanceRuntimeState; -use omicron_common::api::internal::sled_agent::InstanceHardware; use omicron_common::backoff::{self, BackoffError}; use serde::Deserialize; use serde::Serialize; +use sled_agent_client::types::InstanceEnsureBody; +use sled_agent_client::types::InstanceHardware; +use sled_agent_client::types::InstanceMigrateParams; +use sled_agent_client::types::InstanceRuntimeStateMigrateParams; +use sled_agent_client::types::InstanceRuntimeStateRequested; +use sled_agent_client::types::InstanceStateRequested; use slog::warn; use slog::Logger; use std::collections::BTreeMap; @@ -494,7 +499,10 @@ async fn sic_create_instance_record( .map_err(ActionError::action_failed)?; // See also: instance_set_runtime in nexus.rs for a similar construction. - Ok(InstanceHardware { runtime: instance.runtime().clone().into(), nics }) + Ok(InstanceHardware { + runtime: instance.runtime().clone().into(), + nics: nics.into_iter().map(|nic| nic.into()).collect(), + }) } async fn sic_instance_ensure( @@ -504,12 +512,10 @@ async fn sic_instance_ensure( * TODO-correctness is this idempotent? */ let osagactx = sagactx.user_data(); - let runtime_params = - sled_agent_client::types::InstanceRuntimeStateRequested { - run_state: - sled_agent_client::types::InstanceStateRequested::Running, - migration_params: None, - }; + let runtime_params = InstanceRuntimeStateRequested { + run_state: InstanceStateRequested::Running, + migration_params: None, + }; let instance_id = sagactx.lookup::("instance_id")?; let sled_uuid = sagactx.lookup::("server_id")?; let initial_runtime = @@ -527,10 +533,8 @@ async fn sic_instance_ensure( let new_runtime_state = sa .instance_put( &instance_id, - &sled_agent_client::types::InstanceEnsureBody { - initial: sled_agent_client::types::InstanceHardware::from( - initial_runtime, - ), + &InstanceEnsureBody { + initial: initial_runtime, target: runtime_params, migrate: None, }, @@ -653,19 +657,17 @@ async fn sim_instance_migrate( propolis_addr: None, ..old_runtime }; - let instance_hardware = sled_agent_client::types::InstanceHardware { + let instance_hardware = InstanceHardware { runtime: runtime.into(), // TODO: populate NICs nics: vec![], }; - let target = sled_agent_client::types::InstanceRuntimeStateRequested { - run_state: sled_agent_client::types::InstanceStateRequested::Migrating, - migration_params: Some( - sled_agent_client::types::InstanceRuntimeStateMigrateParams { - migration_id, - dst_propolis_id: dst_propolis_uuid, - }, - ), + let target = InstanceRuntimeStateRequested { + run_state: InstanceStateRequested::Migrating, + migration_params: Some(InstanceRuntimeStateMigrateParams { + migration_id, + dst_propolis_id: dst_propolis_uuid, + }), }; let src_propolis_uuid = old_runtime.propolis_uuid; @@ -683,13 +685,13 @@ async fn sim_instance_migrate( let new_runtime_state: InstanceRuntimeState = dst_sa .instance_put( &instance_id, - &sled_agent_client::types::InstanceEnsureBody { + &InstanceEnsureBody { initial: instance_hardware, target, - migrate: Some(omicron_common::api::internal::sled_agent::InstanceMigrateParams { - src_propolis_addr, + migrate: Some(InstanceMigrateParams { + src_propolis_addr: src_propolis_addr.to_string(), src_propolis_uuid, - }.into()), + }), }, ) .await diff --git a/sled-agent-client/src/lib.rs b/sled-agent-client/src/lib.rs index a2c18495a6..6a5517b935 100644 --- a/sled-agent-client/src/lib.rs +++ b/sled-agent-client/src/lib.rs @@ -45,37 +45,18 @@ impl From for types::InstanceState { fn from(s: omicron_common::api::external::InstanceState) -> Self { + use omicron_common::api::external::InstanceState::*; match s { - omicron_common::api::external::InstanceState::Creating => { - Self::Creating - } - omicron_common::api::external::InstanceState::Starting => { - Self::Starting - } - omicron_common::api::external::InstanceState::Running => { - Self::Running - } - omicron_common::api::external::InstanceState::Stopping => { - Self::Stopping - } - omicron_common::api::external::InstanceState::Stopped => { - Self::Stopped - } - omicron_common::api::external::InstanceState::Rebooting => { - Self::Rebooting - } - omicron_common::api::external::InstanceState::Migrating => { - Self::Migrating - } - omicron_common::api::external::InstanceState::Repairing => { - Self::Repairing - } - omicron_common::api::external::InstanceState::Failed => { - Self::Failed - } - omicron_common::api::external::InstanceState::Destroyed => { - Self::Destroyed - } + Creating => Self::Creating, + Starting => Self::Starting, + Running => Self::Running, + Stopping => Self::Stopping, + Stopped => Self::Stopped, + Rebooting => Self::Rebooting, + Migrating => Self::Migrating, + Repairing => Self::Repairing, + Failed => Self::Failed, + Destroyed => Self::Destroyed, } } } @@ -100,42 +81,6 @@ impl From for types::Generation { } } -impl From - for types::InstanceRuntimeStateMigrateParams -{ - fn from( - s: omicron_common::api::internal::sled_agent::InstanceRuntimeStateMigrateParams, - ) -> Self { - Self { migration_id: s.migration_id, dst_propolis_id: s.dst_propolis_id } - } -} - -impl From - for types::InstanceRuntimeStateRequested -{ - fn from( - s: omicron_common::api::internal::sled_agent::InstanceRuntimeStateRequested, - ) -> Self { - Self { run_state: s.run_state.into(), migration_params: s.migration_params.map(|p| p.into()) } - } -} - -impl From - for types::InstanceStateRequested -{ - fn from( - s: omicron_common::api::internal::sled_agent::InstanceStateRequested, - ) -> Self { - match s { - omicron_common::api::internal::sled_agent::InstanceStateRequested::Running => Self::Running, - omicron_common::api::internal::sled_agent::InstanceStateRequested::Stopped => Self::Stopped, - omicron_common::api::internal::sled_agent::InstanceStateRequested::Reboot => Self::Reboot, - omicron_common::api::internal::sled_agent::InstanceStateRequested::Migrating => Self::Migrating, - omicron_common::api::internal::sled_agent::InstanceStateRequested::Destroyed => Self::Destroyed, - } - } -} - impl From for omicron_common::api::internal::nexus::InstanceRuntimeState { @@ -160,17 +105,18 @@ impl From for omicron_common::api::external::InstanceState { fn from(s: types::InstanceState) -> Self { + use types::InstanceState::*; match s { - types::InstanceState::Creating => Self::Creating, - types::InstanceState::Starting => Self::Starting, - types::InstanceState::Running => Self::Running, - types::InstanceState::Stopping => Self::Stopping, - types::InstanceState::Stopped => Self::Stopped, - types::InstanceState::Rebooting => Self::Rebooting, - types::InstanceState::Migrating => Self::Migrating, - types::InstanceState::Repairing => Self::Repairing, - types::InstanceState::Failed => Self::Failed, - types::InstanceState::Destroyed => Self::Destroyed, + Creating => Self::Creating, + Starting => Self::Starting, + Running => Self::Running, + Stopping => Self::Stopping, + Stopped => Self::Stopped, + Rebooting => Self::Rebooting, + Migrating => Self::Migrating, + Repairing => Self::Repairing, + Failed => Self::Failed, + Destroyed => Self::Destroyed, } } } @@ -209,26 +155,15 @@ impl From impl From for types::DiskState { fn from(s: omicron_common::api::external::DiskState) -> Self { + use omicron_common::api::external::DiskState::*; match s { - omicron_common::api::external::DiskState::Creating => { - Self::Creating - } - omicron_common::api::external::DiskState::Detached => { - Self::Detached - } - omicron_common::api::external::DiskState::Attaching(u) => { - Self::Attaching(u) - } - omicron_common::api::external::DiskState::Attached(u) => { - Self::Attached(u) - } - omicron_common::api::external::DiskState::Detaching(u) => { - Self::Detaching(u) - } - omicron_common::api::external::DiskState::Destroyed => { - Self::Destroyed - } - omicron_common::api::external::DiskState::Faulted => Self::Faulted, + Creating => Self::Creating, + Detached => Self::Detached, + Attaching(u) => Self::Attaching(u), + Attached(u) => Self::Attached(u), + Detaching(u) => Self::Detaching(u), + Destroyed => Self::Destroyed, + Faulted => Self::Faulted, } } } @@ -247,48 +182,23 @@ impl From impl From for omicron_common::api::external::DiskState { fn from(s: types::DiskState) -> Self { + use types::DiskState::*; match s { - types::DiskState::Creating => Self::Creating, - types::DiskState::Detached => Self::Detached, - types::DiskState::Attaching(u) => Self::Attaching(u), - types::DiskState::Attached(u) => Self::Attached(u), - types::DiskState::Detaching(u) => Self::Detaching(u), - types::DiskState::Destroyed => Self::Destroyed, - types::DiskState::Faulted => Self::Faulted, - } - } -} - -impl From - for types::InstanceHardware -{ - fn from( - s: omicron_common::api::internal::sled_agent::InstanceHardware, - ) -> Self { - Self { - nics: s.nics.iter().map(Into::into).collect(), - runtime: s.runtime.into(), - } - } -} - -impl From - for types::InstanceMigrateParams -{ - fn from( - s: omicron_common::api::internal::sled_agent::InstanceMigrateParams, - ) -> Self { - Self { - src_propolis_addr: s.src_propolis_addr.to_string(), - src_propolis_uuid: s.src_propolis_uuid, + Creating => Self::Creating, + Detached => Self::Detached, + Attaching(u) => Self::Attaching(u), + Attached(u) => Self::Attached(u), + Detaching(u) => Self::Detaching(u), + Destroyed => Self::Destroyed, + Faulted => Self::Faulted, } } } -impl From<&omicron_common::api::external::NetworkInterface> +impl From for types::NetworkInterface { - fn from(s: &omicron_common::api::external::NetworkInterface) -> Self { + fn from(s: omicron_common::api::external::NetworkInterface) -> Self { Self { description: s.identity.description.clone(), id: s.identity.id, @@ -316,48 +226,6 @@ impl From for types::MacAddr { } } -impl From - for types::DatasetKind -{ - fn from(k: omicron_common::api::internal::sled_agent::DatasetKind) -> Self { - use omicron_common::api::internal::sled_agent::DatasetKind::*; - match k { - CockroachDb { all_addresses } => Self::CockroachDb( - all_addresses.iter().map(|a| a.to_string()).collect(), - ), - Crucible => Self::Crucible, - Clickhouse => Self::Clickhouse, - } - } -} - -impl From - for types::DatasetEnsureBody -{ - fn from( - p: omicron_common::api::internal::sled_agent::DatasetEnsureBody, - ) -> Self { - Self { - zpool_uuid: p.zpool_uuid, - partition_kind: p.partition_kind.into(), - address: p.address.to_string(), - } - } -} - -impl From - for types::ServiceRequest -{ - fn from( - s: omicron_common::api::internal::sled_agent::ServiceRequest, - ) -> Self { - Self { - name: s.name, - addresses: s.addresses.into_iter().map(|s| s.to_string()).collect(), - } - } -} - impl From for types::UpdateArtifact { diff --git a/sled-agent/src/bootstrap/config.rs b/sled-agent/src/bootstrap/config.rs index 0ea57aa3b6..339ecb3613 100644 --- a/sled-agent/src/bootstrap/config.rs +++ b/sled-agent/src/bootstrap/config.rs @@ -7,11 +7,9 @@ */ use crate::config::ConfigError; +use crate::params::{DatasetEnsureBody, ServiceRequest}; use dropshot::ConfigDropshot; use dropshot::ConfigLogging; -use omicron_common::api::internal::sled_agent::{ - DatasetEnsureBody, ServiceRequest, -}; use serde::Deserialize; use serde::Serialize; use std::net::SocketAddr; diff --git a/sled-agent/src/common/instance.rs b/sled-agent/src/common/instance.rs index 8022fe7797..8481c3c7b1 100644 --- a/sled-agent/src/common/instance.rs +++ b/sled-agent/src/common/instance.rs @@ -4,13 +4,14 @@ //! Describes the states of VM instances. +use crate::params::{ + InstanceRuntimeStateMigrateParams, InstanceRuntimeStateRequested, + InstanceStateRequested, +}; use chrono::Utc; use omicron_common::api::external::Error; use omicron_common::api::external::InstanceState; use omicron_common::api::internal::nexus::InstanceRuntimeState; -use omicron_common::api::internal::sled_agent::InstanceRuntimeStateMigrateParams; -use omicron_common::api::internal::sled_agent::InstanceRuntimeStateRequested; -use omicron_common::api::internal::sled_agent::InstanceStateRequested; use propolis_client::api::InstanceState as PropolisInstanceState; /// The port propolis-server listens on inside the propolis zone. @@ -353,19 +354,17 @@ impl InstanceStates { mod test { use super::{Action, InstanceStates}; - use std::assert_matches::assert_matches; - + use crate::params::{ + InstanceRuntimeStateMigrateParams, InstanceRuntimeStateRequested, + InstanceStateRequested as Requested, + }; use chrono::Utc; use omicron_common::api::external::{ ByteCount, Error, Generation, InstanceCpuCount, InstanceState as State, }; - use omicron_common::api::internal::{ - nexus::InstanceRuntimeState, - sled_agent::InstanceRuntimeStateMigrateParams, - sled_agent::InstanceRuntimeStateRequested, - sled_agent::InstanceStateRequested as Requested, - }; + use omicron_common::api::internal::nexus::InstanceRuntimeState; use propolis_client::api::InstanceState as Observed; + use std::assert_matches::assert_matches; use uuid::Uuid; fn make_instance() -> InstanceStates { diff --git a/sled-agent/src/http_entrypoints.rs b/sled-agent/src/http_entrypoints.rs index 6575ce70d3..11f6d9d2eb 100644 --- a/sled-agent/src/http_entrypoints.rs +++ b/sled-agent/src/http_entrypoints.rs @@ -4,7 +4,9 @@ //! HTTP entrypoint functions for the sled agent's exposed API -use super::params::DiskEnsureBody; +use crate::params::{ + DatasetEnsureBody, DiskEnsureBody, InstanceEnsureBody, ServiceEnsureBody, +}; use dropshot::{ endpoint, ApiDescription, HttpError, HttpResponseOk, HttpResponseUpdatedNoContent, Path, RequestContext, TypedBody, @@ -13,9 +15,6 @@ use omicron_common::api::external::Error; use omicron_common::api::internal::nexus::DiskRuntimeState; use omicron_common::api::internal::nexus::InstanceRuntimeState; use omicron_common::api::internal::nexus::UpdateArtifact; -use omicron_common::api::internal::sled_agent::DatasetEnsureBody; -use omicron_common::api::internal::sled_agent::InstanceEnsureBody; -use omicron_common::api::internal::sled_agent::ServiceEnsureBody; use schemars::JsonSchema; use serde::Deserialize; use std::sync::Arc; diff --git a/sled-agent/src/instance.rs b/sled-agent/src/instance.rs index 62375f8cbc..4bafe69f80 100644 --- a/sled-agent/src/instance.rs +++ b/sled-agent/src/instance.rs @@ -14,13 +14,13 @@ use crate::illumos::vnic::VnicAllocator; use crate::illumos::zone::{AddressRequest, PROPOLIS_ZONE_PREFIX}; use crate::instance_manager::InstanceTicket; use crate::nexus::NexusClient; +use crate::params::{ + InstanceHardware, InstanceMigrateParams, InstanceRuntimeStateRequested, +}; use anyhow::anyhow; use futures::lock::{Mutex, MutexGuard}; use omicron_common::api::external::NetworkInterface; use omicron_common::api::internal::nexus::InstanceRuntimeState; -use omicron_common::api::internal::sled_agent::InstanceHardware; -use omicron_common::api::internal::sled_agent::InstanceMigrateParams; -use omicron_common::api::internal::sled_agent::InstanceRuntimeStateRequested; use omicron_common::backoff; use propolis_client::Client as PropolisClient; use slog::Logger; @@ -219,7 +219,7 @@ impl InstanceInner { .cpapi_instances_put( self.id(), &nexus_client::types::InstanceRuntimeState::from( - self.state.current(), + self.state.current().clone(), ), ) .await @@ -635,13 +635,13 @@ impl Instance { mod test { use super::*; use crate::mocks::MockNexusClient; + use crate::params::InstanceStateRequested; use chrono::Utc; use omicron_common::api::external::{ ByteCount, Generation, InstanceCpuCount, InstanceState, }; - use omicron_common::api::internal::{ - nexus::InstanceRuntimeState, sled_agent::InstanceStateRequested, - }; + use omicron_common::api::internal::nexus::InstanceRuntimeState; + static INST_UUID_STR: &str = "e398c5d5-5059-4e55-beac-3a1071083aaa"; static PROPOLIS_UUID_STR: &str = "ed895b13-55d5-4e0b-88e9-3f4e74d0d936"; diff --git a/sled-agent/src/instance_manager.rs b/sled-agent/src/instance_manager.rs index d54c7f5d2e..d8b7e946c8 100644 --- a/sled-agent/src/instance_manager.rs +++ b/sled-agent/src/instance_manager.rs @@ -7,10 +7,10 @@ use crate::common::vlan::VlanID; use crate::illumos::vnic::VnicAllocator; use crate::nexus::NexusClient; +use crate::params::{ + InstanceHardware, InstanceMigrateParams, InstanceRuntimeStateRequested, +}; use omicron_common::api::internal::nexus::InstanceRuntimeState; -use omicron_common::api::internal::sled_agent::InstanceHardware; -use omicron_common::api::internal::sled_agent::InstanceMigrateParams; -use omicron_common::api::internal::sled_agent::InstanceRuntimeStateRequested; use slog::Logger; use std::collections::BTreeMap; use std::sync::{Arc, Mutex}; @@ -199,13 +199,12 @@ mod test { use crate::illumos::{dladm::MockDladm, zone::MockZones}; use crate::instance::MockInstance; use crate::mocks::MockNexusClient; + use crate::params::InstanceStateRequested; use chrono::Utc; use omicron_common::api::external::{ ByteCount, Generation, InstanceCpuCount, InstanceState, }; - use omicron_common::api::internal::{ - nexus::InstanceRuntimeState, sled_agent::InstanceStateRequested, - }; + use omicron_common::api::internal::nexus::InstanceRuntimeState; static INST_UUID_STR: &str = "e398c5d5-5059-4e55-beac-3a1071083aaa"; diff --git a/sled-agent/src/params.rs b/sled-agent/src/params.rs index 993b31b632..0b7bb347ef 100644 --- a/sled-agent/src/params.rs +++ b/sled-agent/src/params.rs @@ -2,9 +2,14 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -use omicron_common::api::internal::nexus::DiskRuntimeState; +use omicron_common::api::external::NetworkInterface; +use omicron_common::api::internal::nexus::{ + DiskRuntimeState, InstanceRuntimeState, +}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use std::fmt::{Debug, Display, Formatter, Result as FormatResult}; +use std::net::SocketAddr; use uuid::Uuid; ///Used to request a Disk state change @@ -39,3 +44,211 @@ pub struct DiskEnsureBody { /// requested runtime state of the Disk pub target: DiskStateRequested, } + +/// Describes the instance hardware. +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +pub struct InstanceHardware { + pub runtime: InstanceRuntimeState, + pub nics: Vec, +} + +/// Sent to a sled agent to establish the runtime state of an Instance +#[derive(Serialize, Deserialize, JsonSchema)] +pub struct InstanceEnsureBody { + /// Last runtime state of the Instance known to Nexus (used if the agent + /// has never seen this Instance before). + pub initial: InstanceHardware, + /// requested runtime state of the Instance + pub target: InstanceRuntimeStateRequested, + /// If we're migrating this instance, the details needed to drive the migration + pub migrate: Option, +} + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct InstanceMigrateParams { + pub src_propolis_uuid: Uuid, + pub src_propolis_addr: SocketAddr, +} + +/// Requestable running state of an Instance. +/// +/// A subset of [`external::InstanceState`]. +#[derive( + Copy, + Clone, + Debug, + Deserialize, + Eq, + Ord, + PartialEq, + PartialOrd, + Serialize, + JsonSchema, +)] +#[serde(rename_all = "lowercase")] +pub enum InstanceStateRequested { + Running, + Stopped, + // Issues a reset command to the instance, such that it should + // stop and then immediately become running. + Reboot, + Migrating, + Destroyed, +} + +impl Display for InstanceStateRequested { + fn fmt(&self, f: &mut Formatter) -> FormatResult { + write!(f, "{}", self.label()) + } +} + +impl InstanceStateRequested { + fn label(&self) -> &str { + match self { + InstanceStateRequested::Running => "running", + InstanceStateRequested::Stopped => "stopped", + InstanceStateRequested::Reboot => "reboot", + InstanceStateRequested::Migrating => "migrating", + InstanceStateRequested::Destroyed => "destroyed", + } + } + + /// Returns true if the state represents a stopped Instance. + pub fn is_stopped(&self) -> bool { + match self { + InstanceStateRequested::Running => false, + InstanceStateRequested::Stopped => true, + InstanceStateRequested::Reboot => false, + InstanceStateRequested::Migrating => false, + InstanceStateRequested::Destroyed => true, + } + } +} + +/// Instance runtime state to update for a migration. +#[derive(Copy, Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct InstanceRuntimeStateMigrateParams { + pub migration_id: Uuid, + pub dst_propolis_id: Uuid, +} + +/// Used to request an Instance state change from a sled agent +/// +/// Right now, it's only the run state and migration id that can +/// be changed, though we might want to support changing properties +/// like "ncpus" here. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct InstanceRuntimeStateRequested { + pub run_state: InstanceStateRequested, + pub migration_params: Option, +} + +/// The type of a dataset, and an auxiliary information necessary +/// to successfully launch a zone managing the associated data. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum DatasetKind { + CockroachDb { + /// The addresses of all nodes within the cluster. + all_addresses: Vec, + }, + Crucible, + Clickhouse, +} + +impl From for sled_agent_client::types::DatasetKind { + fn from(k: DatasetKind) -> Self { + use DatasetKind::*; + match k { + CockroachDb { all_addresses } => Self::CockroachDb( + all_addresses.iter().map(|a| a.to_string()).collect(), + ), + Crucible => Self::Crucible, + Clickhouse => Self::Clickhouse, + } + } +} + +impl From for nexus_client::types::DatasetKind { + fn from(k: DatasetKind) -> Self { + use DatasetKind::*; + match k { + CockroachDb { .. } => Self::Cockroach, + Crucible => Self::Crucible, + Clickhouse => Self::Clickhouse, + } + } +} + +impl std::fmt::Display for DatasetKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use DatasetKind::*; + let s = match self { + Crucible => "crucible", + CockroachDb { .. } => "cockroach", + Clickhouse => "clickhouse", + }; + write!(f, "{}", s) + } +} + +/// Used to request a new partition kind exists within a zpool. +/// +/// Many partition types are associated with services that will be +/// instantiated when the partition is detected. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +pub struct DatasetEnsureBody { + // The name (and UUID) of the Zpool which we are inserting into. + pub zpool_uuid: Uuid, + // The type of the filesystem. + pub partition_kind: DatasetKind, + // The address on which the zone will listen for requests. + pub address: SocketAddr, + // NOTE: We could insert a UUID here, if we want that to be set by the + // caller explicitly? Currently, the lack of a UUID implies that + // "at most one partition type" exists within a zpool. + // + // It's unclear if this is actually necessary - making this change + // would also require the RSS to query existing datasets before + // requesting new ones (after all, we generally wouldn't want to + // create two CRDB datasets with different UUIDs on the same zpool). +} + +impl From for sled_agent_client::types::DatasetEnsureBody { + fn from(p: DatasetEnsureBody) -> Self { + Self { + zpool_uuid: p.zpool_uuid, + partition_kind: p.partition_kind.into(), + address: p.address.to_string(), + } + } +} + +#[derive( + Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq, Eq, Hash, +)] +pub struct ServiceRequest { + // The name of the service to be created. + pub name: String, + // The addresses on which the service should listen for requests. + pub addresses: Vec, +} + +impl From for sled_agent_client::types::ServiceRequest { + fn from(s: ServiceRequest) -> Self { + Self { + name: s.name, + addresses: s.addresses.into_iter().map(|s| s.to_string()).collect(), + } + } +} + +/// Used to request that the Sled initialize certain services on initialization. +/// +/// This may be used to record that certain sleds are responsible for +/// launching services which may not be associated with a partition, such +/// as Nexus. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +pub struct ServiceEnsureBody { + pub services: Vec, +} diff --git a/sled-agent/src/services.rs b/sled-agent/src/services.rs index 3217ac3009..0e2dd2c358 100644 --- a/sled-agent/src/services.rs +++ b/sled-agent/src/services.rs @@ -7,9 +7,7 @@ use crate::illumos::running_zone::{InstalledZone, RunningZone}; use crate::illumos::vnic::VnicAllocator; use crate::illumos::zone::AddressRequest; -use omicron_common::api::internal::sled_agent::{ - ServiceEnsureBody, ServiceRequest, -}; +use crate::params::{ServiceEnsureBody, ServiceRequest}; use slog::Logger; use std::collections::HashSet; use std::iter::FromIterator; diff --git a/sled-agent/src/sim/collection.rs b/sled-agent/src/sim/collection.rs index 886a7fecde..c156adb05d 100644 --- a/sled-agent/src/sim/collection.rs +++ b/sled-agent/src/sim/collection.rs @@ -333,7 +333,10 @@ impl SimCollection { #[cfg(test)] mod test { - use crate::params::DiskStateRequested; + use crate::params::{ + DiskStateRequested, InstanceRuntimeStateRequested, + InstanceStateRequested, + }; use crate::sim::collection::SimObject; use crate::sim::disk::SimDisk; use crate::sim::instance::SimInstance; @@ -349,8 +352,6 @@ mod test { use omicron_common::api::external::InstanceState; use omicron_common::api::internal::nexus::DiskRuntimeState; use omicron_common::api::internal::nexus::InstanceRuntimeState; - use omicron_common::api::internal::sled_agent::InstanceRuntimeStateRequested; - use omicron_common::api::internal::sled_agent::InstanceStateRequested; use omicron_test_utils::dev::test_setup_log; fn make_instance( diff --git a/sled-agent/src/sim/http_entrypoints.rs b/sled-agent/src/sim/http_entrypoints.rs index 28921d3167..d94836af62 100644 --- a/sled-agent/src/sim/http_entrypoints.rs +++ b/sled-agent/src/sim/http_entrypoints.rs @@ -6,7 +6,7 @@ * HTTP entrypoint functions for the sled agent's exposed API */ -use crate::params::DiskEnsureBody; +use crate::params::{DiskEnsureBody, InstanceEnsureBody}; use dropshot::endpoint; use dropshot::ApiDescription; use dropshot::HttpError; @@ -18,7 +18,6 @@ use dropshot::TypedBody; use omicron_common::api::internal::nexus::DiskRuntimeState; use omicron_common::api::internal::nexus::InstanceRuntimeState; use omicron_common::api::internal::nexus::UpdateArtifact; -use omicron_common::api::internal::sled_agent::InstanceEnsureBody; use schemars::JsonSchema; use serde::Deserialize; use std::sync::Arc; diff --git a/sled-agent/src/sim/instance.rs b/sled-agent/src/sim/instance.rs index 36122fc127..88392f1f97 100644 --- a/sled-agent/src/sim/instance.rs +++ b/sled-agent/src/sim/instance.rs @@ -9,14 +9,13 @@ use super::simulatable::Simulatable; use crate::nexus::NexusClient; +use crate::params::{InstanceRuntimeStateRequested, InstanceStateRequested}; use async_trait::async_trait; use nexus_client; use omicron_common::api::external::Error; use omicron_common::api::external::Generation; use omicron_common::api::external::InstanceState; use omicron_common::api::internal::nexus::InstanceRuntimeState; -use omicron_common::api::internal::sled_agent::InstanceRuntimeStateRequested; -use omicron_common::api::internal::sled_agent::InstanceStateRequested; use propolis_client::api::InstanceState as PropolisInstanceState; use std::sync::Arc; use uuid::Uuid; diff --git a/sled-agent/src/sim/sled_agent.rs b/sled-agent/src/sim/sled_agent.rs index d9a65da3e6..8332cc4b6e 100644 --- a/sled-agent/src/sim/sled_agent.rs +++ b/sled-agent/src/sim/sled_agent.rs @@ -7,13 +7,13 @@ */ use crate::nexus::NexusClient; -use crate::params::DiskStateRequested; +use crate::params::{ + DiskStateRequested, InstanceHardware, InstanceRuntimeStateRequested, +}; use futures::lock::Mutex; use omicron_common::api::external::Error; use omicron_common::api::internal::nexus::DiskRuntimeState; use omicron_common::api::internal::nexus::InstanceRuntimeState; -use omicron_common::api::internal::sled_agent::InstanceHardware; -use omicron_common::api::internal::sled_agent::InstanceRuntimeStateRequested; use slog::Logger; use std::sync::Arc; use uuid::Uuid; diff --git a/sled-agent/src/sled_agent.rs b/sled-agent/src/sled_agent.rs index e61ea8a10c..1298afd212 100644 --- a/sled-agent/src/sled_agent.rs +++ b/sled-agent/src/sled_agent.rs @@ -10,16 +10,15 @@ use crate::illumos::zfs::{ }; use crate::instance_manager::InstanceManager; use crate::nexus::NexusClient; -use crate::params::DiskStateRequested; +use crate::params::{ + DatasetKind, DiskStateRequested, InstanceHardware, InstanceMigrateParams, + InstanceRuntimeStateRequested, ServiceEnsureBody, +}; use crate::services::ServiceManager; use crate::storage_manager::StorageManager; use omicron_common::api::{ internal::nexus::DiskRuntimeState, internal::nexus::InstanceRuntimeState, - internal::nexus::UpdateArtifact, internal::sled_agent::DatasetKind, - internal::sled_agent::InstanceHardware, - internal::sled_agent::InstanceMigrateParams, - internal::sled_agent::InstanceRuntimeStateRequested, - internal::sled_agent::ServiceEnsureBody, + internal::nexus::UpdateArtifact, }; use slog::Logger; use std::net::SocketAddr; diff --git a/sled-agent/src/storage_manager.rs b/sled-agent/src/storage_manager.rs index 9c6c42318f..4ebebf8916 100644 --- a/sled-agent/src/storage_manager.rs +++ b/sled-agent/src/storage_manager.rs @@ -12,12 +12,12 @@ use crate::illumos::zone::AddressRequest; use crate::illumos::zpool::ZpoolName; use crate::illumos::{zfs::Mountpoint, zone::ZONE_PREFIX, zpool::ZpoolInfo}; use crate::nexus::NexusClient; +use crate::params::DatasetKind; use futures::stream::FuturesOrdered; use futures::FutureExt; use futures::StreamExt; use nexus_client::types::{DatasetPutRequest, ZpoolPutRequest}; use omicron_common::api::external::{ByteCount, ByteCountRangeError}; -use omicron_common::api::internal::sled_agent::DatasetKind; use omicron_common::backoff; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; From f7f3ff7622e0458a3a9a246a54e4673fd92fc1a8 Mon Sep 17 00:00:00 2001 From: Sean Klein Date: Wed, 16 Mar 2022 10:38:41 -0400 Subject: [PATCH 4/4] Expect propolis-server to be built externally (#768) Let the https://github.com/oxidecomputer/propolis repository own the propolis SMF + packaging process, and depend on it from the top-level package integration tools in Omicron. --- Cargo.lock | 493 ------------------------------- package-manifest.toml | 14 +- package/Cargo.toml | 4 - smf/propolis-server/config.toml | 24 -- smf/propolis-server/manifest.xml | 42 --- 5 files changed, 4 insertions(+), 573 deletions(-) delete mode 100644 smf/propolis-server/config.toml delete mode 100644 smf/propolis-server/manifest.xml diff --git a/Cargo.lock b/Cargo.lock index 824a6f247e..2c202b28e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,53 +8,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "aead" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" -dependencies = [ - "generic-array 0.14.4", -] - -[[package]] -name = "aes" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", - "opaque-debug 0.3.0", -] - -[[package]] -name = "aes-gcm-siv" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589c637f0e68c877bbd59a4599bbe849cac8e5f3e4b5a3ebae8f528cd218dcdc" -dependencies = [ - "aead", - "aes", - "cipher", - "ctr", - "polyval", - "subtle", - "zeroize", -] - -[[package]] -name = "ahash" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" -dependencies = [ - "getrandom", - "once_cell", - "version_check", -] - [[package]] name = "aho-corasick" version = "0.7.18" @@ -197,16 +150,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "bhyve_api" -version = "0.1.0" -source = "git+https://github.com/oxidecomputer/propolis?rev=0e3798510ae190131f63b9df767ec01b2beacf91#0e3798510ae190131f63b9df767ec01b2beacf91" -dependencies = [ - "bitflags", - "libc", - "num_enum", -] - [[package]] name = "bincode" version = "1.3.3" @@ -237,26 +180,6 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" -[[package]] -name = "bitstruct" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1b10c3912af09af44ea1dafe307edb5ed374b2a32658eb610e372270c9017b4" -dependencies = [ - "bitstruct_derive", -] - -[[package]] -name = "bitstruct_derive" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35fd19022c2b750d14eb9724c204d08ab7544570105b3b466d8a9f2f3feded27" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "bitvec" version = "0.22.3" @@ -382,15 +305,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "cipher" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" -dependencies = [ - "generic-array 0.14.4", -] - [[package]] name = "clap" version = "2.34.0" @@ -442,26 +356,6 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d6f2aa4d0537bcc1c74df8755072bd31c1ef1a3a1b85a68e8404a8c353b7b8b" -[[package]] -name = "const_format" -version = "0.2.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22bc6cd49b0ec407b680c3e380182b6ac63b73991cb7602de350352fc309b614" -dependencies = [ - "const_format_proc_macros", -] - -[[package]] -name = "const_format_proc_macros" -version = "0.2.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef196d5d972878a48da7decb7686eded338b4858fbabeed513d63a7c98b2b82d" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - [[package]] name = "convert_case" version = "0.4.0" @@ -624,39 +518,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "crucible" -version = "0.0.1" -source = "git+https://github.com/oxidecomputer/crucible?branch=main#e3559a2ec869c6715b41cd9b23eafb70e08a8a8b" -dependencies = [ - "aes", - "aes-gcm-siv", - "anyhow", - "base64", - "bytes", - "crucible-common", - "crucible-protocol", - "crucible-scope", - "dropshot", - "futures", - "futures-core", - "rand", - "rand_chacha", - "ringbuffer", - "schemars", - "serde", - "serde_json", - "structopt", - "tokio", - "tokio-rustls", - "tokio-util 0.6.9", - "toml", - "tracing", - "usdt 0.3.1", - "uuid", - "xts-mode", -] - [[package]] name = "crucible-agent-client" version = "0.0.1" @@ -672,53 +533,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "crucible-common" -version = "0.0.0" -source = "git+https://github.com/oxidecomputer/crucible?branch=main#e3559a2ec869c6715b41cd9b23eafb70e08a8a8b" -dependencies = [ - "anyhow", - "rusqlite", - "rustls-pemfile 0.2.1", - "serde", - "serde_json", - "tempfile", - "thiserror", - "tokio-rustls", - "toml", - "twox-hash", - "uuid", -] - -[[package]] -name = "crucible-protocol" -version = "0.0.0" -source = "git+https://github.com/oxidecomputer/crucible?branch=main#e3559a2ec869c6715b41cd9b23eafb70e08a8a8b" -dependencies = [ - "anyhow", - "bincode", - "bytes", - "crucible-common", - "serde", - "tokio-util 0.6.9", - "uuid", -] - -[[package]] -name = "crucible-scope" -version = "0.0.0" -source = "git+https://github.com/oxidecomputer/crucible?branch=main#e3559a2ec869c6715b41cd9b23eafb70e08a8a8b" -dependencies = [ - "anyhow", - "futures", - "futures-core", - "serde", - "serde_json", - "tokio", - "tokio-util 0.6.9", - "toml", -] - [[package]] name = "crunchy" version = "0.2.2" @@ -778,15 +592,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "ctr" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" -dependencies = [ - "cipher", -] - [[package]] name = "curve25519-dalek" version = "4.0.0-pre.1" @@ -853,17 +658,6 @@ dependencies = [ "const-oid", ] -[[package]] -name = "derivative" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "diesel" version = "2.0.0" @@ -972,15 +766,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "dladm" -version = "0.1.0" -source = "git+https://github.com/oxidecomputer/propolis?rev=0e3798510ae190131f63b9df767ec01b2beacf91#0e3798510ae190131f63b9df767ec01b2beacf91" -dependencies = [ - "libc", - "num_enum", -] - [[package]] name = "doc-comment" version = "0.3.3" @@ -1122,15 +907,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "erased-serde" -version = "0.3.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3de9ad4541d99dc22b59134e7ff8dc3d6c988c89ecd7324bf10a8362b07a2afa" -dependencies = [ - "serde", -] - [[package]] name = "expectorate" version = "1.0.4" @@ -1153,12 +929,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" -[[package]] -name = "fallible-streaming-iterator" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" - [[package]] name = "fastrand" version = "1.6.0" @@ -1481,18 +1251,6 @@ name = "hashbrown" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" -dependencies = [ - "ahash", -] - -[[package]] -name = "hashlink" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf" -dependencies = [ - "hashbrown", -] [[package]] name = "headers" @@ -1739,15 +1497,6 @@ dependencies = [ "unindent", ] -[[package]] -name = "input_buffer" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f97967975f448f1a7ddb12b0bc41069d09ed6a1c161a92687e057325db35d413" -dependencies = [ - "bytes", -] - [[package]] name = "instant" version = "0.1.12" @@ -1854,16 +1603,6 @@ version = "0.2.119" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" -[[package]] -name = "libsqlite3-sys" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cafc7c74096c336d9d27145f7ebd4f4b6f95ba16aa5a282387267e6925cb58" -dependencies = [ - "pkg-config", - "vcpkg", -] - [[package]] name = "lock_api" version = "0.4.6" @@ -2140,28 +1879,6 @@ dependencies = [ "libc", ] -[[package]] -name = "num_enum" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9bd055fb730c4f8f4f57d45d35cd6b3f0980535b056dc7ff119cee6a66ed6f" -dependencies = [ - "derivative", - "num_enum_derive", -] - -[[package]] -name = "num_enum_derive" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "486ea01961c4a818096de679a8b740b26d9033146ac5291b1c98557658f8cdd9" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "num_threads" version = "0.1.3" @@ -2324,7 +2041,6 @@ dependencies = [ "flate2", "omicron-common", "omicron-zone-package", - "propolis-server", "rayon", "reqwest", "serde", @@ -2977,18 +2693,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "polyval" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" -dependencies = [ - "cfg-if", - "cpufeatures", - "opaque-debug 0.3.0", - "universal-hash", -] - [[package]] name = "postgres-protocol" version = "0.6.3" @@ -3072,16 +2776,6 @@ dependencies = [ "termtree", ] -[[package]] -name = "proc-macro-crate" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebace6889caf889b4d3f76becee12e90353f2b8c7d875534a71e5742f8f6f83" -dependencies = [ - "thiserror", - "toml", -] - [[package]] name = "proc-macro-error" version = "1.0.4" @@ -3178,32 +2872,6 @@ dependencies = [ "syn", ] -[[package]] -name = "propolis" -version = "0.1.0" -source = "git+https://github.com/oxidecomputer/propolis?rev=0e3798510ae190131f63b9df767ec01b2beacf91#0e3798510ae190131f63b9df767ec01b2beacf91" -dependencies = [ - "anyhow", - "bhyve_api", - "bitflags", - "bitstruct", - "byteorder", - "crucible", - "dladm", - "erased-serde", - "futures", - "lazy_static", - "libc", - "num_enum", - "serde", - "serde_arrays", - "slog", - "thiserror", - "tokio", - "usdt 0.2.1", - "viona_api", -] - [[package]] name = "propolis-client" version = "0.1.0" @@ -3220,35 +2888,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "propolis-server" -version = "0.1.0" -source = "git+https://github.com/oxidecomputer/propolis?rev=0e3798510ae190131f63b9df767ec01b2beacf91#0e3798510ae190131f63b9df767ec01b2beacf91" -dependencies = [ - "anyhow", - "bytes", - "const_format", - "dropshot", - "futures", - "hyper", - "num_enum", - "propolis", - "propolis-client", - "ron", - "semver 1.0.4", - "serde", - "serde_derive", - "serde_json", - "slog", - "structopt", - "thiserror", - "tokio", - "tokio-tungstenite", - "tokio-util 0.6.9", - "toml", - "uuid", -] - [[package]] name = "quote" version = "1.0.15" @@ -3466,32 +3105,6 @@ dependencies = [ "array-init", ] -[[package]] -name = "ron" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b861ecaade43ac97886a512b360d01d66be9f41f3c61088b42cedf92e03d678" -dependencies = [ - "base64", - "bitflags", - "serde", -] - -[[package]] -name = "rusqlite" -version = "0.26.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ba4d3462c8b2e4d7f4fcfcf2b296dc6b65404fbbc7b63daa37fd485c149daf7" -dependencies = [ - "bitflags", - "fallible-iterator", - "fallible-streaming-iterator", - "hashlink", - "libsqlite3-sys", - "memchr", - "smallvec", -] - [[package]] name = "rustc_version" version = "0.1.7" @@ -3677,9 +3290,6 @@ name = "semver" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012" -dependencies = [ - "serde", -] [[package]] name = "semver-parser" @@ -3709,15 +3319,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde_arrays" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38636132857f68ec3d5f3eb121166d2af33cb55174c4d5ff645db6165cbef0fd" -dependencies = [ - "serde", -] - [[package]] name = "serde_cbor" version = "0.11.2" @@ -3863,19 +3464,6 @@ dependencies = [ "opaque-debug 0.2.3", ] -[[package]] -name = "sha-1" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if", - "cpufeatures", - "digest 0.9.0", - "opaque-debug 0.3.0", -] - [[package]] name = "sha-1" version = "0.10.0" @@ -4145,12 +3733,6 @@ dependencies = [ "der", ] -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "steno" version = "0.1.0" @@ -4561,19 +4143,6 @@ dependencies = [ "webpki", ] -[[package]] -name = "tokio-tungstenite" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e96bb520beab540ab664bd5a9cfeaa1fcd846fa68c830b42e2c8963071251d2" -dependencies = [ - "futures-util", - "log", - "pin-project", - "tokio", - "tungstenite", -] - [[package]] name = "tokio-util" version = "0.6.9" @@ -4721,37 +4290,6 @@ dependencies = [ "toml", ] -[[package]] -name = "tungstenite" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fe8dada8c1a3aeca77d6b51a4f1314e0f4b8e438b7b1b71e3ddaca8080e4093" -dependencies = [ - "base64", - "byteorder", - "bytes", - "http", - "httparse", - "input_buffer", - "log", - "rand", - "sha-1 0.9.8", - "thiserror", - "url", - "utf-8", -] - -[[package]] -name = "twox-hash" -version = "1.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee73e6e4924fe940354b8d4d98cad5231175d615cd855b758adc658c0aac6a0" -dependencies = [ - "cfg-if", - "rand", - "static_assertions", -] - [[package]] name = "typenum" version = "1.14.0" @@ -4853,16 +4391,6 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f14ee04d9415b52b3aeab06258a3f07093182b88ba0f9b8d203f211a7a7d41c7" -[[package]] -name = "universal-hash" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" -dependencies = [ - "generic-array 0.14.4", - "subtle", -] - [[package]] name = "untrusted" version = "0.7.1" @@ -5003,12 +4531,6 @@ dependencies = [ "usdt-impl 0.3.1", ] -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - [[package]] name = "uuid" version = "0.8.2" @@ -5037,11 +4559,6 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" -[[package]] -name = "viona_api" -version = "0.1.0" -source = "git+https://github.com/oxidecomputer/propolis?rev=0e3798510ae190131f63b9df767ec01b2beacf91#0e3798510ae190131f63b9df767ec01b2beacf91" - [[package]] name = "vsss-rs" version = "2.0.0-pre0" @@ -5286,16 +4803,6 @@ dependencies = [ "libc", ] -[[package]] -name = "xts-mode" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a099a2f21d48275314733f85bc43b6c6213b66394233aaea573fc7a520dcd9" -dependencies = [ - "byteorder", - "cipher", -] - [[package]] name = "zerocopy" version = "0.3.0" diff --git a/package-manifest.toml b/package-manifest.toml index d8313810f1..d592bd7108 100644 --- a/package-manifest.toml +++ b/package-manifest.toml @@ -32,16 +32,6 @@ zone = true from = "smf/oximeter" to = "/var/svc/manifest/site/oximeter" -[package.propolis-server] -rust.binary_names = ["propolis-server"] -rust.release = true -service_name = "propolis-server" -zone = true -blobs = [ "alpine.iso", "OVMF_CODE.fd" ] -[[package.propolis-server.paths]] -from = "smf/propolis-server" -to = "/var/svc/manifest/site/propolis-server" - [package.clickhouse] service_name = "clickhouse" zone = true @@ -70,3 +60,7 @@ to = "/var/svc/manifest/site/cockroachdb" [external_package.crucible] service_name = "crucible" zone = true + +[external_package.propolis-server] +service_name = "propolis-server" +zone = true diff --git a/package/Cargo.toml b/package/Cargo.toml index ec47bab9e1..5b9e4cef28 100644 --- a/package/Cargo.toml +++ b/package/Cargo.toml @@ -10,10 +10,6 @@ crossbeam = "0.8" flate2 = "1.0.22" omicron-common = { path = "../common" } omicron-zone-package = { version = "0.1.2" } -# We depend on the propolis-server here -- a binary, not a library -- to -# make it visible to the packaging tool, which can compile it and shove -# it in a tarball. -propolis-server = { git = "https://github.com/oxidecomputer/propolis", rev = "0e3798510ae190131f63b9df767ec01b2beacf91" } rayon = "1.5" reqwest = { version = "0.11", default-features = false, features = ["rustls-tls"] } serde = { version = "1.0", features = [ "derive" ] } diff --git a/smf/propolis-server/config.toml b/smf/propolis-server/config.toml deleted file mode 100644 index 3592b9be13..0000000000 --- a/smf/propolis-server/config.toml +++ /dev/null @@ -1,24 +0,0 @@ -# Configuration for propolis server. -# -# Refer to https://github.com/oxidecomputer/propolis#readme -# for more detail on the config format. - -bootrom = "/opt/oxide/propolis-server/blob/OVMF_CODE.fd" - -[block_dev.alpine_iso] -type = "file" -path = "/opt/oxide/propolis-server/blob/alpine.iso" -readonly = "true" - -[dev.block0] -driver = "pci-virtio-block" -block_dev = "alpine_iso" -pci-path = "0.4.0" - -# NOTE: This VNIC is here for reference, but VNICs are typically managed by the -# Sled Agent. - -# [dev.net0] -# driver = "pci-virtio-viona" -# vnic = "vnic_prop0" -# pci-path = "0.5.0" diff --git a/smf/propolis-server/manifest.xml b/smf/propolis-server/manifest.xml deleted file mode 100644 index 22b5c4e8bd..0000000000 --- a/smf/propolis-server/manifest.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -