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',
89 sub parse_section_header
{
90 my ($class, $line) = @_;
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
95 my $errmsg = undef; # set if you want to skip whole section
96 eval { parse_replication_job_id
($id); };
99 return ($type, $id, $errmsg, $config);
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.
107 sub get_unique_target_id
{
108 my ($class, $data) = @_;
110 die "please overwrite in subclass";
114 my ($class, $filename, $raw) = @_;
116 my $cfg = $class->SUPER::parse_config
($filename, $raw);
118 my $target_hash = {};
120 foreach my $id (sort keys %{$cfg->{ids
}}) {
121 my $data = $cfg->{ids
}->{$id};
123 my ($guest, $jobnum) = parse_replication_job_id
($id);
125 $data->{guest
} = $guest;
126 $data->{jobnum
} = $jobnum;
129 $data->{comment
} = PVE
::Tools
::decode_text
($data->{comment
})
130 if defined($data->{comment
});
132 my $plugin = $class->lookup($data->{type
});
133 my $tid = $plugin->get_unique_target_id($data);
134 my $vmid = $data->{guest
};
136 # should not happen, but we want to be sure
137 if (defined($target_hash->{$vmid}->{$tid})) {
138 warn "delete job $id: replication job for guest '$vmid' to target '$tid' already exists\n";
139 delete $cfg->{ids
}->{$id};
141 $target_hash->{$vmid}->{$tid} = 1;
148 my ($class, $filename, $cfg) = @_;
150 my $target_hash = {};
152 foreach my $id (keys %{$cfg->{ids
}}) {
153 my $data = $cfg->{ids
}->{$id};
155 my $plugin = $class->lookup($data->{type
});
156 my $tid = $plugin->get_unique_target_id($data);
157 my $vmid = $data->{guest
};
159 die "property 'guest' has wrong value\n" if $id !~ m/^\Q$vmid\E-/;
160 die "replication job for guest '$vmid' to target '$tid' already exists\n"
161 if defined($target_hash->{$vmid}->{$tid});
162 $target_hash->{$vmid}->{$tid} = 1;
164 $data->{comment
} = PVE
::Tools
::encode_text
($data->{comment
})
165 if defined($data->{comment
});
168 $class->SUPER::write_config
($filename, $cfg);
174 my $class = ref($type) || $type;
176 my $cfg = cfs_read_file
($replication_cfg_filename);
178 return bless $cfg, $class;
184 cfs_write_file
($replication_cfg_filename, $cfg);
188 my ($code, $errmsg) = @_;
190 cfs_lock_file
($replication_cfg_filename, undef, $code);
193 $errmsg ?
die "$errmsg: $err" : die $err;
197 sub check_for_existing_jobs
{
198 my ($cfg, $vmid, $noerr) = @_;
200 foreach my $id (keys %{$cfg->{ids
}}) {
201 my $data = $cfg->{ids
}->{$id};
203 if ($data->{guest
} == $vmid) {
205 die "There is a replication job '$id' for guest '$vmid' - " .
206 "Please remove that first.\n"
213 sub find_local_replication_job
{
214 my ($cfg, $vmid, $target) = @_;
216 my $vms = PVE
::Cluster
::get_vmlist
();
218 foreach my $id (keys %{$cfg->{ids
}}) {
219 my $data = $cfg->{ids
}->{$id};
221 $data->{vmtype
} = $vms->{ids
}->{$vmid}->{type
};
223 return $data if $data->{type
} eq 'local' &&
224 $data->{guest
} == $vmid && $data->{target
} eq $target;
230 # switch local replication job target
231 sub switch_replication_job_target
{
232 my ($vmid, $old_target, $new_target) = @_;
234 my $transfer_job = sub {
235 my $cfg = PVE
::ReplicationConfig-
>new();
236 my $jobcfg = find_local_replication_job
($cfg, $vmid, $old_target);
240 $jobcfg->{target
} = $new_target;
252 my $cfg = __PACKAGE__-
>new();
253 delete $cfg->{ids
}->{$jobid};
260 package PVE
::ReplicationConfig
::Cluster
;
262 use base
qw(PVE::ReplicationConfig);
271 description
=> "Target node.",
272 type
=> 'string', format
=> 'pve-node',
279 target
=> { fixed
=> 1, optional
=> 0 },
280 disable
=> { optional
=> 1 },
281 comment
=> { optional
=> 1 },
282 rate
=> { optional
=> 1 },
283 schedule
=> { optional
=> 1 },
284 remove_job
=> { optional
=> 1 },
288 sub get_unique_target_id
{
289 my ($class, $data) = @_;
291 return "local/$data->{target}";
294 PVE
::ReplicationConfig
::Cluster-
>register();
295 PVE
::ReplicationConfig-
>init();