Skip to content

Add some unit tests for sled-agent Instance creation #4489

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 46 additions & 2 deletions illumos-utils/src/running_zone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1169,29 +1169,55 @@ impl ZoneBuilderFactory {
/// Created by [ZoneBuilderFactory].
#[derive(Default)]
pub struct ZoneBuilder<'a> {
/// Logger to which status messages are written during zone installation.
log: Option<Logger>,
/// Allocates the NIC used for control plane communication.
underlay_vnic_allocator: Option<&'a VnicAllocator<Etherstub>>,
/// Filesystem path at which the installed zone will reside.
zone_root_path: Option<&'a Utf8Path>,
/// The directories that will be searched for the image tarball for the
/// provided zone type ([`Self::with_zone_type`]).
zone_image_paths: Option<&'a [Utf8PathBuf]>,
/// The name of the type of zone being created (e.g. "propolis-server")
zone_type: Option<&'a str>,
unique_name: Option<Uuid>, // actually optional
/// Unique ID of the instance of the zone being created. (optional)
// *actually* optional (in contrast to other fields that are `Option` for
// builder purposes - that is, skipping this field in the builder will
// still result in an `Ok(InstalledZone)` from `.install()`, rather than
// an `Err(InstallZoneError::IncompleteBuilder)`.
unique_name: Option<Uuid>,
/// ZFS datasets to be accessed from within the zone.
datasets: Option<&'a [zone::Dataset]>,
/// Filesystems to mount within the zone.
filesystems: Option<&'a [zone::Fs]>,
/// Additional network device names to add to the zone.
data_links: Option<&'a [String]>,
/// Device nodes to pass through to the zone.
devices: Option<&'a [zone::Device]>,
/// OPTE devices for the guest network interfaces.
opte_ports: Option<Vec<(Port, PortTicket)>>,
bootstrap_vnic: Option<Link>, // actually optional
/// NIC to use for creating a bootstrap address on the switch zone.
// actually optional (as above)
bootstrap_vnic: Option<Link>,
/// Physical NICs possibly provisioned to the zone.
links: Option<Vec<Link>>,
/// The maximum set of privileges any process in this zone can obtain.
limit_priv: Option<Vec<String>>,
/// For unit tests only: if `Some`, then no actual zones will be installed
/// by this builder, and minimal facsimiles of them will be placed in
/// temporary directories according to the contents of the provided
/// `FakeZoneBuilderConfig`.
fake_cfg: Option<FakeZoneBuilderConfig>,
}

