]>
git.proxmox.com Git - pve-cluster.git/blob - data/PVE/API2/ClusterConfig.pm
1 package PVE
::API2
::ClusterConfig
;
9 use PVE
::RPCEnvironment
;
10 use PVE
::JSONSchema
qw(get_standard_option);
12 use PVE
::APIClient
::LWP
;
15 use base
qw(PVE::RESTHandler);
17 my $clusterconf = "/etc/pve/corosync.conf";
18 my $authfile = "/etc/corosync/authkey";
19 my $local_cluster_lock = "/var/lock/pvecm.lock";
23 description
=> "Node id for this node.",
27 PVE
::JSONSchema
::register_standard_option
("corosync-nodeid", $nodeid_desc);
29 __PACKAGE__-
>register_method({
33 description
=> "Directory index.",
35 check
=> ['perm', '/', [ 'Sys.Audit' ]],
38 additionalProperties
=> 0,
47 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
61 __PACKAGE__-
>register_method ({
66 description
=> "Generate new cluster configuration.",
68 additionalProperties
=> 0,
71 description
=> "The name of the cluster.",
72 type
=> 'string', format
=> 'pve-node',
75 nodeid
=> get_standard_option
('corosync-nodeid'),
78 description
=> "Number of votes for this node.",
82 link0
=> get_standard_option
('corosync-link'),
83 link1
=> get_standard_option
('corosync-link'),
86 returns
=> { type
=> 'string' },
90 -f
$clusterconf && die "cluster config '$clusterconf' already exists\n";
92 my $rpcenv = PVE
::RPCEnvironment
::get
();
93 my $authuser = $rpcenv->get_user();
97 PVE
::Cluster
::setup_sshd_config
(1);
98 PVE
::Cluster
::setup_rootsshconfig
();
99 PVE
::Cluster
::setup_ssh_keys
();
101 PVE
::Tools
::run_command
(['/usr/sbin/corosync-keygen', '-lk', $authfile])
103 die "no authentication key available\n" if -f
!$authfile;
105 my $nodename = PVE
::INotify
::nodename
();
107 # get the corosync basis config for the new cluster
108 my $config = PVE
::Corosync
::create_conf
($nodename, %$param);
110 print "Writing corosync config to /etc/pve/corosync.conf\n";
111 PVE
::Corosync
::atomic_write_conf
($config);
113 my $local_ip_address = PVE
::Cluster
::remote_node_ip
($nodename);
114 PVE
::Cluster
::ssh_merge_keys
();
115 PVE
::Cluster
::gen_pve_node_files
($nodename, $local_ip_address);
116 PVE
::Cluster
::ssh_merge_known_hosts
($nodename, $local_ip_address, 1);
118 print "Restart corosync and cluster filesystem\n";
119 PVE
::Tools
::run_command
('systemctl restart corosync pve-cluster');
123 PVE
::Tools
::lock_file
($local_cluster_lock, 10, $code);
127 return $rpcenv->fork_worker('clustercreate', $param->{clustername
}, $authuser, $worker);
130 __PACKAGE__-
>register_method({
134 description
=> "Corosync node list.",
136 check
=> ['perm', '/', [ 'Sys.Audit' ]],
139 additionalProperties
=> 0,
147 node
=> { type
=> 'string' },
150 links
=> [ { rel
=> 'child', href
=> "{node}" } ],
156 my $conf = PVE
::Cluster
::cfs_read_file
('corosync.conf');
157 my $nodelist = PVE
::Corosync
::nodelist
($conf);
159 return PVE
::RESTHandler
::hash_to_array
($nodelist, 'node');
162 # lock method to ensure local and cluster wide atomicity
163 # if we're a single node cluster just lock locally, we have no other cluster
164 # node which we could contend with, else also acquire a cluster wide lock
165 my $config_change_lock = sub {
168 PVE
::Tools
::lock_file
($local_cluster_lock, 10, sub {
169 PVE
::Cluster
::cfs_update
(1);
170 my $members = PVE
::Cluster
::get_members
();
171 if (scalar(keys %$members) > 1) {
172 return PVE
::Cluster
::cfs_lock_file
('corosync.conf', 10, $code);
179 __PACKAGE__-
>register_method ({
181 path
=> 'nodes/{node}',
184 description
=> "Adds a node to the cluster configuration. This call is for internal use.",
186 additionalProperties
=> 0,
188 node
=> get_standard_option
('pve-node'),
189 nodeid
=> get_standard_option
('corosync-nodeid'),
192 description
=> "Number of votes for this node",
198 description
=> "Do not throw error if node already exists.",
201 link0
=> get_standard_option
('corosync-link'),
202 link1
=> get_standard_option
('corosync-link'),
208 corosync_authkey
=> {
219 PVE
::Cluster
::check_cfs_quorum
();
222 my $conf = PVE
::Cluster
::cfs_read_file
("corosync.conf");
223 my $nodelist = PVE
::Corosync
::nodelist
($conf);
224 my $totem_cfg = PVE
::Corosync
::totem_config
($conf);
226 my $name = $param->{node
};
228 # ensure we do not reuse an address, that can crash the whole cluster!
229 my $check_duplicate_addr = sub {
231 return if !defined($link) || !defined($link->{address
});
232 my $addr = $link->{address
};
234 while (my ($k, $v) = each %$nodelist) {
235 next if $k eq $name; # allows re-adding a node if force is set
237 for my $linknumber (0..1) {
238 my $id = "ring${linknumber}_addr";
239 next if !defined($v->{$id});
241 die "corosync: address '$addr' already used on link $id by node '$k'\n"
242 if $v->{$id} eq $addr;
247 my $link0 = PVE
::Cluster
::parse_corosync_link
($param->{link0
});
248 my $link1 = PVE
::Cluster
::parse_corosync_link
($param->{link1
});
250 $check_duplicate_addr->($link0);
251 $check_duplicate_addr->($link1);
253 # FIXME: handle all links (0-7), they're all independent now
254 $link0->{address
} //= $name if exists($totem_cfg->{interface
}->{0});
256 die "corosync: using 'link1' parameter needs a interface with linknumber '1' configured!\n"
257 if $link1 && !defined($totem_cfg->{interface
}->{1});
259 die "corosync: totem interface with linknumber 1 configured but 'link1' parameter not defined!\n"
260 if defined($totem_cfg->{interface
}->{1}) && !defined($link1);
262 if (defined(my $res = $nodelist->{$name})) {
263 $param->{nodeid
} = $res->{nodeid
} if !$param->{nodeid
};
264 $param->{votes
} = $res->{quorum_votes
} if !defined($param->{votes
});
266 if ($res->{quorum_votes
} == $param->{votes
} &&
267 $res->{nodeid
} == $param->{nodeid
} && $param->{force
}) {
268 print "forcing overwrite of configured node '$name'\n";
270 die "can't add existing node '$name'\n";
272 } elsif (!$param->{nodeid
}) {
277 foreach my $v (values %$nodelist) {
278 if ($v->{nodeid
} eq $nodeid) {
287 $param->{nodeid
} = $nodeid;
290 $param->{votes
} = 1 if !defined($param->{votes
});
292 PVE
::Cluster
::gen_local_dirs
($name);
294 eval { PVE
::Cluster
::ssh_merge_keys
(); };
297 $nodelist->{$name} = {
298 ring0_addr
=> $link0->{address
},
299 nodeid
=> $param->{nodeid
},
302 $nodelist->{$name}->{ring1_addr
} = $link1->{address
} if defined($link1);
303 $nodelist->{$name}->{quorum_votes
} = $param->{votes
} if $param->{votes
};
305 PVE
::Cluster
::log_msg
('notice', 'root@pam', "adding node $name to cluster");
307 PVE
::Corosync
::update_nodelist
($conf, $nodelist);
310 $config_change_lock->($code);
314 corosync_authkey
=> PVE
::Tools
::file_get_contents
($authfile),
315 corosync_conf
=> PVE
::Tools
::file_get_contents
($clusterconf),
322 __PACKAGE__-
>register_method ({
324 path
=> 'nodes/{node}',
327 description
=> "Removes a node from the cluster configuration.",
329 additionalProperties
=> 0,
331 node
=> get_standard_option
('pve-node'),
334 returns
=> { type
=> 'null' },
338 my $local_node = PVE
::INotify
::nodename
();
339 die "Cannot delete myself from cluster!\n" if $param->{node
} eq $local_node;
341 PVE
::Cluster
::check_cfs_quorum
();
344 my $conf = PVE
::Cluster
::cfs_read_file
("corosync.conf");
345 my $nodelist = PVE
::Corosync
::nodelist
($conf);
350 foreach my $tmp_node (keys %$nodelist) {
351 my $d = $nodelist->{$tmp_node};
352 my $ring0_addr = $d->{ring0_addr
};
353 my $ring1_addr = $d->{ring1_addr
};
354 if (($tmp_node eq $param->{node
}) ||
355 (defined($ring0_addr) && ($ring0_addr eq $param->{node
})) ||
356 (defined($ring1_addr) && ($ring1_addr eq $param->{node
}))) {
358 $nodeid = $d->{nodeid
};
363 die "Node/IP: $param->{node} is not a known host of the cluster.\n"
366 PVE
::Cluster
::log_msg
('notice', 'root@pam', "deleting node $node from cluster");
368 delete $nodelist->{$node};
370 PVE
::Corosync
::update_nodelist
($conf, $nodelist);
372 PVE
::Tools
::run_command
(['corosync-cfgtool','-k', $nodeid]) if defined($nodeid);
375 $config_change_lock->($code);
381 __PACKAGE__-
>register_method ({
385 check
=> ['perm', '/', [ 'Sys.Audit' ]],
388 description
=> "Get information needed to join this cluster over the connected node.",
390 additionalProperties
=> 0,
392 node
=> get_standard_option
('pve-node', {
393 description
=> "The node for which the joinee gets the nodeinfo. ",
394 default => "current connected node",
401 additionalProperties
=> 0,
407 additionalProperties
=> 1,
409 name
=> get_standard_option
('pve-node'),
410 nodeid
=> get_standard_option
('corosync-nodeid'),
411 ring0_addr
=> get_standard_option
('corosync-link'),
412 quorum_votes
=> { type
=> 'integer', minimum
=> 0 },
413 pve_addr
=> { type
=> 'string', format
=> 'ip' },
414 pve_fp
=> get_standard_option
('fingerprint-sha256'),
418 preferred_node
=> get_standard_option
('pve-node'),
419 totem
=> { type
=> 'object' },
420 config_digest
=> { type
=> 'string' },
426 my $nodename = $param->{node
} // PVE
::INotify
::nodename
();
428 PVE
::Cluster
::cfs_update
(1);
429 my $conf = PVE
::Cluster
::cfs_read_file
('corosync.conf');
431 die "node is not in a cluster, no join info available!\n"
432 if !($conf && $conf->{main
});
434 my $totem_cfg = $conf->{main
}->{totem
} // {};
435 my $nodelist = $conf->{main
}->{nodelist
}->{node
} // {};
436 my $corosync_config_digest = $conf->{digest
};
438 die "unknown node '$nodename'\n" if ! $nodelist->{$nodename};
440 foreach my $name (keys %$nodelist) {
441 my $node = $nodelist->{$name};
442 $node->{pve_fp
} = PVE
::Cluster
::get_node_fingerprint
($name);
443 $node->{pve_addr
} = scalar(PVE
::Cluster
::remote_node_ip
($name));
447 nodelist
=> [ values %$nodelist ],
448 preferred_node
=> $nodename,
450 config_digest
=> $corosync_config_digest,
456 __PACKAGE__-
>register_method ({
461 description
=> "Joins this node into an existing cluster.",
463 additionalProperties
=> 0,
467 description
=> "Hostname (or IP) of an existing cluster member."
469 nodeid
=> get_standard_option
('corosync-nodeid'),
472 description
=> "Number of votes for this node",
478 description
=> "Do not throw error if node already exists.",
481 link0
=> get_standard_option
('corosync-link', {
482 default => "IP resolved by node's hostname",
484 link1
=> get_standard_option
('corosync-link'),
485 fingerprint
=> get_standard_option
('fingerprint-sha256'),
487 description
=> "Superuser (root) password of peer node.",
493 returns
=> { type
=> 'string' },
497 my $rpcenv = PVE
::RPCEnvironment
::get
();
498 my $authuser = $rpcenv->get_user();
502 PVE
::Tools
::lock_file
($local_cluster_lock, 10, \
&PVE
::Cluster
::join, $param);
506 return $rpcenv->fork_worker('clusterjoin', undef, $authuser, $worker);
510 __PACKAGE__-
>register_method({
514 description
=> "Get corosync totem protocol settings.",
516 check
=> ['perm', '/', [ 'Sys.Audit' ]],
519 additionalProperties
=> 0,
530 my $conf = PVE
::Cluster
::cfs_read_file
('corosync.conf');
532 my $totem_cfg = $conf->{main
}->{totem
};