]> git.proxmox.com Git - pve-cluster.git/blob - data/PVE/Corosync.pm
corosync config parser: move to hash format
[pve-cluster.git] / data / PVE / Corosync.pm
1 package PVE::Corosync;
2
3 use strict;
4 use warnings;
5
6 use Digest::SHA;
7 use Clone 'clone';
8
9 use PVE::Cluster;
10
11 my $basedir = "/etc/pve";
12
13 my $conf_array_sections = {
14 node => 1,
15 interface => 1,
16 };
17
18 # a very simply parser ...
19 sub parse_conf {
20 my ($filename, $raw) = @_;
21
22 return {} if !$raw;
23
24 my $digest = Digest::SHA::sha1_hex(defined($raw) ? $raw : '');
25
26 $raw =~ s/#.*$//mg;
27 $raw =~ s/\r?\n/ /g;
28 $raw =~ s/\s+/ /g;
29 $raw =~ s/^\s+//;
30 $raw =~ s/\s*$//;
31
32 my @tokens = split(/\s/, $raw);
33
34 my $conf = { 'main' => {} };
35
36 my $stack = [];
37 my $section = $conf->{main};
38
39 while (defined(my $token = shift @tokens)) {
40 my $nexttok = $tokens[0];
41
42 if ($nexttok && ($nexttok eq '{')) {
43 shift @tokens; # skip '{'
44 my $new_section = {};
45 if ($conf_array_sections->{$token}) {
46 $section->{$token} = [] if !defined($section->{$token});
47 push @{$section->{$token}}, $new_section;
48 } elsif (!defined($section->{$token})) {
49 $section->{$token} = $new_section;
50 } else {
51 die "section '$token' already exists and not marked as array!\n";
52 }
53 push @$stack, $section;
54 $section = $new_section;
55 next;
56 }
57
58 if ($token eq '}') {
59 $section = pop @$stack;
60 die "parse error - uncexpected '}'\n" if !$section;
61 next;
62 }
63
64 my $key = $token;
65 die "missing ':' after key '$key'\n" if ! ($key =~ s/:$//);
66
67 die "parse error - no value for '$key'\n" if !defined($nexttok);
68 my $value = shift @tokens;
69
70 $section->{$key} = $value;
71 }
72
73 $conf->{digest} = $digest;
74
75 return $conf;
76 }
77
78 my $dump_section;
79 $dump_section = sub {
80 my ($section, $prefix) = @_;
81
82 my $raw = '';
83
84 foreach my $k (sort keys %$section) {
85 my $v = $section->{$k};
86 if (ref($v) eq 'HASH') {
87 $raw .= $prefix . "$k {\n";
88 $raw .= &$dump_section($v, "$prefix ");
89 $raw .= $prefix . "}\n";
90 $raw .= "\n" if !$prefix; # add extra newline at 1st level only
91 } elsif (ref($v) eq 'ARRAY') {
92 foreach my $child (@$v) {
93 $raw .= $prefix . "$k {\n";
94 $raw .= &$dump_section($child, "$prefix ");
95 $raw .= $prefix . "}\n";
96 }
97 } elsif (!ref($v)) {
98 $raw .= $prefix . "$k: $v\n";
99 } else {
100 die "unexpected reference in config hash: $k => ". ref($v) ."\n";
101 }
102 }
103
104 return $raw;
105 };
106
107 sub write_conf {
108 my ($filename, $conf) = @_;
109
110 die "no main section" if !defined($conf->{main});
111
112 my $raw = &$dump_section($conf->{main}, '');
113
114 return $raw;
115 }
116
117 sub conf_version {
118 my ($conf, $noerr, $new_value) = @_;
119
120 my $totem = $conf->{main}->{totem};
121 if (defined($totem) && defined($totem->{config_version})) {
122 $totem->{config_version} = $new_value if $new_value;
123 return $totem->{config_version};
124 }
125
126 return undef if $noerr;
127
128 die "invalid corosync config - unable to read version\n";
129 }
130
131 # read only - use "rename corosync.conf.new corosync.conf" to write
132 PVE::Cluster::cfs_register_file('corosync.conf', \&parse_conf);
133 # this is read/write
134 PVE::Cluster::cfs_register_file('corosync.conf.new', \&parse_conf,
135 \&write_conf);
136
137 sub check_conf_exists {
138 my ($silent) = @_;
139
140 $silent = $silent // 0;
141
142 my $exists = -f "$basedir/corosync.conf";
143
144 warn "Corosync config '$basedir/corosync.conf' does not exist - is this node part of a cluster?\n"
145 if !$silent && !$exists;
146
147 return $exists;
148 }
149
150 sub update_nodelist {
151 my ($conf, $nodelist) = @_;
152
153 delete $conf->{digest};
154
155 my $version = conf_version($conf);
156 conf_version($conf, undef, $version + 1);
157
158 $conf->{main}->{nodelist}->{node} = [values %$nodelist];
159
160 PVE::Cluster::cfs_write_file("corosync.conf.new", $conf);
161
162 rename("/etc/pve/corosync.conf.new", "/etc/pve/corosync.conf")
163 || die "activate corosync.conf.new failed - $!\n";
164 }
165
166 sub nodelist {
167 my ($conf) = @_;
168
169 my $nodelist = {};
170
171 my $nodes = $conf->{main}->{nodelist}->{node};
172
173 foreach my $node (@$nodes) {
174 # use 'name' over 'ring0_addr' if set
175 my $name = $node->{name} // $node->{ring0_addr};
176 if ($name) {
177 $nodelist->{$name} = $node;
178 }
179 }
180
181 return $nodelist;
182 }
183
184 sub totem_config {
185 my ($conf) = @_;
186
187 # we reorder elements from totem->interface and don't want to change $conf
188 my $totem = clone($conf->{main}->{totem});
189 my $ifs = $totem->{interface};
190
191 $totem->{interface} = {};
192 foreach my $if (@$ifs) {
193 $totem->{interface}->{$if->{ringnumber}} = $if;
194 }
195
196 return $totem;
197 }
198
199 1;