diff --git a/Cargo.lock b/Cargo.lock index 4a0d60c5da2..239eebcdc33 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3765,7 +3765,7 @@ dependencies = [ [[package]] name = "propolis-client" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/propolis?rev=a11ccd5bf001e0c690c67d117f8beb06b0bc6914#a11ccd5bf001e0c690c67d117f8beb06b0bc6914" +source = "git+https://github.com/oxidecomputer/propolis?rev=1538f78c1656bd3ac8ef816f6177ae9b1bef348a#1538f78c1656bd3ac8ef816f6177ae9b1bef348a" dependencies = [ "crucible", "reqwest", diff --git a/docs/how-to-run.adoc b/docs/how-to-run.adoc index aab25ddc434..827b6f1475a 100644 --- a/docs/how-to-run.adoc +++ b/docs/how-to-run.adoc @@ -133,3 +133,83 @@ unique local addresses in the subnet of the first Sled Agent: `fd00:1122:3344:1: Note that Sled Agent runs in the global zone and is the one responsible for bringing up all the other other services and allocating them with vNICs and IPv6 addresses. + +=== How to provision an instance using the CLI + +Here are the current steps to provision an instance using the https://github.com/oxidecomputer/cli[oxide] +command line interface. + +1. Create an organization and project that the resources will live under: + + oxide org create myorg + oxide project create -o myorg myproj + +2. Define a global image that will be used as initial disk contents. This can be the alpine.iso image that ships with propolis, or an ISO / raw disk image / etc hosted at a URL: + + oxide api /images --method POST --input - < -p serial` + diff --git a/nexus/src/app/image.rs b/nexus/src/app/image.rs index ec387429bfe..c0a609f3d03 100644 --- a/nexus/src/app/image.rs +++ b/nexus/src/app/image.rs @@ -203,6 +203,63 @@ impl super::Nexus { &"creating images from snapshots not supported", )); } + + params::ImageSource::YouCanBootAnythingAsLongAsItsAlpine => { + // Each Propolis zone ships with an alpine.iso (it's part of the + // package-manifest.toml blobs), and for development purposes + // allow users to boot that. This should go away when that blob + // does. + let db_block_size = db::model::BlockSize::Traditional; + let block_size: u64 = db_block_size.to_bytes() as u64; + + let volume_construction_request = sled_agent_client::types::VolumeConstructionRequest::Volume { + block_size, + sub_volumes: vec![ + sled_agent_client::types::VolumeConstructionRequest::File { + block_size, + path: "/opt/oxide/propolis-server/blob/alpine.iso".into(), + } + ], + read_only_parent: None, + }; + + let volume_data = + serde_json::to_string(&volume_construction_request)?; + + // Nexus runs in its own zone so we can't ask the propolis zone + // image tar file for size of alpine.iso. Conservatively set the + // size to 100M (at the time of this comment, it's 41M). Any + // disk created from this image has to be larger than it. + let size: u64 = 100 * 1024 * 1024; + let size: external::ByteCount = + size.try_into().map_err(|e| Error::InvalidValue { + label: String::from("size"), + message: format!("size is invalid: {}", e), + })?; + + let new_image_volume = + db::model::Volume::new(Uuid::new_v4(), volume_data); + let volume = + self.db_datastore.volume_create(new_image_volume).await?; + + db::model::GlobalImage { + identity: db::model::GlobalImageIdentity::new( + Uuid::new_v4(), + params.identity.clone(), + ), + volume_id: volume.id(), + url: None, + distribution: "alpine".parse().map_err(|_| { + Error::internal_error( + &"alpine is not a valid distribution?", + ) + })?, + version: "propolis-blob".into(), + digest: None, + block_size: db_block_size, + size: size.into(), + } + } }; self.db_datastore.global_image_create_image(opctx, new_image).await diff --git a/nexus/src/external_api/params.rs b/nexus/src/external_api/params.rs index 63dcadc0f6b..59014e2498a 100644 --- a/nexus/src/external_api/params.rs +++ b/nexus/src/external_api/params.rs @@ -390,8 +390,16 @@ pub struct NetworkInterfaceIdentifier { #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] #[serde(tag = "type", rename_all = "snake_case")] pub enum ImageSource { - Url { url: String }, - Snapshot { id: Uuid }, + Url { + url: String, + }, + Snapshot { + id: Uuid, + }, + + /// Boot the Alpine ISO that ships with the Propolis zone. Intended for + /// development purposes only. + YouCanBootAnythingAsLongAsItsAlpine, } /// OS image distribution diff --git a/openapi/nexus.json b/openapi/nexus.json index c986641b247..fe1ff4c66d8 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -6100,6 +6100,21 @@ "id", "type" ] + }, + { + "description": "Boot the Alpine ISO that ships with the Propolis zone. Intended for development purposes only.", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "you_can_boot_anything_as_long_as_its_alpine" + ] + } + }, + "required": [ + "type" + ] } ] }, diff --git a/package-manifest.toml b/package-manifest.toml index 434163792c3..8dc69d330a2 100644 --- a/package-manifest.toml +++ b/package-manifest.toml @@ -98,7 +98,7 @@ zone = true [external_package.propolis-server.source] type = "prebuilt" repo = "propolis" -commit = "a11ccd5bf001e0c690c67d117f8beb06b0bc6914" +commit = "1538f78c1656bd3ac8ef816f6177ae9b1bef348a" # The SHA256 digest is automatically posted to: # https://buildomat.eng.oxide.computer/public/file/oxidecomputer/propolis/image//propolis-server.sha256.txt -sha256 = "52dd465bf70f3130e16cfb32eefb87f5d01514f1f60a7dd9a1eca970a0669159" +sha256 = "1e21d95a1254f6796a2a7fee3a49f14988beb5646505fc7c9d669ef49f6a50f3" diff --git a/sled-agent/Cargo.toml b/sled-agent/Cargo.toml index 6f2511ded1d..2aa2a12896f 100644 --- a/sled-agent/Cargo.toml +++ b/sled-agent/Cargo.toml @@ -24,7 +24,7 @@ omicron-common = { path = "../common" } p256 = "0.9.0" percent-encoding = "2.1.0" progenitor = { git = "https://github.com/oxidecomputer/progenitor" } -propolis-client = { git = "https://github.com/oxidecomputer/propolis", rev = "a11ccd5bf001e0c690c67d117f8beb06b0bc6914" } +propolis-client = { git = "https://github.com/oxidecomputer/propolis", rev = "1538f78c1656bd3ac8ef816f6177ae9b1bef348a" } rand = { version = "0.8.5", features = ["getrandom"] } reqwest = { version = "0.11.8", default-features = false, features = ["rustls-tls", "stream"] } schemars = { version = "0.8.10", features = [ "chrono", "uuid1" ] }