]> git.proxmox.com Git - pve-guest-common.git/blame - PVE/ReplicationConfig.pm
replication: replicate_volume: rate can be undefined
[pve-guest-common.git] / PVE / ReplicationConfig.pm
CommitLineData
87109d74
DM
1package PVE::ReplicationConfig;
2
3use strict;
4use warnings;
5use Data::Dumper;
6
7use PVE::Tools;
8use PVE::JSONSchema qw(get_standard_option);
9use PVE::INotify;
10use PVE::SectionConfig;
11use PVE::CalendarEvent;
12
13use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_write_file cfs_lock_file);
14
15use base qw(PVE::SectionConfig);
16
17my $replication_cfg_filename = 'replication.cfg';
18
19cfs_register_file($replication_cfg_filename,
20 sub { __PACKAGE__->parse_config(@_); },
21 sub { __PACKAGE__->write_config(@_); });
22
5d31e77b
DM
23PVE::JSONSchema::register_format('pve-replication-job-id',
24 \&parse_replication_job_id);
25sub parse_replication_job_id {
26 my ($id, $noerr) = @_;
27
28 my $msg = "invalid replication job id '$id'";
29
30 if ($id =~ m/^(\d+)-(\d+)$/) {
31 my ($guest, $jobnum) = (int($1), int($2));
32 die "$msg (guest IDs < 100 are reseved)\n" if $guest < 100;
33 my $parsed_id = "$guest-$jobnum"; # use parsed integers
34 return wantarray ? ($guest, $jobnum, $parsed_id) : $parsed_id;
35 }
36
37 return undef if $noerr;
38
39 die "$msg\n";
40}
41
87109d74 42PVE::JSONSchema::register_standard_option('pve-replication-id', {
0de17556 43 description => "Replication Job ID. The ID is composed of a Guest ID and a job number, separated by a hyphen, i.e. '<GUEST>-<JOBNUM>'.",
5d31e77b 44 type => 'string', format => 'pve-replication-job-id',
f87dd81d 45 pattern => '[1-9][0-9]{2,8}-\d{1,9}',
87109d74
DM
46});
47
48my $defaultData = {
49 propertyList => {
50 type => { description => "Section type." },
51 id => get_standard_option('pve-replication-id'),
52 disable => {
53 description => "Flag to disable/deactivate the entry.",
54 type => 'boolean',
55 optional => 1,
56 },
57 comment => {
58 description => "Description.",
59 type => 'string',
60 optional => 1,
61 maxLength => 4096,
62 },
151f1335
DM
63 remove_job => {
64 description => "Mark the replication job for removal. The job will remove all local replication snapshots. When set to 'full', it also tries to remove replicated volumes on the target. The job then removes itself from the configuration file.",
65 type => 'string',
66 enum => ['local', 'full'],
67 optional => 1,
68 },
87109d74
DM
69 rate => {
70 description => "Rate limit in mbps (megabytes per second) as floating point number.",
71 type => 'number',
72 minimum => 1,
73 optional => 1,
74 },
75 schedule => {
76 description => "Storage replication schedule. The format is a subset of `systemd` calender events.",
77 type => 'string', format => 'pve-calendar-event',
78 maxLength => 128,
79 default => '*/15',
80 optional => 1,
81 },
82 },
83};
84
85sub private {
86 return $defaultData;
87}
88
89sub parse_section_header {
90 my ($class, $line) = @_;
91
f87dd81d
DM
92 if ($line =~ m/^(\S+):\s*(\d+)-(\d+)\s*$/) {
93 my ($type, $guest, $subid) = (lc($1), int($2), int($3));
94 my $id = "$guest-$subid"; # use parsed integers
87109d74 95 my $errmsg = undef; # set if you want to skip whole section
5d31e77b 96 eval { parse_replication_job_id($id); };
87109d74 97 $errmsg = $@ if $@;
1fcde52a 98 my $config = {};
87109d74
DM
99 return ($type, $id, $errmsg, $config);
100 }
101 return undef;
102}
103
104# Note: We want only one replication job per target to
105# avoid confusion. This method should return a string
106# which uniquely identifies the target.
107sub get_unique_target_id {
108 my ($class, $data) = @_;
109
110 die "please overwrite in subclass";
111}
112
113sub parse_config {
114 my ($class, $filename, $raw) = @_;
115
116 my $cfg = $class->SUPER::parse_config($filename, $raw);
117
118 my $target_hash = {};
119
120 foreach my $id (sort keys %{$cfg->{ids}}) {
121 my $data = $cfg->{ids}->{$id};
122
1fcde52a
DM
123 my ($guest, $jobnum) = parse_replication_job_id($id);
124
125 $data->{guest} = $guest;
126 $data->{jobnum} = $jobnum;
127
87109d74
DM
128 $data->{comment} = PVE::Tools::decode_text($data->{comment})
129 if defined($data->{comment});
130
131 my $plugin = $class->lookup($data->{type});
132 my $tid = $plugin->get_unique_target_id($data);
133 my $vmid = $data->{guest};
134
135 # should not happen, but we want to be sure
136 if (defined($target_hash->{$vmid}->{$tid})) {
137 warn "delete job $id: replication job for guest '$vmid' to target '$tid' already exists\n";
138 delete $cfg->{ids}->{$id};
139 }
140 $target_hash->{$vmid}->{$tid} = 1;
141 }
142
143 return $cfg;
144}
145
146sub write_config {
147 my ($class, $filename, $cfg) = @_;
148
149 my $target_hash = {};
150
151 foreach my $id (keys %{$cfg->{ids}}) {
152 my $data = $cfg->{ids}->{$id};
153
154 my $plugin = $class->lookup($data->{type});
155 my $tid = $plugin->get_unique_target_id($data);
156 my $vmid = $data->{guest};
157
1fcde52a 158 die "property 'guest' has wrong value\n" if $id !~ m/^\Q$vmid\E-/;
87109d74
DM
159 die "replication job for guest '$vmid' to target '$tid' already exists\n"
160 if defined($target_hash->{$vmid}->{$tid});
161 $target_hash->{$vmid}->{$tid} = 1;
162
163 $data->{comment} = PVE::Tools::encode_text($data->{comment})
164 if defined($data->{comment});
165 }
166
167 $class->SUPER::write_config($filename, $cfg);
168}
169
170sub new {
171 my ($type) = @_;
172
173 my $class = ref($type) || $type;
174
175 my $cfg = cfs_read_file($replication_cfg_filename);
176
177 return bless $cfg, $class;
178}
179
180sub write {
181 my ($cfg) = @_;
182
183 cfs_write_file($replication_cfg_filename, $cfg);
184}
185
186sub lock {
187 my ($code, $errmsg) = @_;
188
189 cfs_lock_file($replication_cfg_filename, undef, $code);
190 my $err = $@;
191 if ($err) {
192 $errmsg ? die "$errmsg: $err" : die $err;
193 }
194}
195
6e55d55a
DM
196sub check_for_existing_jobs {
197 my ($cfg, $vmid, $noerr) = @_;
198
199 foreach my $id (keys %{$cfg->{ids}}) {
200 my $data = $cfg->{ids}->{$id};
201
202 if ($data->{guest} == $vmid) {
203 return 1 if $noerr;
204 die "There is a replication job '$id' for guest '$vmid' - " .
205 "Please remove that first.\n"
206 }
207 }
208
209 return undef;
210}
87109d74 211
571156ee
DM
212sub delete_job {
213 my ($jobid) = @_;
214
215 my $code = sub {
216 my $cfg = __PACKAGE__->new();
217 delete $cfg->{ids}->{$jobid};
218 $cfg->write();
219 };
220
221 lock($code);
222}
223
87109d74
DM
224package PVE::ReplicationConfig::Cluster;
225
226use base qw(PVE::ReplicationConfig);
227
228sub type {
229 return 'local';
230}
231
232sub properties {
233 return {
234 target => {
235 description => "Target node.",
236 type => 'string', format => 'pve-node',
237 },
238 };
239}
240
241sub options {
242 return {
87109d74
DM
243 target => { fixed => 1, optional => 0 },
244 disable => { optional => 1 },
245 comment => { optional => 1 },
246 rate => { optional => 1 },
247 schedule => { optional => 1 },
151f1335 248 remove_job => { optional => 1 },
87109d74
DM
249 };
250}
251
252sub get_unique_target_id {
253 my ($class, $data) = @_;
254
255 return "local/$data->{target}";
256}
257
258PVE::ReplicationConfig::Cluster->register();
259PVE::ReplicationConfig->init();
260
2611;