1 package PVE
::ReplicationConfig
;
8 use PVE
::JSONSchema
qw(get_standard_option);
10 use PVE
::SectionConfig
;
11 use PVE
::CalendarEvent
;
13 use PVE
::Cluster
qw(cfs_register_file cfs_read_file cfs_write_file cfs_lock_file);
15 use base
qw(PVE::SectionConfig);
17 my $replication_cfg_filename = 'replication.cfg';
19 cfs_register_file
($replication_cfg_filename,
20 sub { __PACKAGE__-
>parse_config(@_); },
21 sub { __PACKAGE__-
>write_config(@_); });
23 PVE
::JSONSchema
::register_format
('pve-replication-job-id',
24 \
&parse_replication_job_id
);
25 sub parse_replication_job_id
{
26 my ($id, $noerr) = @_;
28 my $msg = "invalid replication job id '$id'";
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;
37 return undef if $noerr;
42 PVE
::JSONSchema
::register_standard_option
('pve-replication-id', {
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>'.",
44 type
=> 'string', format
=> 'pve-replication-job-id',
45 pattern
=> '[1-9][0-9]{2,8}-\d{1,9}',
50 type
=> { description
=> "Section type." },
51 id
=> get_standard_option
('pve-replication-id'),
53 description
=> "Flag to disable/deactivate the entry.",
58 description
=> "Description.",
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.",
66 enum
=> ['local', 'full'],
70 description
=> "Rate limit in mbps (megabytes per second) as floating point number.",
76 description
=> "Storage replication schedule. The format is a subset of `systemd` calender events.",
77 type
=> 'string', format
=> 'pve-calendar-event',
83 description
=> "Source of the replication.",
84 type
=> 'string', format
=> 'pve-node',
94 sub parse_section_header
{
95 my ($class, $line) = @_;
97 if ($line =~ m/^(\S+):\s*(\d+)-(\d+)\s*$/) {
98 my ($type, $guest, $subid) = (lc($1), int($2), int($3));
99 my $id = "$guest-$subid"; # use parsed integers
100 my $errmsg = undef; # set if you want to skip whole section
101 eval { parse_replication_job_id
($id); };
104 return ($type, $id, $errmsg, $config);
109 # Note: We want only one replication job per target to
110 # avoid confusion. This method should return a string
111 # which uniquely identifies the target.
112 sub get_unique_target_id
{
113 my ($class, $data) = @_;
115 die "please overwrite in subclass";
119 my ($class, $filename, $raw) = @_;
121 my $cfg = $class->SUPER::parse_config
($filename, $raw);
123 my $target_hash = {};
125 foreach my $id (sort keys %{$cfg->{ids
}}) {
126 my $data = $cfg->{ids
}->{$id};
128 my ($guest, $jobnum) = parse_replication_job_id
($id);
130 $data->{guest
} = $guest;
131 $data->{jobnum
} = $jobnum;
134 $data->{comment
} = PVE
::Tools
::decode_text
($data->{comment
})
135 if defined($data->{comment
});
137 my $plugin = $class->lookup($data->{type
});
138 my $tid = $plugin->get_unique_target_id($data);
139 my $vmid = $data->{guest
};
141 # should not happen, but we want to be sure
142 if (defined($target_hash->{$vmid}->{$tid})) {
143 warn "delete job $id: replication job for guest '$vmid' to target '$tid' already exists\n";
144 delete $cfg->{ids
}->{$id};
146 $target_hash->{$vmid}->{$tid} = 1;
153 my ($class, $filename, $cfg) = @_;
155 my $target_hash = {};
157 foreach my $id (keys %{$cfg->{ids
}}) {
158 my $data = $cfg->{ids
}->{$id};
160 my $plugin = $class->lookup($data->{type
});
161 my $tid = $plugin->get_unique_target_id($data);
162 my $vmid = $data->{guest
};
164 die "property 'guest' has wrong value\n" if $id !~ m/^\Q$vmid\E-/;
165 die "replication job for guest '$vmid' to target '$tid' already exists\n"
166 if defined($target_hash->{$vmid}->{$tid});
167 $target_hash->{$vmid}->{$tid} = 1;
169 $data->{comment
} = PVE
::Tools
::encode_text
($data->{comment
})
170 if defined($data->{comment
});
173 return $class->SUPER::write_config
($filename, $cfg);
179 my $class = ref($type) || $type;
181 my $cfg = cfs_read_file
($replication_cfg_filename);
183 return bless $cfg, $class;
189 cfs_write_file
($replication_cfg_filename, $cfg);
193 my ($code, $errmsg) = @_;
195 cfs_lock_file
($replication_cfg_filename, undef, $code);
198 $errmsg ?
die "$errmsg: $err" : die $err;
202 sub check_for_existing_jobs
{
203 my ($cfg, $vmid, $noerr) = @_;
205 foreach my $id (keys %{$cfg->{ids
}}) {
206 my $data = $cfg->{ids
}->{$id};
208 if ($data->{guest
} == $vmid) {
210 die "There is a replication job '$id' for guest '$vmid' - " .
211 "Please remove that first.\n"
218 sub find_local_replication_job
{
219 my ($cfg, $vmid, $target) = @_;
221 foreach my $id (keys %{$cfg->{ids
}}) {
222 my $data = $cfg->{ids
}->{$id};
224 return $data if $data->{type
} eq 'local' &&
225 $data->{guest
} == $vmid && $data->{target
} eq $target;
231 # switch local replication job target
232 sub switch_replication_job_target
{
233 my ($vmid, $old_target, $new_target) = @_;
235 my $transfer_job = sub {
236 my $cfg = PVE
::ReplicationConfig-
>new();
237 my $jobcfg = find_local_replication_job
($cfg, $vmid, $old_target);
241 $jobcfg->{target
} = $new_target;
253 my $cfg = __PACKAGE__-
>new();
254 delete $cfg->{ids
}->{$jobid};
261 sub swap_source_target_nolock
{
264 my $cfg = __PACKAGE__-
>new();
265 my $job = $cfg->{ids
}->{$jobid};
266 my $tmp = $job->{source
};
267 $job->{source
} = $job->{target
};
268 $job->{target
} = $tmp;
271 return $cfg->{ids
}->{$jobid};
274 package PVE
::ReplicationConfig
::Cluster
;
276 use base
qw(PVE::ReplicationConfig);
285 description
=> "Target node.",
286 type
=> 'string', format
=> 'pve-node',
293 target
=> { fixed
=> 1, optional
=> 0 },
294 disable
=> { optional
=> 1 },
295 comment
=> { optional
=> 1 },
296 rate
=> { optional
=> 1 },
297 schedule
=> { optional
=> 1 },
298 remove_job
=> { optional
=> 1 },
299 source
=> { optional
=> 1 },
303 sub get_unique_target_id
{
304 my ($class, $data) = @_;
306 return "local/$data->{target}";
309 PVE
::ReplicationConfig
::Cluster-
>register();
310 PVE
::ReplicationConfig-
>init();