use itertools::Itertools;
use std::collections::BTreeSet;
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use std::process::{Command, Output};
use std::str::FromStr;
use std::string::ToString;
use thiserror::Error;
use zone_cfg_derive::Resource;
const PFEXEC: &str = "/usr/bin/pfexec";
const ZONENAME: &str = "/usr/bin/zonename";
const ZONEADM: &str = "/usr/sbin/zoneadm";
const ZONECFG: &str = "/usr/sbin/zonecfg";
const ZLOGIN: &str = "/usr/sbin/zlogin";
#[derive(Error, Debug)]
#[error("{0}")]
pub struct CommandOutputError(String);
trait OutputExt {
fn read_stdout(&self) -> Result<String, CommandOutputError>;
}
impl OutputExt for std::process::Output {
fn read_stdout(&self) -> Result<String, CommandOutputError> {
let stdout = String::from_utf8_lossy(&self.stdout).trim().to_string();
let stderr = String::from_utf8_lossy(&self.stderr).trim().to_string();
if !self.status.success() {
let exit_code = self
.status
.code()
.map(|code| format!("{}", code))
.unwrap_or_else(|| "<No exit code>".to_string());
return Err(CommandOutputError(format!(
"exit code {}\nstdout:\n{}\nstderr:\n{}",
exit_code, stdout, stderr
)));
}
Ok(stdout)
}
}
#[derive(Error, Debug)]
pub enum ZoneError {
#[error("Failed to parse output: {0}")]
Parse(String),
#[error("Failed to execute command: {0}")]
Command(std::io::Error),
#[error("Failed to parse command output: {0}")]
CommandOutput(#[from] CommandOutputError),
}
enum PropertyName {
Implicit,
Explicit(String),
}
struct Property {
name: PropertyName,
value: String,
}
trait PropertyExtractor {
fn get_properties(&self) -> Vec<Property>;
fn get_clearables(&self) -> Vec<PropertyName>;
}
macro_rules! implement_implicit_extractor {
($t:ty) => {
impl PropertyExtractor for $t {
fn get_properties(&self) -> Vec<Property> {
vec![Property {
name: PropertyName::Implicit,
value: self.to_string(),
}]
}
fn get_clearables(&self) -> Vec<PropertyName> {
vec![]
}
}
};
}
implement_implicit_extractor!(bool);
implement_implicit_extractor!(u8);
implement_implicit_extractor!(u16);
implement_implicit_extractor!(u32);
implement_implicit_extractor!(u64);
implement_implicit_extractor!(i8);
implement_implicit_extractor!(i16);
implement_implicit_extractor!(i32);
implement_implicit_extractor!(i64);
implement_implicit_extractor!(f32);
implement_implicit_extractor!(f64);
implement_implicit_extractor!(String);
impl PropertyExtractor for PathBuf {
fn get_properties(&self) -> Vec<Property> {
vec![Property {
name: PropertyName::Implicit,
value: self.to_string_lossy().to_string(),
}]
}
fn get_clearables(&self) -> Vec<PropertyName> {
vec![]
}
}
impl<T: std::fmt::Display> PropertyExtractor for Option<T> {
fn get_properties(&self) -> Vec<Property> {
if let Some(value) = self {
vec![Property {
name: PropertyName::Implicit,
value: value.to_string(),
}]
} else {
vec![]
}
}
fn get_clearables(&self) -> Vec<PropertyName> {
if self.is_none() {
vec![PropertyName::Implicit]
} else {
vec![]
}
}
}
impl<T: std::fmt::Display> PropertyExtractor for Vec<T> {
fn get_properties(&self) -> Vec<Property> {
let values: String = self
.iter()
.map(|val| val.to_string())
.collect::<Vec<String>>()
.join(",");
vec![Property {
name: PropertyName::Implicit,
value: format!("[{}]", values),
}]
}
fn get_clearables(&self) -> Vec<PropertyName> {
vec![]
}
}
impl<T: std::fmt::Display> PropertyExtractor for BTreeSet<T> {
fn get_properties(&self) -> Vec<Property> {
if self.is_empty() {
return vec![];
}
let value: String = self
.iter()
.map(|val| val.to_string())
.collect::<Vec<String>>()
.join(",");
vec![Property {
name: PropertyName::Implicit,
value,
}]
}
fn get_clearables(&self) -> Vec<PropertyName> {
if self.is_empty() {
vec![PropertyName::Implicit]
} else {
vec![]
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum IpType {
Shared,
Exclusive,
}
impl FromStr for IpType {
type Err = ZoneError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"shared" => Ok(IpType::Shared),
"excl" => Ok(IpType::Exclusive),
_ => Err(ZoneError::Parse(format!("Invalid Ip Type: {}", s))),
}
}
}
impl std::fmt::Display for IpType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
match self {
IpType::Shared => write!(f, "shared"),
IpType::Exclusive => write!(f, "exclusive"),
}
}
}
implement_implicit_extractor!(IpType);
#[derive(Resource)]
pub struct Global {
#[resource(global)]
#[resource(name = "zonename")]
pub name: String,
#[resource(name = "zonepath")]
pub path: PathBuf,
pub autoboot: bool,
pub bootargs: Option<String>,
pub pool: Option<String>,
pub limitpriv: BTreeSet<String>,
pub brand: String,
pub hostid: Option<u32>,
#[resource(name = "ip-type")]
pub ip_type: IpType,
#[resource(name = "cpu-shares")]
pub cpu_shares: Option<u32>,
#[resource(name = "max-lwps")]
pub max_lwps: Option<u32>,
#[resource(name = "max-msg-ids")]
pub max_message_ids: Option<u32>,
#[resource(name = "max-sem-ids")]
pub max_semaphore_ids: Option<u32>,
#[resource(name = "max-shm-ids")]
pub max_shared_memory_ids: Option<u32>,
#[resource(name = "max-shm-memory")]
pub max_shared_memory: Option<u64>,
#[resource(name = "scheduling-class")]
pub scheduling_class: Option<String>,
#[resource(name = "fs-allowed")]
pub fs_allowed: BTreeSet<String>,
}
#[derive(Default, Resource)]
pub struct Fs {
#[resource(name = "type", selector)]
pub ty: String,
#[resource(selector)]
pub dir: String,
#[resource(selector)]
pub special: String,
pub raw: Option<String>,
pub options: Vec<String>,
}
#[derive(Default, Resource)]
pub struct Net {
#[resource(selector)]
pub physical: String,
pub address: Option<String>,
#[resource(name = "allowed-address")]
pub allowed_address: Option<String>,
#[resource(name = "defrouter")]
pub default_router: Option<String>,
}
#[derive(Default, Resource)]
pub struct Device {
#[resource(name = "match")]
pub name: String,
}
#[derive(Default, Resource)]
pub struct Rctl {
#[resource(selector)]
pub name: String,
pub value: String,
}
pub enum AttributeValue {
Int(i64),
UInt(u64),
Boolean(bool),
String(String),
}
impl AttributeValue {
fn type_str(&self) -> String {
match self {
AttributeValue::Int(_) => "int",
AttributeValue::UInt(_) => "uint",
AttributeValue::Boolean(_) => "boolean",
AttributeValue::String(_) => "string",
}
.to_string()
}
fn value_str(&self) -> String {
match self {
AttributeValue::Int(n) => n.to_string(),
AttributeValue::UInt(n) => n.to_string(),
AttributeValue::Boolean(b) => b.to_string(),
AttributeValue::String(s) => s.clone(),
}
}
}
impl PropertyExtractor for AttributeValue {
fn get_properties(&self) -> Vec<Property> {
vec![
Property {
name: PropertyName::Explicit("type".to_string()),
value: self.type_str(),
},
Property {
name: PropertyName::Explicit("value".to_string()),
value: self.value_str(),
},
]
}
fn get_clearables(&self) -> Vec<PropertyName> {
vec![]
}
}
#[derive(Resource)]
pub struct Attr {
#[resource(selector)]
pub name: String,
pub value: AttributeValue,
}
#[derive(Default, Resource)]
pub struct Dataset {
pub name: String,
}
#[derive(Default, Resource)]
pub struct DedicatedCpu {
pub ncpus: String,
pub importance: Option<String>,
}
#[derive(Default, Resource)]
pub struct CappedMemory {
pub physical: Option<String>,
pub swap: Option<String>,
pub locked: Option<String>,
}
#[derive(Default, Resource)]
pub struct CappedCpu {
pub ncpus: f64,
}
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum SecurityFlag {
Aslr,
ForbidNullMap,
NonExecutableStack,
}
impl std::fmt::Display for SecurityFlag {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
match self {
SecurityFlag::Aslr => write!(f, "ASLR"),
SecurityFlag::ForbidNullMap => write!(f, "FORBIDNULLMAP"),
SecurityFlag::NonExecutableStack => write!(f, "NOEXECSTACK"),
}
}
}
#[derive(Default, Resource)]
pub struct SecurityFlags {
pub lower: BTreeSet<SecurityFlag>,
pub default: BTreeSet<SecurityFlag>,
pub upper: BTreeSet<SecurityFlag>,
}
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Auths {
Login,
Manage,
CloneFrom,
}
impl std::fmt::Display for Auths {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
match self {
Auths::Login => write!(f, "login"),
Auths::Manage => write!(f, "manage"),
Auths::CloneFrom => write!(f, "clonefrom"),
}
}
}
#[derive(Default, Resource)]
pub struct Admin {
#[resource(selector)]
pub user: String,
pub auths: BTreeSet<Auths>,
}
pub struct Config {
name: String,
args: Vec<String>,
}
pub enum CreationOptions {
FromDetached(PathBuf),
Blank,
Default,
Template(String),
}
impl Config {
fn push(&mut self, value: impl AsRef<str>) {
self.args.push(value.as_ref().into());
}
pub fn new(name: impl AsRef<str>) -> Self {
Config {
name: name.as_ref().into(),
args: vec![],
}
}
pub fn create(name: impl AsRef<str>, overwrite: bool, options: CreationOptions) -> Self {
let overwrite_flag = if overwrite {
"-F".to_string()
} else {
"".to_string()
};
let options = match options {
CreationOptions::FromDetached(path) => {
format!("-a {}", path.into_os_string().to_string_lossy())
}
CreationOptions::Blank => "-b".to_string(),
CreationOptions::Default => "".to_string(),
CreationOptions::Template(zone) => format!("-t {}", zone),
};
let mut cfg = Self::new(name);
cfg.push(format!("create {} {}", overwrite_flag, options));
cfg
}
pub fn export(&mut self, p: impl AsRef<Path>) -> &mut Self {
self.push(format!("export -f {}", p.as_ref().to_string_lossy()));
self
}
pub fn delete(&mut self, force: bool) -> &mut Self {
self.push(format!("delete {}", if force { "-F" } else { "" }));
self
}
pub fn as_command(&mut self) -> Command {
let separator = ";".to_string();
let args =
Itertools::intersperse(self.args.iter(), &separator).flat_map(|arg| arg.split(' '));
let mut cmd = std::process::Command::new(PFEXEC);
cmd.env_clear()
.arg(ZONECFG)
.arg("-z")
.arg(&self.name)
.args(args);
self.args.clear();
cmd
}
pub fn parse_output(output: &Output) -> Result<String, ZoneError> {
let out = output.read_stdout()?;
Ok(out)
}
#[cfg(feature = "sync")]
pub fn run_blocking(&mut self) -> Result<String, ZoneError> {
let output = self.as_command().output().map_err(ZoneError::Command)?;
Self::parse_output(&output)
}
#[cfg(feature = "async")]
pub async fn run(&mut self) -> Result<String, ZoneError> {
let output = tokio::process::Command::from(self.as_command())
.output()
.await
.map_err(ZoneError::Command)?;
Self::parse_output(&output)
}
}
pub fn current_command() -> Command {
let mut cmd = std::process::Command::new(ZONENAME);
cmd.env_clear();
cmd
}
#[cfg(feature = "sync")]
pub fn current_blocking() -> Result<String, ZoneError> {
Ok(current_command()
.output()
.map_err(ZoneError::Command)?
.read_stdout()?)
}
#[cfg(feature = "async")]
pub async fn current() -> Result<String, ZoneError> {
Ok(tokio::process::Command::from(current_command())
.output()
.await
.map_err(ZoneError::Command)?
.read_stdout()?)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum State {
Configured,
Incomplete,
Installed,
Ready,
Mounted,
Running,
ShuttingDown,
Down,
}
impl FromStr for State {
type Err = ZoneError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
use State::*;
match s {
"configured" => Ok(Configured),
"incomplete" => Ok(Incomplete),
"installed" => Ok(Installed),
"ready" => Ok(Ready),
"mounted" => Ok(Mounted),
"running" => Ok(Running),
"shutting_down" => Ok(ShuttingDown),
"down" => Ok(Down),
_ => Err(ZoneError::Parse(format!("Invalid Zone State: {}", s))),
}
}
}
#[derive(Debug, Clone)]
pub struct Zone {
id: Option<u64>,
name: String,
state: State,
path: PathBuf,
uuid: Option<String>,
brand: String,
ip_type: IpType,
}
impl Zone {
pub fn id(&self) -> Option<u64> {
self.id
}
pub fn global(&self) -> bool {
self.name() == "global"
}
pub fn name(&self) -> &str {
&self.name
}
pub fn state(&self) -> State {
self.state
}
pub fn path(&self) -> &Path {
&self.path
}
pub fn uuid(&self) -> Option<&String> {
self.uuid.as_ref()
}
pub fn brand(&self) -> &str {
&self.brand
}
pub fn ip_type(&self) -> IpType {
self.ip_type
}
}
impl FromStr for Zone {
type Err = ZoneError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut fields = s.split(':');
let id = fields
.next()
.ok_or_else(|| ZoneError::Parse("Missing ID".to_string()))?
.parse::<u64>()
.ok();
let name = fields
.next()
.ok_or_else(|| ZoneError::Parse("Missing name".to_string()))?
.to_string();
let state = fields
.next()
.ok_or_else(|| ZoneError::Parse("Missing state".to_string()))?
.parse::<State>()?;
let path = PathBuf::from(
fields
.next()
.ok_or_else(|| ZoneError::Parse("Missing path".to_string()))?,
);
let uuid = {
let value = fields
.next()
.ok_or_else(|| ZoneError::Parse("Missing UUID".to_string()))?;
if value.is_empty() {
None
} else {
Some(value.to_string())
}
};
let brand = fields
.next()
.ok_or_else(|| ZoneError::Parse("Missing brand".to_string()))?
.to_string();
let ip_type = fields
.next()
.ok_or_else(|| ZoneError::Parse("Missing IP type".to_string()))?
.parse::<IpType>()?;
Ok(Zone {
id,
name,
state,
path,
uuid,
brand,
ip_type,
})
}
}
pub struct Adm {
name: String,
}
impl Adm {
pub fn new(name: impl AsRef<str>) -> Self {
Adm {
name: name.as_ref().into(),
}
}
pub fn boot_command(&mut self) -> Command {
self.as_command(&["boot"])
}
pub fn parse_boot_output(output: &Output) -> Result<String, ZoneError> {
Ok(output.read_stdout()?)
}
#[cfg(feature = "sync")]
pub fn boot_blocking(&mut self) -> Result<String, ZoneError> {
self.run_blocking(&["boot"])
}
#[cfg(feature = "async")]
pub async fn boot(&mut self) -> Result<String, ZoneError> {
self.run(&["boot"]).await
}
pub fn clone_command(&mut self, source: impl AsRef<OsStr>) -> Command {
self.as_command(&[OsStr::new("clone"), source.as_ref()])
}
pub fn parse_clone_output(output: &Output) -> Result<String, ZoneError> {
Ok(output.read_stdout()?)
}
#[cfg(feature = "sync")]
pub fn clone_blocking(&mut self, source: impl AsRef<OsStr>) -> Result<String, ZoneError> {
self.run_blocking(&[OsStr::new("clone"), source.as_ref()])
}
#[cfg(feature = "async")]
pub async fn clone(&mut self, source: impl AsRef<OsStr>) -> Result<String, ZoneError> {
self.run(&[OsStr::new("clone"), source.as_ref()]).await
}
pub fn halt_command(&mut self) -> Command {
self.as_command(&["halt"])
}
pub fn parse_halt_output(output: &Output) -> Result<String, ZoneError> {
Ok(output.read_stdout()?)
}
#[cfg(feature = "sync")]
pub fn halt_blocking(&mut self) -> Result<String, ZoneError> {
self.run_blocking(&["halt"])
}
#[cfg(feature = "async")]
pub async fn halt(&mut self) -> Result<String, ZoneError> {
self.run(&["halt"]).await
}
pub fn mount_command(&mut self) -> Command {
self.as_command(&["mount"])
}
pub fn parse_mount_output(output: &Output) -> Result<String, ZoneError> {
Ok(output.read_stdout()?)
}
#[cfg(feature = "sync")]
pub fn mount_blocking(&mut self) -> Result<String, ZoneError> {
self.run_blocking(&["mount"])
}
#[cfg(feature = "async")]
pub async fn mount(&mut self) -> Result<String, ZoneError> {
self.run(&["mount"]).await
}
pub fn unmount_command(&mut self) -> Command {
self.as_command(&["unmount"])
}
pub fn parse_unmount_output(output: &Output) -> Result<String, ZoneError> {
Ok(output.read_stdout()?)
}
#[cfg(feature = "sync")]
pub fn unmount_blocking(&mut self) -> Result<String, ZoneError> {
self.run_blocking(&["unmount"])
}
#[cfg(feature = "async")]
pub async fn unmount(&mut self) -> Result<String, ZoneError> {
self.run(&["unmount"]).await
}
pub fn install_command(&mut self, brand_specific_options: &[&OsStr]) -> Command {
let command = &[OsStr::new("install")];
self.as_command([command, brand_specific_options].concat())
}
pub fn parse_install_output(output: &Output) -> Result<String, ZoneError> {
Ok(output.read_stdout()?)
}
#[cfg(feature = "sync")]
pub fn install_blocking(
&mut self,
brand_specific_options: &[&OsStr],
) -> Result<String, ZoneError> {
let command = &[OsStr::new("install")];
self.run_blocking([command, brand_specific_options].concat())
}
#[cfg(feature = "async")]
pub async fn install(
&mut self,
brand_specific_options: &[&OsStr],
) -> Result<String, ZoneError> {
let command = &[OsStr::new("install")];
self.run([command, brand_specific_options].concat()).await
}
pub fn uninstall_command(&mut self, force: bool) -> Command {
let mut args = vec!["uninstall"];
if force {
args.push("-F");
}
self.as_command(&args)
}
pub fn parse_uninstall_output(output: &Output) -> Result<String, ZoneError> {
Ok(output.read_stdout()?)
}
#[cfg(feature = "sync")]
pub fn uninstall_blocking(&mut self, force: bool) -> Result<String, ZoneError> {
let mut args = vec!["uninstall"];
if force {
args.push("-F");
}
self.run_blocking(&args)
}
#[cfg(feature = "async")]
pub async fn uninstall(&mut self, force: bool) -> Result<String, ZoneError> {
let mut args = vec!["uninstall"];
if force {
args.push("-F");
}
self.run(&args).await
}
pub fn list_command() -> Command {
let mut cmd = std::process::Command::new(PFEXEC);
cmd.env_clear().arg(ZONEADM).arg("list").arg("-cip");
cmd
}
pub fn parse_list_output(output: &Output) -> Result<Vec<Zone>, ZoneError> {
output
.read_stdout()?
.split('\n')
.map(|s| s.parse::<Zone>())
.collect::<Result<Vec<Zone>, _>>()
}
#[cfg(feature = "sync")]
pub fn list_blocking() -> Result<Vec<Zone>, ZoneError> {
let output = Self::list_command().output().map_err(ZoneError::Command)?;
Self::parse_list_output(&output)
}
#[cfg(feature = "async")]
pub async fn list() -> Result<Vec<Zone>, ZoneError> {
let output = tokio::process::Command::from(Self::list_command())
.output()
.await
.map_err(ZoneError::Command)?;
Self::parse_list_output(&output)
}
fn as_command(&self, args: impl IntoIterator<Item = impl AsRef<OsStr>>) -> Command {
let mut cmd = std::process::Command::new(PFEXEC);
cmd.env_clear()
.arg(ZONEADM)
.arg("-z")
.arg(&self.name)
.args(args);
cmd
}
#[cfg(feature = "sync")]
fn run_blocking(
&self,
args: impl IntoIterator<Item = impl AsRef<OsStr>>,
) -> Result<String, ZoneError> {
let out = self
.as_command(args)
.output()
.map_err(ZoneError::Command)?
.read_stdout()?;
Ok(out)
}
#[cfg(feature = "async")]
async fn run(
&self,
args: impl IntoIterator<Item = impl AsRef<OsStr>>,
) -> Result<String, ZoneError> {
let out = tokio::process::Command::from(self.as_command(args))
.output()
.await
.map_err(ZoneError::Command)?
.read_stdout()?;
Ok(out)
}
}
pub struct Zlogin {
name: String,
}
impl Zlogin {
pub fn new(name: impl AsRef<str>) -> Self {
Zlogin {
name: name.as_ref().into(),
}
}
pub fn as_command(&self, cmds: impl AsRef<OsStr>) -> Command {
let mut cmd = std::process::Command::new(PFEXEC);
cmd.env_clear()
.arg(ZLOGIN)
.arg("-Q")
.arg(&self.name)
.arg(cmds);
cmd
}
#[cfg(feature = "sync")]
pub fn exec_blocking(&self, cmd: impl AsRef<OsStr>) -> Result<String, ZoneError> {
Ok(self
.as_command(cmd)
.output()
.map_err(ZoneError::Command)?
.read_stdout()?)
}
#[cfg(feature = "async")]
pub async fn exec(&self, cmd: impl AsRef<OsStr>) -> Result<String, ZoneError> {
Ok(tokio::process::Command::from(self.as_command(cmd))
.output()
.await
.map_err(ZoneError::Command)?
.read_stdout()?)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(target_os = "illumos")]
#[test]
fn test_current_zone() {
let zone = current_blocking().unwrap();
assert_eq!("global", zone);
}
#[test]
fn test_cfg_fs() {
let mut cfg = Config::new("my-zone");
let fs = Fs {
ty: "my-type".to_string(),
dir: "/path/to/dir".to_string(),
special: "/path/to/special".to_string(),
..Default::default()
};
cfg.add_fs(&fs)
.set_dir("/path/to/other/dir")
.set_raw(None)
.set_raw(Some("/raw".to_string()))
.set_options(vec!["abc".to_string(), "def".to_string()]);
assert_eq!(
cfg.args,
&[
"add fs",
"set type=my-type",
"set dir=/path/to/dir",
"set special=/path/to/special",
"set options=[]",
"set dir=/path/to/other/dir",
"clear raw",
"set raw=/raw",
"set options=[abc,def]",
"end"
]
);
}
#[test]
fn test_cfg_attr() {
let mut cfg = Config::new("my-zone");
let attr = Attr {
name: "my-attr".to_string(),
value: AttributeValue::UInt(10),
};
cfg.add_attr(&attr);
assert_eq!(
cfg.args,
&[
"add attr",
"set name=my-attr",
"set type=uint",
"set value=10",
"end"
]
);
}
#[test]
fn test_cfg_security_flags() {
let mut cfg = Config::new("my-zone");
let mut default = BTreeSet::new();
default.insert(SecurityFlag::Aslr);
default.insert(SecurityFlag::ForbidNullMap);
let security_flags = SecurityFlags {
default,
..Default::default()
};
let mut lower = BTreeSet::new();
lower.insert(SecurityFlag::Aslr);
cfg.add_security_flags(&security_flags).set_lower(lower);
assert_eq!(
cfg.args,
&[
"add security-flags",
"set default=ASLR,FORBIDNULLMAP",
"set lower=ASLR",
"end"
]
);
}
#[cfg(target_os = "illumos")]
#[test]
fn test_cfg_global() {
let mut cfg = Config::new("my-zone");
let mut fs_allowed = BTreeSet::new();
fs_allowed.insert("ufs".to_string());
fs_allowed.insert("pcfs".to_string());
cfg.get_global()
.set_name("my-new-zone")
.set_autoboot(false)
.set_limitpriv(BTreeSet::new())
.set_fs_allowed(fs_allowed)
.set_pool(Some("my-pool".to_string()))
.set_pool(None)
.set_ip_type(IpType::Exclusive);
assert_eq!(
cfg.args,
&[
"set zonename=my-new-zone",
"set autoboot=false",
"clear limitpriv",
"set fs-allowed=pcfs,ufs",
"set pool=my-pool",
"clear pool",
"set ip-type=exclusive",
]
);
}
#[cfg(target_os = "illumos")]
#[test]
fn test_cfg_man_page_example() {
let mut cfg = Config::create("myzone", true, CreationOptions::Default);
cfg.get_global()
.set_path("/export/home/myzone")
.set_autoboot(true)
.set_ip_type(IpType::Shared); cfg.add_fs(&Fs {
ty: "lofs".to_string(),
dir: "/usr/local".to_string(),
special: "/opt/local".to_string(),
options: vec!["ro".to_string(), "nodevices".to_string()],
..Default::default()
});
cfg.add_net(&Net {
address: Some("192.168.0.1/24".to_string()),
physical: "eri0".to_string(),
..Default::default()
});
cfg.add_net(&Net {
address: Some("192.168.1.2/24".to_string()),
physical: "eri0".to_string(),
..Default::default()
});
cfg.add_net(&Net {
address: Some("192.168.2.3/24".to_string()),
physical: "eri0".to_string(),
..Default::default()
});
cfg.get_global().set_cpu_shares(5);
cfg.add_capped_memory(&CappedMemory {
physical: Some("50m".to_string()),
swap: Some("100m".to_string()),
..Default::default()
});
cfg.run_blocking().unwrap();
cfg.delete(true).run_blocking().unwrap();
}
#[cfg(target_os = "illumos")]
#[test]
fn test_list_global() {
let zones = Adm::list_blocking().unwrap();
let global = zones.iter().find(|zone| zone.global()).unwrap();
assert_eq!(global.id().unwrap(), 0);
assert_eq!(global.state(), State::Running);
assert_eq!(global.ip_type(), IpType::Shared);
}
#[cfg(target_os = "illumos")]
#[test]
fn test_zone_lifecycle() {
let name = "lifecycle";
let path = Path::new("/opt/lifecycle");
let mut cfg = Config::create(name, true, CreationOptions::Default);
cfg.get_global().set_path(path).set_autoboot(true);
cfg.run_blocking().unwrap();
let zone = Adm::list_blocking()
.unwrap()
.into_iter()
.find(|z| z.name() == name)
.unwrap();
assert_eq!(zone.path(), path);
cfg.delete(true).run_blocking().unwrap();
assert!(Adm::list_blocking()
.unwrap()
.into_iter()
.find(|z| z.name() == name)
.is_none());
}
}