]> git.proxmox.com Git - pve-guest-common.git/blobdiff - PVE/ReplicationConfig.pm
lock_config: rename lock_config_mode -> lock_config_shared
[pve-guest-common.git] / PVE / ReplicationConfig.pm
index 092c497a616f936a37ceff6859dbff10e2a3c223..66ef84246a5a5ccec13559cc2a6646a880e43ffd 100644 (file)
@@ -20,10 +20,29 @@ cfs_register_file($replication_cfg_filename,
                  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 reserved)\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 = {
@@ -47,9 +66,6 @@ my $defaultData = {
            enum => ['local', 'full'],
            optional => 1,
        },
-       guest => get_standard_option('pve-vmid', {
-           optional => 1,
-           completion => \&PVE::Cluster::complete_vmid }),
        rate => {
            description => "Rate limit in mbps (megabytes per second) as floating point number.",
            type => 'number',
@@ -57,12 +73,17 @@ my $defaultData = {
            optional => 1,
        },
        schedule => {
-           description => "Storage replication schedule. The format is a subset of `systemd` calender events.",
+           description => "Storage replication schedule. The format is a subset of `systemd` calendar events.",
            type => 'string', format => 'pve-calendar-event',
            maxLength => 128,
            default => '*/15',
            optional => 1,
        },
+       source => {
+           description => "Source of the replication.",
+           type => 'string', format => 'pve-node',
+           optional => 1,
+       },
     },
 };
 
@@ -73,10 +94,11 @@ sub private {
 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);
@@ -103,6 +125,12 @@ sub parse_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->{id} = $id;
+
        $data->{comment} = PVE::Tools::decode_text($data->{comment})
            if defined($data->{comment});
 
@@ -133,6 +161,7 @@ sub write_config {
        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;
@@ -141,7 +170,7 @@ sub write_config {
            if defined($data->{comment});
     }
 
-    $class->SUPER::write_config($filename, $cfg);
+    return $class->SUPER::write_config($filename, $cfg);
 }
 
 sub new {
@@ -186,6 +215,76 @@ sub check_for_existing_jobs {
     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;
+}
+
+# switch local replication job target
+sub switch_replication_job_target {
+    my ($vmid, $old_target, $new_target) = @_;
+
+    my $transfer_job = sub {
+       my $cfg = PVE::ReplicationConfig->new();
+       my $jobcfg = find_local_replication_job($cfg, $vmid, $old_target);
+
+       return if !$jobcfg;
+
+       $jobcfg->{target} = $new_target;
+
+       $cfg->write();
+    };
+
+    lock($transfer_job);
+};
+
+sub delete_job {
+    my ($jobid) = @_;
+
+    my $code = sub {
+       my $cfg = __PACKAGE__->new();
+       delete $cfg->{ids}->{$jobid};
+       $cfg->write();
+    };
+
+    lock($code);
+}
+
+sub remove_vmid_jobs {
+    my ($vmid) = @_;
+
+    my $code = sub {
+       my $cfg = __PACKAGE__->new();
+       foreach my $id (keys %{$cfg->{ids}}) {
+           delete $cfg->{ids}->{$id} if ($cfg->{ids}->{$id}->{guest} == $vmid);
+       }
+       $cfg->write();
+    };
+
+    lock($code);
+}
+
+sub swap_source_target_nolock {
+    my ($jobid) = @_;
+
+    my $cfg = __PACKAGE__->new();
+    my $job = $cfg->{ids}->{$jobid};
+    my $tmp = $job->{source};
+    $job->{source} = $job->{target};
+    $job->{target} = $tmp;
+    $cfg->write();
+
+    return $cfg->{ids}->{$jobid};
+}
+
 package PVE::ReplicationConfig::Cluster;
 
 use base qw(PVE::ReplicationConfig);
@@ -205,13 +304,13 @@ sub properties {
 
 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 },
+       source => { optional => 1 },
     };
 }