]> git.proxmox.com Git - pmg-api.git/blame - src/PMG/ClusterConfig.pm
cluster config: restrict slurp scope to avoid issue parsing network interfaces
[pmg-api.git] / src / PMG / ClusterConfig.pm
CommitLineData
9f67f5b3
DM
1package PMG::ClusterConfig::Base;
2
3use strict;
4use warnings;
5use Data::Dumper;
6
7use PVE::Tools;
8use PVE::JSONSchema qw(get_standard_option);
b0d26b8f 9use PVE::Network;
9f67f5b3
DM
10use PVE::SectionConfig;
11
12use base qw(PVE::SectionConfig);
13
14my $defaultData = {
15 propertyList => {
16 type => { description => "Cluster node type." },
17 cid => {
18 description => "Cluster Node ID.",
19 type => 'integer',
20 minimum => 1,
21 },
22 },
23};
24
25sub private {
26 return $defaultData;
27}
28
29sub parse_section_header {
30 my ($class, $line) = @_;
31
32 if ($line =~ m/^(node|master):\s*(\d+)\s*$/) {
33 my ($type, $sectionId) = ($1, $2);
34 my $errmsg = undef; # set if you want to skip whole section
35 my $config = {}; # to return additional attributes
36 return ($type, $sectionId, $errmsg, $config);
37 }
38 return undef;
39}
40
9f67f5b3
DM
41package PMG::ClusterConfig::Node;
42
43use strict;
44use warnings;
45
46use base qw(PMG::ClusterConfig::Base);
47
2eabf880 48sub valid_ssh_pubkey_regex {
69a6a66d 49 return '^[A-Za-z0-9\.\/\+=]{200,}$';
2a5ed7c1
SI
50}
51
9f67f5b3
DM
52sub type {
53 return 'node';
54}
55sub properties {
56 return {
57 ip => {
58 description => "IP address.",
b9b02806 59 type => 'string', format => 'ip',
9f67f5b3
DM
60 },
61 name => {
62 description => "Node name.",
63 type => 'string', format =>'pve-node',
64 },
65 hostrsapubkey => {
66 description => "Public SSH RSA key for the host.",
67 type => 'string',
2eabf880 68 pattern => valid_ssh_pubkey_regex(),
9f67f5b3
DM
69 },
70 rootrsapubkey => {
71 description => "Public SSH RSA key for the root user.",
72 type => 'string',
2eabf880 73 pattern => valid_ssh_pubkey_regex(),
cba17aeb
DM
74 },
75 fingerprint => {
76 description => "SSL certificate fingerprint.",
77 type => 'string',
78 pattern => '^(:?[A-Z0-9][A-Z0-9]:){31}[A-Z0-9][A-Z0-9]$',
9f67f5b3
DM
79 },
80 };
81}
82
83sub options {
84 return {
85 ip => { fixed => 1 },
86 name => { fixed => 1 },
87 hostrsapubkey => {},
88 rootrsapubkey => {},
cba17aeb 89 fingerprint => {},
9f67f5b3
DM
90 };
91}
92
93package PMG::ClusterConfig::Master;
94
95use strict;
96use warnings;
97
98use base qw(PMG::ClusterConfig::Base);
99
100sub type {
101 return 'master';
102}
103
104sub properties {
105 return {
106 maxcid => {
107 description => "Maximum used cluster node ID (used internally, do not modify).",
108 type => 'integer',
109 minimum => 1,
110 },
111 };
112}
113
114sub options {
115 return {
116 maxcid => { fixed => 1 },
117 ip => { fixed => 1 },
118 name => { fixed => 1 },
119 hostrsapubkey => {},
120 rootrsapubkey => {},
cba17aeb 121 fingerprint => {},
9f67f5b3
DM
122 };
123}
124
125package PMG::ClusterConfig;
126
127use strict;
128use warnings;
129use Data::Dumper;
130
131use PVE::SafeSyslog;
132use PVE::Tools;
133use PVE::INotify;
134
135use PMG::Utils;
136
137PMG::ClusterConfig::Node->register;
138PMG::ClusterConfig::Master->register;
139PMG::ClusterConfig::Base->init();
140
141
142sub new {
143 my ($type) = @_;
144
145 my $class = ref($type) || $type;
146
147 my $cfg = PVE::INotify::read_file("cluster.conf");
148
149 return bless $cfg, $class;
150}
151
152sub write {
153 my ($self) = @_;
154
155 PVE::INotify::write_file("cluster.conf", $self);
156}
157
158my $lockfile = "/var/lock/pmgcluster.lck";
159
160sub lock_config {
161 my ($code, $errmsg) = @_;
162
275d6d6d 163 my $res = PVE::Tools::lock_file($lockfile, undef, $code);
9f67f5b3
DM
164 if (my $err = $@) {
165 $errmsg ? die "$errmsg: $err" : die $err;
166 }
275d6d6d 167 return $res;
9f67f5b3
DM
168}
169
170sub read_cluster_conf {
171 my ($filename, $fh) = @_;
172
22b7ba38 173 my $raw = defined($fh) ? do { local $/ = undef; <$fh> } : undef;
9f67f5b3
DM
174
175 my $cinfo = PMG::ClusterConfig::Base->parse_config($filename, $raw);
9f67f5b3
DM
176
177 my $localname = PVE::INotify::nodename();
687cb0ef 178 my $localip = PVE::Network::get_local_ip(); # does gai with fallback to interfaces cfg. & ip-addr
9f67f5b3
DM
179
180 $cinfo->{remnodes} = [];
9f67f5b3
DM
181
182 $cinfo->{local} = {
183 cid => 0,
184 ip => $localip,
185 name => $localname,
186 };
187
188 my $maxcid = 0;
189 my $names_hash = {};
190
191 my $errprefix = "unable to parse $filename";
192
193 foreach my $cid (keys %{$cinfo->{ids}}) {
194 my $d = $cinfo->{ids}->{$cid};
195
196 die "$errprefix: duplicate use of name '$d->{name}'\n" if $names_hash->{$d->{name}};
197 $names_hash->{$d->{name}} = 1;
198
199 $d->{cid} = $cid;
200 $maxcid = $cid > $maxcid ? $cid : $maxcid;
201 $maxcid = $d->{maxcid} if defined($d->{maxcid}) && $d->{maxcid} > $maxcid;
202 $cinfo->{master} = $d if $d->{type} eq 'master';
203 $cinfo->{'local'} = $d if $d->{name} eq $localname;
204 }
205
206 if ($maxcid) {
207 die "$errprefix: cluster without master node\n"
208 if !defined($cinfo->{master});
209 $cinfo->{master}->{maxcid} = $maxcid;
210 }
211
e09e2f54 212 my $local_cid = $cinfo->{local}->{cid};
9f67f5b3 213 foreach my $cid (sort keys %{$cinfo->{ids}}) {
e09e2f54 214 if ($local_cid != $cid) {
cf07e7c3 215 push @{$cinfo->{remnodes}}, $cid;
9f67f5b3
DM
216 }
217 }
218
219 return $cinfo;
220}
221
222sub write_cluster_conf {
223 my ($filename, $fh, $cfg) = @_;
224
225 my $raw = PMG::ClusterConfig::Base->write_config($filename, $cfg);
226
227 PVE::Tools::safe_print($filename, $fh, $raw);
228}
229
3278b571 230PVE::INotify::register_file('cluster.conf', "/etc/pmg/cluster.conf",
9f67f5b3
DM
231 \&read_cluster_conf,
232 \&write_cluster_conf,
233 undef,
234 always_call_parser => 1);
235
2361;