]>
Commit | Line | Data |
---|---|---|
b6973a89 TL |
1 | package PVE::Corosync; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | ||
6 | use Digest::SHA; | |
496de919 | 7 | use Clone 'clone'; |
b01bc84d | 8 | use Net::IP qw(ip_is_ipv6); |
b6973a89 TL |
9 | |
10 | use PVE::Cluster; | |
11 | ||
12 | my $basedir = "/etc/pve"; | |
13 | ||
496de919 TL |
14 | my $conf_array_sections = { |
15 | node => 1, | |
16 | interface => 1, | |
17 | }; | |
18 | ||
b6973a89 TL |
19 | # a very simply parser ... |
20 | sub parse_conf { | |
21 | my ($filename, $raw) = @_; | |
22 | ||
23 | return {} if !$raw; | |
24 | ||
25 | my $digest = Digest::SHA::sha1_hex(defined($raw) ? $raw : ''); | |
26 | ||
27 | $raw =~ s/#.*$//mg; | |
28 | $raw =~ s/\r?\n/ /g; | |
29 | $raw =~ s/\s+/ /g; | |
30 | $raw =~ s/^\s+//; | |
31 | $raw =~ s/\s*$//; | |
32 | ||
33 | my @tokens = split(/\s/, $raw); | |
34 | ||
496de919 | 35 | my $conf = { 'main' => {} }; |
b6973a89 TL |
36 | |
37 | my $stack = []; | |
496de919 | 38 | my $section = $conf->{main}; |
b6973a89 TL |
39 | |
40 | while (defined(my $token = shift @tokens)) { | |
41 | my $nexttok = $tokens[0]; | |
42 | ||
43 | if ($nexttok && ($nexttok eq '{')) { | |
44 | shift @tokens; # skip '{' | |
496de919 TL |
45 | my $new_section = {}; |
46 | if ($conf_array_sections->{$token}) { | |
47 | $section->{$token} = [] if !defined($section->{$token}); | |
48 | push @{$section->{$token}}, $new_section; | |
49 | } elsif (!defined($section->{$token})) { | |
50 | $section->{$token} = $new_section; | |
51 | } else { | |
52 | die "section '$token' already exists and not marked as array!\n"; | |
53 | } | |
b6973a89 TL |
54 | push @$stack, $section; |
55 | $section = $new_section; | |
56 | next; | |
57 | } | |
58 | ||
59 | if ($token eq '}') { | |
60 | $section = pop @$stack; | |
61 | die "parse error - uncexpected '}'\n" if !$section; | |
62 | next; | |
63 | } | |
64 | ||
65 | my $key = $token; | |
66 | die "missing ':' after key '$key'\n" if ! ($key =~ s/:$//); | |
67 | ||
68 | die "parse error - no value for '$key'\n" if !defined($nexttok); | |
69 | my $value = shift @tokens; | |
70 | ||
496de919 | 71 | $section->{$key} = $value; |
b6973a89 TL |
72 | } |
73 | ||
e7ecad20 TL |
74 | # make working with the config way easier |
75 | my ($totem, $nodelist) = $conf->{main}->@{"totem", "nodelist"}; | |
76 | $nodelist->{node} = { map { $_->{name} // $_->{ring0_addr} => $_ } @{$nodelist->{node}} }; | |
77 | $totem->{interface} = { map { $_->{ringnumber} => $_ } @{$totem->{interface}} }; | |
78 | ||
b6973a89 TL |
79 | $conf->{digest} = $digest; |
80 | ||
81 | return $conf; | |
82 | } | |
83 | ||
84 | my $dump_section; | |
85 | $dump_section = sub { | |
86 | my ($section, $prefix) = @_; | |
87 | ||
496de919 | 88 | my $raw = ''; |
b6973a89 | 89 | |
496de919 TL |
90 | foreach my $k (sort keys %$section) { |
91 | my $v = $section->{$k}; | |
92 | if (ref($v) eq 'HASH') { | |
93 | $raw .= $prefix . "$k {\n"; | |
94 | $raw .= &$dump_section($v, "$prefix "); | |
95 | $raw .= $prefix . "}\n"; | |
96 | $raw .= "\n" if !$prefix; # add extra newline at 1st level only | |
97 | } elsif (ref($v) eq 'ARRAY') { | |
98 | foreach my $child (@$v) { | |
99 | $raw .= $prefix . "$k {\n"; | |
100 | $raw .= &$dump_section($child, "$prefix "); | |
101 | $raw .= $prefix . "}\n"; | |
102 | } | |
103 | } elsif (!ref($v)) { | |
94291d49 | 104 | die "got undefined value for key '$k'!\n" if !defined($v); |
496de919 TL |
105 | $raw .= $prefix . "$k: $v\n"; |
106 | } else { | |
107 | die "unexpected reference in config hash: $k => ". ref($v) ."\n"; | |
108 | } | |
b6973a89 TL |
109 | } |
110 | ||
b6973a89 | 111 | return $raw; |
b6973a89 TL |
112 | }; |
113 | ||
114 | sub write_conf { | |
115 | my ($filename, $conf) = @_; | |
116 | ||
e7ecad20 TL |
117 | my $c = clone($conf->{main}) // die "no main section"; |
118 | ||
119 | # retransform back for easier dumping | |
120 | my $hash_to_array = sub { | |
121 | my ($hash) = @_; | |
122 | return [ $hash->@{sort keys %$hash} ]; | |
123 | }; | |
b6973a89 | 124 | |
e7ecad20 TL |
125 | $c->{nodelist}->{node} = &$hash_to_array($c->{nodelist}->{node}); |
126 | $c->{totem}->{interface} = &$hash_to_array($c->{totem}->{interface}); | |
127 | ||
128 | my $raw = &$dump_section($c, ''); | |
b6973a89 TL |
129 | |
130 | return $raw; | |
131 | } | |
132 | ||
2b28b160 | 133 | # read only - use atomic_write_conf method to write |
b6973a89 TL |
134 | PVE::Cluster::cfs_register_file('corosync.conf', \&parse_conf); |
135 | # this is read/write | |
136 | PVE::Cluster::cfs_register_file('corosync.conf.new', \&parse_conf, | |
137 | \&write_conf); | |
138 | ||
139 | sub check_conf_exists { | |
140 | my ($silent) = @_; | |
141 | ||
142 | $silent = $silent // 0; | |
143 | ||
144 | my $exists = -f "$basedir/corosync.conf"; | |
145 | ||
146 | warn "Corosync config '$basedir/corosync.conf' does not exist - is this node part of a cluster?\n" | |
147 | if !$silent && !$exists; | |
148 | ||
149 | return $exists; | |
150 | } | |
151 | ||
152 | sub update_nodelist { | |
153 | my ($conf, $nodelist) = @_; | |
154 | ||
e7ecad20 | 155 | $conf->{main}->{nodelist}->{node} = $nodelist; |
b6973a89 | 156 | |
2b28b160 | 157 | atomic_write_conf($conf); |
b6973a89 TL |
158 | } |
159 | ||
160 | sub nodelist { | |
161 | my ($conf) = @_; | |
e7ecad20 | 162 | return clone($conf->{main}->{nodelist}->{node}); |
b6973a89 TL |
163 | } |
164 | ||
b6973a89 TL |
165 | sub totem_config { |
166 | my ($conf) = @_; | |
e7ecad20 | 167 | return clone($conf->{main}->{totem}); |
b6973a89 TL |
168 | } |
169 | ||
2b28b160 TL |
170 | # caller must hold corosync.conf cfs lock if used in read-modify-write cycle |
171 | sub atomic_write_conf { | |
172 | my ($conf, $no_increase_version) = @_; | |
173 | ||
174 | if (!$no_increase_version) { | |
175 | die "invalid corosync config: unable to read config version\n" | |
176 | if !defined($conf->{main}->{totem}->{config_version}); | |
177 | $conf->{main}->{totem}->{config_version}++; | |
178 | } | |
179 | ||
180 | PVE::Cluster::cfs_write_file("corosync.conf.new", $conf); | |
181 | ||
182 | rename("/etc/pve/corosync.conf.new", "/etc/pve/corosync.conf") | |
183 | || die "activating corosync.conf.new failed - $!\n"; | |
184 | } | |
185 | ||
b01bc84d TL |
186 | # for creating a new cluster with the current node |
187 | # params are those from the API/CLI cluster create call | |
188 | sub create_conf { | |
189 | my ($nodename, %param) = @_; | |
190 | ||
191 | my $clustername = $param{clustername}; | |
192 | my $nodeid = $param{nodeid} || 1; | |
193 | my $votes = $param{votes} || 1; | |
194 | ||
195 | my $local_ip_address = PVE::Cluster::remote_node_ip($nodename); | |
196 | my $ring0_addr = $param{ring0_addr} // $local_ip_address; | |
916c10b3 | 197 | my $bindnet0_addr = $param{bindnet0_addr} // $ring0_addr; |
b01bc84d TL |
198 | |
199 | my $use_ipv6 = ip_is_ipv6($ring0_addr); | |
200 | die "ring 0 addresses must be from same IP family!\n" | |
201 | if $use_ipv6 != ip_is_ipv6($bindnet0_addr); | |
202 | ||
203 | my $conf = { | |
204 | totem => { | |
205 | version => 2, # protocol version | |
206 | secauth => 'on', | |
207 | cluster_name => $clustername, | |
208 | config_version => 0, | |
209 | ip_version => $use_ipv6 ? 'ipv6' : 'ipv4', | |
210 | interface => { | |
211 | 0 => { | |
212 | bindnetaddr => $bindnet0_addr, | |
213 | ringnumber => 0, | |
214 | }, | |
215 | }, | |
216 | }, | |
217 | nodelist => { | |
218 | node => { | |
219 | $nodename => { | |
220 | name => $nodename, | |
221 | nodeid => $nodeid, | |
222 | quorum_votes => $votes, | |
223 | ring0_addr => $ring0_addr, | |
224 | }, | |
225 | }, | |
226 | }, | |
227 | quorum => { | |
228 | provider => 'corosync_votequorum', | |
229 | }, | |
230 | logging => { | |
231 | to_syslog => 'yes', | |
232 | debug => 'off', | |
233 | }, | |
234 | }; | |
235 | ||
236 | die "Param bindnet1_addr set but ring1_addr not specified!\n" | |
237 | if (defined($param{bindnet1_addr}) && !defined($param{ring1_addr})); | |
238 | ||
239 | my $ring1_addr = $param{ring1_addr}; | |
240 | my $bindnet1_addr = $param{bindnet1_addr} // $param{ring1_addr}; | |
241 | ||
242 | if ($bindnet1_addr) { | |
243 | die "ring 1 addresses must be from same IP family as ring 0!\n" | |
244 | if $use_ipv6 != ip_is_ipv6($bindnet1_addr) || | |
245 | $use_ipv6 != ip_is_ipv6($ring1_addr); | |
246 | ||
247 | $conf->{totem}->{interface}->{1} = { | |
248 | bindnetaddr => $bindnet1_addr, | |
249 | ringnumber => 1, | |
250 | }; | |
251 | $conf->{totem}->{rrp_mode} = 'passive'; | |
252 | $conf->{nodelist}->{node}->{$nodename}->{ring1_addr} = $ring1_addr; | |
253 | } | |
254 | ||
255 | return { main => $conf }; | |
256 | } | |
257 | ||
b6973a89 | 258 | 1; |