diff --git a/bin/propolis-server/src/lib/migrate/types.rs b/bin/propolis-server/src/lib/migrate/types.rs index d88e24b15..e8688b14c 100644 --- a/bin/propolis-server/src/lib/migrate/types.rs +++ b/bin/propolis-server/src/lib/migrate/types.rs @@ -88,7 +88,7 @@ use serde::{Deserialize, Serialize}; -use propolis_api_types_versions::v1::instance::ReplacementComponent; +use propolis_api_types::instance::ReplacementComponent; use propolis_api_types_versions::{v1, v2, v3, v6}; use std::collections::BTreeMap; diff --git a/bin/propolis-server/src/lib/server.rs b/bin/propolis-server/src/lib/server.rs index cba4f65cd..361509dfd 100644 --- a/bin/propolis-server/src/lib/server.rs +++ b/bin/propolis-server/src/lib/server.rs @@ -38,7 +38,7 @@ pub use nexus_client::Client as NexusClient; use oximeter::types::ProducerRegistry; use propolis::attestation::server::AttestationServerConfig; use propolis_api_types::disk::{ - InstanceVCRReplace, SnapshotRequestPathParams, VCRRequestPathParams, + InstanceVcrReplace, SnapshotRequestPathParams, VCRRequestPathParams, VolumeStatus, VolumeStatusPathParams, }; use propolis_api_types::instance::{ @@ -534,7 +534,7 @@ impl PropolisServerApi for PropolisServerImpl { async fn instance_issue_crucible_snapshot_request( rqctx: RequestContext, path_params: Path, - ) -> Result, HttpError> { + ) -> Result { let vm = rqctx .context() .vm @@ -555,7 +555,7 @@ impl PropolisServerApi for PropolisServerImpl { HttpError::for_bad_request(Some(e.to_string()), e.to_string()) })?; - Ok(HttpResponseOk(())) + Ok(HttpResponseUpdatedNoContent {}) } async fn disk_volume_status_v1( @@ -617,7 +617,7 @@ impl PropolisServerApi for PropolisServerImpl { async fn instance_issue_crucible_vcr_request( rqctx: RequestContext, path_params: Path, - request: TypedBody, + request: TypedBody, ) -> Result, HttpError> { let path_params = path_params.into_inner(); @@ -663,7 +663,7 @@ impl PropolisServerApi for PropolisServerImpl { async fn instance_issue_nmi( rqctx: RequestContext, - ) -> Result, HttpError> { + ) -> Result { let vm = rqctx .context() .vm @@ -672,7 +672,7 @@ impl PropolisServerApi for PropolisServerImpl { .ok_or_else(not_created_error)?; let _ = vm.objects().lock_shared().await.machine().inject_nmi(); - Ok(HttpResponseOk(())) + Ok(HttpResponseUpdatedNoContent {}) } } diff --git a/bin/propolis-server/src/lib/spec/api_spec_v1.rs b/bin/propolis-server/src/lib/spec/api_spec_v1.rs index 8440299ed..207096277 100644 --- a/bin/propolis-server/src/lib/spec/api_spec_v1.rs +++ b/bin/propolis-server/src/lib/spec/api_spec_v1.rs @@ -8,6 +8,7 @@ use std::collections::BTreeMap; +use propolis_api_types::instance::ReplacementComponent; use propolis_api_types::instance_spec::{ components::{ board::Board as InstanceSpecBoard, @@ -15,9 +16,7 @@ use propolis_api_types::instance_spec::{ }, SpecKey, }; -use propolis_api_types_versions::{ - v1, v1::instance::ReplacementComponent, v2, v3, v6, -}; +use propolis_api_types_versions::{v1, v2, v3, v6}; #[cfg(feature = "falcon")] use propolis_api_types::instance_spec::components::devices::SoftNpuPort as SoftNpuPortSpec; diff --git a/bin/propolis-server/src/lib/spec/api_spec_v2.rs b/bin/propolis-server/src/lib/spec/api_spec_v2.rs index 030f19e30..f01396586 100644 --- a/bin/propolis-server/src/lib/spec/api_spec_v2.rs +++ b/bin/propolis-server/src/lib/spec/api_spec_v2.rs @@ -7,10 +7,9 @@ use std::collections::BTreeMap; +use propolis_api_types::instance::ReplacementComponent; use propolis_api_types::instance_spec::SpecKey; -use propolis_api_types_versions::{ - v1, v1::instance::ReplacementComponent, v2, v3, -}; +use propolis_api_types_versions::{v1, v2, v3}; use super::{builder::SpecBuilder, LegacyApiSpecError, Spec}; use crate::migrate::MigrateError; diff --git a/bin/propolis-server/src/lib/spec/api_spec_v3.rs b/bin/propolis-server/src/lib/spec/api_spec_v3.rs index 3822e5e13..146974c9d 100644 --- a/bin/propolis-server/src/lib/spec/api_spec_v3.rs +++ b/bin/propolis-server/src/lib/spec/api_spec_v3.rs @@ -7,10 +7,9 @@ use std::collections::BTreeMap; +use propolis_api_types::instance::ReplacementComponent; use propolis_api_types::instance_spec::SpecKey; -use propolis_api_types_versions::{ - v1::instance::ReplacementComponent, v2, v3, v6, -}; +use propolis_api_types_versions::{v2, v3, v6}; use super::{api_spec_v6, builder::SpecBuilder, LegacyApiSpecError, Spec}; use crate::migrate::MigrateError; diff --git a/bin/propolis-server/src/lib/spec/api_spec_v6.rs b/bin/propolis-server/src/lib/spec/api_spec_v6.rs index ac32e1e60..40364ff71 100644 --- a/bin/propolis-server/src/lib/spec/api_spec_v6.rs +++ b/bin/propolis-server/src/lib/spec/api_spec_v6.rs @@ -7,8 +7,9 @@ use std::collections::BTreeMap; +use propolis_api_types::instance::ReplacementComponent; use propolis_api_types::instance_spec::SpecKey; -use propolis_api_types_versions::{v1::instance::ReplacementComponent, v3, v6}; +use propolis_api_types_versions::{v3, v6}; use super::{builder::SpecBuilder, ApiSpecError, Disk, Spec, StorageDevice}; use crate::migrate::MigrateError; diff --git a/crates/propolis-api-types-versions/src/latest.rs b/crates/propolis-api-types-versions/src/latest.rs index 5e64474b0..04de08a68 100644 --- a/crates/propolis-api-types-versions/src/latest.rs +++ b/crates/propolis-api-types-versions/src/latest.rs @@ -48,31 +48,32 @@ pub mod components { } pub mod disk { - pub use crate::v1::disk::InstanceVCRReplace; pub use crate::v1::disk::SnapshotRequestPathParams; pub use crate::v1::disk::VCRRequestPathParams; pub use crate::v1::disk::VolumeStatusPathParams; pub use crate::v5::disk::VolumeStatus; + + pub use crate::v7::disk::InstanceVcrReplace; } pub mod instance { pub use crate::v1::instance::ErrorCode; - pub use crate::v1::instance::Instance; pub use crate::v1::instance::InstanceEnsureResponse; - pub use crate::v1::instance::InstanceGetResponse; pub use crate::v1::instance::InstanceMetadata; pub use crate::v1::instance::InstanceNameParams; pub use crate::v1::instance::InstancePathParams; pub use crate::v1::instance::InstanceProperties; - pub use crate::v1::instance::InstanceState; pub use crate::v1::instance::InstanceStateChange; pub use crate::v1::instance::InstanceStateMonitorRequest; - pub use crate::v1::instance::InstanceStateMonitorResponse; - pub use crate::v1::instance::InstanceStateRequested; - pub use crate::v1::instance::ReplacementComponent; - pub use crate::v6::api::InstanceEnsureRequest; - pub use crate::v6::api::InstanceInitializationMethod; + pub use crate::v7::api::InstanceEnsureRequest; + pub use crate::v7::api::InstanceInitializationMethod; + pub use crate::v7::instance::Instance; + pub use crate::v7::instance::InstanceGetResponse; + pub use crate::v7::instance::InstanceState; + pub use crate::v7::instance::InstanceStateMonitorResponse; + pub use crate::v7::instance::InstanceStateRequested; + pub use crate::v7::instance::ReplacementComponent; } pub mod instance_spec { @@ -87,17 +88,19 @@ pub mod instance_spec { pub use crate::v6::instance_spec::Component; pub use crate::v6::instance_spec::InstanceSpec; - pub use crate::v6::instance_spec::InstanceSpecGetResponse; - pub use crate::v6::instance_spec::InstanceSpecStatus; + + pub use crate::v7::instance_spec::InstanceSpecGetResponse; + pub use crate::v7::instance_spec::InstanceSpecStatus; } pub mod migration { pub use crate::v1::migration::InstanceMigrateInitiateRequest; pub use crate::v1::migration::InstanceMigrateInitiateResponse; pub use crate::v1::migration::InstanceMigrateStartRequest; - pub use crate::v1::migration::InstanceMigrateStatusResponse; - pub use crate::v1::migration::InstanceMigrationStatus; - pub use crate::v1::migration::MigrationState; + + pub use crate::v7::migration::InstanceMigrateStatusResponse; + pub use crate::v7::migration::InstanceMigrationStatus; + pub use crate::v7::migration::MigrationState; } pub mod serial { diff --git a/crates/propolis-api-types-versions/src/lib.rs b/crates/propolis-api-types-versions/src/lib.rs index aecc1a645..55bc4f31f 100644 --- a/crates/propolis-api-types-versions/src/lib.rs +++ b/crates/propolis-api-types-versions/src/lib.rs @@ -41,3 +41,5 @@ pub mod v3; pub mod v5; #[path = "nvme_write_cache/mod.rs"] pub mod v6; +#[path = "openapi_lint/mod.rs"] +pub mod v7; diff --git a/crates/propolis-api-types-versions/src/openapi_lint/api.rs b/crates/propolis-api-types-versions/src/openapi_lint/api.rs new file mode 100644 index 000000000..744f157ca --- /dev/null +++ b/crates/propolis-api-types-versions/src/openapi_lint/api.rs @@ -0,0 +1,65 @@ +// 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/. + +use std::{collections::BTreeMap, net::SocketAddr}; + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +use super::instance::ReplacementComponent; +use crate::v1::instance::InstanceProperties; +use crate::v1::instance_spec::SpecKey; +use crate::v6; +use crate::v6::instance_spec::InstanceSpec; + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(tag = "method", content = "value", rename_all = "snake_case")] +pub enum InstanceInitializationMethod { + Spec { + spec: InstanceSpec, + }, + MigrationTarget { + migration_id: Uuid, + src_addr: SocketAddr, + replace_components: BTreeMap, + }, +} + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct InstanceEnsureRequest { + pub properties: InstanceProperties, + pub init: InstanceInitializationMethod, +} + +impl From + for InstanceInitializationMethod +{ + fn from(old: v6::api::InstanceInitializationMethod) -> Self { + match old { + v6::api::InstanceInitializationMethod::Spec { spec } => { + Self::Spec { spec } + } + v6::api::InstanceInitializationMethod::MigrationTarget { + migration_id, + src_addr, + replace_components, + } => Self::MigrationTarget { + migration_id, + src_addr, + replace_components: replace_components + .into_iter() + .map(|(k, v)| (k, ReplacementComponent::from(v))) + .collect(), + }, + } + } +} + +impl From for InstanceEnsureRequest { + fn from(old: v6::api::InstanceEnsureRequest) -> Self { + let v6::api::InstanceEnsureRequest { properties, init } = old; + Self { properties, init: init.into() } + } +} diff --git a/crates/propolis-api-types-versions/src/openapi_lint/disk.rs b/crates/propolis-api-types-versions/src/openapi_lint/disk.rs new file mode 100644 index 000000000..ea4221c30 --- /dev/null +++ b/crates/propolis-api-types-versions/src/openapi_lint/disk.rs @@ -0,0 +1,20 @@ +// 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/. + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::v1; + +#[derive(Clone, Deserialize, Serialize, JsonSchema)] +pub struct InstanceVcrReplace { + pub vcr_json: String, +} + +impl From for InstanceVcrReplace { + fn from(old: v1::disk::InstanceVCRReplace) -> Self { + let v1::disk::InstanceVCRReplace { vcr_json } = old; + Self { vcr_json } + } +} diff --git a/crates/propolis-api-types-versions/src/openapi_lint/instance.rs b/crates/propolis-api-types-versions/src/openapi_lint/instance.rs new file mode 100644 index 000000000..646ae7e1e --- /dev/null +++ b/crates/propolis-api-types-versions/src/openapi_lint/instance.rs @@ -0,0 +1,159 @@ +// 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/. + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use super::migration::InstanceMigrateStatusResponse; +use crate::v1; +use crate::v1::components::{backends, devices}; +use crate::v1::instance::InstanceProperties; + +/// Current state of an Instance. +#[derive( + Clone, Copy, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema, +)] +#[serde(rename_all = "snake_case")] +pub enum InstanceState { + Creating, + Starting, + Running, + Stopping, + Stopped, + Rebooting, + Migrating, + Repairing, + Failed, + Destroyed, +} + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct Instance { + pub properties: InstanceProperties, + pub state: InstanceState, +} + +#[derive(Clone, Deserialize, Serialize, JsonSchema)] +pub struct InstanceGetResponse { + pub instance: Instance, +} + +#[derive(Clone, Copy, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum InstanceStateRequested { + Run, + Stop, + Reboot, +} + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct InstanceStateMonitorResponse { + pub gen: u64, + pub state: InstanceState, + pub migration: InstanceMigrateStatusResponse, +} + +/// An instance spec component that should be replaced during a live migration. +// +// When a caller asks Propolis to initialize via live migration, the target VM +// inherits the migration source's current instance spec. For the most part, +// the target can (and indeed in some cases must) use this spec without +// modifying it; this helps Propolis ensure that guest-visible configuration +// remains unchanged when a VM migrates. However, there are some components +// with no guest-visible state that may need to be reconfigured when a VM +// migrates. These include the following: +// +// - Crucible disks: After migrating, the target Propolis presents itself as a +// new client of the Crucible downstairs servers backing the VM's disks. +// Crucible requires the target to present a newer client generation number +// to allow the target to connect. In a full Oxide deployment, these numbers +// are managed by the control plane (i.e. it is not safe for Propolis to +// manage these values directly--new Crucible volume connection information +// must always come from Nexus). +// - Virtio network devices: Each virtio NIC in the guest needs to bind to a +// named VNIC object on the host. These names can change when a VM migrates +// from host to host. +// +// Each component that can be reconfigured this way has a variant in this enum; +// components not in the enum can't be reconfigured during migration. This +// saves the initialization API from having to reason about requests to replace +// a component that can't legally be replaced. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde( + deny_unknown_fields, + tag = "component", + content = "spec", + rename_all = "snake_case" +)] +pub enum ReplacementComponent { + MigrationFailureInjector(devices::MigrationFailureInjector), + CrucibleStorageBackend(backends::CrucibleStorageBackend), + VirtioNetworkBackend(backends::VirtioNetworkBackend), +} + +impl From for v1::instance::InstanceState { + fn from(new: InstanceState) -> Self { + match new { + 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, + } + } +} + +impl From for v1::instance::Instance { + fn from(new: Instance) -> Self { + let Instance { properties, state } = new; + Self { properties, state: state.into() } + } +} + +impl From for v1::instance::InstanceGetResponse { + fn from(new: InstanceGetResponse) -> Self { + let InstanceGetResponse { instance } = new; + Self { instance: instance.into() } + } +} + +impl From for InstanceStateRequested { + fn from(old: v1::instance::InstanceStateRequested) -> Self { + match old { + v1::instance::InstanceStateRequested::Run => Self::Run, + v1::instance::InstanceStateRequested::Stop => Self::Stop, + v1::instance::InstanceStateRequested::Reboot => Self::Reboot, + } + } +} + +impl From + for v1::instance::InstanceStateMonitorResponse +{ + fn from(new: InstanceStateMonitorResponse) -> Self { + let InstanceStateMonitorResponse { gen, state, migration } = new; + Self { gen, state: state.into(), migration: migration.into() } + } +} + +impl From for ReplacementComponent { + fn from(old: v1::instance::ReplacementComponent) -> Self { + match old { + v1::instance::ReplacementComponent::MigrationFailureInjector(c) => { + Self::MigrationFailureInjector(c) + } + v1::instance::ReplacementComponent::CrucibleStorageBackend(c) => { + Self::CrucibleStorageBackend(c) + } + v1::instance::ReplacementComponent::VirtioNetworkBackend(c) => { + Self::VirtioNetworkBackend(c) + } + } + } +} diff --git a/crates/propolis-api-types-versions/src/openapi_lint/instance_spec.rs b/crates/propolis-api-types-versions/src/openapi_lint/instance_spec.rs new file mode 100644 index 000000000..728208f05 --- /dev/null +++ b/crates/propolis-api-types-versions/src/openapi_lint/instance_spec.rs @@ -0,0 +1,45 @@ +// 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/. + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use super::instance::InstanceState; +use crate::v1::instance::InstanceProperties; +use crate::v6; +use crate::v6::instance_spec::InstanceSpec; + +#[derive(Clone, Deserialize, Serialize, JsonSchema)] +#[serde(tag = "type", content = "value", rename_all = "snake_case")] +pub enum InstanceSpecStatus { + WaitingForMigrationSource, + Present(InstanceSpec), +} + +#[derive(Clone, Deserialize, Serialize, JsonSchema)] +pub struct InstanceSpecGetResponse { + pub properties: InstanceProperties, + pub state: InstanceState, + pub spec: InstanceSpecStatus, +} + +impl From for v6::instance_spec::InstanceSpecStatus { + fn from(new: InstanceSpecStatus) -> Self { + match new { + InstanceSpecStatus::WaitingForMigrationSource => { + Self::WaitingForMigrationSource + } + InstanceSpecStatus::Present(spec) => Self::Present(spec), + } + } +} + +impl From + for v6::instance_spec::InstanceSpecGetResponse +{ + fn from(new: InstanceSpecGetResponse) -> Self { + let InstanceSpecGetResponse { properties, state, spec } = new; + Self { properties, state: state.into(), spec: spec.into() } + } +} diff --git a/crates/propolis-api-types-versions/src/openapi_lint/migration.rs b/crates/propolis-api-types-versions/src/openapi_lint/migration.rs new file mode 100644 index 000000000..b97d282e2 --- /dev/null +++ b/crates/propolis-api-types-versions/src/openapi_lint/migration.rs @@ -0,0 +1,104 @@ +// 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/. + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +use crate::v1; + +/// The status of an individual live migration. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq, Eq)] +pub struct InstanceMigrationStatus { + /// The ID of this migration, supplied either by the external migration + /// requester (for targets) or the other side of the migration (for + /// sources). + pub id: Uuid, + /// The current phase the migration is in. + pub state: MigrationState, +} + +/// The statuses of the most recent attempts to live migrate into and out of +/// this Propolis. +/// +/// If a VM is initialized by migration in and then begins to migrate out, this +/// structure will contain statuses for both migrations. This ensures that +/// clients can always obtain the status of a successful migration in even after +/// a migration out begins. +/// +/// This structure only reports the status of the most recent migration in a +/// single direction. That is, if a migration in or out fails, and a new +/// migration attempt begins, the new migration's status replaces the old's. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq, Eq)] +pub struct InstanceMigrateStatusResponse { + /// The status of the most recent attempt to initialize the current instance + /// via migration in, or `None` if the instance has never been a migration + /// target. + pub migration_in: Option, + /// The status of the most recent attempt to migrate out of the current + /// instance, or `None` if the instance has never been a migration source. + pub migration_out: Option, +} + +#[derive( + Clone, + Copy, + Debug, + Deserialize, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + JsonSchema, +)] +#[serde(rename_all = "snake_case")] +pub enum MigrationState { + Sync, + RamPush, + Pause, + RamPushDirty, + Device, + Resume, + RamPull, + Server, + Finish, + Error, +} + +impl From for v1::migration::MigrationState { + fn from(new: MigrationState) -> Self { + match new { + MigrationState::Sync => Self::Sync, + MigrationState::RamPush => Self::RamPush, + MigrationState::Pause => Self::Pause, + MigrationState::RamPushDirty => Self::RamPushDirty, + MigrationState::Device => Self::Device, + MigrationState::Resume => Self::Resume, + MigrationState::RamPull => Self::RamPull, + MigrationState::Server => Self::Server, + MigrationState::Finish => Self::Finish, + MigrationState::Error => Self::Error, + } + } +} + +impl From for v1::migration::InstanceMigrationStatus { + fn from(new: InstanceMigrationStatus) -> Self { + let InstanceMigrationStatus { id, state } = new; + Self { id, state: state.into() } + } +} + +impl From + for v1::migration::InstanceMigrateStatusResponse +{ + fn from(new: InstanceMigrateStatusResponse) -> Self { + let InstanceMigrateStatusResponse { migration_in, migration_out } = new; + Self { + migration_in: migration_in.map(Into::into), + migration_out: migration_out.map(Into::into), + } + } +} diff --git a/crates/propolis-api-types-versions/src/openapi_lint/mod.rs b/crates/propolis-api-types-versions/src/openapi_lint/mod.rs new file mode 100644 index 000000000..ed150eefd --- /dev/null +++ b/crates/propolis-api-types-versions/src/openapi_lint/mod.rs @@ -0,0 +1,15 @@ +// 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 `OPENAPI_LINT` of the Propolis Server API. +//! +//! This version makes the API conform to openapi-lint: enum values are +//! snake_case, `InstanceVCRReplace` is renamed to `InstanceVcrReplace`, and +//! endpoints that returned a JSON null now return 204 No Content. + +pub mod api; +pub mod disk; +pub mod instance; +pub mod instance_spec; +pub mod migration; diff --git a/crates/propolis-server-api/src/lib.rs b/crates/propolis-server-api/src/lib.rs index 298d0d33e..487a67339 100644 --- a/crates/propolis-server-api/src/lib.rs +++ b/crates/propolis-server-api/src/lib.rs @@ -8,7 +8,7 @@ use dropshot::{ WebsocketChannelResult, WebsocketConnection, }; use dropshot_api_manager_types::api_versions; -use propolis_api_types_versions::{latest, v1, v2, v3, v6}; +use propolis_api_types_versions::{latest, v1, v2, v3, v6, v7}; api_versions!([ // WHEN CHANGING THE API (part 1 of 2): @@ -22,6 +22,7 @@ api_versions!([ // | example for the next person. // v // (next_int, IDENT), + (7, OPENAPI_LINT), (6, NVME_WRITE_CACHE), (5, CRUCIBLE_VOLUME_INFO), (4, DROPSHOT_BUMP_WEBSOCKET), @@ -49,7 +50,7 @@ pub trait PropolisServerApi { #[endpoint { method = PUT, path = "/instance", - versions = VERSION_NVME_WRITE_CACHE.. + versions = VERSION_OPENAPI_LINT.. }] async fn instance_ensure( rqctx: RequestContext, @@ -59,6 +60,26 @@ pub trait PropolisServerApi { HttpError, >; + #[endpoint { + operation_id = "instance_ensure", + method = PUT, + path = "/instance", + versions = VERSION_NVME_WRITE_CACHE..VERSION_OPENAPI_LINT + }] + async fn instance_ensure_v6( + rqctx: RequestContext, + request: TypedBody, + ) -> Result< + HttpResponseCreated, + HttpError, + > { + Self::instance_ensure( + rqctx, + request.map(v7::api::InstanceEnsureRequest::from), + ) + .await + } + #[endpoint { operation_id = "instance_ensure", method = PUT, @@ -72,7 +93,7 @@ pub trait PropolisServerApi { HttpResponseCreated, HttpError, > { - Self::instance_ensure( + Self::instance_ensure_v6( rqctx, request.map(v6::api::InstanceEnsureRequest::from), ) @@ -122,7 +143,7 @@ pub trait PropolisServerApi { #[endpoint { method = GET, path = "/instance/spec", - versions = VERSION_NVME_WRITE_CACHE.. + versions = VERSION_OPENAPI_LINT.. }] async fn instance_spec_get( rqctx: RequestContext, @@ -131,6 +152,23 @@ pub trait PropolisServerApi { HttpError, >; + #[endpoint { + operation_id = "instance_spec_get", + method = GET, + path = "/instance/spec", + versions = VERSION_NVME_WRITE_CACHE..VERSION_OPENAPI_LINT + }] + async fn instance_spec_get_v6( + rqctx: RequestContext, + ) -> Result< + HttpResponseOk, + HttpError, + > { + Ok(Self::instance_spec_get(rqctx) + .await? + .map(v6::instance_spec::InstanceSpecGetResponse::from)) + } + #[endpoint { operation_id = "instance_spec_get", method = GET, @@ -143,7 +181,7 @@ pub trait PropolisServerApi { HttpResponseOk, HttpError, > { - Ok(Self::instance_spec_get(rqctx) + Ok(Self::instance_spec_get_v6(rqctx) .await? .map(v3::instance_spec::InstanceSpecGetResponse::from)) } @@ -193,14 +231,31 @@ pub trait PropolisServerApi { #[endpoint { method = GET, path = "/instance", + versions = VERSION_OPENAPI_LINT.. }] async fn instance_get( rqctx: RequestContext, ) -> Result, HttpError>; + #[endpoint { + operation_id = "instance_get", + method = GET, + path = "/instance", + versions = ..VERSION_OPENAPI_LINT + }] + async fn instance_get_v1( + rqctx: RequestContext, + ) -> Result, HttpError> + { + Ok(Self::instance_get(rqctx) + .await? + .map(v1::instance::InstanceGetResponse::from)) + } + #[endpoint { method = GET, path = "/instance/state-monitor", + versions = VERSION_OPENAPI_LINT.. }] async fn instance_state_monitor( rqctx: RequestContext, @@ -210,15 +265,51 @@ pub trait PropolisServerApi { HttpError, >; + #[endpoint { + operation_id = "instance_state_monitor", + method = GET, + path = "/instance/state-monitor", + versions = ..VERSION_OPENAPI_LINT + }] + async fn instance_state_monitor_v1( + rqctx: RequestContext, + request: TypedBody, + ) -> Result< + HttpResponseOk, + HttpError, + > { + Ok(Self::instance_state_monitor(rqctx, request) + .await? + .map(v1::instance::InstanceStateMonitorResponse::from)) + } + #[endpoint { method = PUT, path = "/instance/state", + versions = VERSION_OPENAPI_LINT.. }] async fn instance_state_put( rqctx: RequestContext, request: TypedBody, ) -> Result; + #[endpoint { + operation_id = "instance_state_put", + method = PUT, + path = "/instance/state", + versions = ..VERSION_OPENAPI_LINT + }] + async fn instance_state_put_v1( + rqctx: RequestContext, + request: TypedBody, + ) -> Result { + Self::instance_state_put( + rqctx, + request.map(v7::instance::InstanceStateRequested::from), + ) + .await + } + #[endpoint { method = GET, path = "/instance/serial/history", @@ -292,7 +383,8 @@ pub trait PropolisServerApi { #[endpoint { method = GET, - path = "/instance/migration-status" + path = "/instance/migration-status", + versions = VERSION_OPENAPI_LINT.. }] async fn instance_migrate_status( rqctx: RequestContext, @@ -301,15 +393,49 @@ pub trait PropolisServerApi { HttpError, >; + #[endpoint { + operation_id = "instance_migrate_status", + method = GET, + path = "/instance/migration-status", + versions = ..VERSION_OPENAPI_LINT + }] + async fn instance_migrate_status_v1( + rqctx: RequestContext, + ) -> Result< + HttpResponseOk, + HttpError, + > { + Ok(Self::instance_migrate_status(rqctx) + .await? + .map(v1::migration::InstanceMigrateStatusResponse::from)) + } + /// Issues a snapshot request to a crucible backend. #[endpoint { method = POST, path = "/instance/disk/{id}/snapshot/{snapshot_id}", + versions = VERSION_OPENAPI_LINT.. }] async fn instance_issue_crucible_snapshot_request( rqctx: RequestContext, path_params: Path, - ) -> Result, HttpError>; + ) -> Result; + + /// Issues a snapshot request to a crucible backend. + #[endpoint { + operation_id = "instance_issue_crucible_snapshot_request", + method = POST, + path = "/instance/disk/{id}/snapshot/{snapshot_id}", + versions = ..VERSION_OPENAPI_LINT + }] + async fn instance_issue_crucible_snapshot_request_v1( + rqctx: RequestContext, + path_params: Path, + ) -> Result, HttpError> { + Self::instance_issue_crucible_snapshot_request(rqctx, path_params) + .await?; + Ok(HttpResponseOk(())) + } /// Gets the status of a Crucible volume backing a disk #[endpoint { @@ -338,19 +464,56 @@ pub trait PropolisServerApi { #[endpoint { method = PUT, path = "/instance/disk/{id}/vcr", + versions = VERSION_OPENAPI_LINT.. }] async fn instance_issue_crucible_vcr_request( rqctx: RequestContext, path_params: Path, - request: TypedBody, + request: TypedBody, ) -> Result, HttpError>; + /// Issues a volume_construction_request replace to a crucible backend. + #[endpoint { + operation_id = "instance_issue_crucible_vcr_request", + method = PUT, + path = "/instance/disk/{id}/vcr", + versions = ..VERSION_OPENAPI_LINT + }] + async fn instance_issue_crucible_vcr_request_v1( + rqctx: RequestContext, + path_params: Path, + request: TypedBody, + ) -> Result, HttpError> + { + Self::instance_issue_crucible_vcr_request( + rqctx, + path_params, + request.map(v7::disk::InstanceVcrReplace::from), + ) + .await + } + /// Issues an NMI to the instance. #[endpoint { method = POST, path = "/instance/nmi", + versions = VERSION_OPENAPI_LINT.. }] async fn instance_issue_nmi( rqctx: RequestContext, - ) -> Result, HttpError>; + ) -> Result; + + /// Issues an NMI to the instance. + #[endpoint { + operation_id = "instance_issue_nmi", + method = POST, + path = "/instance/nmi", + versions = ..VERSION_OPENAPI_LINT + }] + async fn instance_issue_nmi_v1( + rqctx: RequestContext, + ) -> Result, HttpError> { + Self::instance_issue_nmi(rqctx).await?; + Ok(HttpResponseOk(())) + } } diff --git a/openapi/propolis-server/propolis-server-6.0.0-b5b984.json.gitstub b/openapi/propolis-server/propolis-server-6.0.0-b5b984.json.gitstub new file mode 100644 index 000000000..a2c8f9747 --- /dev/null +++ b/openapi/propolis-server/propolis-server-6.0.0-b5b984.json.gitstub @@ -0,0 +1 @@ +046f74302e2ea09a75b0a6810645d42c7df6644a:openapi/propolis-server/propolis-server-6.0.0-b5b984.json diff --git a/openapi/propolis-server/propolis-server-6.0.0-b5b984.json b/openapi/propolis-server/propolis-server-7.0.0-712b68.json similarity index 97% rename from openapi/propolis-server/propolis-server-6.0.0-b5b984.json rename to openapi/propolis-server/propolis-server-7.0.0-712b68.json index 1bfc621c4..8b222f1bb 100644 --- a/openapi/propolis-server/propolis-server-6.0.0-b5b984.json +++ b/openapi/propolis-server/propolis-server-7.0.0-712b68.json @@ -7,7 +7,7 @@ "url": "https://oxide.computer", "email": "api@oxide.computer" }, - "version": "6.0.0" + "version": "7.0.0" }, "paths": { "/instance": { @@ -88,19 +88,8 @@ } ], "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "title": "Null", - "type": "string", - "enum": [ - null - ] - } - } - } + "204": { + "description": "resource updated" }, "4XX": { "$ref": "#/components/responses/Error" @@ -163,7 +152,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/InstanceVCRReplace" + "$ref": "#/components/schemas/InstanceVcrReplace" } } }, @@ -247,19 +236,8 @@ "summary": "Issues an NMI to the instance.", "operationId": "instance_issue_nmi", "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "title": "Null", - "type": "string", - "enum": [ - null - ] - } - } - } + "204": { + "description": "resource updated" }, "4XX": { "$ref": "#/components/responses/Error" @@ -1345,7 +1323,7 @@ "method": { "type": "string", "enum": [ - "Spec" + "spec" ] }, "value": { @@ -1371,7 +1349,7 @@ "method": { "type": "string", "enum": [ - "MigrationTarget" + "migration_target" ] }, "value": { @@ -1610,7 +1588,7 @@ "type": { "type": "string", "enum": [ - "WaitingForMigrationSource" + "waiting_for_migration_source" ] } }, @@ -1624,7 +1602,7 @@ "type": { "type": "string", "enum": [ - "Present" + "present" ] }, "value": { @@ -1642,16 +1620,16 @@ "description": "Current state of an Instance.", "type": "string", "enum": [ - "Creating", - "Starting", - "Running", - "Stopping", - "Stopped", - "Rebooting", - "Migrating", - "Repairing", - "Failed", - "Destroyed" + "creating", + "starting", + "running", + "stopping", + "stopped", + "rebooting", + "migrating", + "repairing", + "failed", + "destroyed" ] }, "InstanceStateMonitorRequest": { @@ -1691,12 +1669,12 @@ "InstanceStateRequested": { "type": "string", "enum": [ - "Run", - "Stop", - "Reboot" + "run", + "stop", + "reboot" ] }, - "InstanceVCRReplace": { + "InstanceVcrReplace": { "type": "object", "properties": { "vcr_json": { @@ -1733,16 +1711,16 @@ "MigrationState": { "type": "string", "enum": [ - "Sync", - "RamPush", - "Pause", - "RamPushDirty", - "Device", - "Resume", - "RamPull", - "Server", - "Finish", - "Error" + "sync", + "ram_push", + "pause", + "ram_push_dirty", + "device", + "resume", + "ram_pull", + "server", + "finish", + "error" ] }, "NvmeDisk": { @@ -1907,7 +1885,7 @@ "component": { "type": "string", "enum": [ - "MigrationFailureInjector" + "migration_failure_injector" ] }, "spec": { @@ -1926,7 +1904,7 @@ "component": { "type": "string", "enum": [ - "CrucibleStorageBackend" + "crucible_storage_backend" ] }, "spec": { @@ -1945,7 +1923,7 @@ "component": { "type": "string", "enum": [ - "VirtioNetworkBackend" + "virtio_network_backend" ] }, "spec": { diff --git a/openapi/propolis-server/propolis-server-latest.json b/openapi/propolis-server/propolis-server-latest.json index 114ec385b..dfaf8de31 120000 --- a/openapi/propolis-server/propolis-server-latest.json +++ b/openapi/propolis-server/propolis-server-latest.json @@ -1 +1 @@ -propolis-server-6.0.0-b5b984.json \ No newline at end of file +propolis-server-7.0.0-712b68.json \ No newline at end of file