use PVE::Cluster;
use PVE::INotify;
use PVE::JSONSchema;
-use PVE::RPCEnvironment;
use PVE::CLIHandler;
use base qw(PVE::CLIHandler);
mkdir $backupdir;
my $ctime = time();
- my $cmd = "echo '.dump' |";
- $cmd .= "sqlite3 '$dbfile' |";
- $cmd .= "gzip - >'${backupdir}/config-${ctime}.sql.gz'";
+ my $cmd = [
+ ['echo', '.dump'],
+ ['sqlite3', $dbfile],
+ ['gzip', '-', \">${backupdir}/config-${ctime}.sql.gz"],
+ ];
- system($cmd) == 0 ||
- die "can't backup old database: $!\n";
+ PVE::Tools::run_command($cmd, 'errmsg' => "can't backup old database\n");
# purge older backup
my $maxfiles = 10;
minimum => 1,
optional => 1,
},
- },
+ bindnet0_addr => {
+ type => 'string', format => 'ip',
+ description => "This specifies the network address the corosync ring 0".
+ " executive should bind to and defaults to the local IP address of the node.",
+ optional => 1,
+ },
+ ring0_addr => {
+ type => 'string', format => 'address',
+ description => "Hostname (or IP) of the corosync ring0 address of this node.".
+ " Defaults to the hostname of the node.",
+ optional => 1,
+ },
+ rrp_mode => {
+ type => 'string',
+ enum => ['none', 'active', 'passive'],
+ description => "This specifies the mode of redundant ring, which" .
+ " may be none, active or passive. Using multiple interfaces".
+ " only allows 'active' or 'passive'.",
+ default => 'none',
+ optional => 1,
+ },
+ bindnet1_addr => {
+ type => 'string', format => 'ip',
+ description => "This specifies the network address the corosync ring 1".
+ " executive should bind to and is optional.",
+ optional => 1,
+ },
+ ring1_addr => {
+ type => 'string', format => 'address',
+ description => "Hostname (or IP) of the corosync ring1 address, this".
+ " needs an valid bindnet1_addr.",
+ optional => 1,
+ },
+ },
},
returns => { type => 'null' },
my $local_ip_address = PVE::Cluster::remote_node_ip($nodename);
+ $param->{bindnet0_addr} = $local_ip_address
+ if !defined($param->{bindnet0_addr});
+
+ $param->{ring0_addr} = $nodename if !defined($param->{ring0_addr});
+
+ die "Param bindnet1_addr and ring1_addr are dependend, use both or none!\n"
+ if (defined($param->{bindnet1_addr}) != defined($param->{ring1_addr}));
+
+ my $bind_is_ipv6 = Net::IP::ip_is_ipv6($param->{bindnet0_addr});
+
+ # use string as here-doc format distracts more
+ my $interfaces = "interface {\n ringnumber: 0\n" .
+ " bindnetaddr: $param->{bindnet0_addr}\n }";
+
+ my $ring_addresses = "ring0_addr: $param->{ring0_addr}" ;
+
+ # allow use of multiple rings (rrp) at cluster creation time
+ if ($param->{bindnet1_addr}) {
+ die "IPv6 and IPv4 cannot be mixed, use one or the other!\n"
+ if Net::IP::ip_is_ipv6($param->{bindnet1_addr}) != $bind_is_ipv6;
+
+ die "rrp_mode 'none' is not allowed when using multiple interfaces,".
+ " use 'active' or 'passive'!\n"
+ if !$param->{rrp_mode} || $param->{rrp_mode} eq 'none';
+
+ $interfaces .= "\n interface {\n ringnumber: 1\n" .
+ " bindnetaddr: $param->{bindnet1_addr}\n }\n";
+
+ $ring_addresses .= "\n ring1_addr: $param->{ring1_addr}";
+
+ } elsif($param->{rrp_mode} && $param->{rrp_mode} ne 'none') {
+
+ warn "rrp_mode '$param->{rrp_mode}' useless when using only one".
+ " ring, using 'none' instead";
+ # corosync defaults to none if only one interface is configured
+ $param->{rrp_mode} = undef;
+
+ }
+
+ $interfaces = "rrp_mode: $param->{rrp_mode}\n " . $interfaces
+ if $param->{rrp_mode};
+
# No, corosync cannot deduce this on its own
- my $ipversion = Net::IP::ip_is_ipv6($local_ip_address) ? 'ipv6' : 'ipv4';
+ my $ipversion = $bind_is_ipv6 ? 'ipv6' : 'ipv4';
my $config = <<_EOD;
totem {
cluster_name: $clustername
config_version: 1
ip_version: $ipversion
- interface {
- ringnumber: 0
- bindnetaddr: $local_ip_address
- }
+ $interfaces
}
nodelist {
node {
- ring0_addr: $nodename
+ $ring_addresses
+ name: $nodename
nodeid: $param->{nodeid}
quorum_votes: $param->{votes}
}
}
-
+
quorum {
provider: corosync_votequorum
}
debug: off
}
_EOD
-;
+;
PVE::Tools::file_set_contents($clusterconf, $config);
PVE::Cluster::ssh_merge_keys();
description => "Do not throw error if node already exists.",
optional => 1,
},
+ ring0_addr => {
+ type => 'string', format => 'address',
+ description => "Hostname (or IP) of the corosync ring0 address of this node.".
+ " Defaults to nodes hostname.",
+ optional => 1,
+ },
+ ring1_addr => {
+ type => 'string', format => 'address',
+ description => "Hostname (or IP) of the corosync ring1 address, this".
+ " needs an valid bindnet1_addr.",
+ optional => 1,
+ },
},
},
returns => { type => 'null' },
my $nodelist = corosync_nodelist($conf);
+ my $totem_cfg = corosync_totem_config($conf);
+
my $name = $param->{node};
+ $param->{ring0_addr} = $name if !$param->{ring0_addr};
+
+ die " ring1_addr needs a configured ring 1 interface!\n"
+ if $param->{ring1_addr} && !defined($totem_cfg->{interface}->{1});
+
if (defined(my $res = $nodelist->{$name})) {
$param->{nodeid} = $res->{nodeid} if !$param->{nodeid};
$param->{votes} = $res->{quorum_votes} if !defined($param->{votes});
eval { PVE::Cluster::ssh_merge_keys(); };
warn $@ if $@;
- $nodelist->{$name} = { ring0_addr => $name, nodeid => $param->{nodeid} };
+ $nodelist->{$name} = {
+ ring0_addr => $param->{ring0_addr},
+ nodeid => $param->{nodeid},
+ name => $name,
+ };
+ $nodelist->{$name}->{ring1_addr} = $param->{ring1_addr} if $param->{ring1_addr};
$nodelist->{$name}->{quorum_votes} = $param->{votes} if $param->{votes};
corosync_update_nodelist($conf, $nodelist);
parameters => {
additionalProperties => 0,
properties => {
- node => PVE::JSONSchema::get_standard_option('pve-node'),
+ node => {
+ type => 'string',
+ description => "Hostname or IP of the corosync ring0 address of this node.",
+ },
},
},
returns => { type => 'null' },
my $nodelist = corosync_nodelist($conf);
- my $nd = delete $nodelist->{$param->{node}};
- die "no such node '$param->{node}'\n" if !$nd;
-
+ my $node;
+ my $nodeid;
+
+ foreach my $tmp_node (keys %$nodelist) {
+ my $d = $nodelist->{$tmp_node};
+ my $ring0_addr = $d->{ring0_addr};
+ my $ring1_addr = $d->{ring1_addr};
+ if (($tmp_node eq $param->{node}) ||
+ (defined($ring0_addr) && ($ring0_addr eq $param->{node})) ||
+ (defined($ring1_addr) && ($ring1_addr eq $param->{node}))) {
+ $node = $tmp_node;
+ $nodeid = $d->{nodeid};
+ last;
+ }
+ }
+
+ die "Node/IP: $param->{node} is not a known host of the cluster.\n"
+ if !defined($node);
+
+ delete $nodelist->{$node};
+
corosync_update_nodelist($conf, $nodelist);
+ PVE::Tools::run_command(['corosync-cfgtool','-k', $nodeid])
+ if defined($nodeid);
+
return undef;
}});
description => "Do not throw error if node already exists.",
optional => 1,
},
+ ring0_addr => {
+ type => 'string', format => 'address',
+ description => "Hostname (or IP) of the corosync ring0 address of this node.".
+ " Defaults to nodes hostname.",
+ optional => 1,
+ },
+ ring1_addr => {
+ type => 'string', format => 'address',
+ description => "Hostname (or IP) of the corosync ring1 address, this".
+ " needs an valid configured ring 1 interface in the cluster.",
+ optional => 1,
+ },
},
},
returns => { type => 'null' },
# make sure known_hosts is on local filesystem
PVE::Cluster::ssh_unmerge_known_hosts();
- my $cmd = "ssh-copy-id -i /root/.ssh/id_rsa 'root\@$host' >/dev/null 2>&1";
- system ($cmd) == 0 ||
- die "unable to copy ssh ID\n";
+ my $cmd = ['ssh-copy-id', '-i', '/root/.ssh/id_rsa', "root\@$host"];
+ PVE::Tools::run_command($cmd, 'outfunc' => sub {}, 'errfunc' => sub {},
+ 'errmsg' => "unable to copy ssh ID");
$cmd = ['ssh', $host, '-o', 'BatchMode=yes',
'pvecm', 'addnode', $nodename, '--force', 1];
push @$cmd, '--votes', $param->{votes} if defined($param->{votes});
+ push @$cmd, '--ring0_addr', $param->{ring0_addr} if defined($param->{ring0_addr});
+
+ push @$cmd, '--ring1_addr', $param->{ring1_addr} if defined($param->{ring1_addr});
+
if (system (@$cmd) != 0) {
my $cmdtxt = join (' ', @$cmd);
die "unable to add node: command failed ($cmdtxt)\n";
code => sub {
my ($param) = @_;
+ PVE::Cluster::check_corosync_conf_exists();
+
my $cmd = ['corosync-quorumtool', '-siH'];
exec (@$cmd);
code => sub {
my ($param) = @_;
+ PVE::Cluster::check_corosync_conf_exists();
+
my $cmd = ['corosync-quorumtool', '-l'];
exec (@$cmd);
code => sub {
my ($param) = @_;
+ PVE::Cluster::check_corosync_conf_exists();
+
my $cmd = ['corosync-quorumtool', '-e', $param->{expected}];
exec (@$cmd);
return undef;
}});
+__PACKAGE__->register_method ({
+ name => 'mtunnel',
+ path => 'mtunnel',
+ method => 'POST',
+ description => "Used by VM/CT migration - do not use manually.",
+ parameters => {
+ additionalProperties => 0,
+ properties => {},
+ },
+ returns => { type => 'null'},
+ code => sub {
+ my ($param) = @_;
+
+ if (!PVE::Cluster::check_cfs_quorum(1)) {
+ print "no quorum\n";
+ return undef;
+ }
+
+ print "tunnel online\n";
+ *STDOUT->flush();
+
+ while (my $line = <>) {
+ chomp $line;
+ last if $line =~ m/^quit$/;
+ }
+
+ return undef;
+ }});
+
our $cmddef = {
keygen => [ __PACKAGE__, 'keygen', ['filename']],
nodes => [ __PACKAGE__, 'nodes' ],
expected => [ __PACKAGE__, 'expected', ['expected']],
updatecerts => [ __PACKAGE__, 'updatecerts', []],
+ mtunnel => [ __PACKAGE__, 'mtunnel', []],
};
1;
-
-__END__
-
-=head1 NAME
-
-pvecm - Proxmox VE cluster manager toolkit
-
-=head1 SYNOPSIS
-
-=include synopsis
-
-=head1 DESCRIPTION
-
-pvecm is a program to manage the cluster configuration. It can be used
-to create a new cluster, join nodes to a cluster, leave the cluster,
-get status information and do various other cluster related tasks.
-
-=include pve_copyright
-
-