]> git.proxmox.com Git - pve-guest-common.git/blame - PVE/ReplicationConfig.pm
PVE::GuestHelpers::guest_migration_lock - new helper
[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
23PVE::JSONSchema::register_standard_option('pve-replication-id', {
24 description => "Replication Job ID.",
25 type => 'string', format => 'pve-configid',
26 maxLength => 32, # keep short to reduce snapshot name length
27});
28
29my $defaultData = {
30 propertyList => {
31 type => { description => "Section type." },
32 id => get_standard_option('pve-replication-id'),
33 disable => {
34 description => "Flag to disable/deactivate the entry.",
35 type => 'boolean',
36 optional => 1,
37 },
38 comment => {
39 description => "Description.",
40 type => 'string',
41 optional => 1,
42 maxLength => 4096,
43 },
44 guest => get_standard_option('pve-vmid', {
45 optional => 1,
46 completion => \&PVE::Cluster::complete_vmid }),
47 rate => {
48 description => "Rate limit in mbps (megabytes per second) as floating point number.",
49 type => 'number',
50 minimum => 1,
51 optional => 1,
52 },
53 schedule => {
54 description => "Storage replication schedule. The format is a subset of `systemd` calender events.",
55 type => 'string', format => 'pve-calendar-event',
56 maxLength => 128,
57 default => '*/15',
58 optional => 1,
59 },
60 },
61};
62
63sub private {
64 return $defaultData;
65}
66
67sub parse_section_header {
68 my ($class, $line) = @_;
69
70 if ($line =~ m/^(\S+):\s*(\S+)\s*$/) {
71 my ($type, $id) = (lc($1), $2);
72 my $errmsg = undef; # set if you want to skip whole section
73 eval { PVE::JSONSchema::pve_verify_configid($id); };
74 $errmsg = $@ if $@;
75 my $config = {};
76 return ($type, $id, $errmsg, $config);
77 }
78 return undef;
79}
80
81# Note: We want only one replication job per target to
82# avoid confusion. This method should return a string
83# which uniquely identifies the target.
84sub get_unique_target_id {
85 my ($class, $data) = @_;
86
87 die "please overwrite in subclass";
88}
89
90sub parse_config {
91 my ($class, $filename, $raw) = @_;
92
93 my $cfg = $class->SUPER::parse_config($filename, $raw);
94
95 my $target_hash = {};
96
97 foreach my $id (sort keys %{$cfg->{ids}}) {
98 my $data = $cfg->{ids}->{$id};
99
100 $data->{comment} = PVE::Tools::decode_text($data->{comment})
101 if defined($data->{comment});
102
103 my $plugin = $class->lookup($data->{type});
104 my $tid = $plugin->get_unique_target_id($data);
105 my $vmid = $data->{guest};
106
107 # should not happen, but we want to be sure
108 if (defined($target_hash->{$vmid}->{$tid})) {
109 warn "delete job $id: replication job for guest '$vmid' to target '$tid' already exists\n";
110 delete $cfg->{ids}->{$id};
111 }
112 $target_hash->{$vmid}->{$tid} = 1;
113 }
114
115 return $cfg;
116}
117
118sub write_config {
119 my ($class, $filename, $cfg) = @_;
120
121 my $target_hash = {};
122
123 foreach my $id (keys %{$cfg->{ids}}) {
124 my $data = $cfg->{ids}->{$id};
125
126 my $plugin = $class->lookup($data->{type});
127 my $tid = $plugin->get_unique_target_id($data);
128 my $vmid = $data->{guest};
129
130 die "replication job for guest '$vmid' to target '$tid' already exists\n"
131 if defined($target_hash->{$vmid}->{$tid});
132 $target_hash->{$vmid}->{$tid} = 1;
133
134 $data->{comment} = PVE::Tools::encode_text($data->{comment})
135 if defined($data->{comment});
136 }
137
138 $class->SUPER::write_config($filename, $cfg);
139}
140
141sub new {
142 my ($type) = @_;
143
144 my $class = ref($type) || $type;
145
146 my $cfg = cfs_read_file($replication_cfg_filename);
147
148 return bless $cfg, $class;
149}
150
151sub write {
152 my ($cfg) = @_;
153
154 cfs_write_file($replication_cfg_filename, $cfg);
155}
156
157sub lock {
158 my ($code, $errmsg) = @_;
159
160 cfs_lock_file($replication_cfg_filename, undef, $code);
161 my $err = $@;
162 if ($err) {
163 $errmsg ? die "$errmsg: $err" : die $err;
164 }
165}
166
6e55d55a
DM
167sub check_for_existing_jobs {
168 my ($cfg, $vmid, $noerr) = @_;
169
170 foreach my $id (keys %{$cfg->{ids}}) {
171 my $data = $cfg->{ids}->{$id};
172
173 if ($data->{guest} == $vmid) {
174 return 1 if $noerr;
175 die "There is a replication job '$id' for guest '$vmid' - " .
176 "Please remove that first.\n"
177 }
178 }
179
180 return undef;
181}
87109d74
DM
182
183package PVE::ReplicationConfig::Cluster;
184
185use base qw(PVE::ReplicationConfig);
186
187sub type {
188 return 'local';
189}
190
191sub properties {
192 return {
193 target => {
194 description => "Target node.",
195 type => 'string', format => 'pve-node',
196 },
197 };
198}
199
200sub options {
201 return {
202 guest => { fixed => 1, optional => 0 },
203 target => { fixed => 1, optional => 0 },
204 disable => { optional => 1 },
205 comment => { optional => 1 },
206 rate => { optional => 1 },
207 schedule => { optional => 1 },
208 };
209}
210
211sub get_unique_target_id {
212 my ($class, $data) = @_;
213
214 return "local/$data->{target}";
215}
216
217PVE::ReplicationConfig::Cluster->register();
218PVE::ReplicationConfig->init();
219
2201;