Skip to content

Conversation

@ytoml
Copy link
Contributor

@ytoml ytoml commented Feb 3, 2022

runc client
Signed-off-by: Yuna Tomida ytomida.mmm@gmail.com

Signed-off-by: Yuna Tomida <ytomida.mmm@gmail.com>
@ytoml
Copy link
Contributor Author

ytoml commented Feb 3, 2022

  • runc client crate, corresponds to go-runc in Go
  • Thanks a lot to earlier efforts rust-runc
    • It's archived then I re-implement based on it.
  • Contains runc client of sync/async version
  • Supported
    • runc create / start / kill / delete / state
    • exec is available only in sync client now
  • What left unimplemented
    • checkpoint related commands
    • console utilities (ref: Go version)

@ytoml
Copy link
Contributor Author

ytoml commented Feb 3, 2022

And updated MSRV to 1.54 for using oci-spec crate: https://github.com/containers/oci-spec-rs

ytoml added 2 commits February 3, 2022 05:09
Signed-off-by: Yuna Tomida <ytomida.mmm@gmail.com>
Signed-off-by: Yuna Tomida <ytomida.mmm@gmail.com>
@AkihiroSuda
Copy link
Member

LGTM, but needs to fix the format error

Run cargo fmt --all -- --check --files-with-diff
/home/runner/work/rust-extensions/rust-extensions/crates/runc-rust/src/lib.rs
Error: Process completed with exit code 1.

https://github.com/containerd/rust-extensions/runs/5048032832?check_suite_focus=true

Thanks!

Signed-off-by: Yuna Tomida <ytomida.mmm@gmail.com>
@ytoml
Copy link
Contributor Author

ytoml commented Feb 3, 2022

I fixed it on local, but I want to ask something before push it.
When I run clippy with options set on CI, this error came out:
スクリーンショット 2022-02-03 20 40 17
Actually it's not what I implemented here, but is it fine fixing on this PR?
Maybe I can fix renaming it to ParseFailed.

@AkihiroSuda
Copy link
Member

Actually it's not what I implemented here, but is it fine fixing on this PR?
Maybe I can fix renaming it to ParseFailed.

Yes, in a separate commit 🙏 Thanks

Signed-off-by: Yuna Tomida <ytomida.mmm@gmail.com>
@mxpv
Copy link
Member

mxpv commented Feb 3, 2022

Actually it's not what I implemented here, but is it fine fixing on this PR?
Maybe I can fix renaming it to ParseFailed.

I think this was introduced after updating rust to 1.54 in rust-toolchain.toml

Cargo.toml Outdated
"crates/shim",
"crates/snapshots"
"crates/snapshots",
"crates/runc-rust"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just crates/runc ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, thank you!

