Merge pull request #82 from ericho/namespace-uts

agent: Add unit tests for `namespace.rs`
This commit is contained in:
Yang Bo 2019-11-14 07:39:56 +08:00 committed by GitHub
commit 60d713e84d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 198 additions and 89 deletions

View File

@ -109,7 +109,7 @@ fn main() -> Result<()> {
if unistd::getpid() == Pid::from_raw(1) { if unistd::getpid() == Pid::from_raw(1) {
// Init a temporary logger used by init agent as init process // Init a temporary logger used by init agent as init process
// since before do the base mount, it wouldn't access "/proc/cmdline" // since before do the base mount, it wouldn't access "/proc/cmdline"
// to get the customzied debug level. // to get the customzied debug level.
let writer = io::stdout(); let writer = io::stdout();
let logger = logging::create_logger(NAME, "agent", slog::Level::Debug, writer); let logger = logging::create_logger(NAME, "agent", slog::Level::Debug, writer);
init_agent_as_init(&logger)?; init_agent_as_init(&logger)?;

View File

@ -6,37 +6,22 @@
use nix::mount::MsFlags; use nix::mount::MsFlags;
use nix::sched::{unshare, CloneFlags}; use nix::sched::{unshare, CloneFlags};
use nix::unistd::{getpid, gettid}; use nix::unistd::{getpid, gettid};
use std::collections::HashMap; use std::fmt;
use std::fs; use std::fs;
use std::fs::File; use std::fs::File;
use std::os::unix::io::AsRawFd; use std::os::unix::io::AsRawFd;
use std::path::Path; use std::path::{Path, PathBuf};
use std::thread; use std::thread;
use crate::mount::{BareMount, FLAGS}; use crate::mount::{BareMount, FLAGS};
use slog::Logger; use slog::Logger;
//use container::Process; //use container::Process;
const PERSISTENT_NS_DIR: &'static str = "/var/run/sandbox-ns"; const PERSISTENT_NS_DIR: &'static str = "/var/run/sandbox-ns";
pub const NSTYPEIPC: &'static str = "ipc"; pub const NSTYPEIPC: &'static str = "ipc";
pub const NSTYPEUTS: &'static str = "uts"; pub const NSTYPEUTS: &'static str = "uts";
pub const NSTYPEPID: &'static str = "pid"; pub const NSTYPEPID: &'static str = "pid";
lazy_static! {
static ref CLONE_FLAG_TABLE: HashMap<&'static str, CloneFlags> = {
let mut m = HashMap::new();
m.insert(NSTYPEIPC, CloneFlags::CLONE_NEWIPC);
m.insert(NSTYPEUTS, CloneFlags::CLONE_NEWUTS);
m
};
}
#[derive(Debug, Default)]
pub struct Namespace {
pub path: String,
}
pub fn get_current_thread_ns_path(ns_type: &str) -> String { pub fn get_current_thread_ns_path(ns_type: &str) -> String {
format!( format!(
"/proc/{}/task/{}/ns/{}", "/proc/{}/task/{}/ns/{}",
@ -46,74 +31,203 @@ pub fn get_current_thread_ns_path(ns_type: &str) -> String {
) )
} }
// setup_persistent_ns creates persistent namespace without switchin to it. #[derive(Debug)]
// Note, pid namespaces cannot be persisted. pub struct Namespace {
pub fn setup_persistent_ns(logger: Logger, ns_type: &'static str) -> Result<Namespace, String> { logger: Logger,
if let Err(err) = fs::create_dir_all(PERSISTENT_NS_DIR) { pub path: String,
return Err(err.to_string()); persistent_ns_dir: String,
ns_type: NamespaceType,
}
impl Namespace {
pub fn new(logger: &Logger) -> Self {
Namespace {
logger: logger.clone(),
path: String::from(""),
persistent_ns_dir: String::from(PERSISTENT_NS_DIR),
ns_type: NamespaceType::IPC,
}
} }
let ns_path = Path::new(PERSISTENT_NS_DIR); pub fn as_ipc(mut self) -> Self {
let new_ns_path = ns_path.join(ns_type); self.ns_type = NamespaceType::IPC;
self
if let Err(err) = File::create(new_ns_path.as_path()) {
return Err(err.to_string());
} }
let new_thread = thread::spawn(move || { pub fn as_uts(mut self) -> Self {
let origin_ns_path = get_current_thread_ns_path(ns_type); self.ns_type = NamespaceType::UTS;
let _origin_ns_fd = match File::open(Path::new(&origin_ns_path)) { self
Err(err) => return Err(err.to_string()), }
Ok(file) => file.as_raw_fd(),
};
// Create a new netns on the current thread. pub fn set_root_dir(mut self, dir: &str) -> Self {
let cf = match CLONE_FLAG_TABLE.get(ns_type) { self.persistent_ns_dir = dir.to_string();
None => return Err(format!("Failed to get ns type {}", ns_type).to_string()), self
Some(cf) => cf, }
};
if let Err(err) = unshare(*cf) { // setup_persistent_ns creates persistent namespace without switchin to it.
// Note, pid namespaces cannot be persisted.
pub fn setup(mut self) -> Result<Self, String> {
if let Err(err) = fs::create_dir_all(&self.persistent_ns_dir) {
return Err(err.to_string()); return Err(err.to_string());
} }
// Bind mount the new namespace from the current thread onto the mount point to persist it. let ns_path = PathBuf::from(&self.persistent_ns_dir);
let source: &str = origin_ns_path.as_str(); let ns_type = self.ns_type.clone();
let destination: &str = new_ns_path.as_path().to_str().unwrap_or("none"); let logger = self.logger.clone();
let _recursive = true; let new_ns_path = ns_path.join(&ns_type.get());
let _readonly = true;
let mut flags = MsFlags::empty();
match FLAGS.get("rbind") { if let Err(err) = File::create(new_ns_path.as_path()) {
Some(x) => { return Err(err.to_string());
let (_, f) = *x;
flags = flags | f;
}
None => (),
};
let bare_mount = BareMount::new(source, destination, "none", flags, "", &logger);
if let Err(err) = bare_mount.mount() {
return Err(format!(
"Failed to mount {} to {} with err:{:?}",
source, destination, err
));
} }
Ok(())
});
match new_thread.join() { self.path = new_ns_path.into_os_string().into_string().unwrap();
Ok(t) => match t {
Err(err) => return Err(err), let new_thread = thread::spawn(move || {
Ok(()) => (), let ns_path = ns_path.clone();
}, let ns_type = ns_type.clone();
Err(err) => return Err(format!("Failed to join thread {:?}!", err)), let logger = logger;
let new_ns_path = ns_path.join(&ns_type.get());
let origin_ns_path = get_current_thread_ns_path(&ns_type.get());
let _origin_ns_fd = match File::open(Path::new(&origin_ns_path)) {
Err(err) => return Err(err.to_string()),
Ok(file) => file.as_raw_fd(),
};
// Create a new netns on the current thread.
let cf = ns_type.get_flags().clone();
if let Err(err) = unshare(cf) {
return Err(err.to_string());
}
// Bind mount the new namespace from the current thread onto the mount point to persist it.
let source: &str = origin_ns_path.as_str();
let destination: &str = new_ns_path.as_path().to_str().unwrap_or("none");
let _recursive = true;
let _readonly = true;
let mut flags = MsFlags::empty();
match FLAGS.get("rbind") {
Some(x) => {
let (_, f) = *x;
flags = flags | f;
}
None => (),
};
let bare_mount = BareMount::new(source, destination, "none", flags, "", &logger);
if let Err(err) = bare_mount.mount() {
return Err(format!(
"Failed to mount {} to {} with err:{:?}",
source, destination, err
));
}
Ok(())
});
match new_thread.join() {
Ok(t) => match t {
Err(err) => return Err(err),
Ok(()) => (),
},
Err(err) => return Err(format!("Failed to join thread {:?}!", err)),
}
Ok(self)
}
}
/// Represents the Namespace type.
#[derive(Clone, Copy)]
enum NamespaceType {
IPC,
UTS,
PID,
}
impl NamespaceType {
/// Get the string representation of the namespace type.
pub fn get(&self) -> String {
match *self {
Self::IPC => String::from("ipc"),
Self::UTS => String::from("uts"),
Self::PID => String::from("pid"),
}
} }
let new_ns_path = ns_path.join(ns_type); /// Get the associate flags with the namespace type.
Ok(Namespace { pub fn get_flags(&self) -> CloneFlags {
path: new_ns_path.into_os_string().into_string().unwrap(), match *self {
}) Self::IPC => CloneFlags::CLONE_NEWIPC,
Self::UTS => CloneFlags::CLONE_NEWUTS,
Self::PID => CloneFlags::CLONE_NEWPID,
}
}
}
impl fmt::Debug for NamespaceType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.get())
}
}
impl Default for NamespaceType {
fn default() -> Self {
NamespaceType::IPC
}
}
#[cfg(test)]
mod tests {
use super::{Namespace, NamespaceType};
use crate::{mount::remove_mounts, skip_if_not_root};
use nix::sched::CloneFlags;
use tempfile::Builder;
#[test]
fn test_setup_persistent_ns() {
skip_if_not_root!();
// Create dummy logger and temp folder.
let logger = slog::Logger::root(slog::Discard, o!());
let tmpdir = Builder::new().prefix("ipc").tempdir().unwrap();
let ns_ipc = Namespace::new(&logger)
.as_ipc()
.set_root_dir(tmpdir.path().to_str().unwrap())
.setup();
assert!(ns_ipc.is_ok());
assert!(remove_mounts(&vec![ns_ipc.unwrap().path]).is_ok());
let logger = slog::Logger::root(slog::Discard, o!());
let tmpdir = Builder::new().prefix("ipc").tempdir().unwrap();
let ns_uts = Namespace::new(&logger)
.as_uts()
.set_root_dir(tmpdir.path().to_str().unwrap())
.setup();
assert!(ns_uts.is_ok());
assert!(remove_mounts(&vec![ns_uts.unwrap().path]).is_ok());
}
#[test]
fn test_namespace_type() {
let ipc = NamespaceType::IPC;
assert_eq!("ipc", ipc.get());
assert_eq!(CloneFlags::CLONE_NEWIPC, ipc.get_flags());
let uts = NamespaceType::UTS;
assert_eq!("uts", uts.get());
assert_eq!(CloneFlags::CLONE_NEWUTS, uts.get_flags());
let pid = NamespaceType::PID;
assert_eq!("pid", pid.get());
assert_eq!(CloneFlags::CLONE_NEWPID, pid.get_flags());
}
} }

