]>
Commit | Line | Data |
---|---|---|
b6973a89 TL |
1 | package PVE::Corosync; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | ||
6 | use Digest::SHA; | |
7 | ||
8 | use PVE::Cluster; | |
9 | ||
10 | my $basedir = "/etc/pve"; | |
11 | ||
12 | # a very simply parser ... | |
13 | sub parse_conf { | |
14 | my ($filename, $raw) = @_; | |
15 | ||
16 | return {} if !$raw; | |
17 | ||
18 | my $digest = Digest::SHA::sha1_hex(defined($raw) ? $raw : ''); | |
19 | ||
20 | $raw =~ s/#.*$//mg; | |
21 | $raw =~ s/\r?\n/ /g; | |
22 | $raw =~ s/\s+/ /g; | |
23 | $raw =~ s/^\s+//; | |
24 | $raw =~ s/\s*$//; | |
25 | ||
26 | my @tokens = split(/\s/, $raw); | |
27 | ||
28 | my $conf = { section => 'main', children => [] }; | |
29 | ||
30 | my $stack = []; | |
31 | my $section = $conf; | |
32 | ||
33 | while (defined(my $token = shift @tokens)) { | |
34 | my $nexttok = $tokens[0]; | |
35 | ||
36 | if ($nexttok && ($nexttok eq '{')) { | |
37 | shift @tokens; # skip '{' | |
38 | my $new_section = { | |
39 | section => $token, | |
40 | children => [], | |
41 | }; | |
42 | push @{$section->{children}}, $new_section; | |
43 | push @$stack, $section; | |
44 | $section = $new_section; | |
45 | next; | |
46 | } | |
47 | ||
48 | if ($token eq '}') { | |
49 | $section = pop @$stack; | |
50 | die "parse error - uncexpected '}'\n" if !$section; | |
51 | next; | |
52 | } | |
53 | ||
54 | my $key = $token; | |
55 | die "missing ':' after key '$key'\n" if ! ($key =~ s/:$//); | |
56 | ||
57 | die "parse error - no value for '$key'\n" if !defined($nexttok); | |
58 | my $value = shift @tokens; | |
59 | ||
60 | push @{$section->{children}}, { key => $key, value => $value }; | |
61 | } | |
62 | ||
63 | $conf->{digest} = $digest; | |
64 | ||
65 | return $conf; | |
66 | } | |
67 | ||
68 | my $dump_section; | |
69 | $dump_section = sub { | |
70 | my ($section, $prefix) = @_; | |
71 | ||
72 | my $raw = $prefix . $section->{section} . " {\n"; | |
73 | ||
74 | my @list = grep { defined($_->{key}) } @{$section->{children}}; | |
75 | foreach my $child (sort {$a->{key} cmp $b->{key}} @list) { | |
76 | $raw .= $prefix . " $child->{key}: $child->{value}\n"; | |
77 | } | |
78 | ||
79 | @list = grep { defined($_->{section}) } @{$section->{children}}; | |
80 | foreach my $child (sort {$a->{section} cmp $b->{section}} @list) { | |
81 | $raw .= &$dump_section($child, "$prefix "); | |
82 | } | |
83 | ||
84 | $raw .= $prefix . "}\n\n"; | |
85 | ||
86 | return $raw; | |
87 | ||
88 | }; | |
89 | ||
90 | sub write_conf { | |
91 | my ($filename, $conf) = @_; | |
92 | ||
93 | my $raw = ''; | |
94 | ||
95 | my $prefix = ''; | |
96 | ||
97 | die "no main section" if $conf->{section} ne 'main'; | |
98 | ||
99 | my @list = grep { defined($_->{key}) } @{$conf->{children}}; | |
100 | foreach my $child (sort {$a->{key} cmp $b->{key}} @list) { | |
101 | $raw .= "$child->{key}: $child->{value}\n"; | |
102 | } | |
103 | ||
104 | @list = grep { defined($_->{section}) } @{$conf->{children}}; | |
105 | foreach my $child (sort {$a->{section} cmp $b->{section}} @list) { | |
106 | $raw .= &$dump_section($child, $prefix); | |
107 | } | |
108 | ||
109 | return $raw; | |
110 | } | |
111 | ||
112 | sub conf_version { | |
113 | my ($conf, $noerr, $new_value) = @_; | |
114 | ||
115 | foreach my $child (@{$conf->{children}}) { | |
116 | next if !defined($child->{section}); | |
117 | if ($child->{section} eq 'totem') { | |
118 | foreach my $e (@{$child->{children}}) { | |
119 | next if !defined($e->{key}); | |
120 | if ($e->{key} eq 'config_version') { | |
121 | if ($new_value) { | |
122 | $e->{value} = $new_value; | |
123 | return $new_value; | |
124 | } elsif (my $version = int($e->{value})) { | |
125 | return $version; | |
126 | } | |
127 | last; | |
128 | } | |
129 | } | |
130 | } | |
131 | } | |
132 | ||
133 | return undef if $noerr; | |
134 | ||
135 | die "invalid corosync config - unable to read version\n"; | |
136 | } | |
137 | ||
138 | # read only - use "rename corosync.conf.new corosync.conf" to write | |
139 | PVE::Cluster::cfs_register_file('corosync.conf', \&parse_conf); | |
140 | # this is read/write | |
141 | PVE::Cluster::cfs_register_file('corosync.conf.new', \&parse_conf, | |
142 | \&write_conf); | |
143 | ||
144 | sub check_conf_exists { | |
145 | my ($silent) = @_; | |
146 | ||
147 | $silent = $silent // 0; | |
148 | ||
149 | my $exists = -f "$basedir/corosync.conf"; | |
150 | ||
151 | warn "Corosync config '$basedir/corosync.conf' does not exist - is this node part of a cluster?\n" | |
152 | if !$silent && !$exists; | |
153 | ||
154 | return $exists; | |
155 | } | |
156 | ||
157 | sub update_nodelist { | |
158 | my ($conf, $nodelist) = @_; | |
159 | ||
160 | delete $conf->{digest}; | |
161 | ||
162 | my $version = conf_version($conf); | |
163 | conf_version($conf, undef, $version + 1); | |
164 | ||
165 | my $children = []; | |
166 | foreach my $v (values %$nodelist) { | |
167 | next if !($v->{ring0_addr} || $v->{name}); | |
168 | my $kv = []; | |
169 | foreach my $k (keys %$v) { | |
170 | push @$kv, { key => $k, value => $v->{$k} }; | |
171 | } | |
172 | my $ns = { section => 'node', children => $kv }; | |
173 | push @$children, $ns; | |
174 | } | |
175 | ||
176 | foreach my $main (@{$conf->{children}}) { | |
177 | next if !defined($main->{section}); | |
178 | if ($main->{section} eq 'nodelist') { | |
179 | $main->{children} = $children; | |
180 | last; | |
181 | } | |
182 | } | |
183 | ||
184 | ||
185 | PVE::Cluster::cfs_write_file("corosync.conf.new", $conf); | |
186 | ||
187 | rename("/etc/pve/corosync.conf.new", "/etc/pve/corosync.conf") | |
188 | || die "activate corosync.conf.new failed - $!\n"; | |
189 | } | |
190 | ||
191 | sub nodelist { | |
192 | my ($conf) = @_; | |
193 | ||
194 | my $nodelist = {}; | |
195 | ||
196 | foreach my $main (@{$conf->{children}}) { | |
197 | next if !defined($main->{section}); | |
198 | if ($main->{section} eq 'nodelist') { | |
199 | foreach my $ne (@{$main->{children}}) { | |
200 | next if !defined($ne->{section}) || ($ne->{section} ne 'node'); | |
201 | my $node = { quorum_votes => 1 }; | |
202 | my $name; | |
203 | foreach my $child (@{$ne->{children}}) { | |
204 | next if !defined($child->{key}); | |
205 | $node->{$child->{key}} = $child->{value}; | |
206 | # use 'name' over 'ring0_addr' if set | |
207 | if ($child->{key} eq 'name') { | |
208 | delete $nodelist->{$name} if $name; | |
209 | $name = $child->{value}; | |
210 | $nodelist->{$name} = $node; | |
211 | } elsif(!$name && $child->{key} eq 'ring0_addr') { | |
212 | $name = $child->{value}; | |
213 | $nodelist->{$name} = $node; | |
214 | } | |
215 | } | |
216 | } | |
217 | } | |
218 | } | |
219 | ||
220 | return $nodelist; | |
221 | } | |
222 | ||
223 | # get a hash representation of the corosync config totem section | |
224 | sub totem_config { | |
225 | my ($conf) = @_; | |
226 | ||
227 | my $res = {}; | |
228 | ||
229 | foreach my $main (@{$conf->{children}}) { | |
230 | next if !defined($main->{section}) || | |
231 | $main->{section} ne 'totem'; | |
232 | ||
233 | foreach my $e (@{$main->{children}}) { | |
234 | ||
235 | if ($e->{section} && $e->{section} eq 'interface') { | |
236 | my $entry = {}; | |
237 | ||
238 | $res->{interface} = {}; | |
239 | ||
240 | foreach my $child (@{$e->{children}}) { | |
241 | next if !defined($child->{key}); | |
242 | $entry->{$child->{key}} = $child->{value}; | |
243 | if($child->{key} eq 'ringnumber') { | |
244 | $res->{interface}->{$child->{value}} = $entry; | |
245 | } | |
246 | } | |
247 | ||
248 | } elsif ($e->{key}) { | |
249 | $res->{$e->{key}} = $e->{value}; | |
250 | } | |
251 | } | |
252 | } | |
253 | ||
254 | return $res; | |
255 | } | |
256 | ||
257 | 1; |