mirror of
https://github.com/containers/youki
synced 2024-05-25 00:46:11 +02:00
601df9ecd3
* Set cgroups path for tenant containers from main container Signed-off-by: Yashodhan Joshi <yjdoc2@gmail.com> * Ignore new_user_ns for creating cgroups path Signed-off-by: Yashodhan Joshi <yjdoc2@gmail.com> * Remove user_ns param completely Signed-off-by: Yashodhan Joshi <yjdoc2@gmail.com> * Add tests in podman rootless for exec Signed-off-by: Yashodhan Joshi <yjdoc2@gmail.com> * Fix add_task implementation for cgroups v2 and systemd Signed-off-by: Yashodhan Joshi <yjdoc2@gmail.com> * minor refactor in tenant builder Signed-off-by: Yashodhan Joshi <yjdoc2@gmail.com> * Add unit test for systemd add_task function Signed-off-by: Yashodhan Joshi <yjdoc2@gmail.com> * Fix task addition to properly add tasks via dbus api Signed-off-by: Yashodhan Joshi <yjdoc2@gmail.com> * Fix cross cotainers for tests running Signed-off-by: Yashodhan Joshi <yjdoc2@gmail.com> --------- Signed-off-by: Yashodhan Joshi <yjdoc2@gmail.com>
250 lines
8.5 KiB
Rust
250 lines
8.5 KiB
Rust
use super::dbus::DbusConnection;
|
|
use super::message::*;
|
|
use super::serialize::{DbusSerialize, Structure, Variant};
|
|
use super::utils::{DbusError, Result};
|
|
|
|
/// Structure to conveniently communicate with
|
|
/// given destination and path for method calls
|
|
pub struct Proxy<'conn> {
|
|
conn: &'conn DbusConnection,
|
|
dest: String,
|
|
path: String,
|
|
}
|
|
|
|
// helper method to check compatibility between
|
|
// actual signature of received reply and expected signature
|
|
// we have to do this, as we don't have dedicated type
|
|
// for object path
|
|
fn check_signature_compatibility(actual: &str, expected: &str) -> bool {
|
|
if actual == expected {
|
|
return true;
|
|
}
|
|
// we don't consider signature (g) here as :
|
|
// 1. length encoding is different than string, so cannot be deserialized by String::deserialize
|
|
// 2. currently we don't expect any method to return signature, so we can get away with this
|
|
if expected == "s" && matches!(actual, "s" | "o") {
|
|
return true;
|
|
}
|
|
|
|
false
|
|
}
|
|
|
|
impl<'conn> Proxy<'conn> {
|
|
/// create a new proxy for given destination and path over given connection
|
|
pub fn new(conn: &'conn DbusConnection, dest: &str, path: &str) -> Self {
|
|
Self {
|
|
conn,
|
|
dest: dest.into(),
|
|
path: path.into(),
|
|
}
|
|
}
|
|
|
|
/// Do a method call for given interface and member by sending given body
|
|
/// If no body is to be sent, set it as `None`
|
|
pub fn method_call<Body: DbusSerialize, Output: DbusSerialize>(
|
|
&self,
|
|
interface: &str,
|
|
member: &str,
|
|
body: Option<Body>,
|
|
) -> Result<Output> {
|
|
let mut headers = Vec::with_capacity(4);
|
|
|
|
// create necessary headers
|
|
headers.push(Header {
|
|
kind: HeaderKind::Path,
|
|
value: HeaderValue::String(self.path.clone()),
|
|
});
|
|
headers.push(Header {
|
|
kind: HeaderKind::Destination,
|
|
value: HeaderValue::String(self.dest.clone()),
|
|
});
|
|
headers.push(Header {
|
|
kind: HeaderKind::Interface,
|
|
value: HeaderValue::String(interface.to_string()),
|
|
});
|
|
headers.push(Header {
|
|
kind: HeaderKind::Member,
|
|
value: HeaderValue::String(member.to_string()),
|
|
});
|
|
|
|
let mut serialized_body = vec![];
|
|
|
|
// if there is some body, serialize it, and set the
|
|
// body signature header accordingly
|
|
if let Some(v) = body {
|
|
headers.push(Header {
|
|
kind: HeaderKind::BodySignature,
|
|
value: HeaderValue::String(Body::get_signature()),
|
|
});
|
|
v.serialize(&mut serialized_body);
|
|
}
|
|
|
|
// send the message and get response
|
|
let reply_messages =
|
|
self.conn
|
|
.send_message(MessageType::MethodCall, headers, serialized_body)?;
|
|
|
|
// check if there is any error message
|
|
let error_message: Vec<_> = reply_messages
|
|
.iter()
|
|
.filter(|m| m.preamble.mtype == MessageType::Error)
|
|
.collect();
|
|
|
|
// if any error, return error
|
|
if !error_message.is_empty() {
|
|
let msg = error_message[0];
|
|
if msg.body.is_empty() {
|
|
// this should rarely be the case
|
|
return Err(DbusError::MethodCallErr("Unknown Dbus Error".into()).into());
|
|
} else {
|
|
// in error message, first item of the body (if present) is always a string
|
|
// indicating the error
|
|
let mut ctr = 0;
|
|
let msg = String::deserialize(&msg.body, &mut ctr)?;
|
|
return Err(DbusError::MethodCallErr(msg).into());
|
|
}
|
|
}
|
|
|
|
// we basically ignore all type of messages apart from method return
|
|
let reply: Vec<_> = reply_messages
|
|
.iter()
|
|
.filter(|m| m.preamble.mtype == MessageType::MethodReturn)
|
|
.collect();
|
|
|
|
// we are only going to consider first reply, cause... so.
|
|
// realistically there should only be at most one method return type of message
|
|
// for a method call
|
|
let reply = reply.first().ok_or(DbusError::MethodCallErr(
|
|
"expected to get a reply for method call, didn't get any".into(),
|
|
))?;
|
|
|
|
let headers = &reply.headers;
|
|
let expected_signature = Output::get_signature();
|
|
|
|
// get the signature header
|
|
let signature_header: Vec<_> = headers
|
|
.iter()
|
|
.filter(|h| h.kind == HeaderKind::BodySignature)
|
|
.collect();
|
|
|
|
// This is also something that should never happen
|
|
// we just check this defensively
|
|
if signature_header.is_empty() && !reply.body.is_empty() {
|
|
return Err(DbusError::MethodCallErr(
|
|
"Body non empty, but body signature header missing".to_string(),
|
|
)
|
|
.into());
|
|
}
|
|
|
|
if expected_signature == *"" {
|
|
// This is for the case when there is no body, i.e. Output = ()
|
|
// we must do this as the signature header will be
|
|
// absent in that case, so instead we choose to
|
|
// parse and return early
|
|
// This is a bit hacky, but works
|
|
let mut ctr = 0;
|
|
return Output::deserialize(&[], &mut ctr);
|
|
}
|
|
|
|
let actual_signature = match &signature_header[0].value {
|
|
HeaderValue::String(s) => s,
|
|
_ => unreachable!("body signature header will always be string type"),
|
|
};
|
|
|
|
// check that signature returned and type we are trying to deserialize
|
|
// match as expected
|
|
if !check_signature_compatibility(actual_signature, &expected_signature) {
|
|
return Err(DbusError::DeserializationError(format!(
|
|
"reply signature mismatch : expected {}, found {} : \n{:?}",
|
|
expected_signature, actual_signature, reply.body
|
|
))
|
|
.into());
|
|
}
|
|
|
|
let mut ctr = 0;
|
|
Output::deserialize(&reply.body, &mut ctr)
|
|
}
|
|
|
|
pub fn get_unit(&mut self, name: &str) -> Result<String> {
|
|
self.method_call(
|
|
"org.freedesktop.systemd1.Manager",
|
|
"GetUnit",
|
|
Some(name.to_string()),
|
|
)
|
|
}
|
|
|
|
// Note that because we do not listen to the jobRemoved signal
|
|
// similar to runc, it is possible that when this method returns
|
|
// the unit is not yet started, however, because we do expect a reply
|
|
// (according to message flags we send), it is possible that dbus only returns
|
|
// after unit is started. Need to investigate more
|
|
pub fn start_transient_unit(
|
|
&self,
|
|
name: &str,
|
|
mode: &str,
|
|
properties: Vec<Structure<Variant>>,
|
|
aux: Vec<Structure<Vec<Structure<Variant>>>>,
|
|
) -> Result<String> {
|
|
self.method_call(
|
|
"org.freedesktop.systemd1.Manager",
|
|
"StartTransientUnit",
|
|
Some((name, mode, properties, aux)),
|
|
)
|
|
}
|
|
|
|
pub fn stop_unit(&self, name: &str, mode: &str) -> Result<String> {
|
|
self.method_call(
|
|
"org.freedesktop.systemd1.Manager",
|
|
"StopUnit",
|
|
Some((name, mode)),
|
|
)
|
|
}
|
|
|
|
pub fn set_unit_properties(
|
|
&self,
|
|
name: &str,
|
|
runtime: bool,
|
|
properties: Vec<Structure<Variant>>,
|
|
) -> Result<()> {
|
|
self.method_call(
|
|
"org.freedesktop.systemd1.Manager",
|
|
"SetUnitProperties",
|
|
Some((name, runtime, properties)),
|
|
)
|
|
}
|
|
|
|
pub fn version(&self) -> Result<String> {
|
|
let t = self.method_call::<_, Variant>(
|
|
"org.freedesktop.DBus.Properties",
|
|
"Get",
|
|
Some(("org.freedesktop.systemd1.Manager", "Version")),
|
|
)?;
|
|
match t {
|
|
Variant::String(s) => Ok(s),
|
|
v => panic!("version expected string variant, got {:?} instead", v),
|
|
}
|
|
}
|
|
|
|
pub fn control_group(&self) -> Result<String> {
|
|
let t = self.method_call::<_, Variant>(
|
|
"org.freedesktop.DBus.Properties",
|
|
"Get",
|
|
Some((
|
|
"org.freedesktop.systemd1.Manager".to_string(),
|
|
"ControlGroup".to_string(),
|
|
)),
|
|
)?;
|
|
match t {
|
|
Variant::String(s) => Ok(s),
|
|
v => panic!("control group expected string variant, got {:?} instead", v),
|
|
}
|
|
}
|
|
pub fn attach_process(&self, name: &str, cgroup: &str, pid: u32) -> Result<()> {
|
|
self.method_call::<_, ()>(
|
|
"org.freedesktop.systemd1.Manager",
|
|
"AttachProcessesToUnit",
|
|
Some((name, cgroup, vec![pid])),
|
|
)
|
|
}
|
|
}
|