View File

@ -5,7 +5,7 @@
//use crate::container::Container; //use crate::container::Container;
use crate::mount::{get_mount_fs_type, remove_mounts, TYPEROOTFS}; use crate::mount::{get_mount_fs_type, remove_mounts, TYPEROOTFS};
use crate::namespace::{setup_persistent_ns, Namespace, NSTYPEIPC, NSTYPEUTS}; use crate::namespace::Namespace;
use crate::netlink::{RtnlHandle, NETLINK_ROUTE}; use crate::netlink::{RtnlHandle, NETLINK_ROUTE};
use crate::network::Network; use crate::network::Network;
use libc::pid_t; use libc::pid_t;
@ -48,7 +48,7 @@ impl Sandbox {
let logger = logger.new(o!("subsystem" => "sandbox")); let logger = logger.new(o!("subsystem" => "sandbox"));
Ok(Sandbox { Ok(Sandbox {
logger: logger, logger: logger.clone(),
id: "".to_string(), id: "".to_string(),
hostname: "".to_string(), hostname: "".to_string(),
network: Network::new(), network: Network::new(),
@ -56,12 +56,8 @@ impl Sandbox {
mounts: Vec::new(), mounts: Vec::new(),
container_mounts: HashMap::new(), container_mounts: HashMap::new(),
pci_device_map: HashMap::new(), pci_device_map: HashMap::new(),
shared_utsns: Namespace { shared_utsns: Namespace::new(&logger),
path: "".to_string(), shared_ipcns: Namespace::new(&logger),
},
shared_ipcns: Namespace {
path: "".to_string(),
},
storages: HashMap::new(), storages: HashMap::new(),
running: false, running: false,
no_pivot_root: fs_type.eq(TYPEROOTFS), no_pivot_root: fs_type.eq(TYPEROOTFS),
@ -129,29 +125,28 @@ impl Sandbox {
pub fn setup_shared_namespaces(&mut self) -> Result<bool> { pub fn setup_shared_namespaces(&mut self) -> Result<bool> {
// Set up shared IPC namespace // Set up shared IPC namespace
self.shared_ipcns = match setup_persistent_ns(self.logger.clone(), NSTYPEIPC) { self.shared_ipcns = match Namespace::new(&self.logger).as_ipc().setup() {
Ok(ns) => ns, Ok(ns) => ns,
Err(err) => { Err(err) => {
return Err(ErrorKind::ErrorCode(format!( return Err(ErrorKind::ErrorCode(format!(
"Failed to setup persisten IPC namespace with error: {}", "Failed to setup persistent IPC namespace with error: {}",
&err err
)) ))
.into()) .into())
} }
}; };
// Set up shared UTS namespace // // Set up shared UTS namespace
self.shared_utsns = match setup_persistent_ns(self.logger.clone(), NSTYPEUTS) { self.shared_utsns = match Namespace::new(&self.logger).as_uts().setup() {
Ok(ns) => ns, Ok(ns) => ns,
Err(err) => { Err(err) => {
return Err(ErrorKind::ErrorCode(format!( return Err(ErrorKind::ErrorCode(format!(
"Failed to setup persisten UTS namespace with error: {} ", "Failed to setup persistent UTS namespace with error: {}",
&err err
)) ))
.into()) .into())
} }
}; };
Ok(true) Ok(true)
} }