]> git.proxmox.com Git - proxmox-backup.git/blob - src/api2/node/services.rs
tree-wide: use 'dyn' for all trait objects
[proxmox-backup.git] / src / api2 / node / services.rs
1 use failure::*;
2
3 use crate::tools;
4 use crate::api_schema::*;
5 use crate::api_schema::router::*;
6 use serde_json::{json, Value};
7
8 use std::sync::Arc;
9 use std::process::{Command, Stdio};
10
11 use crate::api2::types::*;
12
13 static SERVICE_NAME_LIST: [&str; 7] = [
14 "proxmox-backup",
15 "proxmox-backup-proxy",
16 "sshd",
17 "syslog",
18 "cron",
19 "postfix",
20 "systemd-timesyncd",
21 ];
22
23 fn real_service_name(service: &str) -> &str {
24
25 // since postfix package 3.1.0-3.1 the postfix unit is only here
26 // to manage subinstances, of which the default is called "-".
27 // This is where we look for the daemon status
28
29 if service == "postfix" {
30 "postfix@-"
31 } else {
32 service
33 }
34 }
35
36 fn get_full_service_state(service: &str) -> Result<Value, Error> {
37
38 let real_service_name = real_service_name(service);
39
40 let mut child = Command::new("/bin/systemctl")
41 .args(&["show", real_service_name])
42 .stdout(Stdio::piped())
43 .spawn()?;
44
45 use std::io::{BufRead,BufReader};
46
47 let mut result = json!({});
48
49 if let Some(ref mut stdout) = child.stdout {
50 for line in BufReader::new(stdout).lines() {
51 match line {
52 Ok(line) => {
53 let mut iter = line.splitn(2, '=');
54 let key = iter.next();
55 let value = iter.next();
56 if let (Some(key), Some(value)) = (key, value) {
57 result[key] = Value::from(value);
58 }
59 }
60 Err(err) => {
61 log::error!("reading service config failed: {}", err);
62 let _ = child.kill();
63 break;
64 }
65 }
66 }
67 }
68
69 let status = child.wait().unwrap();
70 if !status.success() {
71 bail!("systemctl show failed with {}", status);
72 }
73
74 Ok(result)
75 }
76
77 fn json_service_state(service: &str, status: Value) -> Value {
78
79 if let Some(desc) = status["Description"].as_str() {
80 let name = status["Name"].as_str().unwrap_or(service);
81 let state = status["SubState"].as_str().unwrap_or("unknown");
82 return json!({
83 "service": service,
84 "name": name,
85 "desc": desc,
86 "state": state,
87 });
88 }
89
90 Value::Null
91 }
92
93
94 fn list_services(
95 _param: Value,
96 _info: &ApiMethod,
97 _rpcenv: &mut dyn RpcEnvironment,
98 ) -> Result<Value, Error> {
99
100 let mut list = vec![];
101
102 for service in &SERVICE_NAME_LIST {
103 match get_full_service_state(service) {
104 Ok(status) => {
105 let state = json_service_state(service, status);
106 if state != Value::Null {
107 list.push(state);
108 }
109 }
110 Err(err) => log::error!("{}", err),
111 }
112 }
113
114 Ok(Value::from(list))
115 }
116
117 fn get_service_state(
118 param: Value,
119 _info: &ApiMethod,
120 _rpcenv: &mut dyn RpcEnvironment,
121 ) -> Result<Value, Error> {
122
123 let service = tools::required_string_param(&param, "service")?;
124
125 if !SERVICE_NAME_LIST.contains(&service) {
126 bail!("unknown service name '{}'", service);
127 }
128
129 let status = get_full_service_state(service)?;
130
131 Ok(json_service_state(service, status))
132 }
133
134 fn run_service_command(service: &str, cmd: &str) -> Result<Value, Error> {
135
136 // fixme: run background worker (fork_worker) ???
137
138 match cmd {
139 "start"|"stop"|"restart"|"reload" => {},
140 _ => bail!("unknown service command '{}'", cmd),
141 }
142
143 if service == "proxmox-backup" {
144 if cmd != "restart" {
145 bail!("invalid service cmd '{} {}'", service, cmd);
146 }
147 }
148
149 let real_service_name = real_service_name(service);
150
151 let status = Command::new("/bin/systemctl")
152 .args(&[cmd, real_service_name])
153 .status()?;
154
155 if !status.success() {
156 bail!("systemctl {} failed with {}", cmd, status);
157 }
158
159 Ok(Value::Null)
160 }
161
162 fn start_service(
163 param: Value,
164 _info: &ApiMethod,
165 _rpcenv: &mut dyn RpcEnvironment,
166 ) -> Result<Value, Error> {
167
168 let service = tools::required_string_param(&param, "service")?;
169
170 log::info!("starting service {}", service);
171
172 run_service_command(service, "start")
173 }
174
175 fn stop_service(
176 param: Value,
177 _info: &ApiMethod,
178 _rpcenv: &mut dyn RpcEnvironment,
179 ) -> Result<Value, Error> {
180
181 let service = tools::required_string_param(&param, "service")?;
182
183 log::info!("stoping service {}", service);
184
185 run_service_command(service, "stop")
186 }
187
188 fn restart_service(
189 param: Value,
190 _info: &ApiMethod,
191 _rpcenv: &mut dyn RpcEnvironment,
192 ) -> Result<Value, Error> {
193
194 let service = tools::required_string_param(&param, "service")?;
195
196 log::info!("re-starting service {}", service);
197
198 if service == "proxmox-backup-proxy" {
199 // special case, avoid aborting running tasks
200 run_service_command(service, "reload")
201 } else {
202 run_service_command(service, "restart")
203 }
204 }
205
206 fn reload_service(
207 param: Value,
208 _info: &ApiMethod,
209 _rpcenv: &mut dyn RpcEnvironment,
210 ) -> Result<Value, Error> {
211
212 let service = tools::required_string_param(&param, "service")?;
213
214 log::info!("reloading service {}", service);
215
216 run_service_command(service, "reload")
217 }
218
219 pub fn router() -> Router {
220
221 let service_id_schema : Arc<Schema> = Arc::new(
222 StringSchema::new("Service ID.")
223 .max_length(256)
224 .into()
225 );
226
227 let service_api = Router::new()
228 .subdir(
229 "state",
230 Router::new()
231 .get(ApiMethod::new(
232 get_service_state,
233 ObjectSchema::new("Read service properties.")
234 .required("node", NODE_SCHEMA.clone())
235 .required("service", service_id_schema.clone()))
236 )
237 )
238 .subdir(
239 "start",
240 Router::new()
241 .post(
242 ApiMethod::new(
243 start_service,
244 ObjectSchema::new("Start service.")
245 .required("node", NODE_SCHEMA.clone())
246 .required("service", service_id_schema.clone())
247 ).protected(true)
248 )
249 )
250 .subdir(
251 "stop",
252 Router::new()
253 .post(
254 ApiMethod::new(
255 stop_service,
256 ObjectSchema::new("Stop service.")
257 .required("node", NODE_SCHEMA.clone())
258 .required("service", service_id_schema.clone())
259 ).protected(true)
260 )
261 )
262 .subdir(
263 "restart",
264 Router::new()
265 .post(
266 ApiMethod::new(
267 restart_service,
268 ObjectSchema::new("Restart service.")
269 .required("node", NODE_SCHEMA.clone())
270 .required("service", service_id_schema.clone())
271 ).protected(true)
272 )
273 )
274 .subdir(
275 "reload",
276 Router::new()
277 .post(
278 ApiMethod::new(
279 reload_service,
280 ObjectSchema::new("Reload service.")
281 .required("node", NODE_SCHEMA.clone())
282 .required("service", service_id_schema.clone())
283 ).protected(true)
284 )
285 )
286 .list_subdirs();
287
288 let route = Router::new()
289 .get(
290 ApiMethod::new(
291 list_services,
292 ObjectSchema::new("Service list.")
293 .required("node", NODE_SCHEMA.clone())
294 ).returns(
295 ArraySchema::new(
296 "Returns a list of systemd services.",
297 ObjectSchema::new("Service details.")
298 .required("service", service_id_schema.clone())
299 .required("name", StringSchema::new("systemd service name."))
300 .required("desc", StringSchema::new("systemd service description."))
301 .required("state", StringSchema::new("systemd service 'SubState'."))
302 .into()
303 )
304 )
305 )
306 .match_all("service", service_api);
307
308 route
309 }