]> git.proxmox.com Git - pve-cluster.git/blame - data/PVE/API2/ClusterConfig.pm
api: join info: return explicit error code for no-cluster
[pve-cluster.git] / data / PVE / API2 / ClusterConfig.pm
CommitLineData
963c06bb
DM
1package PVE::API2::ClusterConfig;
2
3use strict;
4use warnings;
b6973a89 5
b2cae1f8 6use PVE::Exception;
963c06bb
DM
7use PVE::Tools;
8use PVE::SafeSyslog;
9use PVE::RESTHandler;
10use PVE::RPCEnvironment;
11use PVE::JSONSchema qw(get_standard_option);
12use PVE::Cluster;
6ed49eb1 13use PVE::APIClient::LWP;
b6973a89 14use PVE::Corosync;
c5204e14 15use PVE::Cluster::Setup;
963c06bb 16
f0f8ee41
OB
17use IO::Socket::UNIX;
18
963c06bb
DM
19use base qw(PVE::RESTHandler);
20
74e09a93
TL
21my $clusterconf = "/etc/pve/corosync.conf";
22my $authfile = "/etc/corosync/authkey";
b184a69d 23my $local_cluster_lock = "/var/lock/pvecm.lock";
74e09a93 24
10c6810e
TL
25my $nodeid_desc = {
26 type => 'integer',
27 description => "Node id for this node.",
28 minimum => 1,
29 optional => 1,
30};
31PVE::JSONSchema::register_standard_option("corosync-nodeid", $nodeid_desc);
32
963c06bb
DM
33__PACKAGE__->register_method({
34 name => 'index',
35 path => '',
36 method => 'GET',
37 description => "Directory index.",
fb7c665a
EK
38 permissions => {
39 check => ['perm', '/', [ 'Sys.Audit' ]],
40 },
963c06bb
DM
41 parameters => {
42 additionalProperties => 0,
43 properties => {},
44 },
45 returns => {
46 type => 'array',
47 items => {
48 type => "object",
49 properties => {},
50 },
51 links => [ { rel => 'child', href => "{name}" } ],
52 },
53 code => sub {
54 my ($param) = @_;
55
56 my $result = [
57 { name => 'nodes' },
58 { name => 'totem' },
6ed49eb1 59 { name => 'join' },
f0f8ee41 60 { name => 'qdevice' },
a755ff54 61 { name => 'apiversion' },
6ed49eb1 62 ];
963c06bb
DM
63
64 return $result;
65 }});
66
a755ff54
SR
67__PACKAGE__->register_method ({
68 name => 'join_api_version',
69 path => 'apiversion',
70 method => 'GET',
71 description => "Return the version of the cluster join API available on this node.",
72 permissions => {
73 check => ['perm', '/', [ 'Sys.Audit' ]],
74 },
75 parameters => {
76 additionalProperties => 0,
77 properties => {},
78 },
79 returns => {
80 type => 'integer',
81 minimum => 0,
82 description => "Cluster Join API version, currently " . PVE::Cluster::Setup::JOIN_API_VERSION,
83 },
84 code => sub {
85 return PVE::Cluster::Setup::JOIN_API_VERSION;
86 }});
87
74e09a93
TL
88__PACKAGE__->register_method ({
89 name => 'create',
90 path => '',
91 method => 'POST',
92 protected => 1,
8ef581e4
SR
93 description => "Generate new cluster configuration. If no links given,"
94 . " default to local IP address as link0.",
74e09a93
TL
95 parameters => {
96 additionalProperties => 0,
8ef581e4 97 properties => PVE::Corosync::add_corosync_link_properties({
74e09a93
TL
98 clustername => {
99 description => "The name of the cluster.",
100 type => 'string', format => 'pve-node',
101 maxLength => 15,
102 },
10c6810e 103 nodeid => get_standard_option('corosync-nodeid'),
74e09a93
TL
104 votes => {
105 type => 'integer',
106 description => "Number of votes for this node.",
107 minimum => 1,
108 optional => 1,
109 },
8ef581e4 110 }),
74e09a93 111 },
333a9bc8 112 returns => { type => 'string' },
74e09a93
TL
113 code => sub {
114 my ($param) = @_;
115
1e8e16ea 116 die "cluster config '$clusterconf' already exists\n" if -f $clusterconf;
74e09a93 117
333a9bc8
TL
118 my $rpcenv = PVE::RPCEnvironment::get();
119 my $authuser = $rpcenv->get_user();
120
b184a69d 121 my $code = sub {
17a4445f 122 STDOUT->autoflush();
c5204e14
FG
123 PVE::Cluster::Setup::setup_sshd_config(1);
124 PVE::Cluster::Setup::setup_rootsshconfig();
125 PVE::Cluster::Setup::setup_ssh_keys();
74e09a93 126
333a9bc8
TL
127 PVE::Tools::run_command(['/usr/sbin/corosync-keygen', '-lk', $authfile])
128 if !-f $authfile;
129 die "no authentication key available\n" if -f !$authfile;
74e09a93 130
333a9bc8 131 my $nodename = PVE::INotify::nodename();
74e09a93 132
333a9bc8 133 # get the corosync basis config for the new cluster
8ef581e4 134 my $config = PVE::Corosync::create_conf($nodename, $param);
74e09a93 135
333a9bc8
TL
136 print "Writing corosync config to /etc/pve/corosync.conf\n";
137 PVE::Corosync::atomic_write_conf($config);
74e09a93 138
333a9bc8 139 my $local_ip_address = PVE::Cluster::remote_node_ip($nodename);
c5204e14
FG
140 PVE::Cluster::Setup::ssh_merge_keys();
141 PVE::Cluster::Setup::gen_pve_node_files($nodename, $local_ip_address);
142 PVE::Cluster::Setup::ssh_merge_known_hosts($nodename, $local_ip_address, 1);
74e09a93 143
333a9bc8
TL
144 print "Restart corosync and cluster filesystem\n";
145 PVE::Tools::run_command('systemctl restart corosync pve-cluster');
146 };
74e09a93 147
b184a69d
TL
148 my $worker = sub {
149 PVE::Tools::lock_file($local_cluster_lock, 10, $code);
150 die $@ if $@;
151 };
152
65ee8001 153 return $rpcenv->fork_worker('clustercreate', $param->{clustername}, $authuser, $worker);
74e09a93
TL
154}});
155
963c06bb
DM
156__PACKAGE__->register_method({
157 name => 'nodes',
158 path => 'nodes',
159 method => 'GET',
160 description => "Corosync node list.",
fb7c665a
EK
161 permissions => {
162 check => ['perm', '/', [ 'Sys.Audit' ]],
163 },
963c06bb
DM
164 parameters => {
165 additionalProperties => 0,
166 properties => {},
167 },
168 returns => {
169 type => 'array',
170 items => {
171 type => "object",
172 properties => {
173 node => { type => 'string' },
174 },
175 },
176 links => [ { rel => 'child', href => "{node}" } ],
177 },
178 code => sub {
179 my ($param) = @_;
180
181
182 my $conf = PVE::Cluster::cfs_read_file('corosync.conf');
b6973a89 183 my $nodelist = PVE::Corosync::nodelist($conf);
963c06bb
DM
184
185 return PVE::RESTHandler::hash_to_array($nodelist, 'node');
186 }});
187
1d26c202
TL
188# lock method to ensure local and cluster wide atomicity
189# if we're a single node cluster just lock locally, we have no other cluster
190# node which we could contend with, else also acquire a cluster wide lock
191my $config_change_lock = sub {
192 my ($code) = @_;
193
b184a69d 194 PVE::Tools::lock_file($local_cluster_lock, 10, sub {
1d26c202
TL
195 PVE::Cluster::cfs_update(1);
196 my $members = PVE::Cluster::get_members();
197 if (scalar(keys %$members) > 1) {
34b23d46 198 my $res = PVE::Cluster::cfs_lock_file('corosync.conf', 10, $code);
7e192a44
TL
199
200 # cfs_lock_file only sets $@ but lock_file doesn't propagates $@ unless we die here
34b23d46 201 die $@ if defined($@);
7e192a44 202
34b23d46 203 return $res;
1d26c202
TL
204 } else {
205 return $code->();
206 }
207 });
208};
209
1d26c202
TL
210__PACKAGE__->register_method ({
211 name => 'addnode',
212 path => 'nodes/{node}',
213 method => 'POST',
214 protected => 1,
79cf5a70 215 description => "Adds a node to the cluster configuration. This call is for internal use.",
1d26c202
TL
216 parameters => {
217 additionalProperties => 0,
8ef581e4 218 properties => PVE::Corosync::add_corosync_link_properties({
1d26c202 219 node => get_standard_option('pve-node'),
10c6810e 220 nodeid => get_standard_option('corosync-nodeid'),
1d26c202
TL
221 votes => {
222 type => 'integer',
223 description => "Number of votes for this node",
224 minimum => 0,
225 optional => 1,
226 },
227 force => {
228 type => 'boolean',
229 description => "Do not throw error if node already exists.",
230 optional => 1,
231 },
88b4cb13
SR
232 new_node_ip => {
233 type => 'string',
234 description => "IP Address of node to add. Used as fallback if no links are given.",
235 format => 'ip',
236 optional => 1,
237 },
a755ff54
SR
238 apiversion => {
239 type => 'integer',
240 description => 'The JOIN_API_VERSION of the new node.',
241 optional => 1,
242 },
8ef581e4 243 }),
1d26c202 244 },
331d957b
TL
245 returns => {
246 type => "object",
247 properties => {
248 corosync_authkey => {
249 type => 'string',
250 },
251 corosync_conf => {
252 type => 'string',
b2cae1f8
SR
253 },
254 warnings => {
255 type => 'array',
256 items => {
257 type => 'string',
258 },
259 },
331d957b
TL
260 },
261 },
1d26c202
TL
262 code => sub {
263 my ($param) = @_;
264
a755ff54 265 $param->{apiversion} //= 0;
498e355e 266 PVE::Cluster::Setup::assert_node_can_join_our_version($param->{apiversion});
a755ff54 267
1d26c202
TL
268 PVE::Cluster::check_cfs_quorum();
269
b2cae1f8
SR
270 my $vc_errors;
271 my $vc_warnings;
272
1d26c202
TL
273 my $code = sub {
274 my $conf = PVE::Cluster::cfs_read_file("corosync.conf");
275 my $nodelist = PVE::Corosync::nodelist($conf);
276 my $totem_cfg = PVE::Corosync::totem_config($conf);
277
b2cae1f8
SR
278 ($vc_errors, $vc_warnings) = PVE::Corosync::verify_conf($conf);
279 die if scalar(@$vc_errors);
280
1d26c202
TL
281 my $name = $param->{node};
282
283 # ensure we do not reuse an address, that can crash the whole cluster!
284 my $check_duplicate_addr = sub {
1584e3a1
TL
285 my $link = shift;
286 return if !defined($link) || !defined($link->{address});
287 my $addr = $link->{address};
1d26c202
TL
288
289 while (my ($k, $v) = each %$nodelist) {
290 next if $k eq $name; # allows re-adding a node if force is set
1584e3a1 291
8ef581e4 292 for my $linknumber (0..PVE::Corosync::MAX_LINK_INDEX) {
1584e3a1
TL
293 my $id = "ring${linknumber}_addr";
294 next if !defined($v->{$id});
295
296 die "corosync: address '$addr' already used on link $id by node '$k'\n"
297 if $v->{$id} eq $addr;
dd92cac5 298 }
1d26c202
TL
299 }
300 };
301
8ef581e4
SR
302 my $links = PVE::Corosync::extract_corosync_link_args($param);
303 foreach my $link (values %$links) {
304 $check_duplicate_addr->($link);
305 }
1d26c202 306
88b4cb13
SR
307 # Make sure that the newly added node has links compatible with the
308 # rest of the cluster. To start, extract all links that currently
309 # exist. Check any node, all have the same links here (because of
310 # verify_conf above).
311 my $node_options = (values %$nodelist)[0];
312 my $cluster_links = {};
313 foreach my $opt (keys %$node_options) {
314 my ($linktype, $linkid) = PVE::Corosync::parse_link_entry($opt);
315 next if !defined($linktype);
316 $cluster_links->{$linkid} = $node_options->{$opt};
317 }
318
319 # in case no fallback IP was passed, but instead only a single link,
320 # treat it as fallback to allow link-IDs to be matched automatically
321 # FIXME: remove in 8.0 or when joining an old node not supporting
322 # new_node_ip becomes infeasible otherwise
323 my $legacy_fallback = 0;
a755ff54 324 if (!$param->{new_node_ip} && scalar(%$links) == 1 && $param->{apiversion} == 0) {
88b4cb13
SR
325 my $passed_link_id = (keys %$links)[0];
326 my $passed_link = delete $links->{$passed_link_id};
327 $param->{new_node_ip} = $passed_link->{address};
328 $legacy_fallback = 1;
329 }
330
331 if (scalar(%$links)) {
332 # verify specified links exist and none are missing
333 for my $linknum (0..PVE::Corosync::MAX_LINK_INDEX) {
334 my $have_cluster_link = defined($cluster_links->{$linknum});
335 my $have_new_link = defined($links->{$linknum});
336
337 die "corosync: link $linknum exists in cluster config but wasn't specified for new node\n"
338 if $have_cluster_link && !$have_new_link;
339 die "corosync: link $linknum specified for new node doesn't exist in cluster config\n"
340 if !$have_cluster_link && $have_new_link;
341 }
342 } else {
343 # when called without any link parameters, fall back to
344 # new_node_ip, if all existing nodes only have a single link too
345 die "no links and no fallback ip (new_node_ip) given, cannot join cluster\n"
346 if !$param->{new_node_ip};
347
348 my $cluster_link_count = scalar(%$cluster_links);
349 if ($cluster_link_count == 1) {
350 my $linknum = (keys %$cluster_links)[0];
351 $links->{$linknum} = { address => $param->{new_node_ip} };
352 } else {
353 die "cluster has $cluster_link_count links, but only 1 given"
354 if $legacy_fallback;
355 die "no links given but multiple links found on other nodes,"
356 . " fallback only supported on single-link clusters\n";
357 }
358 }
359
1d26c202
TL
360 if (defined(my $res = $nodelist->{$name})) {
361 $param->{nodeid} = $res->{nodeid} if !$param->{nodeid};
362 $param->{votes} = $res->{quorum_votes} if !defined($param->{votes});
363
364 if ($res->{quorum_votes} == $param->{votes} &&
365 $res->{nodeid} == $param->{nodeid} && $param->{force}) {
366 print "forcing overwrite of configured node '$name'\n";
367 } else {
368 die "can't add existing node '$name'\n";
369 }
370 } elsif (!$param->{nodeid}) {
371 my $nodeid = 1;
372
373 while(1) {
374 my $found = 0;
375 foreach my $v (values %$nodelist) {
376 if ($v->{nodeid} eq $nodeid) {
377 $found = 1;
378 $nodeid++;
379 last;
380 }
381 }
382 last if !$found;
383 };
384
385 $param->{nodeid} = $nodeid;
386 }
387
388 $param->{votes} = 1 if !defined($param->{votes});
389
c5204e14 390 PVE::Cluster::Setup::gen_local_dirs($name);
1d26c202 391
c5204e14 392 eval { PVE::Cluster::Setup::ssh_merge_keys(); };
1d26c202
TL
393 warn $@ if $@;
394
395 $nodelist->{$name} = {
1d26c202
TL
396 nodeid => $param->{nodeid},
397 name => $name,
398 };
1d26c202
TL
399 $nodelist->{$name}->{quorum_votes} = $param->{votes} if $param->{votes};
400
8ef581e4
SR
401 foreach my $link (keys %$links) {
402 $nodelist->{$name}->{"ring${link}_addr"} = $links->{$link}->{address};
403 }
404
855671ec
TL
405 PVE::Cluster::log_msg('notice', 'root@pam', "adding node $name to cluster");
406
1d26c202
TL
407 PVE::Corosync::update_nodelist($conf, $nodelist);
408 };
409
410 $config_change_lock->($code);
b2cae1f8
SR
411
412 # If vc_errors is set, we died because of verify_conf.
3e1a95d7 413 # Raise this error, since it contains more information than just a single-line string.
b2cae1f8
SR
414 if (defined($vc_errors) && scalar(@$vc_errors)) {
415 my $err_hash = {};
416 my $add_errs = sub {
3e1a95d7 417 my ($type, @arr) = @_;
b2cae1f8 418 return if !scalar(@arr);
3e1a95d7 419 $err_hash->{"${type}$_"} = $arr[$_] for 0..$#arr;
b2cae1f8
SR
420 };
421
422 $add_errs->("warning", @$vc_warnings);
423 $add_errs->("error", @$vc_errors);
424
425 PVE::Exception::raise("invalid corosync.conf\n", errors => $err_hash);
426 }
427
1d26c202
TL
428 die $@ if $@;
429
331d957b
TL
430 my $res = {
431 corosync_authkey => PVE::Tools::file_get_contents($authfile),
432 corosync_conf => PVE::Tools::file_get_contents($clusterconf),
b2cae1f8 433 warnings => $vc_warnings,
331d957b
TL
434 };
435
436 return $res;
1d26c202
TL
437 }});
438
439
440__PACKAGE__->register_method ({
441 name => 'delnode',
442 path => 'nodes/{node}',
443 method => 'DELETE',
444 protected => 1,
445 description => "Removes a node from the cluster configuration.",
446 parameters => {
447 additionalProperties => 0,
448 properties => {
449 node => get_standard_option('pve-node'),
450 },
451 },
452 returns => { type => 'null' },
453 code => sub {
454 my ($param) = @_;
455
456 my $local_node = PVE::INotify::nodename();
457 die "Cannot delete myself from cluster!\n" if $param->{node} eq $local_node;
458
459 PVE::Cluster::check_cfs_quorum();
460
461 my $code = sub {
462 my $conf = PVE::Cluster::cfs_read_file("corosync.conf");
463 my $nodelist = PVE::Corosync::nodelist($conf);
464
465 my $node;
466 my $nodeid;
467
468 foreach my $tmp_node (keys %$nodelist) {
469 my $d = $nodelist->{$tmp_node};
470 my $ring0_addr = $d->{ring0_addr};
471 my $ring1_addr = $d->{ring1_addr};
472 if (($tmp_node eq $param->{node}) ||
473 (defined($ring0_addr) && ($ring0_addr eq $param->{node})) ||
474 (defined($ring1_addr) && ($ring1_addr eq $param->{node}))) {
475 $node = $tmp_node;
476 $nodeid = $d->{nodeid};
477 last;
478 }
479 }
480
481 die "Node/IP: $param->{node} is not a known host of the cluster.\n"
482 if !defined($node);
483
855671ec
TL
484 PVE::Cluster::log_msg('notice', 'root@pam', "deleting node $node from cluster");
485
1d26c202
TL
486 delete $nodelist->{$node};
487
488 PVE::Corosync::update_nodelist($conf, $nodelist);
489
490 PVE::Tools::run_command(['corosync-cfgtool','-k', $nodeid]) if defined($nodeid);
491 };
492
493 $config_change_lock->($code);
494 die $@ if $@;
495
496 return undef;
497 }});
498
fca7797d
TL
499__PACKAGE__->register_method ({
500 name => 'join_info',
501 path => 'join',
be85d00b
TL
502 permissions => {
503 check => ['perm', '/', [ 'Sys.Audit' ]],
504 },
fca7797d
TL
505 method => 'GET',
506 description => "Get information needed to join this cluster over the connected node.",
507 parameters => {
508 additionalProperties => 0,
509 properties => {
510 node => get_standard_option('pve-node', {
511 description => "The node for which the joinee gets the nodeinfo. ",
512 default => "current connected node",
513 optional => 1,
514 }),
515 },
516 },
517 returns => {
518 type => 'object',
519 additionalProperties => 0,
520 properties => {
521 nodelist => {
522 type => 'array',
523 items => {
524 type => "object",
525 additionalProperties => 1,
526 properties => {
527 name => get_standard_option('pve-node'),
528 nodeid => get_standard_option('corosync-nodeid'),
3f1f4d68 529 ring0_addr => get_standard_option('corosync-link'),
fca7797d
TL
530 quorum_votes => { type => 'integer', minimum => 0 },
531 pve_addr => { type => 'string', format => 'ip' },
532 pve_fp => get_standard_option('fingerprint-sha256'),
533 },
534 },
535 },
536 preferred_node => get_standard_option('pve-node'),
537 totem => { type => 'object' },
538 config_digest => { type => 'string' },
539 },
540 },
541 code => sub {
542 my ($param) = @_;
543
544 my $nodename = $param->{node} // PVE::INotify::nodename();
545
546 PVE::Cluster::cfs_update(1);
547 my $conf = PVE::Cluster::cfs_read_file('corosync.conf');
548
a7b6c528
TL
549 # FIXME: just return undef or empty object in PVE 8.0 (check if manager can handle it!)
550 PVE::Exception::raise(
551 'node is not in a cluster, no join info available!',
552 code => HTTP::Status::HTTP_FAILED_DEPENDENCY,
553 ) if !($conf && $conf->{main});
fca7797d
TL
554
555 my $totem_cfg = $conf->{main}->{totem} // {};
556 my $nodelist = $conf->{main}->{nodelist}->{node} // {};
557 my $corosync_config_digest = $conf->{digest};
558
559 die "unknown node '$nodename'\n" if ! $nodelist->{$nodename};
560
561 foreach my $name (keys %$nodelist) {
562 my $node = $nodelist->{$name};
563 $node->{pve_fp} = PVE::Cluster::get_node_fingerprint($name);
564 $node->{pve_addr} = scalar(PVE::Cluster::remote_node_ip($name));
565 }
566
567 my $res = {
568 nodelist => [ values %$nodelist ],
569 preferred_node => $nodename,
570 totem => $totem_cfg,
571 config_digest => $corosync_config_digest,
572 };
573
574 return $res;
575 }});
576
6ed49eb1
TL
577__PACKAGE__->register_method ({
578 name => 'join',
579 path => 'join',
580 method => 'POST',
581 protected => 1,
88b4cb13
SR
582 description => "Joins this node into an existing cluster. If no links are"
583 . " given, default to IP resolved by node's hostname on single"
584 . " link (fallback fails for clusters with multiple links).",
6ed49eb1
TL
585 parameters => {
586 additionalProperties => 0,
8ef581e4 587 properties => PVE::Corosync::add_corosync_link_properties({
6ed49eb1
TL
588 hostname => {
589 type => 'string',
590 description => "Hostname (or IP) of an existing cluster member."
591 },
10c6810e 592 nodeid => get_standard_option('corosync-nodeid'),
6ed49eb1
TL
593 votes => {
594 type => 'integer',
595 description => "Number of votes for this node",
596 minimum => 0,
597 optional => 1,
598 },
599 force => {
600 type => 'boolean',
601 description => "Do not throw error if node already exists.",
602 optional => 1,
603 },
6ed49eb1
TL
604 fingerprint => get_standard_option('fingerprint-sha256'),
605 password => {
606 description => "Superuser (root) password of peer node.",
607 type => 'string',
608 maxLength => 128,
609 },
8ef581e4 610 }),
6ed49eb1
TL
611 },
612 returns => { type => 'string' },
613 code => sub {
614 my ($param) = @_;
615
616 my $rpcenv = PVE::RPCEnvironment::get();
617 my $authuser = $rpcenv->get_user();
618
619 my $worker = sub {
17a4445f 620 STDOUT->autoflush();
c5204e14 621 PVE::Tools::lock_file($local_cluster_lock, 10, \&PVE::Cluster::Setup::join, $param);
b184a69d 622 die $@ if $@;
6ed49eb1
TL
623 };
624
8c4e30d3 625 return $rpcenv->fork_worker('clusterjoin', undef, $authuser, $worker);
6ed49eb1
TL
626 }});
627
628
963c06bb
DM
629__PACKAGE__->register_method({
630 name => 'totem',
631 path => 'totem',
632 method => 'GET',
633 description => "Get corosync totem protocol settings.",
fb7c665a
EK
634 permissions => {
635 check => ['perm', '/', [ 'Sys.Audit' ]],
636 },
963c06bb
DM
637 parameters => {
638 additionalProperties => 0,
639 properties => {},
640 },
641 returns => {
642 type => "object",
643 properties => {},
644 },
645 code => sub {
646 my ($param) = @_;
647
648
649 my $conf = PVE::Cluster::cfs_read_file('corosync.conf');
650
496de919
TL
651 my $totem_cfg = $conf->{main}->{totem};
652
653 return $totem_cfg;
963c06bb
DM
654 }});
655
f0f8ee41
OB
656__PACKAGE__->register_method ({
657 name => 'status',
658 path => 'qdevice',
659 method => 'GET',
660 description => 'Get QDevice status',
661 permissions => {
662 check => ['perm', '/', [ 'Sys.Audit' ]],
663 },
664 parameters => {
665 additionalProperties => 0,
666 properties => {},
667 },
668 returns => {
669 type => "object",
670 },
671 code => sub {
672 my ($param) = @_;
673
674 my $result = {};
675 my $socket_path = "/var/run/corosync-qdevice/corosync-qdevice.sock";
676 return $result if !-S $socket_path;
677
678 my $qdevice_socket = IO::Socket::UNIX->new(
679 Type => SOCK_STREAM,
680 Peer => $socket_path,
681 );
682
683 print $qdevice_socket "status verbose\n";
684 my $qdevice_keys = {
685 "Algorithm" => 1,
686 "Echo reply" => 1,
687 "Last poll call" => 1,
688 "Model" => 1,
689 "QNetd host" => 1,
690 "State" => 1,
691 "Tie-breaker" => 1,
692 };
693 while (my $line = <$qdevice_socket>) {
694 chomp $line;
695 next if $line =~ /^\s/;
696 if ($line =~ /^(.*?)\s*:\s*(.*)$/) {
697 $result->{$1} = $2 if $qdevice_keys->{$1};
698 }
699 }
700
701 return $result;
702 }});
703#TODO: possibly add setup and remove methods
704
705
963c06bb 7061;