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