]> git.proxmox.com Git - pve-manager.git/blob - PVE/API2/ReplicationConfig.pm
api: add proxmox-firewall to versions pkg list
[pve-manager.git] / PVE / API2 / ReplicationConfig.pm
1 package PVE::API2::ReplicationConfig;
2
3 use warnings;
4 use strict;
5
6 use PVE::Tools qw(extract_param);
7 use PVE::Exception qw(raise_perm_exc raise_param_exc);
8 use PVE::JSONSchema qw(get_standard_option);
9 use PVE::RPCEnvironment;
10 use PVE::ReplicationConfig;
11 use PVE::Cluster;
12
13 use PVE::RESTHandler;
14
15 use base qw(PVE::RESTHandler);
16
17 __PACKAGE__->register_method ({
18 name => 'index',
19 path => '',
20 method => 'GET',
21 description => "List replication jobs.",
22 permissions => {
23 description => "Will only return replication jobs for which the calling user has"
24 . " VM.Audit permission on /vms/<vmid>.",
25 user => 'all',
26 },
27 parameters => {
28 additionalProperties => 0,
29 properties => {},
30 },
31 returns => {
32 type => 'array',
33 items => {
34 type => "object",
35 properties => {},
36 },
37 links => [ { rel => 'child', href => "{id}" } ],
38 },
39 code => sub {
40 my ($param) = @_;
41
42 my $rpcenv = PVE::RPCEnvironment::get();
43 my $authuser = $rpcenv->get_user();
44
45 my $cfg = PVE::ReplicationConfig->new();
46
47 my $res = [];
48 foreach my $id (sort keys %{$cfg->{ids}}) {
49 my $d = $cfg->{ids}->{$id};
50 my $vmid = $d->{guest};
51 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
52 $d->{id} = $id;
53 push @$res, $d;
54 }
55
56 return $res;
57 }});
58
59 __PACKAGE__->register_method ({
60 name => 'read',
61 path => '{id}',
62 method => 'GET',
63 description => "Read replication job configuration.",
64 permissions => {
65 description => "Requires the VM.Audit permission on /vms/<vmid>.",
66 user => 'all',
67 },
68 parameters => {
69 additionalProperties => 0,
70 properties => {
71 id => get_standard_option('pve-replication-id'),
72 },
73 },
74 returns => { type => 'object' },
75 code => sub {
76 my ($param) = @_;
77
78 my $rpcenv = PVE::RPCEnvironment::get();
79 my $authuser = $rpcenv->get_user();
80
81 my $cfg = PVE::ReplicationConfig->new();
82
83 my $data = $cfg->{ids}->{$param->{id}};
84
85 die "no such replication job '$param->{id}'\n" if !defined($data);
86
87 my $vmid = $data->{guest};
88
89 raise_perm_exc() if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ]);
90
91 $data->{id} = $param->{id};
92
93 $data->{digest} = $cfg->{digest};
94
95 return $data;
96 }});
97
98 __PACKAGE__->register_method ({
99 name => 'create',
100 path => '',
101 protected => 1,
102 method => 'POST',
103 description => "Create a new replication job",
104 permissions => {
105 check => ['perm', '/storage', ['Datastore.Allocate']],
106 },
107 parameters => PVE::ReplicationConfig->createSchema(),
108 returns => { type => 'null' },
109 code => sub {
110 my ($param) = @_;
111
112 my $type = extract_param($param, 'type');
113 my $plugin = PVE::ReplicationConfig->lookup($type);
114 my $id = extract_param($param, 'id');
115
116 # extract guest ID from job ID
117 my ($guest) = PVE::ReplicationConfig::parse_replication_job_id($id);
118
119 my $nodelist = PVE::Cluster::get_members();
120 my $vmlist = PVE::Cluster::get_vmlist();
121
122 my $guest_info = $vmlist->{ids}->{$guest};
123
124 die "Guest '$guest' does not exist.\n"
125 if !defined($guest_info);
126 die "Target '$param->{target}' does not exist.\n"
127 if !defined($nodelist->{$param->{target}});
128
129 my $source = $guest_info->{node};
130 die "Source '$param->{source}' does not match current node of guest '$guest' ($source)\n"
131 if defined($param->{source}) && $param->{source} ne $source;
132
133 $param->{source} //= $source;
134
135 die "Source and target must not be identical\n"
136 if $param->{target} eq $source;
137
138 my $guest_class = $PVE::API2::Replication::lookup_guest_class->($guest_info->{type});
139 my $guest_conf = $guest_class->load_config($guest, $source);
140 my $rep_volumes = $guest_class->get_replicatable_volumes(PVE::Storage::config(), $guest, $guest_conf, 0, 0);
141 die "No replicatable volumes found\n" if !%$rep_volumes;
142
143 my $code = sub {
144 my $cfg = PVE::ReplicationConfig->new();
145
146 die "replication job '$id' already exists\n"
147 if $cfg->{ids}->{$id};
148
149 my $opts = $plugin->check_config($id, $param, 1, 1);
150
151 $opts->{guest} = $guest;
152
153 $cfg->{ids}->{$id} = $opts;
154
155 $cfg->write();
156 };
157
158 PVE::ReplicationConfig::lock($code);
159
160 return undef;
161 }});
162
163
164 __PACKAGE__->register_method ({
165 name => 'update',
166 protected => 1,
167 path => '{id}',
168 method => 'PUT',
169 description => "Update replication job configuration.",
170 permissions => {
171 check => ['perm', '/storage', ['Datastore.Allocate']],
172 },
173 parameters => PVE::ReplicationConfig->updateSchema(),
174 returns => { type => 'null' },
175 code => sub {
176 my ($param) = @_;
177
178 my $id = extract_param($param, 'id');
179 my $digest = extract_param($param, 'digest');
180 my $delete = extract_param($param, 'delete');
181
182 my $code = sub {
183 my $cfg = PVE::ReplicationConfig->new();
184
185 PVE::SectionConfig::assert_if_modified($cfg, $digest);
186
187 my $data = $cfg->{ids}->{$id};
188 die "no such job '$id'\n" if !$data;
189
190 my $plugin = PVE::ReplicationConfig->lookup($data->{type});
191 my $opts = $plugin->check_config($id, $param, 0, 1);
192
193 foreach my $k (keys %$opts) {
194 $data->{$k} = $opts->{$k};
195 }
196
197 if ($delete) {
198 my $options = $plugin->private()->{options}->{$data->{type}};
199 foreach my $k (PVE::Tools::split_list($delete)) {
200 my $d = $options->{$k} ||
201 die "no such option '$k'\n";
202 die "unable to delete required option '$k'\n"
203 if !$d->{optional};
204 die "unable to delete fixed option '$k'\n"
205 if $d->{fixed};
206 delete $data->{$k};
207 }
208 }
209
210 $cfg->write();
211 };
212
213 PVE::ReplicationConfig::lock($code);
214
215 return undef;
216 }});
217
218 __PACKAGE__->register_method ({
219 name => 'delete',
220 protected => 1,
221 path => '{id}',
222 method => 'DELETE',
223 description => "Mark replication job for removal.",
224 permissions => {
225 check => ['perm', '/storage', ['Datastore.Allocate']],
226 },
227 parameters => {
228 additionalProperties => 0,
229 properties => {
230 id => get_standard_option('pve-replication-id'),
231 keep => {
232 description => "Keep replicated data at target (do not remove).",
233 type => 'boolean',
234 optional => 1,
235 default => 0,
236 },
237 force => {
238 description => "Will remove the jobconfig entry, but will not cleanup.",
239 type => 'boolean',
240 optional => 1,
241 default => 0,
242 },
243 }
244 },
245 returns => { type => 'null' },
246 code => sub {
247 my ($param) = @_;
248
249 my $rpcenv = PVE::RPCEnvironment::get();
250
251 my $code = sub {
252 my $cfg = PVE::ReplicationConfig->new();
253
254 my $id = $param->{id};
255 if ($param->{force}) {
256 raise_param_exc({ 'keep' => "conflicts with parameter 'force'" }) if $param->{keep};
257 delete $cfg->{ids}->{$id};
258 } else {
259 my $jobcfg = $cfg->{ids}->{$id};
260 die "no such job '$id'\n" if !$jobcfg;
261
262 if (!$param->{keep} && $jobcfg->{type} eq 'local') {
263 # remove local snapshots and remote volumes
264 $jobcfg->{remove_job} = 'full';
265 } else {
266 # only remove local snapshots
267 $jobcfg->{remove_job} = 'local';
268 }
269
270 warn "Replication job removal is a background task and will take some time.\n"
271 if $rpcenv->{type} eq 'cli';
272 }
273 $cfg->write();
274 };
275
276 PVE::ReplicationConfig::lock($code);
277
278 return undef;
279 }});
280
281 1;