]>
Commit | Line | Data |
---|---|---|
892821fd DM |
1 | package PVE::API2::Replication; |
2 | ||
3 | use warnings; | |
4 | use strict; | |
5 | ||
6 | use PVE::JSONSchema qw(get_standard_option); | |
7 | use PVE::RPCEnvironment; | |
483f89dd | 8 | use PVE::ProcFSTools; |
892821fd | 9 | use PVE::ReplicationConfig; |
d092dc4f | 10 | use PVE::ReplicationState; |
892821fd | 11 | use PVE::Replication; |
810c6776 DM |
12 | use PVE::QemuConfig; |
13 | use PVE::QemuServer; | |
14 | use PVE::LXC::Config; | |
15 | use PVE::LXC; | |
892821fd DM |
16 | |
17 | use PVE::RESTHandler; | |
18 | ||
19 | use base qw(PVE::RESTHandler); | |
20 | ||
5b358450 | 21 | our $pvesr_lock_path = "/var/lock/pvesr.lck"; |
810c6776 | 22 | |
25420507 | 23 | our $lookup_guest_class = sub { |
810c6776 DM |
24 | my ($vmtype) = @_; |
25 | ||
26 | if ($vmtype eq 'qemu') { | |
27 | return 'PVE::QemuConfig'; | |
28 | } elsif ($vmtype eq 'lxc') { | |
29 | return 'PVE::LXC::Config'; | |
30 | } else { | |
31 | die "unknown guest type '$vmtype' - internal error"; | |
32 | } | |
33 | }; | |
34 | ||
35 | # passing $now is useful for regression testing | |
36 | sub run_single_job { | |
37 | my ($jobid, $now, $logfunc) = @_; | |
38 | ||
39 | my $local_node = PVE::INotify::nodename(); | |
40 | ||
41 | my $code = sub { | |
42 | $now //= time(); | |
43 | ||
44 | my $cfg = PVE::ReplicationConfig->new(); | |
45 | ||
46 | my $jobcfg = $cfg->{ids}->{$jobid}; | |
47 | die "no such job '$jobid'\n" if !$jobcfg; | |
48 | ||
49 | die "internal error - not implemented" if $jobcfg->{type} ne 'local'; | |
50 | ||
51 | die "job '$jobid' is disabled\n" if $jobcfg->{disable}; | |
52 | ||
53 | my $vms = PVE::Cluster::get_vmlist(); | |
54 | my $vmid = $jobcfg->{guest}; | |
55 | ||
56 | die "no such guest '$vmid'\n" if !$vms->{ids}->{$vmid}; | |
57 | ||
58 | die "guest '$vmid' is not on local node\n" | |
59 | if $vms->{ids}->{$vmid}->{node} ne $local_node; | |
60 | ||
61 | die "unable to sync to local node\n" if $jobcfg->{target} eq $local_node; | |
62 | ||
a4beaa94 | 63 | my $vmtype = $vms->{ids}->{$vmid}->{type}; |
810c6776 | 64 | |
a4beaa94 | 65 | my $guest_class = $lookup_guest_class->($vmtype); |
810c6776 DM |
66 | PVE::Replication::run_replication($guest_class, $jobcfg, $now, $now, $logfunc); |
67 | }; | |
68 | ||
69 | my $res = PVE::Tools::lock_file($pvesr_lock_path, 60, $code); | |
70 | die $@ if $@; | |
71 | } | |
72 | ||
2aa02957 | 73 | # passing $now and $verbose is useful for regression testing |
810c6776 | 74 | sub run_jobs { |
2aa02957 | 75 | my ($now, $logfunc, $verbose) = @_; |
810c6776 DM |
76 | |
77 | my $iteration = $now // time(); | |
78 | ||
79 | my $code = sub { | |
c8742096 | 80 | my $start_time = $now // time(); |
810c6776 | 81 | |
c8742096 | 82 | PVE::ReplicationState::purge_old_states(); |
5a26b006 | 83 | |
c8742096 WL |
84 | while (my $jobcfg = PVE::ReplicationState::get_next_job($iteration, $start_time)) { |
85 | my $guest_class = $lookup_guest_class->($jobcfg->{vmtype}); | |
86 | PVE::Replication::run_replication($guest_class, $jobcfg, $iteration, $start_time, $logfunc, 1, $verbose); | |
87 | $start_time = $now // time(); | |
88 | } | |
810c6776 DM |
89 | }; |
90 | ||
91 | my $res = PVE::Tools::lock_file($pvesr_lock_path, 60, $code); | |
92 | die $@ if $@; | |
93 | } | |
94 | ||
fc527b4d DM |
95 | my $extract_job_status = sub { |
96 | my ($jobcfg, $jobid) = @_; | |
97 | ||
98 | # Note: we modify $jobcfg | |
99 | my $state = delete $jobcfg->{state}; | |
100 | my $data = $jobcfg; | |
101 | ||
102 | $data->{id} = $jobid; | |
103 | ||
104 | foreach my $k (qw(last_sync last_try fail_count error duration)) { | |
105 | $data->{$k} = $state->{$k} if defined($state->{$k}); | |
106 | } | |
107 | ||
108 | if ($state->{pid} && $state->{ptime}) { | |
109 | if (PVE::ProcFSTools::check_process_running($state->{pid}, $state->{ptime})) { | |
110 | $data->{pid} = $state->{pid}; | |
111 | } | |
112 | } | |
113 | ||
114 | return $data; | |
115 | }; | |
116 | ||
892821fd | 117 | __PACKAGE__->register_method ({ |
fc527b4d | 118 | name => 'status', |
892821fd DM |
119 | path => '', |
120 | method => 'GET', | |
fc527b4d DM |
121 | description => "List status of all replication jobs on this node.", |
122 | permissions => { | |
123 | description => "Requires the VM.Audit permission on /vms/<vmid>.", | |
124 | user => 'all', | |
125 | }, | |
126 | protected => 1, | |
127 | proxyto => 'node', | |
128 | parameters => { | |
129 | additionalProperties => 0, | |
130 | properties => { | |
131 | node => get_standard_option('pve-node'), | |
132 | guest => get_standard_option('pve-vmid', { | |
133 | optional => 1, | |
134 | description => "Only list replication jobs for this guest.", | |
135 | }), | |
136 | }, | |
137 | }, | |
138 | returns => { | |
139 | type => 'array', | |
140 | items => { | |
141 | type => "object", | |
142 | properties => { | |
143 | id => { type => 'string' }, | |
144 | }, | |
145 | }, | |
146 | links => [ { rel => 'child', href => "{id}" } ], | |
147 | }, | |
148 | code => sub { | |
149 | my ($param) = @_; | |
150 | ||
151 | my $rpcenv = PVE::RPCEnvironment::get(); | |
152 | my $authuser = $rpcenv->get_user(); | |
153 | ||
959f37af | 154 | my $jobs = PVE::ReplicationState::job_status(1); |
fc527b4d DM |
155 | |
156 | my $res = []; | |
157 | foreach my $id (sort keys %$jobs) { | |
158 | my $data = $extract_job_status->($jobs->{$id}, $id); | |
159 | my $guest = $data->{guest}; | |
160 | next if defined($param->{guest}) && $guest != $param->{guest}; | |
161 | next if !$rpcenv->check($authuser, "/vms/$guest", [ 'VM.Audit' ]); | |
162 | push @$res, $data; | |
163 | } | |
164 | ||
165 | return $res; | |
166 | }}); | |
167 | ||
168 | __PACKAGE__->register_method ({ | |
169 | name => 'index', | |
170 | path => '{id}', | |
171 | method => 'GET', | |
892821fd DM |
172 | permissions => { user => 'all' }, |
173 | description => "Directory index.", | |
174 | parameters => { | |
175 | additionalProperties => 0, | |
176 | properties => { | |
fc527b4d | 177 | id => get_standard_option('pve-replication-id'), |
892821fd DM |
178 | node => get_standard_option('pve-node'), |
179 | }, | |
180 | }, | |
181 | returns => { | |
182 | type => 'array', | |
183 | items => { | |
184 | type => "object", | |
185 | properties => {}, | |
186 | }, | |
187 | links => [ { rel => 'child', href => "{name}" } ], | |
188 | }, | |
189 | code => sub { | |
190 | my ($param) = @_; | |
191 | ||
192 | return [ | |
88ea8e67 | 193 | { name => 'schedule_now' }, |
fc527b4d | 194 | { name => 'log' }, |
892821fd | 195 | { name => 'status' }, |
fc527b4d | 196 | ]; |
892821fd DM |
197 | }}); |
198 | ||
199 | ||
200 | __PACKAGE__->register_method ({ | |
fc527b4d DM |
201 | name => 'job_status', |
202 | path => '{id}/status', | |
892821fd | 203 | method => 'GET', |
fc527b4d | 204 | description => "Get replication job status.", |
892821fd DM |
205 | permissions => { |
206 | description => "Requires the VM.Audit permission on /vms/<vmid>.", | |
207 | user => 'all', | |
208 | }, | |
209 | protected => 1, | |
210 | proxyto => 'node', | |
211 | parameters => { | |
212 | additionalProperties => 0, | |
213 | properties => { | |
fc527b4d | 214 | id => get_standard_option('pve-replication-id'), |
892821fd DM |
215 | node => get_standard_option('pve-node'), |
216 | }, | |
217 | }, | |
218 | returns => { | |
fc527b4d DM |
219 | type => "object", |
220 | properties => {}, | |
892821fd DM |
221 | }, |
222 | code => sub { | |
223 | my ($param) = @_; | |
224 | ||
225 | my $rpcenv = PVE::RPCEnvironment::get(); | |
226 | my $authuser = $rpcenv->get_user(); | |
227 | ||
d092dc4f | 228 | my $jobs = PVE::ReplicationState::job_status(); |
fc527b4d DM |
229 | my $jobid = $param->{id}; |
230 | my $jobcfg = $jobs->{$jobid}; | |
892821fd | 231 | |
fc527b4d DM |
232 | die "no such replication job '$jobid'\n" if !defined($jobcfg); |
233 | ||
234 | my $data = $extract_job_status->($jobcfg, $jobid); | |
235 | my $guest = $data->{guest}; | |
236 | ||
237 | raise_perm_exc() if !$rpcenv->check($authuser, "/vms/$guest", [ 'VM.Audit' ]); | |
238 | ||
239 | return $data; | |
240 | }}); | |
241 | ||
242 | __PACKAGE__->register_method({ | |
243 | name => 'read_job_log', | |
244 | path => '{id}/log', | |
245 | method => 'GET', | |
246 | permissions => { | |
247 | description => "Requires the VM.Audit permission on /vms/<vmid>, or 'Sys.Audit' on '/nodes/<node>'", | |
248 | user => 'all', | |
249 | }, | |
250 | protected => 1, | |
251 | description => "Read replication job log.", | |
252 | proxyto => 'node', | |
253 | parameters => { | |
254 | additionalProperties => 0, | |
255 | properties => { | |
256 | id => get_standard_option('pve-replication-id'), | |
257 | node => get_standard_option('pve-node'), | |
258 | start => { | |
259 | type => 'integer', | |
260 | minimum => 0, | |
261 | optional => 1, | |
262 | }, | |
263 | limit => { | |
264 | type => 'integer', | |
265 | minimum => 0, | |
266 | optional => 1, | |
267 | }, | |
268 | }, | |
269 | }, | |
270 | returns => { | |
271 | type => 'array', | |
272 | items => { | |
273 | type => "object", | |
274 | properties => { | |
275 | n => { | |
276 | description=> "Line number", | |
277 | type=> 'integer', | |
278 | }, | |
279 | t => { | |
280 | description=> "Line text", | |
281 | type => 'string', | |
483f89dd DM |
282 | } |
283 | } | |
892821fd | 284 | } |
fc527b4d DM |
285 | }, |
286 | code => sub { | |
287 | my ($param) = @_; | |
892821fd | 288 | |
fc527b4d DM |
289 | my $rpcenv = PVE::RPCEnvironment::get(); |
290 | my $authuser = $rpcenv->get_user(); | |
291 | ||
292 | my $jobid = $param->{id}; | |
d09c076a | 293 | my $filename = PVE::ReplicationState::job_logfile_name($jobid); |
fc527b4d DM |
294 | |
295 | my $cfg = PVE::ReplicationConfig->new(); | |
296 | my $data = $cfg->{ids}->{$jobid}; | |
297 | ||
298 | die "no such replication job '$jobid'\n" if !defined($data); | |
299 | ||
300 | my $node = $param->{node}; | |
301 | ||
302 | my $vmid = $data->{guest}; | |
303 | raise_perm_exc() if (!($rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ]) || | |
304 | $rpcenv->check($authuser, "/nodes/$node", [ 'Sys.Audit' ]))); | |
305 | ||
306 | my ($count, $lines) = PVE::Tools::dump_logfile($filename, $param->{start}, $param->{limit}); | |
307 | ||
308 | $rpcenv->set_result_attrib('total', $count); | |
309 | ||
310 | return $lines; | |
892821fd DM |
311 | }}); |
312 | ||
88ea8e67 DM |
313 | __PACKAGE__->register_method ({ |
314 | name => 'schedule_now', | |
315 | path => '{id}/schedule_now', | |
316 | method => 'POST', | |
317 | description => "Schedule replication job to start as soon as possible.", | |
318 | proxyto => 'node', | |
319 | protected => 1, | |
320 | permissions => { | |
321 | check => ['perm', '/storage', ['Datastore.Allocate']], | |
322 | }, | |
323 | parameters => { | |
324 | additionalProperties => 0, | |
325 | properties => { | |
326 | id => get_standard_option('pve-replication-id'), | |
327 | node => get_standard_option('pve-node'), | |
328 | }, | |
329 | }, | |
330 | returns => { | |
331 | type => 'string', | |
332 | }, | |
333 | code => sub { | |
334 | my ($param) = @_; | |
335 | ||
336 | my $jobid = $param->{id}; | |
337 | ||
338 | my $cfg = PVE::ReplicationConfig->new(); | |
339 | my $jobcfg = $cfg->{ids}->{$jobid}; | |
340 | ||
341 | die "no such replication job '$jobid'\n" if !defined($jobcfg); | |
342 | ||
343 | PVE::ReplicationState::schedule_job_now($jobcfg); | |
344 | ||
345 | }}); | |
346 | ||
892821fd | 347 | 1; |