]> git.proxmox.com Git - proxmox-backup.git/blame - src/api2/node/services.rs
rename src/api to src/api_schema
[proxmox-backup.git] / src / api2 / node / services.rs
CommitLineData
d2ab5f19
DM
1use failure::*;
2
3use crate::tools;
dc9a007b
DM
4use crate::api_schema::schema::*;
5use crate::api_schema::router::*;
d2ab5f19
DM
6use serde_json::{json, Value};
7
8use std::sync::Arc;
d2ab5f19
DM
9use std::process::{Command, Stdio};
10
11static SERVICE_NAME_LIST: [&str; 6] = [
12 "proxmox-backup",
13 "sshd",
14 "syslog",
15 "cron",
16 "postfix",
17 "systemd-timesyncd",
18];
19
48849593 20fn real_service_name(service: &str) -> &str {
d2ab5f19
DM
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
48849593
DM
26 if service == "postfix" {
27 "postfix@-"
28 } else {
29 service
30 }
31}
32
33fn get_full_service_state(service: &str) -> Result<Value, Error> {
34
35 let real_service_name = real_service_name(service);
d2ab5f19
DM
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
48849593
DM
74fn 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
d2ab5f19 91fn list_services(
9f49fe1d 92 _param: Value,
d2ab5f19 93 _info: &ApiMethod,
9f49fe1d 94 _rpcenv: &mut RpcEnvironment,
d2ab5f19
DM
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) => {
48849593
DM
102 let state = json_service_state(service, status);
103 if state != Value::Null {
104 list.push(state);
d2ab5f19
DM
105 }
106 }
107 Err(err) => log::error!("{}", err),
108 }
109 }
110
111 Ok(Value::from(list))
112}
113
48849593
DM
114fn get_service_state(
115 param: Value,
116 _info: &ApiMethod,
9f49fe1d 117 _rpcenv: &mut RpcEnvironment,
48849593
DM
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
131fn 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
159fn start_service(
160 param: Value,
161 _info: &ApiMethod,
9f49fe1d 162 _rpcenv: &mut RpcEnvironment,
48849593
DM
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
172fn stop_service(
173 param: Value,
174 _info: &ApiMethod,
9f49fe1d 175 _rpcenv: &mut RpcEnvironment,
48849593
DM
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
185fn restart_service(
186 param: Value,
187 _info: &ApiMethod,
9f49fe1d 188 _rpcenv: &mut RpcEnvironment,
48849593
DM
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
198fn reload_service(
199 param: Value,
200 _info: &ApiMethod,
9f49fe1d 201 _rpcenv: &mut RpcEnvironment,
48849593
DM
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
d2ab5f19
DM
211pub fn router() -> Router {
212
48849593
DM
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()
a859fa95
DM
243 .post(
244 ApiMethod::new(
245 start_service,
246 ObjectSchema::new("Start service.")
247 .required("service", service_id_schema.clone())
248 ).protected(true)
48849593
DM
249 )
250 )
251 .subdir(
252 "stop",
253 Router::new()
a859fa95
DM
254 .post(
255 ApiMethod::new(
256 stop_service,
257 ObjectSchema::new("Stop service.")
258 .required("service", service_id_schema.clone())
259 ).protected(true)
48849593
DM
260 )
261 )
262 .subdir(
263 "restart",
264 Router::new()
a859fa95
DM
265 .post(
266 ApiMethod::new(
267 restart_service,
268 ObjectSchema::new("Restart service.")
269 .required("service", service_id_schema.clone())
270 ).protected(true)
48849593
DM
271 )
272 )
273 .subdir(
274 "reload",
275 Router::new()
a859fa95
DM
276 .post(
277 ApiMethod::new(
278 reload_service,
279 ObjectSchema::new("Reload service.")
280 .required("service", service_id_schema.clone())
281 ).protected(true)
48849593
DM
282 )
283 )
284 ;
285
d2ab5f19
DM
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.")
48849593 295 .required("service", service_id_schema.clone())
d2ab5f19
DM
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 )
48849593
DM
302 )
303 .match_all("service", service_api);
d2ab5f19
DM
304
305 route
306}