sub { __PACKAGE__->parse_config(@_); },
sub { __PACKAGE__->write_config(@_); });
+PVE::JSONSchema::register_format('pve-replication-job-id',
+ \&parse_replication_job_id);
+sub parse_replication_job_id {
+ my ($id, $noerr) = @_;
+
+ my $msg = "invalid replication job id '$id'";
+
+ if ($id =~ m/^(\d+)-(\d+)$/) {
+ my ($guest, $jobnum) = (int($1), int($2));
+ die "$msg (guest IDs < 100 are reseved)\n" if $guest < 100;
+ my $parsed_id = "$guest-$jobnum"; # use parsed integers
+ return wantarray ? ($guest, $jobnum, $parsed_id) : $parsed_id;
+ }
+
+ return undef if $noerr;
+
+ die "$msg\n";
+}
+
PVE::JSONSchema::register_standard_option('pve-replication-id', {
- description => "Replication Job ID.",
- type => 'string', format => 'pve-configid',
- maxLength => 32, # keep short to reduce snapshot name length
+ description => "Replication Job ID. The ID is composed of a Guest ID and a job number, separated by a hyphen, i.e. '<GUEST>-<JOBNUM>'.",
+ type => 'string', format => 'pve-replication-job-id',
+ pattern => '[1-9][0-9]{2,8}-\d{1,9}',
});
my $defaultData = {
optional => 1,
maxLength => 4096,
},
- guest => get_standard_option('pve-vmid', {
+ remove_job => {
+ 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.",
+ type => 'string',
+ enum => ['local', 'full'],
optional => 1,
- completion => \&PVE::Cluster::complete_vmid }),
+ },
rate => {
description => "Rate limit in mbps (megabytes per second) as floating point number.",
type => 'number',
sub parse_section_header {
my ($class, $line) = @_;
- if ($line =~ m/^(\S+):\s*(\S+)\s*$/) {
- my ($type, $id) = (lc($1), $2);
+ if ($line =~ m/^(\S+):\s*(\d+)-(\d+)\s*$/) {
+ my ($type, $guest, $subid) = (lc($1), int($2), int($3));
+ my $id = "$guest-$subid"; # use parsed integers
my $errmsg = undef; # set if you want to skip whole section
- eval { PVE::JSONSchema::pve_verify_configid($id); };
+ eval { parse_replication_job_id($id); };
$errmsg = $@ if $@;
my $config = {};
return ($type, $id, $errmsg, $config);
foreach my $id (sort keys %{$cfg->{ids}}) {
my $data = $cfg->{ids}->{$id};
+ my ($guest, $jobnum) = parse_replication_job_id($id);
+
+ $data->{guest} = $guest;
+ $data->{jobnum} = $jobnum;
+
$data->{comment} = PVE::Tools::decode_text($data->{comment})
if defined($data->{comment});
my $tid = $plugin->get_unique_target_id($data);
my $vmid = $data->{guest};
+ die "property 'guest' has wrong value\n" if $id !~ m/^\Q$vmid\E-/;
die "replication job for guest '$vmid' to target '$tid' already exists\n"
if defined($target_hash->{$vmid}->{$tid});
$target_hash->{$vmid}->{$tid} = 1;
return undef;
}
+sub find_local_replication_job {
+ my ($cfg, $vmid, $target) = @_;
+
+ foreach my $id (keys %{$cfg->{ids}}) {
+ my $data = $cfg->{ids}->{$id};
+
+ return $data if $data->{type} eq 'local' &&
+ $data->{guest} == $vmid && $data->{target} eq $target;
+ }
+
+ return undef;
+}
+
+sub delete_job {
+ my ($jobid) = @_;
+
+ my $code = sub {
+ my $cfg = __PACKAGE__->new();
+ delete $cfg->{ids}->{$jobid};
+ $cfg->write();
+ };
+
+ lock($code);
+}
+
package PVE::ReplicationConfig::Cluster;
use base qw(PVE::ReplicationConfig);
sub options {
return {
- guest => { fixed => 1, optional => 0 },
target => { fixed => 1, optional => 0 },
disable => { optional => 1 },
comment => { optional => 1 },
rate => { optional => 1 },
schedule => { optional => 1 },
+ remove_job => { optional => 1 },
};
}