]> git.proxmox.com Git - pve-cluster.git/blame - data/PVE/API2/ClusterConfig.pm
use run_command instead of system
[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 },
139 returns => { type => 'null' },
140 code => sub {
141 my ($param) = @_;
142
143 PVE::Cluster::check_cfs_quorum();
144
145 my $code = sub {
146 my $conf = PVE::Cluster::cfs_read_file("corosync.conf");
147 my $nodelist = PVE::Corosync::nodelist($conf);
148 my $totem_cfg = PVE::Corosync::totem_config($conf);
149
150 my $name = $param->{node};
151
152 # ensure we do not reuse an address, that can crash the whole cluster!
153 my $check_duplicate_addr = sub {
154 my $addr = shift;
155 return if !defined($addr);
156
157 while (my ($k, $v) = each %$nodelist) {
158 next if $k eq $name; # allows re-adding a node if force is set
159 if ($v->{ring0_addr} eq $addr || ($v->{ring1_addr} && $v->{ring1_addr} eq $addr)) {
160 die "corosync: address '$addr' already defined by node '$k'\n";
161 }
162 }
163 };
164
165 &$check_duplicate_addr($param->{ring0_addr});
166 &$check_duplicate_addr($param->{ring1_addr});
167
168 $param->{ring0_addr} = $name if !$param->{ring0_addr};
169
170 die "corosync: using 'ring1_addr' parameter needs a configured ring 1 interface!\n"
171 if $param->{ring1_addr} && !defined($totem_cfg->{interface}->{1});
172
173 die "corosync: ring 1 interface configured but 'ring1_addr' parameter not defined!\n"
174 if defined($totem_cfg->{interface}->{1}) && !defined($param->{ring1_addr});
175
176 if (defined(my $res = $nodelist->{$name})) {
177 $param->{nodeid} = $res->{nodeid} if !$param->{nodeid};
178 $param->{votes} = $res->{quorum_votes} if !defined($param->{votes});
179
180 if ($res->{quorum_votes} == $param->{votes} &&
181 $res->{nodeid} == $param->{nodeid} && $param->{force}) {
182 print "forcing overwrite of configured node '$name'\n";
183 } else {
184 die "can't add existing node '$name'\n";
185 }
186 } elsif (!$param->{nodeid}) {
187 my $nodeid = 1;
188
189 while(1) {
190 my $found = 0;
191 foreach my $v (values %$nodelist) {
192 if ($v->{nodeid} eq $nodeid) {
193 $found = 1;
194 $nodeid++;
195 last;
196 }
197 }
198 last if !$found;
199 };
200
201 $param->{nodeid} = $nodeid;
202 }
203
204 $param->{votes} = 1 if !defined($param->{votes});
205
206 PVE::Cluster::gen_local_dirs($name);
207
208 eval { PVE::Cluster::ssh_merge_keys(); };
209 warn $@ if $@;
210
211 $nodelist->{$name} = {
212 ring0_addr => $param->{ring0_addr},
213 nodeid => $param->{nodeid},
214 name => $name,
215 };
216 $nodelist->{$name}->{ring1_addr} = $param->{ring1_addr} if $param->{ring1_addr};
217 $nodelist->{$name}->{quorum_votes} = $param->{votes} if $param->{votes};
218
855671ec
TL
219 PVE::Cluster::log_msg('notice', 'root@pam', "adding node $name to cluster");
220
1d26c202
TL
221 PVE::Corosync::update_nodelist($conf, $nodelist);
222 };
223
224 $config_change_lock->($code);
225 die $@ if $@;
226
227 return undef;
228 }});
229
230
231__PACKAGE__->register_method ({
232 name => 'delnode',
233 path => 'nodes/{node}',
234 method => 'DELETE',
235 protected => 1,
236 description => "Removes a node from the cluster configuration.",
237 parameters => {
238 additionalProperties => 0,
239 properties => {
240 node => get_standard_option('pve-node'),
241 },
242 },
243 returns => { type => 'null' },
244 code => sub {
245 my ($param) = @_;
246
247 my $local_node = PVE::INotify::nodename();
248 die "Cannot delete myself from cluster!\n" if $param->{node} eq $local_node;
249
250 PVE::Cluster::check_cfs_quorum();
251
252 my $code = sub {
253 my $conf = PVE::Cluster::cfs_read_file("corosync.conf");
254 my $nodelist = PVE::Corosync::nodelist($conf);
255
256 my $node;
257 my $nodeid;
258
259 foreach my $tmp_node (keys %$nodelist) {
260 my $d = $nodelist->{$tmp_node};
261 my $ring0_addr = $d->{ring0_addr};
262 my $ring1_addr = $d->{ring1_addr};
263 if (($tmp_node eq $param->{node}) ||
264 (defined($ring0_addr) && ($ring0_addr eq $param->{node})) ||
265 (defined($ring1_addr) && ($ring1_addr eq $param->{node}))) {
266 $node = $tmp_node;
267 $nodeid = $d->{nodeid};
268 last;
269 }
270 }
271
272 die "Node/IP: $param->{node} is not a known host of the cluster.\n"
273 if !defined($node);
274
855671ec
TL
275 PVE::Cluster::log_msg('notice', 'root@pam', "deleting node $node from cluster");
276
1d26c202
TL
277 delete $nodelist->{$node};
278
279 PVE::Corosync::update_nodelist($conf, $nodelist);
280
281 PVE::Tools::run_command(['corosync-cfgtool','-k', $nodeid]) if defined($nodeid);
282 };
283
284 $config_change_lock->($code);
285 die $@ if $@;
286
287 return undef;
288 }});
289
963c06bb
DM
290__PACKAGE__->register_method({
291 name => 'totem',
292 path => 'totem',
293 method => 'GET',
294 description => "Get corosync totem protocol settings.",
fb7c665a
EK
295 permissions => {
296 check => ['perm', '/', [ 'Sys.Audit' ]],
297 },
963c06bb
DM
298 parameters => {
299 additionalProperties => 0,
300 properties => {},
301 },
302 returns => {
303 type => "object",
304 properties => {},
305 },
306 code => sub {
307 my ($param) = @_;
308
309
310 my $conf = PVE::Cluster::cfs_read_file('corosync.conf');
311
496de919
TL
312 my $totem_cfg = $conf->{main}->{totem};
313
314 return $totem_cfg;
963c06bb
DM
315 }});
316
3171;