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