]> git.proxmox.com Git - pve-manager-legacy.git/blame - PVE/CLI/pvesr.pm
PVE::API2::ReplicationConfig - implement delete
[pve-manager-legacy.git] / PVE / CLI / pvesr.pm
CommitLineData
0880c088
DM
1package PVE::CLI::pvesr;
2
3use strict;
4use warnings;
5use POSIX qw(strftime);
6use JSON;
7
8use PVE::JSONSchema qw(get_standard_option);
9use PVE::INotify;
10use PVE::RPCEnvironment;
11use PVE::Tools qw(extract_param);
12use PVE::SafeSyslog;
13use PVE::CLIHandler;
14
be9adcf2 15use PVE::Cluster;
0880c088
DM
16use PVE::Replication;
17use PVE::API2::ReplicationConfig;
18use PVE::API2::Replication;
19
20use base qw(PVE::CLIHandler);
21
22my $nodename = PVE::INotify::nodename();
23
24sub setup_environment {
25 PVE::RPCEnvironment->setup_default_cli_env();
26}
27
be9adcf2
DM
28__PACKAGE__->register_method ({
29 name => 'prepare_local_job',
30 path => 'prepare_local_job',
31 method => 'POST',
32 description => "Prepare for starting a replication job. This is called on the target node before replication starts. This call is for internal use, and return a JSON object on stdout. The method first test if VM <vmid> reside on the local node. If so, stop immediately. After that the method scans all volume IDs for snapshots, and removes all replications snapshots with timestamps different than <last_sync>. It also removes any unused volumes. Returns a hash with boolean markers for all volumes with existing replication snapshots.",
33 parameters => {
34 additionalProperties => 0,
35 properties => {
36 id => get_standard_option('pve-replication-id'),
37 vmid => get_standard_option('pve-vmid', { completion => \&PVE::Cluster::complete_vmid }),
38 'extra-args' => get_standard_option('extra-args', {
39 description => "The list of volume IDs to consider." }),
20698b56
DM
40 force => {
41 description => "Allow to remove all existion volumes (empty volume list).",
42 type => 'boolean',
43 optional => 1,
44 default => 0,
45 },
be9adcf2
DM
46 last_sync => {
47 description => "Time (UNIX epoch) of last successful sync. If not specified, all replication snapshots get removed.",
48 type => 'integer',
49 minimum => 0,
50 optional => 1,
51 },
52 },
53 },
54 returns => { type => 'null' },
55 code => sub {
56 my ($param) = @_;
57
58 my $jobid = $param->{id};
59 my $vmid = $param->{vmid};
60 my $last_sync = $param->{last_sync} // 0;
61
62 my $local_node = PVE::INotify::nodename();
63
64 my $vms = PVE::Cluster::get_vmlist();
65 die "guest '$vmid' is on local node\n"
66 if $vms->{ids}->{$vmid} && $vms->{ids}->{$vmid}->{node} eq $local_node;
67
68 my $storecfg = PVE::Storage::config();
69
70 my $dl = PVE::Storage::vdisk_list($storecfg, undef, $vmid);
71
72 my $volids = [];
73
20698b56
DM
74 die "no volumes specified\n"
75 if !$param->{force} && !scalar(@{$param->{'extra-args'}});
be9adcf2
DM
76
77 foreach my $volid (@{$param->{'extra-args'}}) {
78
79 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid);
80 my $scfg = PVE::Storage::storage_check_enabled($storecfg, $storeid, $local_node);
81 die "storage '$storeid' is a shared storage\n" if $scfg->{shared};
82
83 my ($vtype, undef, $ownervm) = PVE::Storage::parse_volname($storecfg, $volid);
84 die "volume '$volid' has wrong vtype ($vtype != 'images')\n"
85 if $vtype ne 'images';
86 die "volume '$volid' has wrong owner\n"
87 if !$ownervm || $vmid != $ownervm;
88
89 my $found = 0;
90 foreach my $info (@{$dl->{$storeid}}) {
91 if ($info->{volid} eq $volid) {
92 $found = 1;
93 last;
94 }
95 }
96
97 push @$volids, $volid if $found;
98 }
99
100 $volids = [ sort @$volids ];
101
102 my $logfunc = sub {
103 my ($start_time, $msg) = @_;
104 print STDERR "$msg\n";
105 };
106
107 # remove stale volumes
108 foreach my $storeid (keys %$dl) {
109 my $scfg = PVE::Storage::storage_check_enabled($storecfg, $storeid, $local_node, 1);
110 next if !$scfg || $scfg->{shared};
111 foreach my $info (@{$dl->{$storeid}}) {
112 my $volid = $info->{volid};
113 next if grep { $_ eq $volid } @$volids;
114 $logfunc->(undef, "$jobid: delete stale volume '$volid'");
115 PVE::Storage::vdisk_free($storecfg, $volid);
116 }
117 }
118
119 my $last_snapshots = PVE::Replication::prepare(
120 $storecfg, $volids, $jobid, $last_sync, undef, $logfunc);
121
122 print to_json($last_snapshots) . "\n";
123
124 return undef;
125 }});
126
c7fa37c0
DM
127__PACKAGE__->register_method ({
128 name => 'finalize_local_job',
129 path => 'finalize_local_job',
130 method => 'POST',
131 description => "Finalize a replication job. This removes all replications snapshots with timestamps different than <last_sync>.",
132 parameters => {
133 additionalProperties => 0,
134 properties => {
135 id => get_standard_option('pve-replication-id'),
136 vmid => get_standard_option('pve-vmid', { completion => \&PVE::Cluster::complete_vmid }),
137 'extra-args' => get_standard_option('extra-args', {
138 description => "The list of volume IDs to consider." }),
139 last_sync => {
140 description => "Time (UNIX epoch) of last successful sync. If not specified, all replication snapshots gets removed.",
141 type => 'integer',
142 minimum => 0,
143 optional => 1,
144 },
145 },
146 },
147 returns => { type => 'null' },
148 code => sub {
149 my ($param) = @_;
150
151 my $jobid = $param->{id};
152 my $vmid = $param->{vmid};
153 my $last_sync = $param->{last_sync} // 0;
154
155 my $local_node = PVE::INotify::nodename();
156
157 my $vms = PVE::Cluster::get_vmlist();
158 die "guest '$vmid' is on local node\n"
159 if $vms->{ids}->{$vmid} && $vms->{ids}->{$vmid}->{node} eq $local_node;
160
161 my $storecfg = PVE::Storage::config();
162
163 my $volids = [];
164
165 die "no volumes specified\n" if !scalar(@{$param->{'extra-args'}});
166
167 foreach my $volid (@{$param->{'extra-args'}}) {
168
169 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid);
170 my $scfg = PVE::Storage::storage_check_enabled($storecfg, $storeid, $local_node);
171 die "storage '$storeid' is a shared storage\n" if $scfg->{shared};
172
173 my ($vtype, undef, $ownervm) = PVE::Storage::parse_volname($storecfg, $volid);
174 die "volume '$volid' has wrong vtype ($vtype != 'images')\n"
175 if $vtype ne 'images';
176 die "volume '$volid' has wrong owner\n"
177 if !$ownervm || $vmid != $ownervm;
178
179 push @$volids, $volid;
180 }
181
182 $volids = [ sort @$volids ];
183
184 my $logfunc = sub {
185 my ($start_time, $msg) = @_;
186 print STDERR "$msg\n";
187 };
188
189 my $last_snapshots = PVE::Replication::prepare(
190 $storecfg, $volids, $jobid, $last_sync, undef, $logfunc);
191
192 return undef;
193 }});
194
0880c088
DM
195__PACKAGE__->register_method ({
196 name => 'run',
197 path => 'run',
198 method => 'POST',
199 description => "This method is called by the systemd-timer and executes all (or a specific) sync jobs.",
200 parameters => {
201 additionalProperties => 0,
202 properties => {
203 id => get_standard_option('pve-replication-id', { optional => 1 }),
7c42d171
DM
204 verbose => {
205 description => "Print more verbose logs to stdout.",
206 type => 'boolean',
207 default => 0,
208 optional => 1,
209 },
0880c088
DM
210 },
211 },
212 returns => { type => 'null' },
213 code => sub {
214 my ($param) = @_;
215
7c42d171
DM
216 my $logfunc;
217
218 if ($param->{verbose}) {
219 $logfunc = sub {
220 my ($start_time, $msg) = @_;
221 print "$msg\n";
222 };
223 }
224
0880c088
DM
225 if (my $id = extract_param($param, 'id')) {
226
7c42d171 227 PVE::Replication::run_single_job($id, undef, $logfunc);
0880c088
DM
228
229 } else {
230
7c42d171 231 PVE::Replication::run_jobs(undef, $logfunc);
0880c088
DM
232 }
233
234 return undef;
235 }});
236
237__PACKAGE__->register_method ({
238 name => 'enable',
239 path => 'enable',
240 method => 'POST',
241 description => "Enable a replication job.",
242 parameters => {
243 additionalProperties => 0,
244 properties => {
245 id => get_standard_option('pve-replication-id'),
246 },
247 },
248 returns => { type => 'null' },
249 code => sub {
250 my ($param) = @_;
251
252 $param->{disable} = 0;
253
254 return PVE::API2::ReplicationConfig->update($param);
255 }});
256
257__PACKAGE__->register_method ({
258 name => 'disable',
259 path => 'disable',
260 method => 'POST',
261 description => "Disable a replication job.",
262 parameters => {
263 additionalProperties => 0,
264 properties => {
265 id => get_standard_option('pve-replication-id'),
266 },
267 },
268 returns => { type => 'null' },
269 code => sub {
270 my ($param) = @_;
271
272 $param->{disable} = 1;
273
274 return PVE::API2::ReplicationConfig->update($param);
275 }});
276
277my $print_job_list = sub {
278 my ($list) = @_;
279
280 my $format = "%-20s %10s %-20s %10s %5s %8s\n";
281
8ad9177c 282 printf($format, "JobID", "GuestID", "Target", "Schedule", "Rate", "Enabled");
0880c088
DM
283
284 foreach my $job (sort { $a->{guest} <=> $b->{guest} } @$list) {
285 my $plugin = PVE::ReplicationConfig->lookup($job->{type});
286 my $tid = $plugin->get_unique_target_id($job);
287
288 printf($format, $job->{id}, $job->{guest}, $tid,
8ad9177c 289 defined($job->{schedule}) ? $job->{schedule} : '*/15',
0880c088
DM
290 defined($job->{rate}) ? $job->{rate} : '-',
291 $job->{disable} ? 'no' : 'yes'
292 );
293 }
294};
295
296my $print_job_status = sub {
297 my ($list) = @_;
298
8ad9177c 299 my $format = "%-20s %10s %-20s %20s %20s %10s %10s %s\n";
0880c088 300
8ad9177c 301 printf($format, "JobID", "GuestID", "Target", "LastSync", "NextSync", "Duration", "FailCount", "State");
0880c088
DM
302
303 foreach my $job (sort { $a->{guest} <=> $b->{guest} } @$list) {
304 my $plugin = PVE::ReplicationConfig->lookup($job->{type});
305 my $tid = $plugin->get_unique_target_id($job);
306
8ad9177c
DM
307 my $timestr = '-';
308 if ($job->{last_sync}) {
309 $timestr = strftime("%Y-%m-%d_%H:%M:%S", localtime($job->{last_sync}));
310 }
311
312 my $nextstr = '-';
313 if (my $next = $job->{next_sync}) {
314 my $now = time();
315 if ($next > $now) {
316 $nextstr = strftime("%Y-%m-%d_%H:%M:%S", localtime($job->{next_sync}));
317 } else {
318 $nextstr = 'now';
319 }
320 }
0880c088 321
b9b5b749
DM
322 my $state = $job->{pid} ? "SYNCING" : $job->{error} // 'OK';
323
0880c088 324 printf($format, $job->{id}, $job->{guest}, $tid,
8ad9177c 325 $timestr, $nextstr, $job->{duration} // '-',
b9b5b749 326 $job->{fail_count}, $state);
0880c088
DM
327 }
328};
329
330our $cmddef = {
331 status => [ 'PVE::API2::Replication', 'status', [], { node => $nodename }, $print_job_status ],
332
8ad9177c 333 list => [ 'PVE::API2::ReplicationConfig', 'index' , [], {}, $print_job_list ],
0880c088
DM
334 read => [ 'PVE::API2::ReplicationConfig', 'read' , ['id'], {},
335 sub { my $res = shift; print to_json($res, { utf8 => 1, pretty => 1, canonical => 1}); }],
336 update => [ 'PVE::API2::ReplicationConfig', 'update' , ['id'], {} ],
337 delete => [ 'PVE::API2::ReplicationConfig', 'delete' , ['id'], {} ],
338 'create-local-job' => [ 'PVE::API2::ReplicationConfig', 'create' , ['id', 'guest', 'target'],
339 { type => 'local' } ],
340
341 enable => [ __PACKAGE__, 'enable', ['id'], {}],
342 disable => [ __PACKAGE__, 'disable', ['id'], {}],
343
be9adcf2 344 'prepare-local-job' => [ __PACKAGE__, 'prepare_local_job', ['id', 'vmid', 'extra-args'], {} ],
c7fa37c0 345 'finalize-local-job' => [ __PACKAGE__, 'finalize_local_job', ['id', 'vmid', 'extra-args'], {} ],
be9adcf2 346
0880c088
DM
347 run => [ __PACKAGE__ , 'run'],
348};
349
3501;