impl<'a> ZoneBuilder<'a> {
/// Logger to which status messages are written during zone installation.
pub fn with_log(mut self, log: Logger) -> Self {
self.log = Some(log);
self
}

/// Allocates the NIC used for control plane communication.
pub fn with_underlay_vnic_allocator(
mut self,
vnic_allocator: &'a VnicAllocator<Etherstub>,
Expand All @@ -1200,11 +1226,14 @@ impl<'a> ZoneBuilder<'a> {
self
}

/// Filesystem path at which the installed zone will reside.
pub fn with_zone_root_path(mut self, root_path: &'a Utf8Path) -> Self {
self.zone_root_path = Some(root_path);
self
}

/// The directories that will be searched for the image tarball for the
/// provided zone type ([`Self::with_zone_type`]).
pub fn with_zone_image_paths(
mut self,
image_paths: &'a [Utf8PathBuf],
Expand All @@ -1213,56 +1242,68 @@ impl<'a> ZoneBuilder<'a> {
self
}

/// The name of the type of zone being created (e.g. "propolis-server")
pub fn with_zone_type(mut self, zone_type: &'a str) -> Self {
self.zone_type = Some(zone_type);
self
}

/// Unique ID of the instance of the zone being created. (optional)
pub fn with_unique_name(mut self, uuid: Uuid) -> Self {
self.unique_name = Some(uuid);
self
}

/// ZFS datasets to be accessed from within the zone.
pub fn with_datasets(mut self, datasets: &'a [zone::Dataset]) -> Self {
self.datasets = Some(datasets);
self
}

/// Filesystems to mount within the zone.
pub fn with_filesystems(mut self, filesystems: &'a [zone::Fs]) -> Self {
self.filesystems = Some(filesystems);
self
}

/// Additional network device names to add to the zone.
pub fn with_data_links(mut self, links: &'a [String]) -> Self {
self.data_links = Some(links);
self
}

/// Device nodes to pass through to the zone.
pub fn with_devices(mut self, devices: &'a [zone::Device]) -> Self {
self.devices = Some(devices);
self
}

/// OPTE devices for the guest network interfaces.
pub fn with_opte_ports(mut self, ports: Vec<(Port, PortTicket)>) -> Self {
self.opte_ports = Some(ports);
self
}

/// NIC to use for creating a bootstrap address on the switch zone.
/// (optional)
pub fn with_bootstrap_vnic(mut self, vnic: Link) -> Self {
self.bootstrap_vnic = Some(vnic);
self
}

/// Physical NICs possibly provisioned to the zone.
pub fn with_links(mut self, links: Vec<Link>) -> Self {
self.links = Some(links);
self
}

/// The maximum set of privileges any process in this zone can obtain.
pub fn with_limit_priv(mut self, limit_priv: Vec<String>) -> Self {
self.limit_priv = Some(limit_priv);
self
}

// (used in unit tests)
fn fake_install(self) -> Result<InstalledZone, InstallZoneError> {
let zone = self
.zone_type
Expand Down Expand Up @@ -1300,6 +1341,9 @@ impl<'a> ZoneBuilder<'a> {
.ok_or(InstallZoneError::IncompleteBuilder)
}

/// Create the zone with the provided parameters.
/// Returns `Err(InstallZoneError::IncompleteBuilder)` if a necessary
/// parameter was not provided.
pub async fn install(self) -> Result<InstalledZone, InstallZoneError> {
if self.fake_cfg.is_some() {
return self.fake_install();
Expand Down
41 changes: 38 additions & 3 deletions sled-agent/src/fakes/nexus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,18 @@
//! to operate correctly.

use dropshot::{
endpoint, ApiDescription, FreeformBody, HttpError, HttpResponseOk, Path,
RequestContext,
endpoint, ApiDescription, FreeformBody, HttpError, HttpResponseOk,
HttpResponseUpdatedNoContent, Path, RequestContext, TypedBody,
};
use hyper::Body;
use internal_dns::ServiceName;
use omicron_common::api::external::Error;
use omicron_common::api::internal::nexus::UpdateArtifactId;
use omicron_common::api::internal::nexus::{
SledInstanceState, UpdateArtifactId,
};
use schemars::JsonSchema;
use serde::Deserialize;
use uuid::Uuid;

/// Implements a fake Nexus.
///
Expand All @@ -28,6 +33,13 @@ pub trait FakeNexusServer: Send + Sync {
) -> Result<Vec<u8>, Error> {
Err(Error::internal_error("Not implemented"))
}
fn cpapi_instances_put(
&self,
_instance_id: Uuid,
_new_runtime_state: SledInstanceState,
) -> Result<(), Error> {
Err(Error::internal_error("Not implemented"))
}
}

/// Describes the server context type.
Expand All @@ -52,9 +64,32 @@ async fn cpapi_artifact_download(
))
}

#[derive(Deserialize, JsonSchema)]
struct InstancePathParam {
instance_id: Uuid,
}

#[endpoint {
method = PUT,
path = "/instances/{instance_id}",
}]
async fn cpapi_instances_put(
request_context: RequestContext<ServerContext>,
path_params: Path<InstancePathParam>,
new_runtime_state: TypedBody<SledInstanceState>,
) -> Result<HttpResponseUpdatedNoContent, HttpError> {
let context = request_context.context();
context.cpapi_instances_put(
path_params.into_inner().instance_id,
new_runtime_state.into_inner(),
)?;
Ok(HttpResponseUpdatedNoContent())
}

fn api() -> ApiDescription<ServerContext> {
let mut api = ApiDescription::new();
api.register(cpapi_artifact_download).unwrap();
api.register(cpapi_instances_put).unwrap();
api
}

Expand Down
Loading