Note that async client depends on [tokio](https://github.com/tokio-rs/tokio), then please use it on tokio runtime.

```rust
use runc_rust as runc;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use runc

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, thank you!

stderr: Option<Pipe>,
}

impl RuncPipedIO {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could omit these Runc prefixes everywhere.
runc::RuncPipedIO -> runc::PipedIo
Same for other structs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, thank you!

serde_json::to_string(resources).map_err(Error::JsonDeserializationFailed)?;
f.write(spec_json.as_bytes())
.map_err(Error::SpecFileCreationError)?;
f.flush().map_err(Error::SpecFileCreationError)?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This block can be reduced to:

fs::write(path, spec_json)?;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, thank you!


#[test]
fn serde_test() {
let j = "
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about using raw string literals here? So that you can write " without escaping.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's good, thank you!

}";

let c: Container = serde_json::from_str(j).unwrap();
println!("{:#?}", c);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally prefer to keep stdout clean. If you have some properties you want to check, write them as tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for recommend but I'm sorry, I couldn't see your point.
This function is in mod tests and attributed as #[test], but it's not enough?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What you'd like to do with the println! output? If you want to sometimes manually check the result, it would be better to put more context or use dbg!.

That being said, it would be great if you can automate the manual checking part by writing assertions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I see, thank you!
I'll rewrite them with assertions.

Comment on lines 43 to 60
fn close(&self) {
unimplemented!()
}

/// Set IO for passed command.
/// Read side of stdin, write side of stdout and write side of stderr should be provided to command.
fn set(&self, _cmd: &mut Command) -> std::io::Result<()> {
unimplemented!()
}

// tokio version of set()
fn set_tk(&self, _cmd: &mut tokio::process::Command) -> std::io::Result<()> {
unimplemented!()
}

fn close_after_start(&self) {
unimplemented!()
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not clear about these unimplemented. Due to time constraints?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I' ll remove them, thank you!

Comment on lines 108 to 126
pub fn take_read(&self) -> Option<File> {
let mut m = self.rd.lock().unwrap();
m.take()
}

pub fn take_write(&self) -> Option<File> {
let mut m = self.wr.lock().unwrap();
m.take()
}

pub fn close_read(&self) {
let mut m = self.rd.lock().unwrap();
let _ = m.take();
}

pub fn close_write(&self) {
let mut m = self.wr.lock().unwrap();
let _ = m.take();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lock() returns Result. Does it make sense to return the errors rather than panic?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

impl RuncPipedIO {
pub fn new(uid: isize, gid: isize, opts: IOOption) -> std::io::Result<Self> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this use isize here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I just followed Go's but it seems to be unnecessary...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, I'll change it to u32

}

fn close_after_start(&self) {
unimplemented!()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unimplemented is not necessary here, just have it as

fn close_after_start(&self);

so the compiler will require implementation at compile time when implementing this trait.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, thank you!

/// when one side of closed, this struct represent it with [`None`]
#[derive(Debug)]
pub struct Pipe {
// Might be ugly hack: using mutex in order to take rd/wr under immutable [`Pipe`]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a particular reason why it can't be mutable?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually what I want to do is accessing struct containing Pipe like PipeIO here from multipile thread, and extract file, and copy the stdio to other io entry from higher layer(e.g. Fifo that passed from containerd to shim).
In such case, we should Arc it to reference from multiple thread and need Mutex to be mutable(to read/write), but any two threads will not access the same entry(e.g. stdout and stdout) and I thought it's redundant to make Mutex<PipedIO>.
Is there any other good way to do this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, like close_after_write, I want to close some files(but not all) manually from threads.

None
}

fn close(&self) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

impl Drop instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, thank you!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As destructor automatically drop files inside structs, I will just erase this trait method.

Comment on lines 108 to 126
pub fn take_read(&self) -> Option<File> {
let mut m = self.rd.lock().unwrap();
m.take()
}

pub fn take_write(&self) -> Option<File> {
let mut m = self.wr.lock().unwrap();
m.take()
}

pub fn close_read(&self) {
let mut m = self.rd.lock().unwrap();
let _ = m.take();
}

pub fn close_write(&self) {
let mut m = self.wr.lock().unwrap();
let _ = m.take();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ytoml and others added 4 commits February 4, 2022 00:39
Signed-off-by: Yuna Tomida <ytomida.mmm@gmail.com>
Signed-off-by: Yuna Tomida <ytomida.mmm@gmail.com>
Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
Copy link
Member

@AkihiroSuda AkihiroSuda left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CI green.

Let's merge this toward reopening the actual shim part (#21)

@AkihiroSuda AkihiroSuda requested a review from mxpv February 6, 2022 10:20
@mxpv
Copy link
Member

mxpv commented Feb 6, 2022

@AkihiroSuda do we need 6e17be8 for this PR? Seems like runc crate has no dependencies on these.

@mxpv
Copy link
Member

mxpv commented Feb 6, 2022

@kzys PTAL

@AkihiroSuda
Copy link
Member

@AkihiroSuda do we need 6e17be8 for this PR? Seems like runc crate has no dependencies on these.

Cargo automatically bumped up them, so I committed.

@AkihiroSuda AkihiroSuda requested a review from kzys February 8, 2022 07:03
- run: cargo fmt --all -- --check --files-with-diff
- run: cargo clippy --all-targets --all-features -- -D warnings
- run: cargo test --all-features
# runc::tests::test_exec needs $XDG_RUNTIME_DIR to be set
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't the test set the environment variable if so?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, usually set by pam_systemd (which is missing in GHA)

#[derive(Debug, Serialize, Deserialize)]
pub struct Container {
pub id: String,
pub pid: usize,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

u32? Response's pid is u32.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's right, thank you!

@mxpv
Copy link
Member

mxpv commented Feb 8, 2022

Cargo automatically bumped up them, so I committed.

@AkihiroSuda can we revert it? Seems like its not relevant for this PR (also looks like it depends on environment, I'll TAL later).

Copy link
Member

@kzys kzys left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly nits. I'm fine merging the PR as is and address them later.

Comment on lines +236 to +237
let stdout = String::from_utf8(result.stdout).unwrap();
let stderr = String::from_utf8(result.stderr).unwrap();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it fail if the command outputs some non UTF-8 bytes (e.g. gzip)?

Copy link
Contributor Author

@ytoml ytoml Feb 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. However which is preferable in such case, returning parse error or return the raw value to caller?

This is just my personal opinion but I think returning error is better. Higher level wrappers which use this crate, like shim, will bend the stdout/err of command to IO that they prepare in advance. And they can handle such non-utf8 within their procedure if needed.
In such situation, these result.stdout and result.stderr will be empty string (and never be non-utf8).

let args = ["list".to_string(), "--format-json".to_string()];
let res = self.launch(self.command(&args)?, true).await?;
let output = res.output.trim();
// Ugly hack to work around golang
Copy link
Member

@kzys kzys Feb 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to know more about the thing you need to workaround :)

Copy link
Contributor Author

@ytoml ytoml Feb 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, this is for just handling null json. But I think this way (with comparering output and "null") is not concise...
Is it comprehensive if I change it like below?

Ok(serde_json::from_str::<Option<Vec<Container>>>(output).map_err(/*omit*/)?.unwrap_or_default())


fn dummy_process() -> Process {
serde_json::from_str(
"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

raw string?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. Thank you!

@AkihiroSuda
Copy link
Member

Seems like its not relevant for this PR (also looks like it depends on environment, I'll TAL later).

Not depending on local env, simply 2.27.1 is the latest ( https://crates.io/crates/protobuf ), and Cargo picks up the latest one because we do not commit Cargo.lock in the repo (why?)

@mxpv
Copy link
Member

mxpv commented Feb 8, 2022

we do not commit Cargo.lock in the repo (why?)

We generally don't commit Cargo.lock for libraries: https://doc.rust-lang.org/cargo/faq.html#why-do-binaries-have-cargolock-in-version-control-but-not-libraries

pub struct Container {
pub id: String,
pub pid: usize,
pub status: String,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should status be defined as enum type for only a handful of container status in act? @mxpv @kzys


/// Return the state of a container
pub fn state(&self, id: &str) -> Result<Container> {
let args = ["state".to_string(), id.to_string()];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it seems that containerd-shim-runc-v2(Go version) uses its memory variable to strore container state(eg. https://github.com/containerd/containerd/blob/release%2F1.5/pkg/process/init_state.go) instead of runc state cli command, cause shim has its own state system maybe.

];
self.launch(self.command(&args)?, true)?;
Ok(())
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

update normaly takes stdin as arg, we should provide this way for compatibility

@mxpv
Copy link
Member

mxpv commented Feb 11, 2022

Ok, I'm going to merge this in. Let's address remaining items in follow up PRs.

@mxpv mxpv merged commit 73d1dfa into containerd:main Feb 11, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants