]> git.proxmox.com Git - pve-cluster.git/blame - data/PVE/API2/ClusterConfig.pm
return cluster config and authkey in addnode API call
[pve-cluster.git] / data / PVE / API2 / ClusterConfig.pm
CommitLineData
963c06bb
DM
1package PVE::API2::ClusterConfig;
2
3use strict;
4use warnings;
b6973a89 5
963c06bb
DM
6use PVE::Tools;
7use PVE::SafeSyslog;
8use PVE::RESTHandler;
9use PVE::RPCEnvironment;
10use PVE::JSONSchema qw(get_standard_option);
11use PVE::Cluster;
b6973a89 12use PVE::Corosync;
963c06bb
DM
13
14use base qw(PVE::RESTHandler);
15
16__PACKAGE__->register_method({
17 name => 'index',
18 path => '',
19 method => 'GET',
20 description => "Directory index.",
fb7c665a
EK
21 permissions => {
22 check => ['perm', '/', [ 'Sys.Audit' ]],
23 },
963c06bb
DM
24 parameters => {
25 additionalProperties => 0,
26 properties => {},
27 },
28 returns => {
29 type => 'array',
30 items => {
31 type => "object",
32 properties => {},
33 },
34 links => [ { rel => 'child', href => "{name}" } ],
35 },
36 code => sub {
37 my ($param) = @_;
38
39 my $result = [
40 { name => 'nodes' },
41 { name => 'totem' },
42 ];
43
44 return $result;
45 }});
46
47__PACKAGE__->register_method({
48 name => 'nodes',
49 path => 'nodes',
50 method => 'GET',
51 description => "Corosync node list.",
fb7c665a
EK
52 permissions => {
53 check => ['perm', '/', [ 'Sys.Audit' ]],
54 },
963c06bb
DM
55 parameters => {
56 additionalProperties => 0,
57 properties => {},
58 },
59 returns => {
60 type => 'array',
61 items => {
62 type => "object",
63 properties => {
64 node => { type => 'string' },
65 },
66 },
67 links => [ { rel => 'child', href => "{node}" } ],
68 },
69 code => sub {
70 my ($param) = @_;
71
72
73 my $conf = PVE::Cluster::cfs_read_file('corosync.conf');
b6973a89 74 my $nodelist = PVE::Corosync::nodelist($conf);
963c06bb
DM
75
76 return PVE::RESTHandler::hash_to_array($nodelist, 'node');
77 }});
78
1d26c202
TL
79# lock method to ensure local and cluster wide atomicity
80# if we're a single node cluster just lock locally, we have no other cluster
81# node which we could contend with, else also acquire a cluster wide lock
82my $config_change_lock = sub {
83 my ($code) = @_;
84
85 my $local_lock_fn = "/var/lock/pvecm.lock";
86 PVE::Tools::lock_file($local_lock_fn, 10, sub {
87 PVE::Cluster::cfs_update(1);
88 my $members = PVE::Cluster::get_members();
89 if (scalar(keys %$members) > 1) {
90 return PVE::Cluster::cfs_lock_file('corosync.conf', 10, $code);
91 } else {
92 return $code->();
93 }
94 });
95};
96
97
98__PACKAGE__->register_method ({
99 name => 'addnode',
100 path => 'nodes/{node}',
101 method => 'POST',
102 protected => 1,
103 description => "Adds a node to the cluster configuration.",
104 parameters => {
105 additionalProperties => 0,
106 properties => {
107 node => get_standard_option('pve-node'),
108 nodeid => {
109 type => 'integer',
110 description => "Node id for this node.",
111 minimum => 1,
112 optional => 1,
113 },
114 votes => {
115 type => 'integer',
116 description => "Number of votes for this node",
117 minimum => 0,
118 optional => 1,
119 },
120 force => {
121 type => 'boolean',
122 description => "Do not throw error if node already exists.",
123 optional => 1,
124 },
125 ring0_addr => {
126 type => 'string', format => 'address',
127 description => "Hostname (or IP) of the corosync ring0 address of this node.".
128 " Defaults to nodes hostname.",
129 optional => 1,
130 },
131 ring1_addr => {
132 type => 'string', format => 'address',
133 description => "Hostname (or IP) of the corosync ring1 address, this".
134 " needs an valid bindnet1_addr.",
135 optional => 1,
136 },
137 },
138 },
331d957b
TL
139 returns => {
140 type => "object",
141 properties => {
142 corosync_authkey => {
143 type => 'string',
144 },
145 corosync_conf => {
146 type => 'string',
147 }
148 },
149 },
1d26c202
TL
150 code => sub {
151 my ($param) = @_;
152
153 PVE::Cluster::check_cfs_quorum();
154
155 my $code = sub {
156 my $conf = PVE::Cluster::cfs_read_file("corosync.conf");
157 my $nodelist = PVE::Corosync::nodelist($conf);
158 my $totem_cfg = PVE::Corosync::totem_config($conf);
159
160 my $name = $param->{node};
161
162 # ensure we do not reuse an address, that can crash the whole cluster!
163 my $check_duplicate_addr = sub {
164 my $addr = shift;
165 return if !defined($addr);
166
167 while (my ($k, $v) = each %$nodelist) {
168 next if $k eq $name; # allows re-adding a node if force is set
169 if ($v->{ring0_addr} eq $addr || ($v->{ring1_addr} && $v->{ring1_addr} eq $addr)) {
170 die "corosync: address '$addr' already defined by node '$k'\n";
171 }
172 }
173 };
174
175 &$check_duplicate_addr($param->{ring0_addr});
176 &$check_duplicate_addr($param->{ring1_addr});
177
178 $param->{ring0_addr} = $name if !$param->{ring0_addr};
179
180 die "corosync: using 'ring1_addr' parameter needs a configured ring 1 interface!\n"
181 if $param->{ring1_addr} && !defined($totem_cfg->{interface}->{1});
182
183 die "corosync: ring 1 interface configured but 'ring1_addr' parameter not defined!\n"
184 if defined($totem_cfg->{interface}->{1}) && !defined($param->{ring1_addr});
185
186 if (defined(my $res = $nodelist->{$name})) {
187 $param->{nodeid} = $res->{nodeid} if !$param->{nodeid};
188 $param->{votes} = $res->{quorum_votes} if !defined($param->{votes});
189
190 if ($res->{quorum_votes} == $param->{votes} &&
191 $res->{nodeid} == $param->{nodeid} && $param->{force}) {
192 print "forcing overwrite of configured node '$name'\n";
193 } else {
194 die "can't add existing node '$name'\n";
195 }
196 } elsif (!$param->{nodeid}) {
197 my $nodeid = 1;
198
199 while(1) {
200 my $found = 0;
201 foreach my $v (values %$nodelist) {
202 if ($v->{nodeid} eq $nodeid) {
203 $found = 1;
204 $nodeid++;
205 last;
206 }
207 }
208 last if !$found;
209 };
210
211 $param->{nodeid} = $nodeid;
212 }
213
214 $param->{votes} = 1 if !defined($param->{votes});
215
216 PVE::Cluster::gen_local_dirs($name);
217
218 eval { PVE::Cluster::ssh_merge_keys(); };
219 warn $@ if $@;
220
221 $nodelist->{$name} = {
222 ring0_addr => $param->{ring0_addr},
223 nodeid => $param->{nodeid},
224 name => $name,
225 };
226 $nodelist->{$name}->{ring1_addr} = $param->{ring1_addr} if $param->{ring1_addr};
227 $nodelist->{$name}->{quorum_votes} = $param->{votes} if $param->{votes};
228
855671ec
TL
229 PVE::Cluster::log_msg('notice', 'root@pam', "adding node $name to cluster");
230
1d26c202
TL
231 PVE::Corosync::update_nodelist($conf, $nodelist);
232 };
233
234 $config_change_lock->($code);
235 die $@ if $@;
236
331d957b
TL
237 my $clusterconf = "/etc/pve/corosync.conf";
238 my $authfile = "/etc/corosync/authkey";
239
240 my $res = {
241 corosync_authkey => PVE::Tools::file_get_contents($authfile),
242 corosync_conf => PVE::Tools::file_get_contents($clusterconf),
243 };
244
245 return $res;
1d26c202
TL
246 }});
247
248
249__PACKAGE__->register_method ({
250 name => 'delnode',
251 path => 'nodes/{node}',
252 method => 'DELETE',
253 protected => 1,
254 description => "Removes a node from the cluster configuration.",
255 parameters => {
256 additionalProperties => 0,
257 properties => {
258 node => get_standard_option('pve-node'),
259 },
260 },
261 returns => { type => 'null' },
262 code => sub {
263 my ($param) = @_;
264
265 my $local_node = PVE::INotify::nodename();
266 die "Cannot delete myself from cluster!\n" if $param->{node} eq $local_node;
267
268 PVE::Cluster::check_cfs_quorum();
269
270 my $code = sub {
271 my $conf = PVE::Cluster::cfs_read_file("corosync.conf");
272 my $nodelist = PVE::Corosync::nodelist($conf);
273
274 my $node;
275 my $nodeid;
276
277 foreach my $tmp_node (keys %$nodelist) {
278 my $d = $nodelist->{$tmp_node};
279 my $ring0_addr = $d->{ring0_addr};
280 my $ring1_addr = $d->{ring1_addr};
281 if (($tmp_node eq $param->{node}) ||
282 (defined($ring0_addr) && ($ring0_addr eq $param->{node})) ||
283 (defined($ring1_addr) && ($ring1_addr eq $param->{node}))) {
284 $node = $tmp_node;
285 $nodeid = $d->{nodeid};
286 last;
287 }
288 }
289
290 die "Node/IP: $param->{node} is not a known host of the cluster.\n"
291 if !defined($node);
292
855671ec
TL
293 PVE::Cluster::log_msg('notice', 'root@pam', "deleting node $node from cluster");
294
1d26c202
TL
295 delete $nodelist->{$node};
296
297 PVE::Corosync::update_nodelist($conf, $nodelist);
298
299 PVE::Tools::run_command(['corosync-cfgtool','-k', $nodeid]) if defined($nodeid);
300 };
301
302 $config_change_lock->($code);
303 die $@ if $@;
304
305 return undef;
306 }});
307
963c06bb
DM
308__PACKAGE__->register_method({
309 name => 'totem',
310 path => 'totem',
311 method => 'GET',
312 description => "Get corosync totem protocol settings.",
fb7c665a
EK
313 permissions => {
314 check => ['perm', '/', [ 'Sys.Audit' ]],
315 },
963c06bb
DM
316 parameters => {
317 additionalProperties => 0,
318 properties => {},
319 },
320 returns => {
321 type => "object",
322 properties => {},
323 },
324 code => sub {
325 my ($param) = @_;
326
327
328 my $conf = PVE::Cluster::cfs_read_file('corosync.conf');
329
496de919
TL
330 my $totem_cfg = $conf->{main}->{totem};
331
332 return $totem_cfg;
963c06bb
DM
333 }});
334
3351;