]>
Commit | Line | Data |
---|---|---|
aff192e6 DM |
1 | package PVE::API2::Services; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | ||
6 | use PVE::Tools; | |
7 | use PVE::SafeSyslog; | |
8 | use PVE::Cluster; | |
9 | use PVE::INotify; | |
10 | use PVE::Exception qw(raise_param_exc); | |
11 | use PVE::RESTHandler; | |
12 | use PVE::RPCEnvironment; | |
13 | use PVE::JSONSchema qw(get_standard_option); | |
14 | use PVE::AccessControl; | |
15 | use IO::File; | |
16 | ||
17 | use base qw(PVE::RESTHandler); | |
18 | ||
50d4dc17 | 19 | my $service_name_list = [ |
b188fcd8 | 20 | 'chrony', |
50d4dc17 | 21 | 'corosync', |
b188fcd8 DC |
22 | 'cron', |
23 | 'ksmtuned', | |
24 | 'postfix', | |
25 | 'pve-cluster', | |
50d4dc17 | 26 | 'pve-firewall', |
50d4dc17 DM |
27 | 'pve-ha-crm', |
28 | 'pve-ha-lrm', | |
b188fcd8 DC |
29 | 'pvedaemon', |
30 | 'pvefw-logger', | |
31 | 'pveproxy', | |
7aa5b131 | 32 | 'pvescheduler', |
b188fcd8 DC |
33 | 'pvestatd', |
34 | 'spiceproxy', | |
50d4dc17 DM |
35 | 'sshd', |
36 | 'syslog', | |
48197df2 | 37 | 'systemd-journald', |
28b699bc | 38 | 'systemd-timesyncd', |
05d5632d | 39 | ]; |
d438bb3e TL |
40 | my $essential_services = { |
41 | pveproxy => 1, | |
42 | pvedaemon => 1, | |
43 | 'pve-cluster' => 1, | |
44 | }; | |
50d4dc17 | 45 | |
7a581660 EK |
46 | # since postfix package 3.1.0-3.1 the postfix unit is only here to |
47 | # manage subinstances, of which the default is called "-". | |
48 | # This is where we look for the daemon status | |
49 | my $unit_extra_names = { | |
50 | postfix => 'postfix@-' | |
51 | }; | |
52 | ||
50d4dc17 DM |
53 | my $get_full_service_state = sub { |
54 | my ($service) = @_; | |
7a581660 | 55 | $service = $unit_extra_names->{$service} if $unit_extra_names->{$service}; |
50d4dc17 | 56 | my $res; |
05d5632d | 57 | |
50d4dc17 DM |
58 | my $parser = sub { |
59 | my $line = shift; | |
60 | if ($line =~ m/^([^=\s]+)=(.*)$/) { | |
61 | $res->{$1} = $2; | |
62 | } | |
63 | }; | |
64 | ||
05d5632d TL |
65 | PVE::Tools::run_command(['systemctl', 'show', $service], outfunc => $parser); |
66 | ||
50d4dc17 | 67 | return $res; |
aff192e6 DM |
68 | }; |
69 | ||
50d4dc17 DM |
70 | my $static_service_list; |
71 | ||
72 | sub get_service_list { | |
73 | ||
74 | return $static_service_list if $static_service_list; | |
05d5632d | 75 | |
50d4dc17 DM |
76 | my $list = {}; |
77 | foreach my $name (@$service_name_list) { | |
5fa0c204 | 78 | my $ss = eval { $get_full_service_state->($name) }; |
50d4dc17 DM |
79 | warn $@ if $@; |
80 | next if !$ss; | |
81 | next if !defined($ss->{Description}); | |
82 | $list->{$name} = { name => $name, desc => $ss->{Description} }; | |
83 | } | |
84 | ||
85 | $static_service_list = $list; | |
05d5632d | 86 | |
50d4dc17 DM |
87 | return $static_service_list; |
88 | } | |
89 | ||
90 | ||
c661ad50 DM |
91 | my $service_prop_desc = { |
92 | description => "Service ID", | |
93 | type => 'string', | |
50d4dc17 | 94 | enum => $service_name_list, |
c661ad50 DM |
95 | }; |
96 | ||
aff192e6 DM |
97 | my $service_cmd = sub { |
98 | my ($service, $cmd) = @_; | |
99 | ||
100 | my $initd_cmd; | |
101 | ||
d438bb3e | 102 | die "unknown service command '$cmd'\n" if $cmd !~ m/^(start|stop|restart|reload|try-reload-or-restart)$/; |
aff192e6 | 103 | |
d438bb3e TL |
104 | if ($essential_services->{$service} && $cmd eq 'stop') { |
105 | die "invalid service cmd '$service $cmd': refusing to stop essential service!\n"; | |
50d4dc17 | 106 | } |
d438bb3e | 107 | |
50d4dc17 | 108 | PVE::Tools::run_command(['systemctl', $cmd, $service]); |
aff192e6 DM |
109 | }; |
110 | ||
111 | my $service_state = sub { | |
112 | my ($service) = @_; | |
113 | ||
3546270c TL |
114 | my $res = { state => 'unknown' }; |
115 | ||
116 | my $ss = eval { $get_full_service_state->($service) }; | |
50d4dc17 | 117 | if (my $err = $@) { |
3546270c | 118 | return $res; |
aff192e6 | 119 | } |
4ec19e84 TL |
120 | my $state = $ss->{SubState} || 'unknown'; |
121 | if ($state eq 'dead' && $ss->{Type} && $ss->{Type} eq 'oneshot' && $ss->{Result}) { | |
122 | $res->{state} = $ss->{Result}; | |
123 | } else { | |
124 | $res->{state} = $ss->{SubState} || 'unknown'; | |
125 | } | |
ad7dd5a9 TL |
126 | |
127 | if ($ss->{LoadState} eq 'not-found') { | |
128 | $res->{'unit-state'} = 'not-found'; # not installed | |
129 | } else { | |
130 | $res->{'unit-state'} = $ss->{UnitFileState} || 'unknown'; | |
131 | } | |
3546270c | 132 | $res->{'active-state'} = $ss->{ActiveState} || 'unknown'; |
aff192e6 | 133 | |
3546270c | 134 | return $res; |
aff192e6 DM |
135 | }; |
136 | ||
137 | __PACKAGE__->register_method ({ | |
6d9807b6 TL |
138 | name => 'index', |
139 | path => '', | |
aff192e6 DM |
140 | method => 'GET', |
141 | permissions => { | |
7d020b42 | 142 | check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]], |
aff192e6 DM |
143 | }, |
144 | description => "Service list.", | |
145 | proxyto => 'node', | |
146 | protected => 1, | |
147 | parameters => { | |
6d9807b6 | 148 | additionalProperties => 0, |
aff192e6 DM |
149 | properties => { |
150 | node => get_standard_option('pve-node'), | |
151 | }, | |
152 | }, | |
153 | returns => { | |
154 | type => 'array', | |
155 | items => { | |
156 | type => "object", | |
157 | properties => {}, | |
158 | }, | |
159 | links => [ { rel => 'child', href => "{service}" } ], | |
160 | }, | |
161 | code => sub { | |
162 | my ($param) = @_; | |
6d9807b6 | 163 | |
50d4dc17 | 164 | my $service_list = get_service_list(); |
6d9807b6 | 165 | |
3546270c TL |
166 | my $res = []; |
167 | for my $id (sort keys %{$service_list}) { | |
168 | my $state = $service_state->($id); | |
6d9807b6 | 169 | push @$res, { |
aff192e6 DM |
170 | service => $id, |
171 | name => $service_list->{$id}->{name}, | |
172 | desc => $service_list->{$id}->{desc}, | |
3546270c | 173 | %$state, |
aff192e6 DM |
174 | }; |
175 | } | |
176 | ||
177 | return $res; | |
178 | }}); | |
179 | ||
c661ad50 DM |
180 | __PACKAGE__->register_method({ |
181 | name => 'srvcmdidx', | |
6d9807b6 | 182 | path => '{service}', |
aff192e6 | 183 | method => 'GET', |
c661ad50 | 184 | description => "Directory index", |
461e4a46 DM |
185 | permissions => { |
186 | check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]], | |
187 | }, | |
c661ad50 | 188 | parameters => { |
6d9807b6 | 189 | additionalProperties => 0, |
c661ad50 DM |
190 | properties => { |
191 | node => get_standard_option('pve-node'), | |
192 | service => $service_prop_desc, | |
193 | }, | |
194 | }, | |
195 | returns => { | |
196 | type => 'array', | |
197 | items => { | |
198 | type => "object", | |
199 | properties => { | |
200 | subdir => { type => 'string' }, | |
201 | }, | |
202 | }, | |
203 | links => [ { rel => 'child', href => "{subdir}" } ], | |
204 | }, | |
205 | code => sub { | |
206 | my ($param) = @_; | |
207 | ||
208 | my $res = [ | |
209 | { subdir => 'state' }, | |
210 | { subdir => 'start' }, | |
211 | { subdir => 'stop' }, | |
212 | { subdir => 'restart' }, | |
213 | { subdir => 'reload' }, | |
214 | ]; | |
6d9807b6 | 215 | |
c661ad50 DM |
216 | return $res; |
217 | }}); | |
218 | ||
219 | __PACKAGE__->register_method ({ | |
6d9807b6 TL |
220 | name => 'service_state', |
221 | path => '{service}/state', | |
c661ad50 | 222 | method => 'GET', |
aff192e6 | 223 | permissions => { |
7d020b42 | 224 | check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]], |
aff192e6 DM |
225 | }, |
226 | description => "Read service properties", | |
227 | proxyto => 'node', | |
228 | protected => 1, | |
229 | parameters => { | |
6d9807b6 | 230 | additionalProperties => 0, |
aff192e6 DM |
231 | properties => { |
232 | node => get_standard_option('pve-node'), | |
c661ad50 | 233 | service => $service_prop_desc, |
aff192e6 DM |
234 | }, |
235 | }, | |
236 | returns => { | |
237 | type => "object", | |
238 | properties => {}, | |
239 | }, | |
240 | code => sub { | |
241 | my ($param) = @_; | |
6d9807b6 | 242 | |
3546270c TL |
243 | my $id = $param->{service}; |
244 | ||
50d4dc17 | 245 | my $service_list = get_service_list(); |
6d9807b6 | 246 | |
3546270c TL |
247 | my $si = $service_list->{$id}; |
248 | ||
249 | my $state = $service_state->($id); | |
250 | ||
aff192e6 DM |
251 | return { |
252 | service => $param->{service}, | |
253 | name => $si->{name}, | |
254 | desc => $si->{desc}, | |
3546270c | 255 | %$state, |
aff192e6 DM |
256 | }; |
257 | }}); | |
258 | ||
259 | __PACKAGE__->register_method ({ | |
6d9807b6 TL |
260 | name => 'service_start', |
261 | path => '{service}/start', | |
c661ad50 DM |
262 | method => 'POST', |
263 | description => "Start service.", | |
461e4a46 DM |
264 | permissions => { |
265 | check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]], | |
266 | }, | |
aff192e6 DM |
267 | proxyto => 'node', |
268 | protected => 1, | |
269 | parameters => { | |
6d9807b6 | 270 | additionalProperties => 0, |
aff192e6 DM |
271 | properties => { |
272 | node => get_standard_option('pve-node'), | |
c661ad50 DM |
273 | service => $service_prop_desc, |
274 | }, | |
275 | }, | |
6d9807b6 | 276 | returns => { |
c661ad50 DM |
277 | type => 'string', |
278 | }, | |
279 | code => sub { | |
280 | my ($param) = @_; | |
6d9807b6 | 281 | |
c661ad50 DM |
282 | my $rpcenv = PVE::RPCEnvironment::get(); |
283 | ||
284 | my $user = $rpcenv->get_user(); | |
285 | ||
c661ad50 DM |
286 | my $realcmd = sub { |
287 | my $upid = shift; | |
288 | ||
289 | syslog('info', "starting service $param->{service}: $upid\n"); | |
290 | ||
05d5632d | 291 | $service_cmd->($param->{service}, 'start'); |
c661ad50 DM |
292 | |
293 | }; | |
294 | ||
295 | return $rpcenv->fork_worker('srvstart', $param->{service}, $user, $realcmd); | |
296 | }}); | |
297 | ||
298 | __PACKAGE__->register_method ({ | |
6d9807b6 TL |
299 | name => 'service_stop', |
300 | path => '{service}/stop', | |
c661ad50 DM |
301 | method => 'POST', |
302 | description => "Stop service.", | |
461e4a46 DM |
303 | permissions => { |
304 | check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]], | |
305 | }, | |
c661ad50 DM |
306 | proxyto => 'node', |
307 | protected => 1, | |
308 | parameters => { | |
6d9807b6 | 309 | additionalProperties => 0, |
c661ad50 DM |
310 | properties => { |
311 | node => get_standard_option('pve-node'), | |
312 | service => $service_prop_desc, | |
313 | }, | |
314 | }, | |
6d9807b6 | 315 | returns => { |
c661ad50 DM |
316 | type => 'string', |
317 | }, | |
318 | code => sub { | |
319 | my ($param) = @_; | |
6d9807b6 | 320 | |
c661ad50 DM |
321 | my $rpcenv = PVE::RPCEnvironment::get(); |
322 | ||
323 | my $user = $rpcenv->get_user(); | |
324 | ||
c661ad50 DM |
325 | my $realcmd = sub { |
326 | my $upid = shift; | |
327 | ||
76189130 | 328 | syslog('info', "stopping service $param->{service}: $upid\n"); |
c661ad50 | 329 | |
05d5632d | 330 | $service_cmd->($param->{service}, 'stop'); |
c661ad50 DM |
331 | |
332 | }; | |
333 | ||
334 | return $rpcenv->fork_worker('srvstop', $param->{service}, $user, $realcmd); | |
335 | }}); | |
336 | ||
337 | __PACKAGE__->register_method ({ | |
05d5632d TL |
338 | name => 'service_restart', |
339 | path => '{service}/restart', | |
c661ad50 | 340 | method => 'POST', |
3cd0759a | 341 | description => "Hard restart service. Use reload if you want to reduce interruptions.", |
461e4a46 DM |
342 | permissions => { |
343 | check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]], | |
344 | }, | |
c661ad50 DM |
345 | proxyto => 'node', |
346 | protected => 1, | |
347 | parameters => { | |
05d5632d | 348 | additionalProperties => 0, |
c661ad50 DM |
349 | properties => { |
350 | node => get_standard_option('pve-node'), | |
351 | service => $service_prop_desc, | |
aff192e6 DM |
352 | }, |
353 | }, | |
6d9807b6 | 354 | returns => { |
c661ad50 DM |
355 | type => 'string', |
356 | }, | |
aff192e6 DM |
357 | code => sub { |
358 | my ($param) = @_; | |
c661ad50 | 359 | |
05d5632d | 360 | my $rpcenv = PVE::RPCEnvironment::get(); |
c661ad50 DM |
361 | my $user = $rpcenv->get_user(); |
362 | ||
c661ad50 DM |
363 | my $realcmd = sub { |
364 | my $upid = shift; | |
c661ad50 DM |
365 | syslog('info', "re-starting service $param->{service}: $upid\n"); |
366 | ||
05d5632d | 367 | $service_cmd->($param->{service}, 'restart'); |
c661ad50 DM |
368 | }; |
369 | ||
370 | return $rpcenv->fork_worker('srvrestart', $param->{service}, $user, $realcmd); | |
371 | }}); | |
372 | ||
373 | __PACKAGE__->register_method ({ | |
05d5632d TL |
374 | name => 'service_reload', |
375 | path => '{service}/reload', | |
c661ad50 | 376 | method => 'POST', |
3cd0759a | 377 | description => "Reload service. Falls back to restart if service cannot be reloaded.", |
461e4a46 DM |
378 | permissions => { |
379 | check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]], | |
380 | }, | |
c661ad50 DM |
381 | proxyto => 'node', |
382 | protected => 1, | |
383 | parameters => { | |
05d5632d | 384 | additionalProperties => 0, |
c661ad50 DM |
385 | properties => { |
386 | node => get_standard_option('pve-node'), | |
387 | service => $service_prop_desc, | |
388 | }, | |
389 | }, | |
6d9807b6 | 390 | returns => { |
c661ad50 DM |
391 | type => 'string', |
392 | }, | |
393 | code => sub { | |
394 | my ($param) = @_; | |
c661ad50 | 395 | |
05d5632d | 396 | my $rpcenv = PVE::RPCEnvironment::get(); |
c661ad50 DM |
397 | my $user = $rpcenv->get_user(); |
398 | ||
c661ad50 DM |
399 | my $realcmd = sub { |
400 | my $upid = shift; | |
c661ad50 DM |
401 | syslog('info', "reloading service $param->{service}: $upid\n"); |
402 | ||
3cd0759a | 403 | $service_cmd->($param->{service}, 'try-reload-or-restart'); |
c661ad50 DM |
404 | |
405 | }; | |
406 | ||
407 | return $rpcenv->fork_worker('srvreload', $param->{service}, $user, $realcmd); | |
aff192e6 | 408 | }}); |