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