#[allow(unused_imports)] use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] use std::convert::TryFrom; #[derive(Clone, Debug, Deserialize, Serialize)] pub struct CrucibleOpts { #[serde(default, skip_serializing_if = "Option::is_none")] pub cert_pem: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub control: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub flush_timeout: Option, pub id: uuid::Uuid, #[serde(default, skip_serializing_if = "Option::is_none")] pub key: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub key_pem: Option, pub lossy: bool, pub read_only: bool, #[serde(default, skip_serializing_if = "Option::is_none")] pub root_cert_pem: Option, pub target: Vec, } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct DiskAttachment { pub disk_id: uuid::Uuid, pub generation_id: u64, pub state: DiskAttachmentState, } #[derive(Clone, Debug, Deserialize, Serialize)] pub enum DiskAttachmentState { Detached, Destroyed, Faulted, Attached(uuid::Uuid), } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct DiskRequest { pub device: String, pub gen: u64, pub name: String, pub read_only: bool, pub slot: Slot, pub volume_construction_request: VolumeConstructionRequest, } ///Error information from a response. #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Error { #[serde(default, skip_serializing_if = "Option::is_none")] pub error_code: Option, pub message: String, pub request_id: String, } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Instance { pub disks: Vec, pub nics: Vec, pub properties: InstanceProperties, pub state: InstanceState, } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct InstanceEnsureRequest { #[serde(default, skip_serializing_if = "Option::is_none")] pub cloud_init_bytes: Option, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub disks: Vec, #[serde(default, skip_serializing_if = "Option::is_none")] pub migrate: Option, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub nics: Vec, pub properties: InstanceProperties, } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct InstanceEnsureResponse { #[serde(default, skip_serializing_if = "Option::is_none")] pub migrate: Option, } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct InstanceGetResponse { pub instance: Instance, } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct InstanceMigrateInitiateRequest { pub migration_id: uuid::Uuid, pub src_addr: String, pub src_uuid: uuid::Uuid, } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct InstanceMigrateInitiateResponse { pub migration_id: uuid::Uuid, } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct InstanceMigrateStatusRequest { pub migration_id: uuid::Uuid, } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct InstanceMigrateStatusResponse { pub state: MigrationState, } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct InstanceProperties { ///ID of the bootrom used to initialize this Instance. pub bootrom_id: uuid::Uuid, ///Free-form text description of an Instance. pub description: String, ///Unique identifier for this Instance. pub id: uuid::Uuid, ///ID of the image used to initialize this Instance. pub image_id: uuid::Uuid, ///Size of memory allocated to the Instance, in MiB. pub memory: u64, ///Human-readable name of the Instance. pub name: String, ///Number of vCPUs to be allocated to the Instance. pub vcpus: u8, } ///Current state of an Instance. #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] pub enum InstanceState { Creating, Starting, Running, Stopping, Stopped, Rebooting, Migrating, Repairing, Failed, Destroyed, } impl ToString for InstanceState { fn to_string(&self) -> String { match *self { Self::Creating => "Creating".to_string(), Self::Starting => "Starting".to_string(), Self::Running => "Running".to_string(), Self::Stopping => "Stopping".to_string(), Self::Stopped => "Stopped".to_string(), Self::Rebooting => "Rebooting".to_string(), Self::Migrating => "Migrating".to_string(), Self::Repairing => "Repairing".to_string(), Self::Failed => "Failed".to_string(), Self::Destroyed => "Destroyed".to_string(), } } } impl std::str::FromStr for InstanceState { type Err = &'static str; fn from_str(value: &str) -> Result { match value { "Creating" => Ok(Self::Creating), "Starting" => Ok(Self::Starting), "Running" => Ok(Self::Running), "Stopping" => Ok(Self::Stopping), "Stopped" => Ok(Self::Stopped), "Rebooting" => Ok(Self::Rebooting), "Migrating" => Ok(Self::Migrating), "Repairing" => Ok(Self::Repairing), "Failed" => Ok(Self::Failed), "Destroyed" => Ok(Self::Destroyed), _ => Err("invalid value"), } } } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct InstanceStateMonitorRequest { pub gen: u64, } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct InstanceStateMonitorResponse { pub gen: u64, pub state: InstanceState, } #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] pub enum InstanceStateRequested { Run, Stop, Reboot, MigrateStart, } impl ToString for InstanceStateRequested { fn to_string(&self) -> String { match *self { Self::Run => "Run".to_string(), Self::Stop => "Stop".to_string(), Self::Reboot => "Reboot".to_string(), Self::MigrateStart => "MigrateStart".to_string(), } } } impl std::str::FromStr for InstanceStateRequested { type Err = &'static str; fn from_str(value: &str) -> Result { match value { "Run" => Ok(Self::Run), "Stop" => Ok(Self::Stop), "Reboot" => Ok(Self::Reboot), "MigrateStart" => Ok(Self::MigrateStart), _ => Err("invalid value"), } } } #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] pub enum MigrationState { Sync, RamPush, Pause, RamPushDirty, Device, Arch, Resume, RamPull, Finish, Error, } impl ToString for MigrationState { fn to_string(&self) -> String { match *self { Self::Sync => "Sync".to_string(), Self::RamPush => "RamPush".to_string(), Self::Pause => "Pause".to_string(), Self::RamPushDirty => "RamPushDirty".to_string(), Self::Device => "Device".to_string(), Self::Arch => "Arch".to_string(), Self::Resume => "Resume".to_string(), Self::RamPull => "RamPull".to_string(), Self::Finish => "Finish".to_string(), Self::Error => "Error".to_string(), } } } impl std::str::FromStr for MigrationState { type Err = &'static str; fn from_str(value: &str) -> Result { match value { "Sync" => Ok(Self::Sync), "RamPush" => Ok(Self::RamPush), "Pause" => Ok(Self::Pause), "RamPushDirty" => Ok(Self::RamPushDirty), "Device" => Ok(Self::Device), "Arch" => Ok(Self::Arch), "Resume" => Ok(Self::Resume), "RamPull" => Ok(Self::RamPull), "Finish" => Ok(Self::Finish), "Error" => Ok(Self::Error), _ => Err("invalid value"), } } } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct NetworkInterface { pub attachment: NetworkInterfaceAttachmentState, pub name: String, } #[derive(Clone, Debug, Deserialize, Serialize)] pub enum NetworkInterfaceAttachmentState { Detached, Faulted, Attached(Slot), } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct NetworkInterfaceRequest { pub name: String, pub slot: Slot, } ///A stable index which is translated by Propolis into a PCI BDF, visible /// to the guest. #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Slot(pub u8); impl std::ops::Deref for Slot { type Target = u8; fn deref(&self) -> &Self::Target { &self.0 } } #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(tag = "type")] pub enum VolumeConstructionRequest { #[serde(rename = "volume")] Volume { block_size: u64, id: uuid::Uuid, #[serde(default, skip_serializing_if = "Option::is_none")] read_only_parent: Option>, sub_volumes: Vec, }, #[serde(rename = "url")] Url { block_size: u64, id: uuid::Uuid, url: String, }, #[serde(rename = "region")] Region { block_size: u64, gen: u64, opts: CrucibleOpts, }, #[serde(rename = "file")] File { block_size: u64, id: uuid::Uuid, path: String, }, } } #[derive(Clone, Debug)] ///Client for Oxide Propolis Server API /// ///API for interacting with the Propolis hypervisor frontend. pub struct Client { pub(crate) baseurl: String, pub(crate) client: reqwest::Client, } impl Client { /// Create a new client. /// /// `baseurl` is the base URL provided to the internal /// `reqwest::Client`, and should include a scheme and hostname, /// as well as port and a path stem if applicable. pub fn new(baseurl: &str) -> Self { let dur = std::time::Duration::from_secs(15); let client = reqwest::ClientBuilder::new() .connect_timeout(dur) .timeout(dur) .build() .unwrap(); Self::new_with_client(baseurl, client) } /// Construct a new client with an existing `reqwest::Client`, /// allowing more control over its configuration. /// /// `baseurl` is the base URL provided to the internal /// `reqwest::Client`, and should include a scheme and hostname, /// as well as port and a path stem if applicable. pub fn new_with_client(baseurl: &str, client: reqwest::Client) -> Self { Self { baseurl: baseurl.to_string(), client, } } /// Return the base URL to which requests are made. pub fn baseurl(&self) -> &String { &self.baseurl } /// Return the internal `reqwest::Client` used to make requests. pub fn client(&self) -> &reqwest::Client { &self.client } } impl Client { ///Sends a `GET` request to `/instance` pub async fn instance_get<'a>( &'a self, ) -> Result, Error> { let url = format!("{}/instance", self.baseurl,); let request = self.client.get(url).build()?; let result = self.client.execute(request).await; let response = result?; match response.status().as_u16() { 200u16 => ResponseValue::from_response(response).await, 400u16..=499u16 => Err(Error::ErrorResponse( ResponseValue::from_response(response).await?, )), 500u16..=599u16 => Err(Error::ErrorResponse( ResponseValue::from_response(response).await?, )), _ => Err(Error::UnexpectedResponse(response)), } } ///Sends a `PUT` request to `/instance` pub async fn instance_ensure<'a>( &'a self, body: &'a types::InstanceEnsureRequest, ) -> Result, Error> { let url = format!("{}/instance", self.baseurl,); let request = self.client.put(url).json(&body).build()?; let result = self.client.execute(request).await; let response = result?; match response.status().as_u16() { 201u16 => ResponseValue::from_response(response).await, 400u16..=499u16 => Err(Error::ErrorResponse( ResponseValue::from_response(response).await?, )), 500u16..=599u16 => Err(Error::ErrorResponse( ResponseValue::from_response(response).await?, )), _ => Err(Error::UnexpectedResponse(response)), } } ///Issue a snapshot request to a crucible backend /// ///Sends a `POST` request to `/instance/disk/{id}/snapshot/{snapshot_id}` pub async fn instance_issue_crucible_snapshot_request<'a>( &'a self, id: &'a uuid::Uuid, snapshot_id: &'a uuid::Uuid, ) -> Result, Error> { let url = format!( "{}/instance/disk/{}/snapshot/{}", self.baseurl, encode_path(&id.to_string()), encode_path(&snapshot_id.to_string()), ); let request = self.client.post(url).build()?; let result = self.client.execute(request).await; let response = result?; match response.status().as_u16() { 200u16 => ResponseValue::from_response(response).await, 400u16..=499u16 => Err(Error::ErrorResponse( ResponseValue::from_response(response).await?, )), 500u16..=599u16 => Err(Error::ErrorResponse( ResponseValue::from_response(response).await?, )), _ => Err(Error::UnexpectedResponse(response)), } } ///Sends a `GET` request to `/instance/migrate/status` pub async fn instance_migrate_status<'a>( &'a self, body: &'a types::InstanceMigrateStatusRequest, ) -> Result, Error> { let url = format!("{}/instance/migrate/status", self.baseurl,); let request = self.client.get(url).json(&body).build()?; let result = self.client.execute(request).await; let response = result?; match response.status().as_u16() { 200u16 => ResponseValue::from_response(response).await, 400u16..=499u16 => Err(Error::ErrorResponse( ResponseValue::from_response(response).await?, )), 500u16..=599u16 => Err(Error::ErrorResponse( ResponseValue::from_response(response).await?, )), _ => Err(Error::UnexpectedResponse(response)), } } ///Sends a `GET` request to `/instance/serial` pub async fn instance_serial<'a>( &'a self, ) -> Result, Error> { let url = format!("{}/instance/serial", self.baseurl,); let request = self .client .get(url) .header(reqwest::header::CONNECTION, "Upgrade") .header(reqwest::header::UPGRADE, "websocket") .header(reqwest::header::SEC_WEBSOCKET_VERSION, "13") .header( reqwest::header::SEC_WEBSOCKET_KEY, base64::Engine::encode( &base64::engine::general_purpose::STANDARD, rand::random::<[u8; 16]>(), ), ) .build()?; let result = self.client.execute(request).await; let response = result?; match response.status().as_u16() { 101u16 => ResponseValue::upgrade(response).await, 200..=299 => ResponseValue::upgrade(response).await, _ => Err(Error::UnexpectedResponse(response)), } } ///Sends a `PUT` request to `/instance/state` pub async fn instance_state_put<'a>( &'a self, body: types::InstanceStateRequested, ) -> Result, Error> { let url = format!("{}/instance/state", self.baseurl,); let request = self.client.put(url).json(&body).build()?; let result = self.client.execute(request).await; let response = result?; match response.status().as_u16() { 204u16 => Ok(ResponseValue::empty(response)), 400u16..=499u16 => Err(Error::ErrorResponse( ResponseValue::from_response(response).await?, )), 500u16..=599u16 => Err(Error::ErrorResponse( ResponseValue::from_response(response).await?, )), _ => Err(Error::UnexpectedResponse(response)), } } ///Sends a `GET` request to `/instance/state-monitor` pub async fn instance_state_monitor<'a>( &'a self, body: &'a types::InstanceStateMonitorRequest, ) -> Result, Error> { let url = format!("{}/instance/state-monitor", self.baseurl,); let request = self.client.get(url).json(&body).build()?; let result = self.client.execute(request).await; let response = result?; match response.status().as_u16() { 200u16 => ResponseValue::from_response(response).await, 400u16..=499u16 => Err(Error::ErrorResponse( ResponseValue::from_response(response).await?, )), 500u16..=599u16 => Err(Error::ErrorResponse( ResponseValue::from_response(response).await?, )), _ => Err(Error::UnexpectedResponse(response)), } } } pub mod prelude { pub use super::Client; }