Skip to content

Commit a3ca586

Browse files
authored
Unify error handling (#86)
* Unify error handling across all services Signed-off-by: Guvenc Gulce <[email protected]> * thiserror crate should respect the cargo hierarachy Signed-off-by: Guvenc Gulce <[email protected]> * Use also error.rs with vm-service similar to the rest Signed-off-by: Guvenc Gulce <[email protected]> * Remove unused crate dependencies Signed-off-by: Guvenc Gulce <[email protected]> * Fix clippy warnings Signed-off-by: Guvenc Gulce <[email protected]> * Bump crates hyper, hyper-util, tower to latest versions Signed-off-by: Guvenc Gulce <[email protected]> * Bump nix crate to 0.30.1 Signed-off-by: Guvenc Gulce <[email protected]> --------- Signed-off-by: Guvenc Gulce <[email protected]>
1 parent 14980e1 commit a3ca586

File tree

27 files changed

+736
-758
lines changed

27 files changed

+736
-758
lines changed

Cargo.lock

Lines changed: 85 additions & 79 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ tonic = "0.13.0"
2121
prost = "0.13.5"
2222
prost-types = "0.13.5"
2323
anyhow = "1.0.100"
24-
nix = { version = "0.29.0", features = ["mount", "user", "reboot", "feature", "net", "aio", "signal", "process", "fs", "hostname"] }
24+
nix = { version = "0.30.1", features = ["mount", "user", "reboot", "feature", "net", "aio", "signal", "process", "fs", "hostname"] }
2525
serde = { version = "1.0", features = ["derive"] }
2626
serde_json = "1.0.132"
2727
uuid = { version = "1.18.1", features = ["v4"] }
@@ -30,8 +30,8 @@ log = "0.4.28"
3030
env_logger = "0.11"
3131
openssl = { version = "0.10.72", features = ["vendored"] }
3232
oci-distribution = "0.11.0"
33-
tempfile = "3.22.0"
34-
tower = { version = "0.4", features = ["full"] }
33+
tempfile = "3.23.0"
34+
tower = { version = "0.5.2", features = ["full"] }
3535
sha2 = "0.10"
3636
hex = "0.4"
3737
digest = "0.10"
@@ -45,5 +45,9 @@ dhcproto = "0.13.0"
4545
socket2 = "0.6.0"
4646
futures = "0.3.31"
4747
chrono = "0.4.42"
48-
hyper-util = { version = "0.1.14", features = ["tokio"] }
48+
thiserror = "2.0.16"
49+
hyper = "1.7.0"
50+
hyper-util = { version = "0.1.17", features = ["full"] }
51+
termcolor = "1.1"
52+
once_cell = "1.19"
4953
feos-proto = { path = "feos/proto" }

cli/Cargo.toml

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,8 @@ edition.workspace = true
55
description = "A gRPC CLI for the FeOS control plane"
66

77
[dependencies]
8-
feos-proto = { workspace = true }
9-
sha2 = { workspace = true }
10-
hex = { workspace = true }
11-
digest = { workspace = true }
12-
138
# Workspace dependencies
9+
feos-proto = { workspace = true }
1410
tokio = { workspace = true }
1511
tonic = { workspace = true }
1612
anyhow = { workspace = true }
@@ -20,8 +16,8 @@ hyper-util = { workspace = true }
2016
prost = { workspace = true }
2117
tower = { workspace = true }
2218
clap = { workspace = true, features = ["derive", "env"] }
19+
env_logger = { workspace = true }
20+
chrono = { workspace = true }
2321

2422
# CLI specific dependencies
25-
env_logger = { workspace = true }
26-
crossterm = "0.29"
27-
chrono = { workspace = true }
23+
crossterm = "0.29"

feos/Cargo.toml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ dhcproto = { workspace = true }
3838
socket2 = { workspace = true }
3939
futures = { workspace = true }
4040
chrono = { workspace = true }
41-
termcolor = "1.1"
41+
termcolor = { workspace = true }
4242

4343
[dev-dependencies]
4444
feos-utils = { path = "utils" }
@@ -54,7 +54,6 @@ env_logger = { workspace = true }
5454
tokio-stream = { workspace = true }
5555
prost = { workspace = true }
5656
hyper-util = { workspace = true }
57-
once_cell = "1.19"
58-
regex = "1.10"
57+
once_cell = { workspace = true }
5958
tower = { workspace = true }
60-
tempfile = "3.10.1"
59+
tempfile = { workspace = true }

feos/services/host-service/Cargo.toml

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,19 @@ tempfile = { workspace = true }
1010
sha2 = { workspace = true }
1111
hex = { workspace = true }
1212
digest = { workspace = true }
13-
hyper = "1.4.0"
14-
hyper-util = { version = "0.1.3", features = ["full"] }
15-
hyper-rustls = "0.27.2"
16-
http-body-util = "0.1.2"
17-
rustls-pki-types = "1.0"
18-
19-
# Workspace dependencies
2013
tokio = { workspace = true }
2114
tokio-stream = { workspace = true }
2215
tonic = { workspace = true }
2316
anyhow = { workspace = true }
2417
nix = { workspace = true , features = ["hostname", "reboot"] }
2518
log = { workspace = true }
2619
prost = { workspace = true }
27-
prost-types = { workspace = true }
20+
prost-types = { workspace = true }
21+
thiserror = { workspace = true }
22+
hyper = {workspace = true}
23+
hyper-util = { workspace = true }
24+
25+
26+
hyper-rustls = "0.27.2"
27+
http-body-util = "0.1.2"
28+
rustls-pki-types = "1.0"

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

Lines changed: 41 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,30 @@ impl HostApiHandler {
2525
}
2626
}
2727

