Skip to content

Commit b557e9e

Browse files
committed
Add host-service GetNetworkInfo GRPC call including test and cli
Signed-off-by: Guvenc Gulce <guevenc.guelce@sap.com>
1 parent 9cfaa9c commit b557e9e

File tree

9 files changed

+211
-16
lines changed

9 files changed

+211
-16
lines changed

cli/src/host_commands.rs

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ use anyhow::{Context, Result};
22
use clap::{Args, Subcommand};
33
use digest::Digest;
44
use feos_proto::host_service::{
5-
host_service_client::HostServiceClient, upgrade_request, GetCpuInfoRequest, HostnameRequest,
6-
MemoryRequest, RebootRequest, ShutdownRequest, StreamKernelLogsRequest, UpgradeMetadata,
7-
UpgradeRequest,
5+
host_service_client::HostServiceClient, upgrade_request, GetCpuInfoRequest,
6+
GetNetworkInfoRequest, HostnameRequest, MemoryRequest, RebootRequest, ShutdownRequest,
7+
StreamKernelLogsRequest, UpgradeMetadata, UpgradeRequest,
88
};
99
use sha2::Sha256;
1010
use std::path::PathBuf;
@@ -34,6 +34,7 @@ pub enum HostCommand {
3434
Hostname,
3535
Memory,
3636
CpuInfo,
37+
NetworkInfo,
3738
Upgrade {
3839
#[arg(required = true)]
3940
binary_path: PathBuf,
@@ -55,6 +56,7 @@ pub async fn handle_host_command(args: HostArgs) -> Result<()> {
5556
HostCommand::Hostname => get_hostname(&mut client).await?,
5657
HostCommand::Memory => get_memory(&mut client).await?,
5758
HostCommand::CpuInfo => get_cpu_info(&mut client).await?,
59+
HostCommand::NetworkInfo => get_network_info(&mut client).await?,
5860
HostCommand::Upgrade { binary_path } => upgrade_feos(&mut client, binary_path).await?,
5961
HostCommand::Klogs => stream_klogs(&mut client).await?,
6062
HostCommand::Shutdown => shutdown_host(&mut client).await?,
@@ -185,6 +187,33 @@ async fn get_cpu_info(client: &mut HostServiceClient<Channel>) -> Result<()> {
185187
Ok(())
186188
}
187189

190+
async fn get_network_info(client: &mut HostServiceClient<Channel>) -> Result<()> {
191+
let request = GetNetworkInfoRequest {};
192+
let response = client.get_network_info(request).await?.into_inner();
193+
194+
if response.devices.is_empty() {
195+
println!("No network devices found on the host.");
196+
return Ok(());
197+
}
198+
199+
for dev in response.devices {
200+
println!("Interface: {}", dev.name);
201+
println!(" RX");
202+
println!(" Bytes: {:>15}", dev.rx_bytes);
203+
println!(" Packets: {:>15}", dev.rx_packets);
204+
println!(" Errors: {:>15}", dev.rx_errors);
205+
println!(" Dropped: {:>15}", dev.rx_dropped);
206+
println!(" TX");
207+
println!(" Bytes: {:>15}", dev.tx_bytes);
208+
println!(" Packets: {:>15}", dev.tx_packets);
209+
println!(" Errors: {:>15}", dev.tx_errors);
210+
println!(" Dropped: {:>15}", dev.tx_dropped);
211+
println!();
212+
}
213+
214+
Ok(())
215+
}
216+
188217
async fn stream_klogs(client: &mut HostServiceClient<Channel>) -> Result<()> {
189218
println!("Streaming kernel logs... Press Ctrl+C to stop.");
190219
let request = StreamKernelLogsRequest {};

feos/services/host-service/src/api.rs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use crate::Command;
22
use feos_proto::host_service::{
3-
host_service_server::HostService, GetCpuInfoRequest, GetCpuInfoResponse, HostnameRequest,
4-
HostnameResponse, KernelLogEntry, MemoryRequest, MemoryResponse, RebootRequest, RebootResponse,
5-
ShutdownRequest, ShutdownResponse, StreamKernelLogsRequest, UpgradeRequest, UpgradeResponse,
3+
host_service_server::HostService, GetCpuInfoRequest, GetCpuInfoResponse, GetNetworkInfoRequest,
4+
GetNetworkInfoResponse, HostnameRequest, HostnameResponse, KernelLogEntry, MemoryRequest,
5+
MemoryResponse, RebootRequest, RebootResponse, ShutdownRequest, ShutdownResponse,
6+
StreamKernelLogsRequest, UpgradeRequest, UpgradeResponse,
67
};
78
use log::info;
89
use std::pin::Pin;
@@ -88,6 +89,27 @@ impl HostService for HostApiHandler {
8889
}
8990
}
9091

92+
async fn get_network_info(
93+
&self,
94+
_request: Request<GetNetworkInfoRequest>,
95+
) -> Result<Response<GetNetworkInfoResponse>, Status> {
96+
info!("HOST_API_HANDLER: Received GetNetworkInfo request.");
97+
let (resp_tx, resp_rx) = oneshot::channel();
98+
let cmd = Command::GetNetworkInfo(resp_tx);
99+
self.dispatcher_tx
100+
.send(cmd)
101+
.await
102+
.map_err(|e| Status::internal(format!("Failed to send command to dispatcher: {e}")))?;
103+
104+
match resp_rx.await {
105+
Ok(Ok(result)) => Ok(Response::new(result)),
106+
Ok(Err(status)) => Err(status),
107+
Err(_) => Err(Status::internal(
108+
"Dispatcher task dropped response channel.",
109+
)),
110+
}
111+
}
112+
91113
async fn shutdown(
92114
&self,
93115
request: Request<ShutdownRequest>,

feos/services/host-service/src/dispatcher.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ impl HostServiceDispatcher {
2525
Command::GetCPUInfo(responder) => {
2626
tokio::spawn(worker::handle_get_cpu_info(responder));
2727
}
28+
Command::GetNetworkInfo(responder) => {
29+
tokio::spawn(worker::handle_get_network_info(responder));
30+
}
2831
Command::UpgradeFeosBinary(stream, responder) => {
2932
let restart_tx = self.restart_tx.clone();
3033
tokio::spawn(worker::handle_upgrade(restart_tx, *stream, responder));

feos/services/host-service/src/lib.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use feos_proto::host_service::{
2-
GetCpuInfoResponse, HostnameResponse, KernelLogEntry, MemoryResponse, RebootRequest,
3-
RebootResponse, ShutdownRequest, ShutdownResponse, UpgradeRequest, UpgradeResponse,
2+
GetCpuInfoResponse, GetNetworkInfoResponse, HostnameResponse, KernelLogEntry, MemoryResponse,
3+
RebootRequest, RebootResponse, ShutdownRequest, ShutdownResponse, UpgradeRequest,
4+
UpgradeResponse,
45
};
56
use std::path::PathBuf;
67
use tokio::sync::{mpsc, oneshot};
@@ -15,6 +16,7 @@ pub enum Command {
1516
GetHostname(oneshot::Sender<Result<HostnameResponse, Status>>),
1617
GetMemory(oneshot::Sender<Result<MemoryResponse, Status>>),
1718
GetCPUInfo(oneshot::Sender<Result<GetCpuInfoResponse, Status>>),
19+
GetNetworkInfo(oneshot::Sender<Result<GetNetworkInfoResponse, Status>>),
1820
UpgradeFeosBinary(
1921
Box<Streaming<UpgradeRequest>>,
2022
oneshot::Sender<Result<UpgradeResponse, Status>>,

feos/services/host-service/src/worker.rs

Lines changed: 80 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
use crate::RestartSignal;
22
use digest::Digest;
33
use feos_proto::host_service::{
4-
upgrade_request, CpuInfo, GetCpuInfoResponse, HostnameResponse, KernelLogEntry, MemInfo,
5-
MemoryResponse, RebootRequest, RebootResponse, ShutdownRequest, ShutdownResponse,
6-
UpgradeRequest, UpgradeResponse,
4+
upgrade_request, CpuInfo, GetCpuInfoResponse, GetNetworkInfoResponse, HostnameResponse,
5+
KernelLogEntry, MemInfo, MemoryResponse, NetDev, RebootRequest, RebootResponse,
6+
ShutdownRequest, ShutdownResponse, UpgradeRequest, UpgradeResponse,
77
};
88
use log::{error, info, warn};
99
use nix::sys::reboot::{reboot, RebootMode};
@@ -13,9 +13,9 @@ use std::collections::HashMap;
1313
use std::fs::Permissions;
1414
use std::io::Write;
1515
use std::os::unix::fs::PermissionsExt;
16-
use std::path::PathBuf;
16+
use std::path::{Path, PathBuf};
1717
use tempfile::NamedTempFile;
18-
use tokio::fs::File;
18+
use tokio::fs::{self, File};
1919
use tokio::io::{AsyncBufReadExt, AsyncReadExt, BufReader};
2020
use tokio::sync::mpsc;
2121
use tokio::sync::oneshot;
@@ -235,6 +235,81 @@ pub async fn handle_get_cpu_info(responder: oneshot::Sender<Result<GetCpuInfoRes
235235
}
236236
}
237237

238+
async fn read_net_stat(base_path: &Path, stat_name: &str) -> u64 {
239+
let stat_path = base_path.join(stat_name);
240+
fs::read_to_string(stat_path)
241+
.await
242+
.ok()
243+
.and_then(|s| s.trim().parse::<u64>().ok())
244+
.unwrap_or(0)
245+
}
246+
247+
async fn read_all_net_stats() -> Result<Vec<NetDev>, std::io::Error> {
248+
let mut devices = Vec::new();
249+
let mut entries = fs::read_dir("/sys/class/net").await?;
250+
251+
while let Some(entry) = entries.next_entry().await? {
252+
let path = entry.path();
253+
if !path.is_dir() {
254+
continue;
255+
}
256+
257+
let name = entry
258+
.file_name()
259+
.into_string()
260+
.unwrap_or_else(|_| "invalid_utf8".to_string());
261+
let stats_path = path.join("statistics");
262+
263+
if !stats_path.is_dir() {
264+
continue;
265+
}
266+
267+
let device = NetDev {
268+
name,
269+
rx_bytes: read_net_stat(&stats_path, "rx_bytes").await,
270+
rx_packets: read_net_stat(&stats_path, "rx_packets").await,
271+
rx_errors: read_net_stat(&stats_path, "rx_errors").await,
272+
rx_dropped: read_net_stat(&stats_path, "rx_dropped").await,
273+
rx_fifo: read_net_stat(&stats_path, "rx_fifo_errors").await,
274+
rx_frame: read_net_stat(&stats_path, "rx_frame_errors").await,
275+
rx_compressed: read_net_stat(&stats_path, "rx_compressed").await,
276+
rx_multicast: read_net_stat(&stats_path, "multicast").await,
277+
tx_bytes: read_net_stat(&stats_path, "tx_bytes").await,
278+
tx_packets: read_net_stat(&stats_path, "tx_packets").await,
279+
tx_errors: read_net_stat(&stats_path, "tx_errors").await,
280+
tx_dropped: read_net_stat(&stats_path, "tx_dropped").await,
281+
tx_fifo: read_net_stat(&stats_path, "tx_fifo_errors").await,
282+
tx_collisions: read_net_stat(&stats_path, "collisions").await,
283+
tx_carrier: read_net_stat(&stats_path, "tx_carrier_errors").await,
284+
tx_compressed: read_net_stat(&stats_path, "tx_compressed").await,
285+
};
286+
devices.push(device);
287+
}
288+
289+
Ok(devices)
290+
}
291+
292+
pub async fn handle_get_network_info(
293+
responder: oneshot::Sender<Result<GetNetworkInfoResponse, Status>>,
294+
) {
295+
info!("HOST_WORKER: Processing GetNetworkInfo request.");
296+
let result = match read_all_net_stats().await {
297+
Ok(devices) => Ok(GetNetworkInfoResponse { devices }),
298+
Err(e) => {
299+
error!("HOST_WORKER: ERROR - Failed to get network info: {e}");
300+
Err(Status::internal(format!(
301+
"Failed to get network info from sysfs: {e}"
302+
)))
303+
}
304+
};
305+
306+
if responder.send(result).is_err() {
307+
error!(
308+
"HOST_WORKER: Failed to send response for GetNetworkInfo. API handler may have timed out."
309+
);
310+
}
311+
}
312+
238313
pub async fn handle_stream_kernel_logs(grpc_tx: mpsc::Sender<Result<KernelLogEntry, Status>>) {
239314
info!("HOST_WORKER: Opening {KMSG_PATH} for streaming kernel logs.");
240315

feos/services/vm-service/src/dispatcher.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use feos_proto::{
1212
VmStateChangedEvent,
1313
},
1414
};
15-
use log::{error, info, warn, debug};
15+
use log::{debug, error, info, warn};
1616
use prost::Message;
1717
use prost_types::Any;
1818
use std::sync::Arc;

feos/services/vm-service/src/worker.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,9 @@ pub async fn handle_stream_vm_events(
211211
loop {
212212
match broadcast_rx.recv().await {
213213
Ok(VmEventWrapper { event, .. }) => {
214-
if vm_id_to_watch.as_ref().is_none_or(|id| event.vm_id == *id) && stream_tx.send(Ok(event)).await.is_err() {
214+
if vm_id_to_watch.as_ref().is_none_or(|id| event.vm_id == *id)
215+
&& stream_tx.send(Ok(event)).await.is_err()
216+
{
215217
info!("VM_WORKER (Stream): Client for '{watcher_desc}' disconnected.");
216218
break;
217219
}

feos/tests/integration_tests.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use anyhow::{Context, Result};
22
use feos_proto::{
33
host_service::{
4-
host_service_client::HostServiceClient, GetCpuInfoRequest, HostnameRequest, MemoryRequest,
4+
host_service_client::HostServiceClient, GetCpuInfoRequest, GetNetworkInfoRequest,
5+
HostnameRequest, MemoryRequest,
56
},
67
image_service::{
78
image_service_client::ImageServiceClient, DeleteImageRequest, ImageState,
@@ -434,6 +435,39 @@ async fn test_get_cpu_info() -> Result<()> {
434435
Ok(())
435436
}
436437

438+
#[tokio::test]
439+
async fn test_get_network_info() -> Result<()> {
440+
ensure_server().await;
441+
let (_, mut host_client) = get_public_clients().await?;
442+
443+
info!("Sending GetNetworkInfo request");
444+
let response = host_client
445+
.get_network_info(GetNetworkInfoRequest {})
446+
.await?
447+
.into_inner();
448+
449+
assert!(
450+
!response.devices.is_empty(),
451+
"The list of network devices should not be empty"
452+
);
453+
info!("Received {} network devices", response.devices.len());
454+
455+
let lo = response
456+
.devices
457+
.iter()
458+
.find(|d| d.name == "lo")
459+
.context("Could not find the loopback interface 'lo'")?;
460+
461+
info!("Found loopback interface 'lo'");
462+
assert_eq!(lo.name, "lo");
463+
assert!(
464+
lo.rx_packets > 0 || lo.tx_packets > 0,
465+
"Loopback interface should have some packets transferred"
466+
);
467+
468+
Ok(())
469+
}
470+
437471
#[tokio::test]
438472
async fn test_image_lifecycle() -> Result<()> {
439473
if skip_if_ch_binary_missing() {

proto/v1/host.proto

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ service HostService {
88
rpc Hostname(HostnameRequest) returns (HostnameResponse);
99
rpc GetMemory(MemoryRequest) returns (MemoryResponse);
1010
rpc GetCPUInfo(GetCPUInfoRequest) returns (GetCPUInfoResponse);
11+
// Retrieves statistics for all network interfaces.
12+
rpc GetNetworkInfo(GetNetworkInfoRequest) returns (GetNetworkInfoResponse);
1113
rpc Shutdown(ShutdownRequest) returns (ShutdownResponse);
1214
rpc Reboot(RebootRequest) returns (RebootResponse);
1315

@@ -148,4 +150,30 @@ message CPUInfo {
148150
uint32 cache_alignment = 24;
149151
string address_sizes = 25;
150152
string power_management = 26;
153+
}
154+
155+
message GetNetworkInfoRequest {}
156+
157+
message GetNetworkInfoResponse {
158+
repeated NetDev devices = 1;
159+
}
160+
161+
message NetDev {
162+
string name = 1;
163+
uint64 rx_bytes = 2;
164+
uint64 rx_packets = 3;
165+
uint64 rx_errors = 4;
166+
uint64 rx_dropped = 5;
167+
uint64 rx_fifo = 6;
168+
uint64 rx_frame = 7;
169+
uint64 rx_compressed = 8;
170+
uint64 rx_multicast = 9;
171+
uint64 tx_bytes = 10;
172+
uint64 tx_packets = 11;
173+
uint64 tx_errors = 12;
174+
uint64 tx_dropped = 13;
175+
uint64 tx_fifo = 14;
176+
uint64 tx_collisions = 15;
177+
uint64 tx_carrier = 16;
178+
uint64 tx_compressed = 17;
151179
}

0 commit comments

Comments
 (0)