]>
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";
20 __PACKAGE__-
>register_method({
24 description
=> "Directory index.",
26 check
=> ['perm', '/', [ 'Sys.Audit' ]],
29 additionalProperties
=> 0,
38 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
52 __PACKAGE__-
>register_method ({
57 description
=> "Generate new cluster configuration.",
59 additionalProperties
=> 0,
62 description
=> "The name of the cluster.",
63 type
=> 'string', format
=> 'pve-node',
68 description
=> "Node id for this node.",
74 description
=> "Number of votes for this node.",
79 type
=> 'string', format
=> 'ip',
80 description
=> "This specifies the network address the corosync ring 0".
81 " executive should bind to and defaults to the local IP address of the node.",
85 type
=> 'string', format
=> 'address',
86 description
=> "Hostname (or IP) of the corosync ring0 address of this node.".
87 " Defaults to the hostname of the node.",
91 type
=> 'string', format
=> 'ip',
92 description
=> "This specifies the network address the corosync ring 1".
93 " executive should bind to and is optional.",
97 type
=> 'string', format
=> 'address',
98 description
=> "Hostname (or IP) of the corosync ring1 address, this".
99 " needs an valid bindnet1_addr.",
104 returns
=> { type
=> 'null' },
109 -f
$clusterconf && die "cluster config '$clusterconf' already exists\n";
111 PVE
::Cluster
::setup_sshd_config
(1);
112 PVE
::Cluster
::setup_rootsshconfig
();
113 PVE
::Cluster
::setup_ssh_keys
();
115 PVE
::Tools
::run_command
(['/usr/sbin/corosync-keygen', '-lk', $authfile])
117 die "no authentication key available\n" if -f
!$authfile;
119 my $nodename = PVE
::INotify
::nodename
();
121 # get the corosync basis config for the new cluster
122 my $config = PVE
::Corosync
::create_conf
($nodename, %$param);
124 print "Writing corosync config to /etc/pve/corosync.conf\n";
125 PVE
::Corosync
::atomic_write_conf
($config);
127 my $local_ip_address = PVE
::Cluster
::remote_node_ip
($nodename);
128 PVE
::Cluster
::ssh_merge_keys
();
129 PVE
::Cluster
::gen_pve_node_files
($nodename, $local_ip_address);
130 PVE
::Cluster
::ssh_merge_known_hosts
($nodename, $local_ip_address, 1);
132 print "Restart corosync and cluster filesystem\n";
133 PVE
::Tools
::run_command
('systemctl restart corosync pve-cluster');
138 __PACKAGE__-
>register_method({
142 description
=> "Corosync node list.",
144 check
=> ['perm', '/', [ 'Sys.Audit' ]],
147 additionalProperties
=> 0,
155 node
=> { type
=> 'string' },
158 links
=> [ { rel
=> 'child', href
=> "{node}" } ],
164 my $conf = PVE
::Cluster
::cfs_read_file
('corosync.conf');
165 my $nodelist = PVE
::Corosync
::nodelist
($conf);
167 return PVE
::RESTHandler
::hash_to_array
($nodelist, 'node');
170 # lock method to ensure local and cluster wide atomicity
171 # if we're a single node cluster just lock locally, we have no other cluster
172 # node which we could contend with, else also acquire a cluster wide lock
173 my $config_change_lock = sub {
176 my $local_lock_fn = "/var/lock/pvecm.lock";
177 PVE
::Tools
::lock_file
($local_lock_fn, 10, sub {
178 PVE
::Cluster
::cfs_update
(1);
179 my $members = PVE
::Cluster
::get_members
();
180 if (scalar(keys %$members) > 1) {
181 return PVE
::Cluster
::cfs_lock_file
('corosync.conf', 10, $code);
188 __PACKAGE__-
>register_method ({
190 path
=> 'nodes/{node}',
193 description
=> "Adds a node to the cluster configuration.",
195 additionalProperties
=> 0,
197 node
=> get_standard_option
('pve-node'),
200 description
=> "Node id for this node.",
206 description
=> "Number of votes for this node",
212 description
=> "Do not throw error if node already exists.",
216 type
=> 'string', format
=> 'address',
217 description
=> "Hostname (or IP) of the corosync ring0 address of this node.".
218 " Defaults to nodes hostname.",
222 type
=> 'string', format
=> 'address',
223 description
=> "Hostname (or IP) of the corosync ring1 address, this".
224 " needs an valid bindnet1_addr.",
232 corosync_authkey
=> {
243 PVE
::Cluster
::check_cfs_quorum
();
246 my $conf = PVE
::Cluster
::cfs_read_file
("corosync.conf");
247 my $nodelist = PVE
::Corosync
::nodelist
($conf);
248 my $totem_cfg = PVE
::Corosync
::totem_config
($conf);
250 my $name = $param->{node
};
252 # ensure we do not reuse an address, that can crash the whole cluster!
253 my $check_duplicate_addr = sub {
255 return if !defined($addr);
257 while (my ($k, $v) = each %$nodelist) {
258 next if $k eq $name; # allows re-adding a node if force is set
259 if ($v->{ring0_addr
} eq $addr || ($v->{ring1_addr
} && $v->{ring1_addr
} eq $addr)) {
260 die "corosync: address '$addr' already defined by node '$k'\n";
265 &$check_duplicate_addr($param->{ring0_addr
});
266 &$check_duplicate_addr($param->{ring1_addr
});
268 $param->{ring0_addr
} = $name if !$param->{ring0_addr
};
270 die "corosync: using 'ring1_addr' parameter needs a configured ring 1 interface!\n"
271 if $param->{ring1_addr
} && !defined($totem_cfg->{interface
}->{1});
273 die "corosync: ring 1 interface configured but 'ring1_addr' parameter not defined!\n"
274 if defined($totem_cfg->{interface
}->{1}) && !defined($param->{ring1_addr
});
276 if (defined(my $res = $nodelist->{$name})) {
277 $param->{nodeid
} = $res->{nodeid
} if !$param->{nodeid
};
278 $param->{votes
} = $res->{quorum_votes
} if !defined($param->{votes
});
280 if ($res->{quorum_votes
} == $param->{votes
} &&
281 $res->{nodeid
} == $param->{nodeid
} && $param->{force
}) {
282 print "forcing overwrite of configured node '$name'\n";
284 die "can't add existing node '$name'\n";
286 } elsif (!$param->{nodeid
}) {
291 foreach my $v (values %$nodelist) {
292 if ($v->{nodeid
} eq $nodeid) {
301 $param->{nodeid
} = $nodeid;
304 $param->{votes
} = 1 if !defined($param->{votes
});
306 PVE
::Cluster
::gen_local_dirs
($name);
308 eval { PVE
::Cluster
::ssh_merge_keys
(); };
311 $nodelist->{$name} = {
312 ring0_addr
=> $param->{ring0_addr
},
313 nodeid
=> $param->{nodeid
},
316 $nodelist->{$name}->{ring1_addr
} = $param->{ring1_addr
} if $param->{ring1_addr
};
317 $nodelist->{$name}->{quorum_votes
} = $param->{votes
} if $param->{votes
};
319 PVE
::Cluster
::log_msg
('notice', 'root@pam', "adding node $name to cluster");
321 PVE
::Corosync
::update_nodelist
($conf, $nodelist);
324 $config_change_lock->($code);
328 corosync_authkey
=> PVE
::Tools
::file_get_contents
($authfile),
329 corosync_conf
=> PVE
::Tools
::file_get_contents
($clusterconf),
336 __PACKAGE__-
>register_method ({
338 path
=> 'nodes/{node}',
341 description
=> "Removes a node from the cluster configuration.",
343 additionalProperties
=> 0,
345 node
=> get_standard_option
('pve-node'),
348 returns
=> { type
=> 'null' },
352 my $local_node = PVE
::INotify
::nodename
();
353 die "Cannot delete myself from cluster!\n" if $param->{node
} eq $local_node;
355 PVE
::Cluster
::check_cfs_quorum
();
358 my $conf = PVE
::Cluster
::cfs_read_file
("corosync.conf");
359 my $nodelist = PVE
::Corosync
::nodelist
($conf);
364 foreach my $tmp_node (keys %$nodelist) {
365 my $d = $nodelist->{$tmp_node};
366 my $ring0_addr = $d->{ring0_addr
};
367 my $ring1_addr = $d->{ring1_addr
};
368 if (($tmp_node eq $param->{node
}) ||
369 (defined($ring0_addr) && ($ring0_addr eq $param->{node
})) ||
370 (defined($ring1_addr) && ($ring1_addr eq $param->{node
}))) {
372 $nodeid = $d->{nodeid
};
377 die "Node/IP: $param->{node} is not a known host of the cluster.\n"
380 PVE
::Cluster
::log_msg
('notice', 'root@pam', "deleting node $node from cluster");
382 delete $nodelist->{$node};
384 PVE
::Corosync
::update_nodelist
($conf, $nodelist);
386 PVE
::Tools
::run_command
(['corosync-cfgtool','-k', $nodeid]) if defined($nodeid);
389 $config_change_lock->($code);
395 __PACKAGE__-
>register_method ({
400 description
=> "Joins this node into an existing cluster.",
402 additionalProperties
=> 0,
406 description
=> "Hostname (or IP) of an existing cluster member."
410 description
=> "Node id for this node.",
416 description
=> "Number of votes for this node",
422 description
=> "Do not throw error if node already exists.",
426 type
=> 'string', format
=> 'address',
427 description
=> "Hostname (or IP) of the corosync ring0 address of this node.".
428 " Defaults to nodes hostname.",
432 type
=> 'string', format
=> 'address',
433 description
=> "Hostname (or IP) of the corosync ring1 address, this".
434 " needs an valid configured ring 1 interface in the cluster.",
437 fingerprint
=> get_standard_option
('fingerprint-sha256'),
439 description
=> "Superuser (root) password of peer node.",
445 returns
=> { type
=> 'string' },
449 my $rpcenv = PVE
::RPCEnvironment
::get
();
450 my $authuser = $rpcenv->get_user();
453 PVE
::Cluster
::join($param);
456 return $rpcenv->fork_worker('clusterjoin', '', $authuser, $worker);
460 __PACKAGE__-
>register_method({
464 description
=> "Get corosync totem protocol settings.",
466 check
=> ['perm', '/', [ 'Sys.Audit' ]],
469 additionalProperties
=> 0,
480 my $conf = PVE
::Cluster
::cfs_read_file
('corosync.conf');
482 my $totem_cfg = $conf->{main
}->{totem
};