28+
async fn dispatch_and_wait<T, E>(
29+
dispatcher: &mpsc::Sender<Command>,
30+
command_constructor: impl FnOnce(oneshot::Sender<Result<T, E>>) -> Command,
31+
) -> Result<Response<T>, Status>
32+
where
33+
E: Into<Status>,
34+
{
35+
let (resp_tx, resp_rx) = oneshot::channel();
36+
let cmd = command_constructor(resp_tx);
37+
38+
dispatcher
39+
.send(cmd)
40+
.await
41+
.map_err(|e| Status::internal(format!("Failed to send command to dispatcher: {e}")))?;
42+
43+
match resp_rx.await {
44+
Ok(Ok(result)) => Ok(Response::new(result)),
45+
Ok(Err(e)) => Err(e.into()),
46+
Err(_) => Err(Status::internal(
47+
"Dispatcher task dropped response channel.",
48+
)),
49+
}
50+
}
51+
2852
#[tonic::async_trait]
2953
impl HostService for HostApiHandler {
3054
type StreamKernelLogsStream =
@@ -36,146 +60,64 @@ impl HostService for HostApiHandler {
3660
_request: Request<HostnameRequest>,
3761
) -> Result<Response<HostnameResponse>, Status> {
3862
info!("HostApi: Received Hostname request.");
39-
let (resp_tx, resp_rx) = oneshot::channel();
40-
let cmd = Command::GetHostname(resp_tx);
41-
self.dispatcher_tx
42-
.send(cmd)
43-
.await
44-
.map_err(|e| Status::internal(format!("Failed to send command to dispatcher: {e}")))?;
45-
46-
match resp_rx.await {
47-
Ok(Ok(result)) => Ok(Response::new(result)),
48-
Ok(Err(status)) => Err(status),
49-
Err(_) => Err(Status::internal(
50-
"Dispatcher task dropped response channel.",
51-
)),
52-
}
63+
dispatch_and_wait(&self.dispatcher_tx, Command::GetHostname).await
5364
}
5465

5566
async fn get_memory(
5667
&self,
5768
_request: Request<MemoryRequest>,
5869
) -> Result<Response<MemoryResponse>, Status> {
5970
info!("HostApi: Received GetMemory request.");
60-
let (resp_tx, resp_rx) = oneshot::channel();
61-
let cmd = Command::GetMemory(resp_tx);
62-
self.dispatcher_tx
63-
.send(cmd)
64-
.await
65-
.map_err(|e| Status::internal(format!("Failed to send command to dispatcher: {e}")))?;
66-
67-
match resp_rx.await {
68-
Ok(Ok(result)) => Ok(Response::new(result)),
69-
Ok(Err(status)) => Err(status),
70-
Err(_) => Err(Status::internal(
71-
"Dispatcher task dropped response channel.",
72-
)),
73-
}
71+
dispatch_and_wait(&self.dispatcher_tx, Command::GetMemory).await
7472
}
7573

7674
async fn get_cpu_info(
7775
&self,
7876
_request: Request<GetCpuInfoRequest>,
7977
) -> Result<Response<GetCpuInfoResponse>, Status> {
8078
info!("HostApi: Received GetCPUInfo request.");
81-
let (resp_tx, resp_rx) = oneshot::channel();
82-
let cmd = Command::GetCPUInfo(resp_tx);
83-
self.dispatcher_tx
84-
.send(cmd)
85-
.await
86-
.map_err(|e| Status::internal(format!("Failed to send command to dispatcher: {e}")))?;
87-
88-
match resp_rx.await {
89-
Ok(Ok(result)) => Ok(Response::new(result)),
90-
Ok(Err(status)) => Err(status),
91-
Err(_) => Err(Status::internal(
92-
"Dispatcher task dropped response channel.",
93-
)),
94-
}
79+
dispatch_and_wait(&self.dispatcher_tx, Command::GetCPUInfo).await
9580
}
9681

9782
async fn get_network_info(
9883
&self,
9984
_request: Request<GetNetworkInfoRequest>,
10085
) -> Result<Response<GetNetworkInfoResponse>, Status> {
10186
info!("HostApi: Received GetNetworkInfo request.");
102-
let (resp_tx, resp_rx) = oneshot::channel();
103-
let cmd = Command::GetNetworkInfo(resp_tx);
104-
self.dispatcher_tx
105-
.send(cmd)
106-
.await
107-
.map_err(|e| Status::internal(format!("Failed to send command to dispatcher: {e}")))?;
108-
109-
match resp_rx.await {
110-
Ok(Ok(result)) => Ok(Response::new(result)),
111-
Ok(Err(status)) => Err(status),
112-
Err(_) => Err(Status::internal(
113-
"Dispatcher task dropped response channel.",
114-
)),
115-
}
87+
dispatch_and_wait(&self.dispatcher_tx, Command::GetNetworkInfo).await
11688
}
11789

11890
async fn shutdown(
11991
&self,
12092
request: Request<ShutdownRequest>,
12193
) -> Result<Response<ShutdownResponse>, Status> {
12294
info!("HostApi: Received Shutdown request.");
123-
let (resp_tx, resp_rx) = oneshot::channel();
124-
let cmd = Command::Shutdown(request.into_inner(), resp_tx);
125-
self.dispatcher_tx
126-
.send(cmd)
127-
.await
128-
.map_err(|e| Status::internal(format!("Failed to send command to dispatcher: {e}")))?;
129-
130-
match resp_rx.await {
131-
Ok(Ok(result)) => Ok(Response::new(result)),
132-
Ok(Err(status)) => Err(status),
133-
Err(_) => Err(Status::internal(
134-
"Dispatcher task dropped response channel.",
135-
)),
136-
}
95+
dispatch_and_wait(&self.dispatcher_tx, |resp_tx| {
96+
Command::Shutdown(request.into_inner(), resp_tx)
97+
})
98+
.await
13799
}
138100

139101
async fn reboot(
140102
&self,
141103
request: Request<RebootRequest>,
142104
) -> Result<Response<RebootResponse>, Status> {
143105
info!("HostApi: Received Reboot request.");
144-
let (resp_tx, resp_rx) = oneshot::channel();
145-
let cmd = Command::Reboot(request.into_inner(), resp_tx);
146-
self.dispatcher_tx
147-
.send(cmd)
148-
.await
149-
.map_err(|e| Status::internal(format!("Failed to send command to dispatcher: {e}")))?;
150-
151-
match resp_rx.await {
152-
Ok(Ok(result)) => Ok(Response::new(result)),
153-
Ok(Err(status)) => Err(status),
154-
Err(_) => Err(Status::internal(
155-
"Dispatcher task dropped response channel.",
156-
)),
157-
}
106+
dispatch_and_wait(&self.dispatcher_tx, |resp_tx| {
107+
Command::Reboot(request.into_inner(), resp_tx)
108+
})
109+
.await
158110
}
159111

160112
async fn upgrade_feos_binary(
161113
&self,
162114
request: Request<UpgradeFeosBinaryRequest>,
163115
) -> Result<Response<UpgradeFeosBinaryResponse>, Status> {
164116
info!("HostApi: Received UpgradeFeosBinary request.");
165-
let (resp_tx, resp_rx) = oneshot::channel();
166-
let cmd = Command::UpgradeFeosBinary(request.into_inner(), resp_tx);
167-
self.dispatcher_tx
168-
.send(cmd)
169-
.await
170-
.map_err(|e| Status::internal(format!("Failed to send command to dispatcher: {e}")))?;
171-
172-
match resp_rx.await {
173-
Ok(Ok(result)) => Ok(Response::new(result)),
174-
Ok(Err(status)) => Err(status),
175-
Err(_) => Err(Status::internal(
176-
"Dispatcher task dropped response channel.",
177-
)),
178-
}
117+
dispatch_and_wait(&self.dispatcher_tx, |resp_tx| {
118+
Command::UpgradeFeosBinary(request.into_inner(), resp_tx)
119+
})
120+
.await
179121
}
180122

181123
async fn stream_kernel_logs(
@@ -213,19 +155,6 @@ impl HostService for HostApiHandler {
213155
_request: Request<GetVersionInfoRequest>,
214156
) -> Result<Response<GetVersionInfoResponse>, Status> {
215157
info!("HostApi: Received GetVersionInfo request.");
216-
let (resp_tx, resp_rx) = oneshot::channel();
217-
let cmd = Command::GetVersionInfo(resp_tx);
218-
self.dispatcher_tx
219-
.send(cmd)
220-
.await
221-
.map_err(|e| Status::internal(format!("Failed to send command to dispatcher: {e}")))?;
222-
223-
match resp_rx.await {
224-
Ok(Ok(result)) => Ok(Response::new(result)),
225-
Ok(Err(status)) => Err(status),
226-
Err(_) => Err(Status::internal(
227-
"Dispatcher task dropped response channel.",
228-
)),
229-
}
158+
dispatch_and_wait(&self.dispatcher_tx, Command::GetVersionInfo).await
230159
}
231160
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and IronCore contributors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use tonic::Status;
5+
6+
#[derive(Debug, thiserror::Error)]
7+
pub enum HostError {
8+
#[error("Failed to get system hostname")]
9+
Hostname(#[from] nix::Error),
10+
11+
#[error("Failed to read system info from {path}")]
12+
SystemInfoRead {
13+
#[source]
14+
source: std::io::Error,
15+
path: String,
16+
},
17+
18+
#[error("Host power operation failed")]
19+
PowerOperation(#[source] nix::Error),
20+
21+
#[error("Failed to create log reader: {0}")]
22+
LogReader(String),
23+
}
24+
25+
impl From<HostError> for Status {
26+
fn from(err: HostError) -> Self {
27+
log::error!("HostServiceError: {err}");
28+
match err {
29+
HostError::SystemInfoRead { path, .. } => {
30+
Status::internal(format!("Failed to read system info from {path}"))
31+
}
32+
HostError::Hostname(_) | HostError::PowerOperation(_) => {
33+
Status::internal("An internal host error occurred")
34+
}
35+
HostError::LogReader(msg) => Status::internal(msg),
36+
}
37+
}
38+
}

0 commit comments

Comments
 (0)