Skip to content
This repository has been archived by the owner on Jun 23, 2022. It is now read-only.

*: Data collection from FCOS machines #31

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
6 changes: 5 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ rust:
- stable
- beta
- nightly
services:
- docker
before_install:
- docker build -t pinger-test .
matrix:
allow_failures:
- rust: nightly
script:
- cargo test
- docker run --rm pinger-test
14 changes: 14 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,15 @@ env_logger = "^0.6.1"
failure = "^0.1.5"
liboverdrop = "^0.0.2"
log = "^0.4.6"
maplit = "^1.0"
serde = { version = "^1.0.91", features = ["derive"] }
serde_json = "1.0.40"
slog-scope = "~4.1"
tempfile = "3.1.0"
toml = "^0.5.1"
reqwest = "0.9.22"
bincode = "1.2.0"
nix = "0.15.0"

[package.metadata.release]
sign-commit = true
Expand All @@ -32,3 +39,10 @@ pre-release-commit-message = "cargo: Fedora CoreOS Pinger release {{version}}"
pro-release-commit-message = "cargo: development version bump"
tag-message = "Fedora CoreOS Pinger v{{version}}"
tag-prefix = "v"

[dependencies.slog]
version = "2.5"
features = ["max_level_trace", "release_max_level_info"]

[dev-dependencies]
mockito = "^0.17.1"
10 changes: 10 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM registry.access.redhat.com/ubi8/ubi
WORKDIR /fedora-coreos-pinger

COPY . .
RUN yum install -y gcc openssl-devel
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y -q
RUN echo "export PATH=\"$HOME/.cargo/bin:$PATH\"" > ~/.bashrc \
&& source ~/.bashrc \
&& cargo build
CMD [ "/root/.cargo/bin/cargo", "test" ]
1 change: 1 addition & 0 deletions dist/systemd/fedora-coreos-pinger.service
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ After=network-online.target
DynamicUser=yes
Type=oneshot
RemainAfterExit=yes
LogsDirectory=fedora-coreos-pinger
ExecStart=/usr/libexec/fedora-coreos-pinger

[Install]
Expand Down
156 changes: 156 additions & 0 deletions src/agent/full/container_runtime.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
//! Collect container runtime information
//!
//! Currently four container runtimes are considered: docker, podman, systemd-nspawn, crio
//! Following commands are called to check whether the runtime is running and
//! the number of containers run by the specific runtime.
//!
//! Podman:
//! - pgrep podman
//! - pgrep conmon
//! Docker:
//! - pgrep dockerd
//! - pgrep containerd-shim
//! Systemd-nspawn:
//! - pgrep systemd-nspawn
//! Crio:
//! - pgrep crio
//! - pgrep crictl
//!
//! Note: none of the commands require root access

use failure::{self, bail, Fallible};
use serde::{Deserialize, Serialize};
use std::io::Write;
use std::process;

/// wrapper struct for single container runtime
#[derive(Debug, Serialize, Deserialize)]
pub(crate) struct ContainerRTInfo {
is_running: bool,
num_containers: i32,
}

/// struct for storing all container runtime info
#[derive(Debug, Serialize, Deserialize)]
pub(crate) struct ContainerRT {
/// is_running: pgrep podman
/// num_containers: pgrep conmon | wc -l
podman: ContainerRTInfo,
/// is_running: pgrep dockerd
/// num_containers: pgrep containerd-shim | wc -l
docker: ContainerRTInfo,
/// is_running and num_containers: pgrep systemd-nspawn | wc -l
systemd_nspawn: ContainerRTInfo,
/// is_running: pgrep crio
/// num_containers: pgrep crictl | wc -l
crio: ContainerRTInfo,
}

/// function to run `${command} ${args}`
fn run_command(command: &str, args: &Vec<&str>) -> Result<process::Output, std::io::Error> {
process::Command::new(command).args(args).output()
}

/// spawn a child process and pass the output of previous command through pipe
fn spawn_child(
input: &str,
command: &str,
args: Vec<&str>,
) -> Result<process::Output, std::io::Error> {
let mut child = process::Command::new(command)
.args(args)
.stdin(process::Stdio::piped())
.stdout(process::Stdio::piped())
.spawn()?;
child
.stdin
.as_mut()
.unwrap()
.write_all(input.as_bytes())
.unwrap();
let output = child.wait_with_output()?;
Ok(output)
}

