Skip to content

Commit 88c6e0d

Browse files
Martin Moreira de Jesusnoetarbouriech
andcommitted
feat(node-agent): implement gRPC server
Signed-off-by: Martin Moreira de Jesus <martin.moreiradj@icloud.com> Signed-off-by: Noé Tarbouriech <noe.tarbouriech@etu.umontpellier.fr> Co-Authored-By: Noé Tarbouriech <noe.tarbouriech@etu.umontpellier.fr>
1 parent 70a664b commit 88c6e0d

6 files changed

Lines changed: 329 additions & 64 deletions

File tree

node-agent/src/args.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,6 @@ pub struct CliArguments {
99
#[arg(long, default_value_t = 3, env)]
1010
pub lifecycle_retries: i32,
1111

12-
/// The port to start the node agent on.
13-
#[arg(long, default_value_t = 50052, env)]
14-
pub node_agent_port: u16,
1512

1613
/// The address of the scheduler to connect the node agent to.
1714
#[arg(long, default_value = "[::]", env)]
@@ -21,6 +18,14 @@ pub struct CliArguments {
2118
#[arg(long, default_value_t = 50051, env)]
2219
pub scheduler_port: u16,
2320

21+
/// The address of the node agent for the scheduler to connect to.
22+
#[arg(long, default_value = "[::]", env)]
23+
pub node_agent_address: String,
24+
25+
/// The port of the node agent for the scheduler to connect to.
26+
#[arg(long, default_value_t = 50052, env)]
27+
pub node_agent_port: u16,
28+
2429
/// Verbosity level.
2530
#[command(flatten)]
2631
pub verbose: Verbosity<InfoLevel>,

node-agent/src/main.rs

Lines changed: 94 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
mod args;
22
mod workload_manager;
33

4+
use std::process::exit;
5+
46
use clap::Parser;
5-
use orka_proto::{node_agent::Workload, scheduler_agent::{lifecycle_service_client::LifecycleServiceClient, ConnectionRequest, status_update_service_client::StatusUpdateServiceClient}};
6-
use tracing::{info, error};
7+
use orka_proto::scheduler_agent::{lifecycle_service_client::LifecycleServiceClient, ConnectionRequest, status_update_service_client::StatusUpdateServiceClient, DisconnectionNotice};
8+
use tokio::sync::mpsc::{Sender, Receiver, self};
9+
use tracing::{info, error, warn};
710
use uuid::Uuid;
8-
use workload_manager::container::client::ContainerClient;
911
use anyhow::Result;
1012
use tracing_log::AsTrace;
1113

12-
use crate::{workload_manager::container::metrics::metrics::any_to_resource, args::CliArguments};
14+
use crate::workload_manager::grpc::server::GrpcServer;
15+
use crate::args::CliArguments;
1316
use crate::workload_manager::node::metrics::stream_node_status;
1417

1518
async fn execute_node_lifecycle(
@@ -73,53 +76,8 @@ async fn execute_node_lifecycle(
7376
}
7477
}
7578

76-
const CID: &str = "nginx";
77-
7879
#[tokio::main]
7980
async fn main() {
80-
let mut workload_manager = ContainerClient::new("/var/run/containerd/containerd.sock").await.unwrap();
81-
82-
let workload = Workload {
83-
instance_id: CID.to_string(),
84-
image: "docker.io/library/nginx:latest".to_string(),
85-
environment: vec!["FOO=BAR".to_string()],
86-
..Default::default()
87-
};
88-
89-
workload_manager.create(&workload).await.unwrap();
90-
91-
let response = workload_manager.info(CID).await.unwrap();
92-
93-
println!("{:?}", response);
94-
95-
let response = workload_manager.status(CID).await.unwrap();
96-
97-
println!("{:?}", response);
98-
99-
for _ in 0..10 {
100-
let response = workload_manager.metrics(CID).await.unwrap();
101-
102-
for metric in response.into_inner().metrics {
103-
let data = metric.data.unwrap();
104-
105-
println!("{:?}", any_to_resource(&data));
106-
}
107-
108-
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
109-
}
110-
111-
let signal = 9;
112-
workload_manager.kill(CID, signal).await.unwrap();
113-
114-
workload_manager.wait(CID).await.unwrap();
115-
116-
workload_manager.cleanup(CID).await.unwrap();
117-
118-
match workload_manager.status(CID).await {
119-
Ok(_) => panic!("Workload should not exist"),
120-
Err(_) => println!("Workload does not exist"),
121-
};
122-
12381
let args = CliArguments::parse();
12482

12583
tracing_subscriber::fmt()
@@ -139,22 +97,97 @@ async fn main() {
13997

14098
let node_id = Uuid::new_v4();
14199

142-
let retries = args.lifecycle_retries;
100+
info!("Node ID: {}", node_id);
143101

144-
for _ in 0..retries {
145-
match execute_node_lifecycle(
146-
node_id,
147-
args.node_agent_port,
148-
scheduler_connection_string.clone(),
149-
)
150-
.await
151-
{
152-
Ok(_) => {
153-
// will never be reached
102+
let (tx, mut rx): (Sender<i32>, Receiver<i32>) = mpsc::channel(1);
103+
104+
let lifecycle_tx = tx.clone();
105+
let lifecycle_connection_string = scheduler_connection_string.clone();
106+
107+
// join scheduler and stream node status to scheduler, retrying on failure
108+
tokio::spawn(async move {
109+
let retries = args.lifecycle_retries;
110+
111+
for _ in 0..retries {
112+
match execute_node_lifecycle(
113+
node_id,
114+
args.node_agent_port,
115+
lifecycle_connection_string.clone(),
116+
)
117+
.await
118+
{
119+
Ok(_) => {
120+
// will never be reached
121+
}
122+
Err(e) => {
123+
error!("Failed to execute node lifecycle: {:?}", e);
124+
}
154125
}
126+
}
127+
128+
error!("
129+
Node lifecycle failed, initiating graceful shutdown"
130+
);
131+
132+
let _ = lifecycle_tx.send(1).await;
133+
});
134+
135+
// start grpc server
136+
tokio::spawn(async move {
137+
error!("Starting gRPC server on {}:{}",
138+
args.node_agent_address,
139+
args.node_agent_port
140+
);
141+
142+
let grpc = GrpcServer::new(args.node_agent_address, args.node_agent_port);
143+
144+
let server = match grpc.map_err(|e| {
145+
error!("Failed to create gRPC server: {:?}", e);
146+
}) {
147+
Ok(server) => server,
155148
Err(e) => {
156-
error!("Failed to execute node lifecycle: {:?}", e);
149+
error!("Failed to create gRPC server: {:?}", e);
150+
let _ = tx.send(1).await;
151+
exit(1);
157152
}
153+
};
154+
155+
match server.start_server().await {
156+
Ok(_) => {}
157+
Err(e) => {
158+
error!("Failed to start gRPC server: {:?}", e);
159+
let _ = tx.send(1).await;
160+
exit(1);
161+
}
162+
};
163+
});
164+
165+
let _ = rx.recv().await;
166+
167+
info!("Exiting");
168+
169+
info!("Trying to quit cluster");
170+
171+
match LifecycleServiceClient::connect(scheduler_connection_string).await {
172+
Err(error) => {
173+
warn!("Failed connecting to scheduler when exiting {:?}", error)
174+
}
175+
Ok(mut client) => {
176+
match client
177+
.leave_cluster(DisconnectionNotice {
178+
id: node_id.to_string(),
179+
})
180+
.await
181+
{
182+
Ok(_) => {
183+
info!("Successfully left cluster")
184+
}
185+
Err(error) => {
186+
warn!("Failed existing cluster {:?}", error)
187+
}
188+
};
158189
}
159190
}
191+
192+
exit(1);
160193
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pub mod scheduler_workload_service;
2+
pub mod server;
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
use crate::workload_manager::container::client::ContainerClient;
2+
use crate::workload_manager::container::error::into_tonic_status;
3+
use crate::workload_manager::container::metrics::metrics::any_to_resource;
4+
use orka_proto::node_agent::workload_service_server::WorkloadService;
5+
use orka_proto::node_agent::workload_signal::Signal;
6+
use orka_proto::node_agent::{workload_status, Empty, Workload, WorkloadSignal, WorkloadStatus};
7+
use tokio_stream::wrappers::ReceiverStream;
8+
use tonic::{Request, Response, Result, Status};
9+
use tracing::debug;
10+
11+
pub struct WorkloadSvc {}
12+
13+
impl WorkloadSvc {
14+
/// Create a new `WorkloadService` gRPC service manager.
15+
pub fn new() -> Self {
16+
Self {}
17+
}
18+
}
19+
20+
#[tonic::async_trait]
21+
impl WorkloadService for WorkloadSvc {
22+
type CreateStream = ReceiverStream<Result<WorkloadStatus>>;
23+
24+
async fn create(
25+
&self,
26+
request: Request<Workload>,
27+
) -> Result<Response<Self::CreateStream>, Status> {
28+
let workload = request.into_inner();
29+
let mut client = match ContainerClient::new("/var/run/containerd/containerd.sock").await {
30+
Ok(x) => x,
31+
Err(e) => {
32+
return Err(Status::new(
33+
tonic::Code::Internal,
34+
format!("Failed to create container client{:?}", e),
35+
))
36+
}
37+
};
38+
39+
let container = match client.create(&workload).await {
40+
Ok(x) => x,
41+
Err(e) => {
42+
return Err(Status::new(
43+
tonic::Code::Internal,
44+
format!("Failed to create container: {:?}", e),
45+
))
46+
}
47+
}
48+
.into_inner();
49+
50+
let (tx, rx) = tokio::sync::mpsc::channel(4);
51+
tokio::spawn(async move {
52+
loop {
53+
let metrics = match client.metrics(&container.container_id).await {
54+
Ok(x) => x,
55+
Err(e) => {
56+
return Status::new(
57+
tonic::Code::Internal,
58+
format!("Failed to get container info: {:?}", e),
59+
)
60+
}
61+
}
62+
.into_inner()
63+
.metrics;
64+
65+
let status = match client.status(&container.container_id).await {
66+
Ok(x) => x,
67+
Err(e) => {
68+
return Status::new(
69+
tonic::Code::Internal,
70+
format!("Failed to get container info: {:?}", e),
71+
)
72+
}
73+
}
74+
.into_inner()
75+
.process;
76+
77+
for metric in metrics {
78+
let data = metric.data.unwrap();
79+
let resource = any_to_resource(&data);
80+
let status_code = containerd_client::types::v1::Status::from_i32(
81+
status.clone().unwrap().status,
82+
)
83+
.unwrap();
84+
let workload_status = WorkloadStatus {
85+
instance_id: workload.instance_id.clone(),
86+
status: Some(workload_status::Status {
87+
code: match status_code {
88+
containerd_client::types::v1::Status::Running => {
89+
workload_status::status::StatusCode::Running as i32
90+
}
91+
containerd_client::types::v1::Status::Stopped => {
92+
workload_status::status::StatusCode::Terminated as i32
93+
}
94+
_ => workload_status::status::StatusCode::Waiting as i32,
95+
},
96+
..Default::default()
97+
}),
98+
resource_usage: resource.ok(),
99+
};
100+
match tx.send(Ok(workload_status)).await {
101+
Ok(x) => x,
102+
Err(e) => {
103+
return Status::new(
104+
tonic::Code::Internal,
105+
format!("Failed to send workload status: {:?}", e),
106+
)
107+
}
108+
};
109+
}
110+
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
111+
}
112+
});
113+
114+
Ok(Response::new(ReceiverStream::new(rx)))
115+
}
116+
117+
async fn signal(&self, request: Request<WorkloadSignal>) -> Result<Response<Empty>, Status> {
118+
let workload_signal = request.into_inner();
119+
120+
let mut client = ContainerClient::new("/var/run/containerd/containerd.sock")
121+
.await
122+
.map_err(|error| {
123+
Status::new(
124+
tonic::Code::Internal,
125+
format!("Failed to create container client{:?}", error),
126+
)
127+
})?;
128+
129+
let signal = if workload_signal.signal == Signal::Stop as i32 {
130+
sysinfo::Signal::Quit
131+
} else {
132+
sysinfo::Signal::Kill
133+
};
134+
135+
// kill task
136+
client
137+
.kill(&workload_signal.instance_id, signal as u32)
138+
.await
139+
.map_err(|error| {
140+
debug!("Failed to kill container {:?}", error);
141+
into_tonic_status(error)
142+
})?;
143+
144+
// wait for container to stop
145+
client
146+
.wait(&workload_signal.instance_id)
147+
.await
148+
.map_err(|error| {
149+
debug!("Failed to wait for container {:?}", error);
150+
into_tonic_status(error)
151+
})?;
152+
153+
// cleanup container
154+
client
155+
.cleanup(&workload_signal.instance_id)
156+
.await
157+
.map_err(|error| {
158+
debug!("Failed to cleanup container {:?}", error);
159+
into_tonic_status(error)
160+
})?;
161+
162+
Ok(Response::new(Empty {}))
163+
}
164+
}

0 commit comments

Comments
 (0)