]> git.proxmox.com Git - pve-manager.git/blob - PVE/API2/Replication.pm
bump version to 5.4-15
[pve-manager.git] / PVE / API2 / Replication.pm
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;
8 use PVE::ProcFSTools;
9 use PVE::ReplicationConfig;
10 use PVE::ReplicationState;
11 use PVE::Replication;
12 use PVE::QemuConfig;
13 use PVE::QemuServer;
14 use PVE::LXC::Config;
15 use PVE::LXC;
16
17 use PVE::RESTHandler;
18
19 use base qw(PVE::RESTHandler);
20
21 our $pvesr_lock_path = "/var/lock/pvesr.lck";
22
23 our $lookup_guest_class = sub {
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
63 my $vmtype = $vms->{ids}->{$vmid}->{type};
64
65 my $guest_class = $lookup_guest_class->($vmtype);
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
73 # passing $now and $verbose is useful for regression testing
74 sub run_jobs {
75 my ($now, $logfunc, $verbose, $mail) = @_;
76
77 my $iteration = $now // time();
78
79 my $code = sub {
80 my $start_time = $now // time();
81
82 PVE::ReplicationState::purge_old_states();
83
84 while (my $jobcfg = PVE::ReplicationState::get_next_job($iteration, $start_time)) {
85 my $guest_class = $lookup_guest_class->($jobcfg->{vmtype});
86
87 eval {
88 PVE::Replication::run_replication($guest_class, $jobcfg, $iteration, $start_time, $logfunc, $verbose);
89 };
90 if (my $err = $@) {
91 warn "$jobcfg->{id}: got unexpected replication job error - $err";
92 my $state = PVE::ReplicationState::read_state();
93 my $jobstate = PVE::ReplicationState::extract_job_state($state, $jobcfg);
94 eval {
95 PVE::Tools::sendmail('root', "Replication Job: $jobcfg->{id} failed", $err)
96 if $jobstate->{fail_count} == 1 && $mail;
97 };
98 warn ": $@" if $@;
99 }
100
101 $start_time = $now // time();
102 }
103 };
104
105 my $res = PVE::Tools::lock_file($pvesr_lock_path, 60, $code);
106 die $@ if $@;
107 }
108
109 my $extract_job_status = sub {
110 my ($jobcfg, $jobid) = @_;
111
112 # Note: we modify $jobcfg
113 my $state = delete $jobcfg->{state};
114 my $data = $jobcfg;
115
116 $data->{id} = $jobid;
117
118 foreach my $k (qw(last_sync last_try fail_count error duration)) {
119 $data->{$k} = $state->{$k} if defined($state->{$k});
120 }
121
122 if ($state->{pid} && $state->{ptime}) {
123 if (PVE::ProcFSTools::check_process_running($state->{pid}, $state->{ptime})) {
124 $data->{pid} = $state->{pid};
125 }
126 }
127
128 return $data;
129 };
130
131 __PACKAGE__->register_method ({
132 name => 'status',
133 path => '',
134 method => 'GET',
135 description => "List status of all replication jobs on this node.",
136 permissions => {
137 description => "Requires the VM.Audit permission on /vms/<vmid>.",
138 user => 'all',
139 },
140 protected => 1,
141 proxyto => 'node',
142 parameters => {
143 additionalProperties => 0,
144 properties => {
145 node => get_standard_option('pve-node'),
146 guest => get_standard_option('pve-vmid', {
147 optional => 1,
148 description => "Only list replication jobs for this guest.",
149 }),
150 },
151 },
152 returns => {
153 type => 'array',
154 items => {
155 type => "object",
156 properties => {
157 id => { type => 'string' },
158 },
159 },
160 links => [ { rel => 'child', href => "{id}" } ],
161 },
162 code => sub {
163 my ($param) = @_;
164
165 my $rpcenv = PVE::RPCEnvironment::get();
166 my $authuser = $rpcenv->get_user();
167
168 my $jobs = PVE::ReplicationState::job_status(1);
169
170 my $res = [];
171 foreach my $id (sort keys %$jobs) {
172 my $data = $extract_job_status->($jobs->{$id}, $id);
173 my $guest = $data->{guest};
174 next if defined($param->{guest}) && $guest != $param->{guest};
175 next if !$rpcenv->check($authuser, "/vms/$guest", [ 'VM.Audit' ]);
176 push @$res, $data;
177 }
178
179 return $res;
180 }});
181
182 __PACKAGE__->register_method ({
183 name => 'index',
184 path => '{id}',
185 method => 'GET',
186 permissions => { user => 'all' },
187 description => "Directory index.",
188 parameters => {
189 additionalProperties => 0,
190 properties => {
191 id => get_standard_option('pve-replication-id'),
192 node => get_standard_option('pve-node'),
193 },
194 },
195 returns => {
196 type => 'array',
197 items => {
198 type => "object",
199 properties => {},
200 },
201 links => [ { rel => 'child', href => "{name}" } ],
202 },
203 code => sub {
204 my ($param) = @_;
205
206 return [
207 { name => 'schedule_now' },
208 { name => 'log' },
209 { name => 'status' },
210 ];
211 }});
212
213
214 __PACKAGE__->register_method ({
215 name => 'job_status',
216 path => '{id}/status',
217 method => 'GET',
218 description => "Get replication job status.",
219 permissions => {
220 description => "Requires the VM.Audit permission on /vms/<vmid>.",
221 user => 'all',
222 },
223 protected => 1,
224 proxyto => 'node',
225 parameters => {
226 additionalProperties => 0,
227 properties => {
228 id => get_standard_option('pve-replication-id'),
229 node => get_standard_option('pve-node'),
230 },
231 },
232 returns => {
233 type => "object",
234 properties => {},
235 },
236 code => sub {
237 my ($param) = @_;
238
239 my $rpcenv = PVE::RPCEnvironment::get();
240 my $authuser = $rpcenv->get_user();
241
242 my $jobs = PVE::ReplicationState::job_status();
243 my $jobid = $param->{id};
244 my $jobcfg = $jobs->{$jobid};
245
246 die "no such replication job '$jobid'\n" if !defined($jobcfg);
247
248 my $data = $extract_job_status->($jobcfg, $jobid);
249 my $guest = $data->{guest};
250
251 raise_perm_exc() if !$rpcenv->check($authuser, "/vms/$guest", [ 'VM.Audit' ]);
252
253 return $data;
254 }});
255
256 __PACKAGE__->register_method({
257 name => 'read_job_log',
258 path => '{id}/log',
259 method => 'GET',
260 permissions => {
261 description => "Requires the VM.Audit permission on /vms/<vmid>, or 'Sys.Audit' on '/nodes/<node>'",
262 user => 'all',
263 },
264 protected => 1,
265 description => "Read replication job log.",
266 proxyto => 'node',
267 parameters => {
268 additionalProperties => 0,
269 properties => {
270 id => get_standard_option('pve-replication-id'),
271 node => get_standard_option('pve-node'),
272 start => {
273 type => 'integer',
274 minimum => 0,
275 optional => 1,
276 },
277 limit => {
278 type => 'integer',
279 minimum => 0,
280 optional => 1,
281 },
282 },
283 },
284 returns => {
285 type => 'array',
286 items => {
287 type => "object",
288 properties => {
289 n => {
290 description=> "Line number",
291 type=> 'integer',
292 },
293 t => {
294 description=> "Line text",
295 type => 'string',
296 }
297 }
298 }
299 },
300 code => sub {
301 my ($param) = @_;
302
303 my $rpcenv = PVE::RPCEnvironment::get();
304 my $authuser = $rpcenv->get_user();
305
306 my $jobid = $param->{id};
307 my $filename = PVE::ReplicationState::job_logfile_name($jobid);
308
309 my $cfg = PVE::ReplicationConfig->new();
310 my $data = $cfg->{ids}->{$jobid};
311
312 die "no such replication job '$jobid'\n" if !defined($data);
313
314 my $node = $param->{node};
315
316 my $vmid = $data->{guest};
317 raise_perm_exc() if (!($rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ]) ||
318 $rpcenv->check($authuser, "/nodes/$node", [ 'Sys.Audit' ])));
319
320 my ($count, $lines) = PVE::Tools::dump_logfile($filename, $param->{start}, $param->{limit});
321
322 $rpcenv->set_result_attrib('total', $count);
323
324 return $lines;
325 }});
326
327 __PACKAGE__->register_method ({
328 name => 'schedule_now',
329 path => '{id}/schedule_now',
330 method => 'POST',
331 description => "Schedule replication job to start as soon as possible.",
332 proxyto => 'node',
333 protected => 1,
334 permissions => {
335 check => ['perm', '/storage', ['Datastore.Allocate']],
336 },
337 parameters => {
338 additionalProperties => 0,
339 properties => {
340 id => get_standard_option('pve-replication-id'),
341 node => get_standard_option('pve-node'),
342 },
343 },
344 returns => {
345 type => 'string',
346 },
347 code => sub {
348 my ($param) = @_;
349
350 my $jobid = $param->{id};
351
352 my $cfg = PVE::ReplicationConfig->new();
353 my $jobcfg = $cfg->{ids}->{$jobid};
354
355 die "no such replication job '$jobid'\n" if !defined($jobcfg);
356
357 PVE::ReplicationState::schedule_job_now($jobcfg);
358
359 }});
360
361 1;