impl ContainerRT {
pub(crate) fn new() -> ContainerRT {
ContainerRT {
podman: ContainerRTInfo {
is_running: Self::rt_is_running("podman").unwrap_or(false),
num_containers: Self::rt_count_running("podman").unwrap_or(0),
},
docker: ContainerRTInfo {
is_running: Self::rt_is_running("docker").unwrap_or(false),
num_containers: Self::rt_count_running("docker").unwrap_or(0),
},
systemd_nspawn: ContainerRTInfo {
is_running: Self::rt_is_running("systemd_nspawn").unwrap_or(false),
num_containers: Self::rt_count_running("systemd_nspawn").unwrap_or(0),
},
crio: ContainerRTInfo {
is_running: Self::rt_is_running("crio").unwrap_or(false),
num_containers: Self::rt_count_running("crio").unwrap_or(0),
},
}
}

/// checks if the runtime is running
fn rt_is_running(container_rt: &str) -> Fallible<bool> {
let command = "pgrep";
match container_rt {
"podman" => {
let options = vec!["podman"];
let output = run_command(command, &options)?;
Ok(output.status.success())
}
"docker" => {
let options = vec!["dockerd"];
let output = run_command(command, &options)?;
Ok(output.status.success())
}
"systemd_nspawn" => {
let options = vec!["systemd-nspawn"];
let output = run_command(command, &options)?;
Ok(output.status.success())
}
"crio" => {
let options = vec!["crio"];
let output = run_command(command, &options)?;
Ok(output.status.success())
}
_ => Ok(false),
}
}

/// counts the number of running containers
fn rt_count_running(container_rt: &str) -> Fallible<i32> {
let command = "pgrep";
let options = match container_rt {
"podman" => vec!["conmon"],
"docker" => vec!["containerd-shim"],
"systemd-nspawn" => vec!["systemd-nspawn"],
"crio" => vec!["crictl"],
_ => bail!("container runtime {} is not supported", container_rt),
};

// run `${command} ${args}`
let mut output = run_command(command, &options)?;
if !output.status.success() {
return Ok(0);
}
let mut std_out = String::from_utf8(output.stdout)?;

// count lines of previous output
output = spawn_child(std_out.as_str(), "wc", vec!["-l"])?;
if !output.status.success() {
return Ok(0);
}
std_out = String::from_utf8(output.stdout)?
.trim()
.trim_end_matches("\n")
.to_string();
let count: i32 = std_out.parse()?;

Ok(count)
}
}
40 changes: 40 additions & 0 deletions src/agent/full/hardware/lsblk.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//! Module for running `lsblk --fs --json`, and storing the
//! output in the struct LsblkJSON
use failure::{bail, format_err, Fallible, ResultExt};
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub(crate) struct LsblkJSON {
pub(crate) blockdevices: Vec<DeviceJSON>,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub(crate) struct DeviceJSON {
name: String,
fstype: Option<String>,
label: Option<String>,
fsavail: Option<String>,
#[serde(rename = "fsuse%")]
fsuse_percentage: Option<String>,
mountpoint: Option<String>,
children: Option<Box<Vec<DeviceJSON>>>,
}

impl LsblkJSON {
pub(crate) fn new() -> Fallible<LsblkJSON> {
let mut cmd = std::process::Command::new("lsblk");
let cmdrun = cmd
.arg("--fs")
.arg("--json")
.output()
.with_context(|e| format_err!("failed to run lsblk --fs --json: {}", e))?;

if !cmdrun.status.success() {
bail!(
"lsblk --fs --json failed:\n{}",
String::from_utf8_lossy(&cmdrun.stderr)
);
}
Ok(serde_json::from_slice(&cmdrun.stdout)?)
}
}
33 changes: 33 additions & 0 deletions src/agent/full/hardware/lscpu.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//! Module for running `lscpu --json`, and storing the output
//! in the struct LscpuJSON
use failure::{bail, format_err, Fallible, ResultExt};
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub(crate) struct LscpuJSON {
pub(crate) lscpu: Vec<CPUInfoJSON>,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub(crate) struct CPUInfoJSON {
field: String,
data: String,
}

impl LscpuJSON {
pub(crate) fn new() -> Fallible<LscpuJSON> {
let mut cmd = std::process::Command::new("lscpu");
let cmdrun = cmd
.arg("--json")
.output()
.with_context(|e| format_err!("failed to run lscpu --json: {}", e))?;

if !cmdrun.status.success() {
bail!(
"lscpu --json failed:\n{}",
String::from_utf8_lossy(&cmdrun.stderr)
);
}
Ok(serde_json::from_slice(&cmdrun.stdout)?)
}
}
77 changes: 77 additions & 0 deletions src/agent/full/hardware/lsmem.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//! Module for running `lsmem --json`, and storing the output
//! in the struct LsmemJSON
use failure::{bail, format_err, Fallible, ResultExt};
use serde::de::{self, Unexpected};
use serde::{Deserialize, Deserializer, Serialize};
use std::fmt;

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub(crate) struct LsmemJSON {
pub(crate) memory: Vec<MemoryJSON>,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub(crate) struct MemoryJSON {
size: String,
state: String,
#[serde(deserialize_with = "deserialize_bool_or_string")]
removable: bool,
block: String,
}

impl LsmemJSON {
pub(crate) fn new() -> Fallible<LsmemJSON> {
let mut cmd = std::process::Command::new("lsmem");
let cmdrun = cmd
.arg("--json")
.output()
.with_context(|e| format_err!("failed to run lsmem --json: {}", e))?;

if !cmdrun.status.success() {
bail!(
"lsmem --json failed:\n{}",
String::from_utf8_lossy(&cmdrun.stderr)
);
}
Ok(serde_json::from_slice(&cmdrun.stdout)?)
}
}

struct DeserializeBoolOrString;

impl<'de> de::Visitor<'de> for DeserializeBoolOrString {
type Value = bool;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a bool or a string")
}

fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(v)
}

fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
if v == "yes" {
Ok(true)
} else if v == "no" {
Ok(false)
} else {
Err(E::invalid_value(Unexpected::Str(v), &self))
}
}
}

/// In some version of lsmem, the field `removable` is 'yes'/'no' instead of 'true'/'false',
/// causing failure of deserialization by serde, hence adds this customized deserializing function
fn deserialize_bool_or_string<'de, D>(deserializer: D) -> Result<bool, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_any(DeserializeBoolOrString)
}
Loading