Add agent
This commit is contained in:
		
							parent
							
								
									ffbd1d6f75
								
							
						
					
					
						commit
						1056446778
					
				
					 9 changed files with 1299 additions and 0 deletions
				
			
		
							
								
								
									
										1
									
								
								agent/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								agent/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1 @@
 | 
				
			||||||
 | 
					/target/
 | 
				
			||||||
							
								
								
									
										1059
									
								
								agent/Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										1059
									
								
								agent/Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										15
									
								
								agent/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								agent/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,15 @@
 | 
				
			||||||
 | 
					[package]
 | 
				
			||||||
 | 
					name = "prymn_agent"
 | 
				
			||||||
 | 
					version = "0.1.0"
 | 
				
			||||||
 | 
					edition = "2021"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[dependencies]
 | 
				
			||||||
 | 
					anyhow = "1.0.71"
 | 
				
			||||||
 | 
					prost = "0.11.9"
 | 
				
			||||||
 | 
					sysinfo = { version = "0.29.2", default-features = false }
 | 
				
			||||||
 | 
					tokio = { version = "1.28.2", features = ["rt-multi-thread", "macros", "io-util", "process"] }
 | 
				
			||||||
 | 
					tokio-stream = "0.1.14"
 | 
				
			||||||
 | 
					tonic = "0.9.2"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[build-dependencies]
 | 
				
			||||||
 | 
					tonic-build = "0.9.2"
 | 
				
			||||||
							
								
								
									
										5
									
								
								agent/build.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								agent/build.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,5 @@
 | 
				
			||||||
 | 
					fn main() {
 | 
				
			||||||
 | 
					    tonic_build::configure()
 | 
				
			||||||
 | 
					        .compile(&["proto/agent.proto"], &["proto"])
 | 
				
			||||||
 | 
					        .unwrap();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										53
									
								
								agent/proto/agent.proto
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								agent/proto/agent.proto
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,53 @@
 | 
				
			||||||
 | 
					syntax = "proto3";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "google/protobuf/empty.proto";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package prymn;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					message EchoRequest {
 | 
				
			||||||
 | 
					    string message = 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					message EchoResponse {
 | 
				
			||||||
 | 
					    string message = 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					message SysInfoResponse {
 | 
				
			||||||
 | 
					    message Cpu {
 | 
				
			||||||
 | 
					        uint64 freq_mhz = 1;
 | 
				
			||||||
 | 
					        float  usage = 2;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    message Disk {
 | 
				
			||||||
 | 
					        string name = 1;
 | 
				
			||||||
 | 
					        uint64 total_bytes = 2;
 | 
				
			||||||
 | 
					        uint64 avail_bytes = 3;
 | 
				
			||||||
 | 
					        string mount_point = 4;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    uint64 uptime = 1;
 | 
				
			||||||
 | 
					    string hostname = 2;
 | 
				
			||||||
 | 
					    string os = 3;
 | 
				
			||||||
 | 
					    uint64 mem_total_bytes = 4;
 | 
				
			||||||
 | 
					    uint64 mem_avail_bytes = 5;
 | 
				
			||||||
 | 
					    uint64 swap_total_bytes = 6;
 | 
				
			||||||
 | 
					    uint64 swap_free_bytes = 7;
 | 
				
			||||||
 | 
					    repeated Cpu  cpus = 8;
 | 
				
			||||||
 | 
					    repeated Disk disks = 9;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					message ExecRequest {
 | 
				
			||||||
 | 
					    string program = 1;
 | 
				
			||||||
 | 
					    repeated string args = 2;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					message ExecResponse {
 | 
				
			||||||
 | 
					    string stdout = 1;
 | 
				
			||||||
 | 
					    string stderr = 2;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					service Agent {
 | 
				
			||||||
 | 
					    rpc Echo(EchoRequest) returns (EchoResponse);
 | 
				
			||||||
 | 
					    rpc GetSysInfo(google.protobuf.Empty) returns (SysInfoResponse);
 | 
				
			||||||
 | 
					    rpc Exec(ExecRequest) returns (stream ExecResponse);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										8
									
								
								agent/src/bin/prymn_agent.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								agent/src/bin/prymn_agent.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,8 @@
 | 
				
			||||||
 | 
					use prymn_agent::new_server;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[tokio::main]
 | 
				
			||||||
 | 
					async fn main() -> anyhow::Result<()> {
 | 
				
			||||||
 | 
					    new_server().serve("0.0.0.0:5043".parse()?).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										3
									
								
								agent/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								agent/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,3 @@
 | 
				
			||||||
 | 
					mod server;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub use server::new_server;
 | 
				
			||||||
							
								
								
									
										61
									
								
								agent/src/server/exec.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								agent/src/server/exec.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,61 @@
 | 
				
			||||||
 | 
					use std::{ffi::OsStr, process::Stdio};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use tokio::{
 | 
				
			||||||
 | 
					    io::{AsyncBufReadExt, BufReader},
 | 
				
			||||||
 | 
					    process::{Child, Command},
 | 
				
			||||||
 | 
					    sync::mpsc,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug)]
 | 
				
			||||||
 | 
					pub(super) struct ExecOutput {
 | 
				
			||||||
 | 
					    pub(super) stdout: Option<String>,
 | 
				
			||||||
 | 
					    pub(super) stderr: Option<String>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub(super) fn exec<S>(program: &S, args: &[S]) -> anyhow::Result<mpsc::Receiver<ExecOutput>>
 | 
				
			||||||
 | 
					where
 | 
				
			||||||
 | 
					    S: AsRef<OsStr>,
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    let (tx, rx) = mpsc::channel(4);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let command = Command::new(program)
 | 
				
			||||||
 | 
					        .args(args)
 | 
				
			||||||
 | 
					        .stdout(Stdio::piped())
 | 
				
			||||||
 | 
					        .stderr(Stdio::piped())
 | 
				
			||||||
 | 
					        .spawn()?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    tokio::spawn(async move { run_command(command, tx).await });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(rx)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn run_command(mut command: Child, tx: mpsc::Sender<ExecOutput>) {
 | 
				
			||||||
 | 
					    let mut stdout = {
 | 
				
			||||||
 | 
					        let stdout = command.stdout.take().expect("bug: no pipe for stdout");
 | 
				
			||||||
 | 
					        BufReader::new(stdout).lines()
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mut stderr = {
 | 
				
			||||||
 | 
					        let stderr = command.stderr.take().expect("bug: no pipe for stderr");
 | 
				
			||||||
 | 
					        BufReader::new(stderr).lines()
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    loop {
 | 
				
			||||||
 | 
					        match (stdout.next_line().await, stderr.next_line().await) {
 | 
				
			||||||
 | 
					            // TODO: Handle errors
 | 
				
			||||||
 | 
					            (Err(_err), _) | (_, Err(_err)) => break,
 | 
				
			||||||
 | 
					            (stdout, stderr) => tx
 | 
				
			||||||
 | 
					                .send(ExecOutput {
 | 
				
			||||||
 | 
					                    stdout: stdout.unwrap(),
 | 
				
			||||||
 | 
					                    stderr: stderr.unwrap(),
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					                .await
 | 
				
			||||||
 | 
					                .expect("bug: channel closed"),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    match command.wait().await {
 | 
				
			||||||
 | 
					        Ok(exit_status) => println!("exit: {}", exit_status),
 | 
				
			||||||
 | 
					        Err(err) => panic!("errorrrrr {}", err),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										94
									
								
								agent/src/server/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								agent/src/server/mod.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,94 @@
 | 
				
			||||||
 | 
					mod exec;
 | 
				
			||||||
 | 
					mod rpc {
 | 
				
			||||||
 | 
					    tonic::include_proto!("prymn");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use std::{
 | 
				
			||||||
 | 
					    pin::Pin,
 | 
				
			||||||
 | 
					    sync::{Arc, Mutex},
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use sysinfo::{CpuExt, DiskExt, System, SystemExt};
 | 
				
			||||||
 | 
					use tokio_stream::{wrappers::ReceiverStream, Stream, StreamExt};
 | 
				
			||||||
 | 
					use tonic::{transport::server::Router, Request, Response, Status};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct Server {
 | 
				
			||||||
 | 
					    sys: Arc<Mutex<System>>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Result<T> = std::result::Result<T, tonic::Status>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[tonic::async_trait]
 | 
				
			||||||
 | 
					impl rpc::agent_server::Agent for Server {
 | 
				
			||||||
 | 
					    async fn echo(&self, req: Request<rpc::EchoRequest>) -> Result<Response<rpc::EchoResponse>> {
 | 
				
			||||||
 | 
					        Ok(Response::new(rpc::EchoResponse {
 | 
				
			||||||
 | 
					            message: req.into_inner().message,
 | 
				
			||||||
 | 
					        }))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async fn get_sys_info(&self, _: Request<()>) -> Result<Response<rpc::SysInfoResponse>> {
 | 
				
			||||||
 | 
					        let sys = self.sys.lock().unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let cpus = sys
 | 
				
			||||||
 | 
					            .cpus()
 | 
				
			||||||
 | 
					            .iter()
 | 
				
			||||||
 | 
					            .map(|cpu| rpc::sys_info_response::Cpu {
 | 
				
			||||||
 | 
					                freq_mhz: cpu.frequency(),
 | 
				
			||||||
 | 
					                usage: cpu.cpu_usage(),
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .collect();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let disks = sys
 | 
				
			||||||
 | 
					            .disks()
 | 
				
			||||||
 | 
					            .iter()
 | 
				
			||||||
 | 
					            .map(|disk| rpc::sys_info_response::Disk {
 | 
				
			||||||
 | 
					                name: disk.name().to_string_lossy().into_owned(),
 | 
				
			||||||
 | 
					                total_bytes: disk.total_space(),
 | 
				
			||||||
 | 
					                avail_bytes: disk.available_space(),
 | 
				
			||||||
 | 
					                mount_point: disk.mount_point().to_string_lossy().into_owned(),
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .collect();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let response = Response::new(rpc::SysInfoResponse {
 | 
				
			||||||
 | 
					            uptime: sys.uptime(),
 | 
				
			||||||
 | 
					            hostname: sys.host_name().unwrap_or_default(),
 | 
				
			||||||
 | 
					            os: sys.long_os_version().unwrap_or_default(),
 | 
				
			||||||
 | 
					            mem_total_bytes: sys.total_memory(),
 | 
				
			||||||
 | 
					            mem_avail_bytes: sys.available_memory(),
 | 
				
			||||||
 | 
					            swap_total_bytes: sys.total_swap(),
 | 
				
			||||||
 | 
					            swap_free_bytes: sys.free_swap(),
 | 
				
			||||||
 | 
					            cpus,
 | 
				
			||||||
 | 
					            disks,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Ok(response)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    type ExecStream = Pin<Box<dyn Stream<Item = Result<rpc::ExecResponse>> + Send + Sync>>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async fn exec(&self, req: Request<rpc::ExecRequest>) -> Result<Response<Self::ExecStream>> {
 | 
				
			||||||
 | 
					        let rpc::ExecRequest { program, args } = req.into_inner();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        match exec::exec(&program, &args) {
 | 
				
			||||||
 | 
					            Ok(receiver) => {
 | 
				
			||||||
 | 
					                let stream = ReceiverStream::new(receiver).map(|inner| {
 | 
				
			||||||
 | 
					                    Ok(rpc::ExecResponse {
 | 
				
			||||||
 | 
					                        stdout: inner.stdout.unwrap_or_default(),
 | 
				
			||||||
 | 
					                        stderr: inner.stderr.unwrap_or_default(),
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Ok(Response::new(Box::pin(stream)))
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            Err(err) => Err(Status::failed_precondition(err.to_string())),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn new_server() -> Router {
 | 
				
			||||||
 | 
					    let server = Server {
 | 
				
			||||||
 | 
					        sys: Arc::new(Mutex::new(System::new_all())),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    tonic::transport::Server::builder().add_service(rpc::agent_server::AgentServer::new(server))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
		Reference in a new issue