diff --git a/Cargo.lock b/Cargo.lock index d65c05264e3..dac254223eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14191,6 +14191,7 @@ version = "0.1.0" dependencies = [ "anyhow", "async-trait", + "byte-wrapper", "chrono", "omicron-common", "omicron-generation-kinds", diff --git a/clients/sled-agent-client/Cargo.toml b/clients/sled-agent-client/Cargo.toml index 32ed1a263a5..323f5007c00 100644 --- a/clients/sled-agent-client/Cargo.toml +++ b/clients/sled-agent-client/Cargo.toml @@ -10,6 +10,7 @@ workspace = true [dependencies] anyhow.workspace = true async-trait.workspace = true +byte-wrapper.workspace = true chrono.workspace = true omicron-generation-kinds.workspace = true sled-agent-types-versions.workspace = true diff --git a/clients/sled-agent-client/src/lib.rs b/clients/sled-agent-client/src/lib.rs index 5ae69fd2a33..88b472b2aa1 100644 --- a/clients/sled-agent-client/src/lib.rs +++ b/clients/sled-agent-client/src/lib.rs @@ -33,11 +33,13 @@ progenitor::generate_api!( VirtualNetworkInterfaceHost = { derives = [Eq, Hash] }, }, crates = { + "byte-wrapper" = "0.1.0", "omicron-uuid-kinds" = "*", "oxnet" = "0.1.0", }, replace = { ArtifactConfig = sled_agent_types::artifact::ArtifactConfig, + ArtifactConfigGeneration = omicron_generation_kinds::ArtifactConfigGeneration, Attestation = sled_agent_types_versions::latest::rot::Attestation, Baseboard = sled_agent_types_versions::latest::inventory::Baseboard, BaseboardId = sled_hardware_types::BaseboardId, diff --git a/generation-kinds/src/lib.rs b/generation-kinds/src/lib.rs index e82f5613cba..9a11089ddc8 100644 --- a/generation-kinds/src/lib.rs +++ b/generation-kinds/src/lib.rs @@ -40,6 +40,7 @@ impl_typed_generation_kinds! { }, kinds = { Alert = {}, + ArtifactConfig = {}, InstanceState = {}, InstanceUpdater = {}, Nexus = {}, diff --git a/nexus/db-model/src/tuf_repo.rs b/nexus/db-model/src/tuf_repo.rs index 79d69d9fa35..5d15e7aed21 100644 --- a/nexus/db-model/src/tuf_repo.rs +++ b/nexus/db-model/src/tuf_repo.rs @@ -5,7 +5,8 @@ use std::collections::BTreeMap; use std::str::FromStr; -use crate::{ByteCount, Generation, SemverVersion, typed_uuid::DbTypedUuid}; +use crate::typed_generation::DbTypedGeneration; +use crate::{ByteCount, SemverVersion, typed_uuid::DbTypedUuid}; use chrono::{DateTime, Utc}; use diesel::sql_types::{Jsonb, Text}; use diesel::{deserialize::FromSql, serialize::ToSql}; @@ -16,6 +17,9 @@ use nexus_db_schema::schema::{ }; use nexus_types::external_api::update as update_types; use omicron_common::api::external; +use omicron_generation_kinds::{ + ArtifactConfigGeneration, ArtifactConfigGenerationKind, +}; use omicron_uuid_kinds::GenericUuid; use omicron_uuid_kinds::TufArtifactKind; use omicron_uuid_kinds::TufRepoKind; @@ -48,7 +52,7 @@ impl TufRepoDescription { /// [`nexus_types::tuf_repo::TufRepoDescription`]. pub fn new( description: nexus_types::tuf_repo::TufRepoDescription, - generation_added: omicron_generation_kinds::Generation, + generation_added: ArtifactConfigGeneration, ) -> Result { let id = TypedUuid::new_v4().into(); Ok(Self { @@ -117,7 +121,7 @@ impl TufArtifactDescription { /// [`tufaceous_artifact::Artifact`]. pub fn new( artifact: tufaceous_artifact::Artifact, - generation_added: omicron_generation_kinds::Generation, + generation_added: ArtifactConfigGeneration, ) -> Result { let id = TypedUuid::new_v4().into(); Ok(Self { @@ -237,7 +241,7 @@ pub struct TufArtifact { pub id: DbTypedUuid, pub time_created: DateTime, pub sha256: ArtifactHash, - pub generation_added: Generation, + pub generation_added: DbTypedGeneration, } impl TufArtifact { diff --git a/nexus/db-queries/src/db/datastore/update.rs b/nexus/db-queries/src/db/datastore/update.rs index fdeb5b0e891..08276111f91 100644 --- a/nexus/db-queries/src/db/datastore/update.rs +++ b/nexus/db-queries/src/db/datastore/update.rs @@ -26,9 +26,9 @@ use nexus_db_errors::OptionalError; use nexus_db_errors::{ErrorHandler, public_error_from_diesel}; use nexus_db_lookup::DbConnection; use nexus_db_model::{ - ArtifactHash, TargetRelease, TufArtifact, TufArtifactDescription, - TufArtifactFile, TufArtifactTag, TufMetadataEntry, TufRepo, - TufRepoDescription, TufRepoUpload, TufTrustRoot, to_db_typed_uuid, + ArtifactHash, DbTypedGeneration, TargetRelease, TufArtifact, + TufArtifactDescription, TufArtifactFile, TufArtifactTag, TufMetadataEntry, + TufRepo, TufRepoDescription, TufRepoUpload, TufTrustRoot, to_db_typed_uuid, }; use nexus_types::external_api::update::TufRepoUploadStatus; use omicron_common::api::external::{ @@ -36,7 +36,9 @@ use omicron_common::api::external::{ ListResultVec, LookupResult, LookupType, ResourceType, UpdateResult, }; use omicron_common::api::external::{Error, InternalContext}; -use omicron_generation_kinds::Generation; +use omicron_generation_kinds::{ + ArtifactConfigGeneration, ArtifactConfigGenerationKind, +}; use omicron_uuid_kinds::{GenericUuid, TufRepoUuid}; use semver::Version; use sled_agent_types::artifact::ArtifactConfig; @@ -417,7 +419,7 @@ impl DataStore { pub async fn tuf_repo_mark_pruned( &self, opctx: &OpContext, - initial_tuf_generation: Generation, + initial_tuf_generation: ArtifactConfigGeneration, recent_releases: &RecentTargetReleases, tuf_repo_id: TufRepoUuid, ) -> UpdateResult<()> { @@ -578,7 +580,7 @@ impl DataStore { pub async fn tuf_get_generation( &self, opctx: &OpContext, - ) -> LookupResult { + ) -> LookupResult { opctx.authorize(authz::Action::Read, &authz::FLEET).await?; get_generation(&*self.pool_connection_authorized(opctx).await?) .await @@ -1110,22 +1112,23 @@ async fn insert_impl( async fn get_generation( conn: &async_bb8_diesel::Connection, -) -> Result { +) -> Result { use nexus_db_schema::schema::tuf_generation::dsl; - let generation: nexus_db_model::Generation = dsl::tuf_generation - .filter(dsl::singleton.eq(true)) - .select(dsl::generation) - .get_result_async(conn) - .await?; - Ok(generation.0) + let generation: DbTypedGeneration = + dsl::tuf_generation + .filter(dsl::singleton.eq(true)) + .select(dsl::generation) + .get_result_async(conn) + .await?; + Ok(generation.into()) } async fn put_generation( conn: &async_bb8_diesel::Connection, - old_generation: nexus_db_model::Generation, - new_generation: nexus_db_model::Generation, -) -> Result { + old_generation: DbTypedGeneration, + new_generation: DbTypedGeneration, +) -> Result, DieselError> { use nexus_db_schema::schema::tuf_generation::dsl; // We use `get_result_async` instead of `execute_async` to check that we diff --git a/nexus/src/app/background/tasks/tuf_artifact_replication.rs b/nexus/src/app/background/tasks/tuf_artifact_replication.rs index bea333e0444..7b3383ae8d7 100644 --- a/nexus/src/app/background/tasks/tuf_artifact_replication.rs +++ b/nexus/src/app/background/tasks/tuf_artifact_replication.rs @@ -76,7 +76,7 @@ use nexus_types::internal_api::background::{ TufArtifactReplicationCounters, TufArtifactReplicationOperation, TufArtifactReplicationRequest, TufArtifactReplicationStatus, }; -use omicron_generation_kinds::Generation; +use omicron_generation_kinds::ArtifactConfigGeneration; use omicron_uuid_kinds::SledUuid; use rand::seq::{IndexedRandom, SliceRandom}; use serde_json::json; @@ -250,7 +250,7 @@ impl<'a> Requests<'a> { fn into_stream( self, log: &'a slog::Logger, - generation: Generation, + generation: ArtifactConfigGeneration, ) -> impl Stream< Item = impl Future + use<'a>, > + use<'a> { @@ -323,7 +323,7 @@ impl Request<'_> { async fn execute( self, log: &slog::Logger, - generation: Generation, + generation: ArtifactConfigGeneration, _permit: Option, ) -> TufArtifactReplicationRequest { let err: Option> = async { diff --git a/nexus/types/src/internal_api/background.rs b/nexus/types/src/internal_api/background.rs index 2e3031133e0..a328252d145 100644 --- a/nexus/types/src/internal_api/background.rs +++ b/nexus/types/src/internal_api/background.rs @@ -10,7 +10,7 @@ use gateway_types::component::SpType; use iddqd::IdOrdItem; use iddqd::IdOrdMap; use iddqd::id_upcast; -use omicron_generation_kinds::Generation; +use omicron_generation_kinds::ArtifactConfigGeneration; use omicron_uuid_kinds::AlertReceiverUuid; use omicron_uuid_kinds::AlertUuid; use omicron_uuid_kinds::BlueprintUuid; @@ -372,7 +372,7 @@ impl SupportBundleCollectionReport { /// The status of a `tuf_artifact_replication` background task activation #[derive(Debug, Serialize, Deserialize, PartialEq)] pub struct TufArtifactReplicationStatus { - pub generation: Generation, + pub generation: ArtifactConfigGeneration, pub last_run_counters: TufArtifactReplicationCounters, pub lifetime_counters: TufArtifactReplicationCounters, pub request_debug_ringbuf: Arc>, @@ -472,7 +472,7 @@ pub struct TufArtifactReplicationRequest { )] #[serde(tag = "operation", rename_all = "snake_case")] pub enum TufArtifactReplicationOperation { - PutConfig { generation: Generation }, + PutConfig { generation: ArtifactConfigGeneration }, List, Put { hash: ArtifactHash }, Copy { hash: ArtifactHash, source_sled: SledUuid }, diff --git a/openapi/sled-agent/sled-agent-51.0.0-9a12e4.json.gitstub b/openapi/sled-agent/sled-agent-51.0.0-9a12e4.json.gitstub new file mode 100644 index 00000000000..e23cab20988 --- /dev/null +++ b/openapi/sled-agent/sled-agent-51.0.0-9a12e4.json.gitstub @@ -0,0 +1 @@ +cb538d50fc99c594c0eecb8e8a577b874dac37d1:openapi/sled-agent/sled-agent-51.0.0-9a12e4.json diff --git a/openapi/sled-agent/sled-agent-51.0.0-9a12e4.json b/openapi/sled-agent/sled-agent-52.0.0-912674.json similarity index 99% rename from openapi/sled-agent/sled-agent-51.0.0-9a12e4.json rename to openapi/sled-agent/sled-agent-52.0.0-912674.json index 9d4df1e4c3d..3c8b8b05ee5 100644 --- a/openapi/sled-agent/sled-agent-51.0.0-9a12e4.json +++ b/openapi/sled-agent/sled-agent-52.0.0-912674.json @@ -7,7 +7,7 @@ "url": "https://oxide.computer", "email": "api@oxide.computer" }, - "version": "51.0.0" + "version": "52.0.0" }, "paths": { "/artifacts": { @@ -51,7 +51,7 @@ "name": "generation", "required": true, "schema": { - "$ref": "#/components/schemas/Generation" + "$ref": "#/components/schemas/ArtifactConfigGeneration" } } ], @@ -104,7 +104,7 @@ "name": "generation", "required": true, "schema": { - "$ref": "#/components/schemas/Generation" + "$ref": "#/components/schemas/ArtifactConfigGeneration" } } ], @@ -2966,13 +2966,20 @@ "artifacts": { "type": "array", "items": { + "x-rust-type": { + "crate": "byte-wrapper", + "path": "byte_wrapper::HexArray::<32>", + "version": "0.1.0" + }, "type": "string", - "format": "hex string (32 bytes)" + "pattern": "^[0-9a-fA-F]{64}$", + "minLength": 64, + "maxLength": 64 }, "uniqueItems": true }, "generation": { - "$ref": "#/components/schemas/Generation" + "$ref": "#/components/schemas/ArtifactConfigGeneration" } }, "required": [ @@ -2980,6 +2987,17 @@ "generation" ] }, + "ArtifactConfigGeneration": { + "description": "Generation numbers stored in the database, used for optimistic concurrency control", + "x-rust-type": { + "crate": "omicron-generation-kinds", + "path": "omicron_generation_kinds::ArtifactConfigGeneration", + "version": "*" + }, + "type": "integer", + "format": "uint64", + "minimum": 0 + }, "ArtifactCopyFromDepotBody": { "description": "Request body for copying artifacts from a depot.", "type": "object", @@ -3001,7 +3019,7 @@ "type": "object", "properties": { "generation": { - "$ref": "#/components/schemas/Generation" + "$ref": "#/components/schemas/ArtifactConfigGeneration" }, "list": { "type": "object", diff --git a/openapi/sled-agent/sled-agent-latest.json b/openapi/sled-agent/sled-agent-latest.json index 708f8bce0e2..8057ba7de57 120000 --- a/openapi/sled-agent/sled-agent-latest.json +++ b/openapi/sled-agent/sled-agent-latest.json @@ -1 +1 @@ -sled-agent-51.0.0-9a12e4.json \ No newline at end of file +sled-agent-52.0.0-912674.json \ No newline at end of file diff --git a/sled-agent/api/src/lib.rs b/sled-agent/api/src/lib.rs index 9e287fdfdb8..0ea0720eb3d 100644 --- a/sled-agent/api/src/lib.rs +++ b/sled-agent/api/src/lib.rs @@ -39,6 +39,7 @@ api_versions!([ // | example for the next person. // v // (next_int, IDENT), + (52, TYPED_ARTIFACT_CONFIG_GENERATION), (51, MULTIPLE_ZONE_EXTERNAL_IPS), (50, TYPED_SLED_CONFIG_GENERATION), (49, ADD_UPDATE_DISPOSITION), @@ -747,32 +748,78 @@ pub trait SledAgentApi { #[endpoint { method = GET, - path = "/artifacts-config" + path = "/artifacts-config", + versions = VERSION_TYPED_ARTIFACT_CONFIG_GENERATION.., }] async fn artifact_config_get( rqctx: RequestContext, ) -> Result, HttpError>; + #[endpoint { + operation_id = "artifact_config_get", + method = GET, + path = "/artifacts-config", + versions = ..VERSION_TYPED_ARTIFACT_CONFIG_GENERATION, + }] + async fn artifact_config_get_v1( + rqctx: RequestContext, + ) -> Result, HttpError> { + Self::artifact_config_get(rqctx) + .await + .map(|response| response.map(v1::artifact::ArtifactConfig::from)) + } + #[endpoint { method = PUT, - path = "/artifacts-config" + path = "/artifacts-config", + versions = VERSION_TYPED_ARTIFACT_CONFIG_GENERATION.., }] async fn artifact_config_put( rqctx: RequestContext, body: TypedBody, ) -> Result; + #[endpoint { + operation_id = "artifact_config_put", + method = PUT, + path = "/artifacts-config", + versions = ..VERSION_TYPED_ARTIFACT_CONFIG_GENERATION, + }] + async fn artifact_config_put_v1( + rqctx: RequestContext, + body: TypedBody, + ) -> Result { + Self::artifact_config_put(rqctx, body.map(Into::into)).await + } + #[endpoint { method = GET, - path = "/artifacts" + path = "/artifacts", + versions = VERSION_TYPED_ARTIFACT_CONFIG_GENERATION.., }] async fn artifact_list( rqctx: RequestContext, ) -> Result, HttpError>; + #[endpoint { + operation_id = "artifact_list", + method = GET, + path = "/artifacts", + versions = ..VERSION_TYPED_ARTIFACT_CONFIG_GENERATION, + }] + async fn artifact_list_v1( + rqctx: RequestContext, + ) -> Result, HttpError> + { + Self::artifact_list(rqctx).await.map(|response| { + response.map(v1::artifact::ArtifactListResponse::from) + }) + } + #[endpoint { method = POST, - path = "/artifacts/{sha256}/copy-from-depot" + path = "/artifacts/{sha256}/copy-from-depot", + versions = VERSION_TYPED_ARTIFACT_CONFIG_GENERATION.., }] async fn artifact_copy_from_depot( rqctx: RequestContext, @@ -784,10 +831,35 @@ pub trait SledAgentApi { HttpError, >; + #[endpoint { + operation_id = "artifact_copy_from_depot", + method = POST, + path = "/artifacts/{sha256}/copy-from-depot", + versions = ..VERSION_TYPED_ARTIFACT_CONFIG_GENERATION, + }] + async fn artifact_copy_from_depot_v1( + rqctx: RequestContext, + path_params: Path, + query_params: Query, + body: TypedBody, + ) -> Result< + HttpResponseAccepted, + HttpError, + > { + Self::artifact_copy_from_depot( + rqctx, + path_params, + query_params.map(Into::into), + body, + ) + .await + } + #[endpoint { method = PUT, path = "/artifacts/{sha256}", request_body_max_bytes = UPDATE_ARTIFACT_MAX_BYTES, + versions = VERSION_TYPED_ARTIFACT_CONFIG_GENERATION.., }] async fn artifact_put( rqctx: RequestContext, @@ -796,6 +868,29 @@ pub trait SledAgentApi { body: StreamingBody, ) -> Result, HttpError>; + #[endpoint { + operation_id = "artifact_put", + method = PUT, + path = "/artifacts/{sha256}", + request_body_max_bytes = UPDATE_ARTIFACT_MAX_BYTES, + versions = ..VERSION_TYPED_ARTIFACT_CONFIG_GENERATION, + }] + async fn artifact_put_v1( + rqctx: RequestContext, + path_params: Path, + query_params: Query, + body: StreamingBody, + ) -> Result, HttpError> + { + Self::artifact_put( + rqctx, + path_params, + query_params.map(Into::into), + body, + ) + .await + } + /// Take a snapshot of a disk that is attached to an instance #[endpoint { method = POST, diff --git a/sled-agent/config-reconciler/src/ledger.rs b/sled-agent/config-reconciler/src/ledger.rs index b00db0458d2..80efc36c221 100644 --- a/sled-agent/config-reconciler/src/ledger.rs +++ b/sled-agent/config-reconciler/src/ledger.rs @@ -684,7 +684,7 @@ mod tests { use camino_tempfile::tempfile; use iddqd::IdOrdMap; use illumos_utils::zpool::ZpoolName; - use omicron_generation_kinds::Generation; + use omicron_generation_kinds::ArtifactConfigGeneration; use omicron_test_utils::dev; use omicron_test_utils::dev::poll::wait_for_watch_channel_condition; use omicron_uuid_kinds::InternalZpoolUuid; @@ -1275,7 +1275,7 @@ mod tests { .await; let mut artifact_config = ArtifactConfig { - generation: Generation::new(), + generation: ArtifactConfigGeneration::new(), artifacts: BTreeSet::new(), }; diff --git a/sled-agent/config-reconciler/src/ledger/ledgered_config_versioning.rs b/sled-agent/config-reconciler/src/ledger/ledgered_config_versioning.rs index b80217f9b7d..3878d18ccc5 100644 --- a/sled-agent/config-reconciler/src/ledger/ledgered_config_versioning.rs +++ b/sled-agent/config-reconciler/src/ledger/ledgered_config_versioning.rs @@ -19,6 +19,7 @@ use sled_agent_types_versions::v14; use sled_agent_types_versions::v49; use sled_agent_types_versions::v50; use sled_agent_types_versions::v51; +use sled_agent_types_versions::v52; use slog::Logger; use slog::info; use slog::warn; @@ -81,6 +82,7 @@ version_conversion_chain!( ); version_conversion_chain!( + v52::artifact::ArtifactConfig, v1::artifact::ArtifactConfig, VersionConversionChainTerminal, ); @@ -442,7 +444,7 @@ pub(super) mod tests { // Use an explicit type so that adding a new artifact config version // breaks compilation here. Bump the version and add the new version's // path to the array of ledger paths below. - type LatestConfig = v1::artifact::ArtifactConfig; + type LatestConfig = v52::artifact::ArtifactConfig; let counts = check_ledger_reads::( log, diff --git a/sled-agent/src/artifact_store.rs b/sled-agent/src/artifact_store.rs index 0b09165967d..5235bc6ce29 100644 --- a/sled-agent/src/artifact_store.rs +++ b/sled-agent/src/artifact_store.rs @@ -34,7 +34,7 @@ use dropshot::{ }; use futures::{Stream, TryStreamExt}; use omicron_common::address::REPO_DEPOT_PORT; -use omicron_generation_kinds::Generation; +use omicron_generation_kinds::ArtifactConfigGeneration; use omicron_ledger::Ledger; use repo_depot_api::*; use sha2::{Digest, Sha256}; @@ -353,7 +353,7 @@ where async fn writer( &self, sha256: ArtifactHash, - attempted_generation: Generation, + attempted_generation: ArtifactConfigGeneration, ) -> Result { if let Some(config) = self.config.borrow().as_ref() { if attempted_generation != config.generation { @@ -398,7 +398,7 @@ where pub(crate) async fn put_body( &self, sha256: ArtifactHash, - generation: Generation, + generation: ArtifactConfigGeneration, body: StreamingBody, ) -> Result { self.writer(sha256, generation) @@ -411,7 +411,7 @@ where pub(crate) async fn copy_from_depot( &self, sha256: ArtifactHash, - generation: Generation, + generation: ArtifactConfigGeneration, depot_base_url: &str, ) -> Result<(), Error> { // Check that there's no conflict before we send the upstream request. @@ -615,7 +615,7 @@ pub trait DatasetsManager: Clone + Send + Sync + 'static { Ok(None) } - fn signal_delete_done(&self, _generation: Generation) {} + fn signal_delete_done(&self, _generation: ArtifactConfigGeneration) {} } impl DatasetsManager for InternalDisksReceiver { @@ -821,8 +821,8 @@ pub enum Error { while at {current_generation}" )] GenerationConfig { - attempted_generation: Generation, - current_generation: Generation, + attempted_generation: ArtifactConfigGeneration, + current_generation: ArtifactConfigGeneration, }, #[error( @@ -830,8 +830,8 @@ pub enum Error { while at {current_generation}" )] GenerationPut { - attempted_generation: Generation, - current_generation: Generation, + attempted_generation: ArtifactConfigGeneration, + current_generation: ArtifactConfigGeneration, }, #[error("Digest mismatch: expected {expected}, actual {actual}")] @@ -868,7 +868,7 @@ pub enum Error { #[error( "Attempt to put artifact {sha256} not in config generation {generation}" )] - NotInConfig { sha256: ArtifactHash, generation: Generation }, + NotInConfig { sha256: ArtifactHash, generation: ArtifactConfigGeneration }, } impl From for Error { @@ -926,7 +926,7 @@ mod test { use camino_tempfile::Utf8TempDir; use futures::stream::{self, StreamExt}; use hex_literal::hex; - use omicron_generation_kinds::Generation; + use omicron_generation_kinds::ArtifactConfigGeneration; use omicron_test_utils::dev::test_setup_log; use sled_agent_types::artifact::ArtifactConfig; use tokio::io::AsyncReadExt; @@ -938,8 +938,8 @@ mod test { #[derive(Clone)] struct TestBackend { - delete_done_tx: watch::Sender, - delete_done_rx: watch::Receiver, + delete_done_tx: watch::Sender, + delete_done_rx: watch::Receiver, datasets: Vec, _tempdir: Arc, } @@ -975,7 +975,7 @@ mod test { self.datasets.iter().cloned() } - fn signal_delete_done(&self, generation: Generation) { + fn signal_delete_done(&self, generation: ArtifactConfigGeneration) { self.delete_done_tx.send_if_modified(|old| { let modified = *old != generation; *old = generation; diff --git a/sled-agent/src/sim/artifact_store.rs b/sled-agent/src/sim/artifact_store.rs index 0a482ba819c..d02ddb53507 100644 --- a/sled-agent/src/sim/artifact_store.rs +++ b/sled-agent/src/sim/artifact_store.rs @@ -13,7 +13,7 @@ use dropshot::{ Body, ConfigDropshot, FreeformBody, HttpError, HttpResponseOk, HttpServer, Path, RequestContext, ServerBuilder, }; -use omicron_generation_kinds::Generation; +use omicron_generation_kinds::ArtifactConfigGeneration; use repo_depot_api::*; use tokio::sync::{AcquireError, OwnedSemaphorePermit, Semaphore, watch}; @@ -37,7 +37,7 @@ pub struct SimArtifactStorage { // Watch channel to be able to await on the delete reconciler completing in // integration tests. - delete_done: watch::Sender, + delete_done: watch::Sender, } impl SimArtifactStorage { @@ -78,7 +78,7 @@ impl DatasetsManager for SimArtifactStorage { } } - fn signal_delete_done(&self, generation: Generation) { + fn signal_delete_done(&self, generation: ArtifactConfigGeneration) { self.delete_done.send_if_modified(|old| { let modified = *old != generation; *old = generation; @@ -135,7 +135,9 @@ impl ArtifactStore { self.storage.write_semaphore.close(); } - pub fn subscribe_delete_done(&self) -> watch::Receiver { + pub fn subscribe_delete_done( + &self, + ) -> watch::Receiver { self.storage.delete_done.subscribe() } } diff --git a/sled-agent/types/versions/src/latest.rs b/sled-agent/types/versions/src/latest.rs index 7f4936b69b3..fa6b86d7662 100644 --- a/sled-agent/types/versions/src/latest.rs +++ b/sled-agent/types/versions/src/latest.rs @@ -5,13 +5,14 @@ //! Re-exports of the latest versions of all types. pub mod artifact { - pub use crate::v1::artifact::ArtifactConfig; pub use crate::v1::artifact::ArtifactCopyFromDepotBody; pub use crate::v1::artifact::ArtifactCopyFromDepotResponse; - pub use crate::v1::artifact::ArtifactListResponse; pub use crate::v1::artifact::ArtifactPathParam; pub use crate::v1::artifact::ArtifactPutResponse; - pub use crate::v1::artifact::ArtifactQueryParam; + + pub use crate::v52::artifact::ArtifactConfig; + pub use crate::v52::artifact::ArtifactListResponse; + pub use crate::v52::artifact::ArtifactQueryParam; } pub mod attached_subnet { diff --git a/sled-agent/types/versions/src/lib.rs b/sled-agent/types/versions/src/lib.rs index d2947d657e9..df68e5a06db 100644 --- a/sled-agent/types/versions/src/lib.rs +++ b/sled-agent/types/versions/src/lib.rs @@ -107,6 +107,8 @@ pub mod v49; pub mod v50; #[path = "multiple_zone_external_ips/mod.rs"] pub mod v51; +#[path = "typed_artifact_config_generation/mod.rs"] +pub mod v52; #[path = "add_probe_put_endpoint/mod.rs"] pub mod v6; #[path = "multicast_support/mod.rs"] diff --git a/sled-agent/types/versions/src/typed_artifact_config_generation/artifact.rs b/sled-agent/types/versions/src/typed_artifact_config_generation/artifact.rs new file mode 100644 index 00000000000..c40d8c1552b --- /dev/null +++ b/sled-agent/types/versions/src/typed_artifact_config_generation/artifact.rs @@ -0,0 +1,83 @@ +// 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/. + +//! Artifact types for Sled Agent API v52. + +use std::collections::{BTreeMap, BTreeSet}; + +use omicron_generation_kinds::{ArtifactConfigGeneration, GenericGeneration}; +use omicron_ledger::Ledgerable; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use tufaceous_artifact::ArtifactHash; + +use crate::v1; + +/// Query parameters for artifact requests. +#[derive(Deserialize, JsonSchema)] +pub struct ArtifactQueryParam { + pub generation: ArtifactConfigGeneration, +} + +impl From for ArtifactQueryParam { + fn from(value: v1::artifact::ArtifactQueryParam) -> Self { + let v1::artifact::ArtifactQueryParam { generation } = value; + Self { + generation: ArtifactConfigGeneration::from_untyped_generation( + generation, + ), + } + } +} + +/// Response for listing artifacts. +#[derive(Debug, Serialize, JsonSchema)] +pub struct ArtifactListResponse { + pub generation: ArtifactConfigGeneration, + pub list: BTreeMap, +} + +impl From for v1::artifact::ArtifactListResponse { + fn from(value: ArtifactListResponse) -> Self { + let ArtifactListResponse { generation, list } = value; + Self { generation: generation.into_untyped_generation(), list } + } +} + +/// Artifact configuration. +/// +/// This type is used in both GET (response) and PUT (request) operations. +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] +pub struct ArtifactConfig { + pub generation: ArtifactConfigGeneration, + pub artifacts: BTreeSet, +} + +impl Ledgerable for ArtifactConfig { + fn is_newer_than(&self, other: &ArtifactConfig) -> bool { + self.generation > other.generation + } + + // No need to do this, the generation number is provided externally. + fn generation_bump(&mut self) {} +} + +impl From for v1::artifact::ArtifactConfig { + fn from(value: ArtifactConfig) -> Self { + let ArtifactConfig { generation, artifacts } = value; + Self { generation: generation.into_untyped_generation(), artifacts } + } +} + +impl From for ArtifactConfig { + fn from(value: v1::artifact::ArtifactConfig) -> Self { + let v1::artifact::ArtifactConfig { generation, artifacts } = value; + Self { + generation: ArtifactConfigGeneration::from_untyped_generation( + generation, + ), + artifacts, + } + } +} diff --git a/sled-agent/types/versions/src/typed_artifact_config_generation/mod.rs b/sled-agent/types/versions/src/typed_artifact_config_generation/mod.rs new file mode 100644 index 00000000000..5cc3fdf7e09 --- /dev/null +++ b/sled-agent/types/versions/src/typed_artifact_config_generation/mod.rs @@ -0,0 +1,11 @@ +// 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/. + +//! Version `TYPED_ARTIFACT_CONFIG_GENERATION` of the Sled Agent API. +//! +//! This version changes the generation number of `ArtifactConfig`, +//! `ArtifactListResponse` and `ArtifactQueryParam` from an untyped +//! `Generation` to a typed `ArtifactConfigGeneration`. + +pub mod artifact;