From a18612cd3e99925c313e4ce16adbc6c9342f86cb Mon Sep 17 00:00:00 2001 From: Greg Colombo Date: Wed, 10 Jan 2024 01:04:12 +0000 Subject: [PATCH 1/4] PHD: add guest adapter for WS2022 The adapter assumes that - sysprep has been run such that the guest OS is generalized and, at first boot, its hostname will be set to "PHD-WINDOWS" - Cygwin is installed to C:\cygwin - the local administrator account is enabled with the appropriate password A suitable image is available on catacomb. This image passes `nproc_test`, but currently fails several other tests in ways that appear to be unrelated to the correctness of the adapter: - The Cygwin version in the test image doesn't include a `pciutils` package in-box, so the `lspci` lifecycle test doesn't work as expected. There are Windows ports of `pciutils` available that could resolve this, but a more flexible approach (which I will probably take in a future PR) is to allow tests to ask the framework what "flavor" of user space the current guest provides so that they can tailor their commands accordingly. - The Crucible tests unconditionally specify that the guest disk size should be 10 GiB, which is smaller than the guest image, which (unsurprisingly) leads to a very unhappy guest. This is a general problem with the way Crucible disk sizes work and will be fixed in a separate PR. Tests: local test run of the `nproc` smoke test with a WS2022 image. --- phd-tests/framework/src/guest_os/mod.rs | 6 ++ .../src/guest_os/windows_server_2022.rs | 67 +++++++++++++++++++ phd-tests/framework/src/test_vm/mod.rs | 4 +- 3 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 phd-tests/framework/src/guest_os/windows_server_2022.rs diff --git a/phd-tests/framework/src/guest_os/mod.rs b/phd-tests/framework/src/guest_os/mod.rs index 1d2baf0d7..5cd109f7e 100644 --- a/phd-tests/framework/src/guest_os/mod.rs +++ b/phd-tests/framework/src/guest_os/mod.rs @@ -12,6 +12,7 @@ use serde::{Deserialize, Serialize}; mod alpine; mod debian11_nocloud; mod ubuntu22_04; +mod windows_server_2022; /// An entry in a sequence of interactions with the guest's command prompt. pub(super) enum CommandSequenceEntry { @@ -43,6 +44,7 @@ pub enum GuestOsKind { Alpine, Debian11NoCloud, Ubuntu2204, + WindowsServer2022, } impl FromStr for GuestOsKind { @@ -53,6 +55,7 @@ impl FromStr for GuestOsKind { "alpine" => Ok(Self::Alpine), "debian11nocloud" => Ok(Self::Debian11NoCloud), "ubuntu2204" => Ok(Self::Ubuntu2204), + "windowsserver2022" => Ok(Self::WindowsServer2022), _ => Err(std::io::Error::new( std::io::ErrorKind::InvalidInput, format!("Unrecognized guest OS kind {}", s), @@ -68,5 +71,8 @@ pub(super) fn get_guest_os_adapter(kind: GuestOsKind) -> Box { Box::new(debian11_nocloud::Debian11NoCloud) } GuestOsKind::Ubuntu2204 => Box::new(ubuntu22_04::Ubuntu2204), + GuestOsKind::WindowsServer2022 => { + Box::new(windows_server_2022::WindowsServer2022) + } } } diff --git a/phd-tests/framework/src/guest_os/windows_server_2022.rs b/phd-tests/framework/src/guest_os/windows_server_2022.rs new file mode 100644 index 000000000..0d36b5f76 --- /dev/null +++ b/phd-tests/framework/src/guest_os/windows_server_2022.rs @@ -0,0 +1,67 @@ +// 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/. + +//! Guest OS adaptations for Windows Server 2022 images. This adapter assumes +//! the guest OS image has been generalized, that Cygwin has been installed, and +//! that the local administrator user was configured at image generation time to +//! have the appropriate password. + +use super::{CommandSequence, CommandSequenceEntry, GuestOs}; + +pub(super) struct WindowsServer2022; + +impl GuestOs for WindowsServer2022 { + fn get_login_sequence(&self) -> CommandSequence { + CommandSequence(vec![ + // The image is generalized and needs to be specialized. Let it boot + // once, then wait for it to boot again and only then start waiting + // for the command prompt to become available. + CommandSequenceEntry::WaitFor( + "Computer is booting, SAC started and initialized.", + ), + CommandSequenceEntry::WaitFor( + "Computer is booting, SAC started and initialized.", + ), + CommandSequenceEntry::WaitFor( + "EVENT: The CMD command is now available.", + ), + CommandSequenceEntry::WaitFor("SAC>"), + CommandSequenceEntry::WriteStr("cmd"), + CommandSequenceEntry::WaitFor("Channel: Cmd0001"), + CommandSequenceEntry::WaitFor("SAC>"), + CommandSequenceEntry::WriteStr("ch -sn Cmd0001"), + CommandSequenceEntry::WaitFor( + "Use any other key to view this channel.", + ), + CommandSequenceEntry::WriteStr(""), + CommandSequenceEntry::WaitFor("Username:"), + CommandSequenceEntry::WriteStr("Administrator"), + CommandSequenceEntry::WaitFor("Domain :"), + CommandSequenceEntry::WriteStr(""), + CommandSequenceEntry::WaitFor("Password:"), + CommandSequenceEntry::WriteStr("0xide#1Fan"), + // For reasons unknown, the first command prompt the serial console + // produces is flaky when being sent actual commands (it appears to + // eat the command and just process the newline). It also appears to + // prefer carriage returns to linefeeds. Accommodate this behavior + // until Cygwin is launched. + CommandSequenceEntry::WaitFor("C:\\Windows\\system32>"), + CommandSequenceEntry::WriteStr("\r"), + CommandSequenceEntry::WaitFor("C:\\Windows\\system32>"), + CommandSequenceEntry::WriteStr("C:\\cygwin\\cygwin.bat\r"), + CommandSequenceEntry::WaitFor("$ "), + // Tweak the command prompt so that it appears on a single line with + // no leading newlines. + CommandSequenceEntry::WriteStr("PS1='\\u@\\h:$ '"), + ]) + } + + fn get_shell_prompt(&self) -> &'static str { + "Administrator@PHD-WINDOWS:$ " + } + + fn read_only_fs(&self) -> bool { + false + } +} diff --git a/phd-tests/framework/src/test_vm/mod.rs b/phd-tests/framework/src/test_vm/mod.rs index f93ac6bfd..494cb2ae7 100644 --- a/phd-tests/framework/src/test_vm/mod.rs +++ b/phd-tests/framework/src/test_vm/mod.rs @@ -612,8 +612,8 @@ impl TestVm { async fn send_serial_str_async(&self, string: &str) -> Result<()> { let mut bytes = Vec::new(); bytes.extend_from_slice(string.as_bytes()); - self.send_serial_bytes_async(bytes).await?; - self.send_serial_bytes_async(vec![b'\n']).await + bytes.extend_from_slice(&[b'\n']); + self.send_serial_bytes_async(bytes).await } async fn send_serial_bytes_async(&self, bytes: Vec) -> Result<()> { From 40aed6a221aeb17e89db6e9e663b8215faba9417 Mon Sep 17 00:00:00 2001 From: Greg Colombo Date: Tue, 16 Jan 2024 21:56:51 +0000 Subject: [PATCH 2/4] readme: add guest OS section --- phd-tests/README.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/phd-tests/README.md b/phd-tests/README.md index 4c372fcaa..c30cbc5e4 100644 --- a/phd-tests/README.md +++ b/phd-tests/README.md @@ -133,6 +133,33 @@ path = "/home/oxide/propolis/target/debug" # sha256 = "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" ``` +## Guest OS support + +Different guest OS images may have different feature sets and login +requirements. The PHD framework abstracts these differences out guest OS +adapters that implement the `GuestOs` trait, whose methods supply PHD with +guest-specific information like the sequence of commands needed to log on or the +expected guest command prompt. + +The full list of supported OSes is defined in the framework's +[guest OS module](framework/src/guest_os/mod.rs). Each guest OS artifact in the +artifact TOML (see above) must have a `kind` that corresponds to a variant of +the `GuestOsKind` enum in this module. + +Some guest OSes are presumed to use password-based login credentials. These are +encoded into the logon sequences for each adapter and reproduced below: + +| Guest adapter | Username | Password | +|---------------------|-----------------|--------------| +| Alpine Linux | `root` | | +| Debian 11 (nocloud) | `root` | | +| Ubuntu 20.04 | `ubuntu` | `1!Passw0rd` | +| Windows Server 2022 | `Administrator` | `0xide#1Fan` | + +If you add a custom image to your artifact file, you must make sure either to +configure the image to accept the credentials its adapter supplies or to change +the adapter to provide the correct credentials. + ## Authoring tests PHD's test cases live in the `tests` crate. To write a new test, add a function From fbe93529f73752063ef4dd40d4b2eeebb3cf3be4 Mon Sep 17 00:00:00 2001 From: Greg Colombo Date: Tue, 16 Jan 2024 22:17:14 +0000 Subject: [PATCH 3/4] phd: improve doc comment --- .../src/guest_os/windows_server_2022.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/phd-tests/framework/src/guest_os/windows_server_2022.rs b/phd-tests/framework/src/guest_os/windows_server_2022.rs index 0d36b5f76..c3d33bd6d 100644 --- a/phd-tests/framework/src/guest_os/windows_server_2022.rs +++ b/phd-tests/framework/src/guest_os/windows_server_2022.rs @@ -9,14 +9,24 @@ use super::{CommandSequence, CommandSequenceEntry, GuestOs}; +/// Provides a guest OS adapter for Windows Server 2022 images. This adapter +/// assumes the following: +/// +/// - The image has been generalized (by running `sysprep /generalize`) and is +/// configured so that on first boot it will skip the out-of-box experience +/// (OOBE) and initialize the local administrator account with the appropriate +/// password. See [MSDN's Windows Setup +/// documentation](https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/generalize?view=windows-11) +/// for more details. +/// - Cygwin is installed to C:\cygwin and can be launched by invoking +/// C:\cygwin\cygwin.bat. pub(super) struct WindowsServer2022; impl GuestOs for WindowsServer2022 { fn get_login_sequence(&self) -> CommandSequence { CommandSequence(vec![ - // The image is generalized and needs to be specialized. Let it boot - // once, then wait for it to boot again and only then start waiting - // for the command prompt to become available. + // Assume the image will need to reboot one last time after being + // specialized. CommandSequenceEntry::WaitFor( "Computer is booting, SAC started and initialized.", ), From 1be9de55170677dd3d7a68d965136d62c0dd4720 Mon Sep 17 00:00:00 2001 From: Greg Colombo Date: Tue, 16 Jan 2024 22:17:34 +0000 Subject: [PATCH 4/4] phd: improve idiom --- phd-tests/framework/src/test_vm/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phd-tests/framework/src/test_vm/mod.rs b/phd-tests/framework/src/test_vm/mod.rs index 494cb2ae7..dca6d5e07 100644 --- a/phd-tests/framework/src/test_vm/mod.rs +++ b/phd-tests/framework/src/test_vm/mod.rs @@ -612,7 +612,7 @@ impl TestVm { async fn send_serial_str_async(&self, string: &str) -> Result<()> { let mut bytes = Vec::new(); bytes.extend_from_slice(string.as_bytes()); - bytes.extend_from_slice(&[b'\n']); + bytes.push(b'\n'); self.send_serial_bytes_async(bytes).await }