]>
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";
22 type
=> 'string', format
=> 'address',
23 description
=> "Hostname (or IP) of the corosync ring0 address of this node.",
24 default => "Hostname of the node",
27 PVE
::JSONSchema
::register_standard_option
("corosync-ring0-addr", $ring0_desc);
30 type
=> 'string', format
=> 'address',
31 description
=> "Hostname (or IP) of the corosync ring1 address of this node.".
32 " Requires a valid configured ring 1 (bindnet1_addr) in the cluster.",
35 PVE
::JSONSchema
::register_standard_option
("corosync-ring1-addr", $ring1_desc);
39 description
=> "Node id for this node.",
43 PVE
::JSONSchema
::register_standard_option
("corosync-nodeid", $nodeid_desc);
45 __PACKAGE__-
>register_method({
49 description
=> "Directory index.",
51 check
=> ['perm', '/', [ 'Sys.Audit' ]],
54 additionalProperties
=> 0,
63 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
77 __PACKAGE__-
>register_method ({
82 description
=> "Generate new cluster configuration.",
84 additionalProperties
=> 0,
87 description
=> "The name of the cluster.",
88 type
=> 'string', format
=> 'pve-node',
91 nodeid
=> get_standard_option
('corosync-nodeid'),
94 description
=> "Number of votes for this node.",
99 type
=> 'string', format
=> 'ip',
100 description
=> "This specifies the network address the corosync ring 0".
101 " executive should bind to and defaults to the local IP address of the node.",
104 ring0_addr
=> get_standard_option
('corosync-ring0-addr'),
106 type
=> 'string', format
=> 'ip',
107 description
=> "This specifies the network address the corosync ring 1".
108 " executive should bind to and is optional.",
111 ring1_addr
=> get_standard_option
('corosync-ring1-addr'),
114 returns
=> { type
=> 'string' },
118 -f
$clusterconf && die "cluster config '$clusterconf' already exists\n";
120 my $rpcenv = PVE
::RPCEnvironment
::get
();
121 my $authuser = $rpcenv->get_user();
124 PVE
::Cluster
::setup_sshd_config
(1);
125 PVE
::Cluster
::setup_rootsshconfig
();
126 PVE
::Cluster
::setup_ssh_keys
();
128 PVE
::Tools
::run_command
(['/usr/sbin/corosync-keygen', '-lk', $authfile])
130 die "no authentication key available\n" if -f
!$authfile;
132 my $nodename = PVE
::INotify
::nodename
();
134 # get the corosync basis config for the new cluster
135 my $config = PVE
::Corosync
::create_conf
($nodename, %$param);
137 print "Writing corosync config to /etc/pve/corosync.conf\n";
138 PVE
::Corosync
::atomic_write_conf
($config);
140 my $local_ip_address = PVE
::Cluster
::remote_node_ip
($nodename);
141 PVE
::Cluster
::ssh_merge_keys
();
142 PVE
::Cluster
::gen_pve_node_files
($nodename, $local_ip_address);
143 PVE
::Cluster
::ssh_merge_known_hosts
($nodename, $local_ip_address, 1);
145 print "Restart corosync and cluster filesystem\n";
146 PVE
::Tools
::run_command
('systemctl restart corosync pve-cluster');
150 PVE
::Tools
::lock_file
($local_cluster_lock, 10, $code);
154 return $rpcenv->fork_worker('clustercreate', $param->{clustername
}, $authuser, $worker);
157 __PACKAGE__-
>register_method({
161 description
=> "Corosync node list.",
163 check
=> ['perm', '/', [ 'Sys.Audit' ]],
166 additionalProperties
=> 0,
174 node
=> { type
=> 'string' },
177 links
=> [ { rel
=> 'child', href
=> "{node}" } ],
183 my $conf = PVE
::Cluster
::cfs_read_file
('corosync.conf');
184 my $nodelist = PVE
::Corosync
::nodelist
($conf);
186 return PVE
::RESTHandler
::hash_to_array
($nodelist, 'node');
189 # lock method to ensure local and cluster wide atomicity
190 # if we're a single node cluster just lock locally, we have no other cluster
191 # node which we could contend with, else also acquire a cluster wide lock
192 my $config_change_lock = sub {
195 PVE
::Tools
::lock_file
($local_cluster_lock, 10, sub {
196 PVE
::Cluster
::cfs_update
(1);
197 my $members = PVE
::Cluster
::get_members
();
198 if (scalar(keys %$members) > 1) {
199 return PVE
::Cluster
::cfs_lock_file
('corosync.conf', 10, $code);
206 __PACKAGE__-
>register_method ({
208 path
=> 'nodes/{node}',
211 description
=> "Adds a node to the cluster configuration.",
213 additionalProperties
=> 0,
215 node
=> get_standard_option
('pve-node'),
216 nodeid
=> get_standard_option
('corosync-nodeid'),
219 description
=> "Number of votes for this node",
225 description
=> "Do not throw error if node already exists.",
228 ring0_addr
=> get_standard_option
('corosync-ring0-addr'),
229 ring1_addr
=> get_standard_option
('corosync-ring1-addr'),
235 corosync_authkey
=> {
246 PVE
::Cluster
::check_cfs_quorum
();
249 my $conf = PVE
::Cluster
::cfs_read_file
("corosync.conf");
250 my $nodelist = PVE
::Corosync
::nodelist
($conf);
251 my $totem_cfg = PVE
::Corosync
::totem_config
($conf);
253 my $name = $param->{node
};
255 # ensure we do not reuse an address, that can crash the whole cluster!
256 my $check_duplicate_addr = sub {
258 return if !defined($addr);
260 while (my ($k, $v) = each %$nodelist) {
261 next if $k eq $name; # allows re-adding a node if force is set
262 if ($v->{ring0_addr
} eq $addr || ($v->{ring1_addr
} && $v->{ring1_addr
} eq $addr)) {
263 die "corosync: address '$addr' already defined by node '$k'\n";
268 &$check_duplicate_addr($param->{ring0_addr
});
269 &$check_duplicate_addr($param->{ring1_addr
});
271 $param->{ring0_addr
} = $name if !$param->{ring0_addr
};
273 die "corosync: using 'ring1_addr' parameter needs a configured ring 1 interface!\n"
274 if $param->{ring1_addr
} && !defined($totem_cfg->{interface
}->{1});
276 die "corosync: ring 1 interface configured but 'ring1_addr' parameter not defined!\n"
277 if defined($totem_cfg->{interface
}->{1}) && !defined($param->{ring1_addr
});
279 if (defined(my $res = $nodelist->{$name})) {
280 $param->{nodeid
} = $res->{nodeid
} if !$param->{nodeid
};
281 $param->{votes
} = $res->{quorum_votes
} if !defined($param->{votes
});
283 if ($res->{quorum_votes
} == $param->{votes
} &&
284 $res->{nodeid
} == $param->{nodeid
} && $param->{force
}) {
285 print "forcing overwrite of configured node '$name'\n";
287 die "can't add existing node '$name'\n";
289 } elsif (!$param->{nodeid
}) {
294 foreach my $v (values %$nodelist) {
295 if ($v->{nodeid
} eq $nodeid) {
304 $param->{nodeid
} = $nodeid;
307 $param->{votes
} = 1 if !defined($param->{votes
});
309 PVE
::Cluster
::gen_local_dirs
($name);
311 eval { PVE
::Cluster
::ssh_merge_keys
(); };
314 $nodelist->{$name} = {
315 ring0_addr
=> $param->{ring0_addr
},
316 nodeid
=> $param->{nodeid
},
319 $nodelist->{$name}->{ring1_addr
} = $param->{ring1_addr
} if $param->{ring1_addr
};
320 $nodelist->{$name}->{quorum_votes
} = $param->{votes
} if $param->{votes
};
322 PVE
::Cluster
::log_msg
('notice', 'root@pam', "adding node $name to cluster");
324 PVE
::Corosync
::update_nodelist
($conf, $nodelist);
327 $config_change_lock->($code);
331 corosync_authkey
=> PVE
::Tools
::file_get_contents
($authfile),
332 corosync_conf
=> PVE
::Tools
::file_get_contents
($clusterconf),
339 __PACKAGE__-
>register_method ({
341 path
=> 'nodes/{node}',
344 description
=> "Removes a node from the cluster configuration.",
346 additionalProperties
=> 0,
348 node
=> get_standard_option
('pve-node'),
351 returns
=> { type
=> 'null' },
355 my $local_node = PVE
::INotify
::nodename
();
356 die "Cannot delete myself from cluster!\n" if $param->{node
} eq $local_node;
358 PVE
::Cluster
::check_cfs_quorum
();
361 my $conf = PVE
::Cluster
::cfs_read_file
("corosync.conf");
362 my $nodelist = PVE
::Corosync
::nodelist
($conf);
367 foreach my $tmp_node (keys %$nodelist) {
368 my $d = $nodelist->{$tmp_node};
369 my $ring0_addr = $d->{ring0_addr
};
370 my $ring1_addr = $d->{ring1_addr
};
371 if (($tmp_node eq $param->{node
}) ||
372 (defined($ring0_addr) && ($ring0_addr eq $param->{node
})) ||
373 (defined($ring1_addr) && ($ring1_addr eq $param->{node
}))) {
375 $nodeid = $d->{nodeid
};
380 die "Node/IP: $param->{node} is not a known host of the cluster.\n"
383 PVE
::Cluster
::log_msg
('notice', 'root@pam', "deleting node $node from cluster");
385 delete $nodelist->{$node};
387 PVE
::Corosync
::update_nodelist
($conf, $nodelist);
389 PVE
::Tools
::run_command
(['corosync-cfgtool','-k', $nodeid]) if defined($nodeid);
392 $config_change_lock->($code);
398 __PACKAGE__-
>register_method ({
402 description
=> "Get information needed to join this cluster over the connected node.",
404 additionalProperties
=> 0,
406 node
=> get_standard_option
('pve-node', {
407 description
=> "The node for which the joinee gets the nodeinfo. ",
408 default => "current connected node",
415 additionalProperties
=> 0,
421 additionalProperties
=> 1,
423 name
=> get_standard_option
('pve-node'),
424 nodeid
=> get_standard_option
('corosync-nodeid'),
425 ring0_addr
=> get_standard_option
('corosync-ring0-addr'),
426 quorum_votes
=> { type
=> 'integer', minimum
=> 0 },
427 pve_addr
=> { type
=> 'string', format
=> 'ip' },
428 pve_fp
=> get_standard_option
('fingerprint-sha256'),
432 preferred_node
=> get_standard_option
('pve-node'),
433 totem
=> { type
=> 'object' },
434 config_digest
=> { type
=> 'string' },
440 my $nodename = $param->{node
} // PVE
::INotify
::nodename
();
442 PVE
::Cluster
::cfs_update
(1);
443 my $conf = PVE
::Cluster
::cfs_read_file
('corosync.conf');
445 die "node is not in a cluster, no join info available!\n"
446 if !($conf && $conf->{main
});
448 my $totem_cfg = $conf->{main
}->{totem
} // {};
449 my $nodelist = $conf->{main
}->{nodelist
}->{node
} // {};
450 my $corosync_config_digest = $conf->{digest
};
452 die "unknown node '$nodename'\n" if ! $nodelist->{$nodename};
454 foreach my $name (keys %$nodelist) {
455 my $node = $nodelist->{$name};
456 $node->{pve_fp
} = PVE
::Cluster
::get_node_fingerprint
($name);
457 $node->{pve_addr
} = scalar(PVE
::Cluster
::remote_node_ip
($name));
461 nodelist
=> [ values %$nodelist ],
462 preferred_node
=> $nodename,
464 config_digest
=> $corosync_config_digest,
470 __PACKAGE__-
>register_method ({
475 description
=> "Joins this node into an existing cluster.",
477 additionalProperties
=> 0,
481 description
=> "Hostname (or IP) of an existing cluster member."
483 nodeid
=> get_standard_option
('corosync-nodeid'),
486 description
=> "Number of votes for this node",
492 description
=> "Do not throw error if node already exists.",
495 ring0_addr
=> get_standard_option
('corosync-ring0-addr', {
496 default => "IP resolved by node's hostname",
498 ring1_addr
=> get_standard_option
('corosync-ring1-addr'),
499 fingerprint
=> get_standard_option
('fingerprint-sha256'),
501 description
=> "Superuser (root) password of peer node.",
507 returns
=> { type
=> 'string' },
511 my $rpcenv = PVE
::RPCEnvironment
::get
();
512 my $authuser = $rpcenv->get_user();
515 PVE
::Tools
::lock_file
($local_cluster_lock, 10, \
&PVE
::Cluster
::join, $param);
519 return $rpcenv->fork_worker('clusterjoin', $param->{hostname
}, $authuser, $worker);
523 __PACKAGE__-
>register_method({
527 description
=> "Get corosync totem protocol settings.",
529 check
=> ['perm', '/', [ 'Sys.Audit' ]],
532 additionalProperties
=> 0,
543 my $conf = PVE
::Cluster
::cfs_read_file
('corosync.conf');
545 my $totem_cfg = $conf->{main
}->{totem
};