]> git.proxmox.com Git - pve-cluster.git/blobdiff - data/PVE/CLI/pvecm.pm
add 'mtunnel' to pvecm
[pve-cluster.git] / data / PVE / CLI / pvecm.pm
index ae39af50f1d9664b5ac2f31bdf34a2ac2b8f4e18..624c09613847118be27c56d48d686a716ff09f8b 100755 (executable)
@@ -13,7 +13,6 @@ use PVE::Tools;
 use PVE::Cluster;
 use PVE::INotify;
 use PVE::JSONSchema;
-use PVE::RPCEnvironment;
 use PVE::CLIHandler;
 
 use base qw(PVE::CLIHandler);
@@ -34,12 +33,13 @@ sub backup_database {
     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;
@@ -121,7 +121,40 @@ __PACKAGE__->register_method ({
                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' },
     
@@ -148,8 +181,50 @@ __PACKAGE__->register_method ({
        
        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 {
@@ -158,20 +233,18 @@ 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
 }
@@ -181,7 +254,7 @@ logging {
   debug: off
 }
 _EOD
-;      
+;
        PVE::Tools::file_set_contents($clusterconf, $config);
 
        PVE::Cluster::ssh_merge_keys();
@@ -223,6 +296,18 @@ __PACKAGE__->register_method ({
                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' },
@@ -236,8 +321,15 @@ __PACKAGE__->register_method ({
 
        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});
@@ -278,7 +370,12 @@ __PACKAGE__->register_method ({
        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);
@@ -295,7 +392,10 @@ __PACKAGE__->register_method ({
     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' },
@@ -309,11 +409,32 @@ __PACKAGE__->register_method ({
 
        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;
     }});
 
@@ -346,6 +467,18 @@ __PACKAGE__->register_method ({
                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' },
@@ -384,9 +517,9 @@ __PACKAGE__->register_method ({
        # 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];
@@ -395,6 +528,10 @@ __PACKAGE__->register_method ({
 
        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";
@@ -488,6 +625,8 @@ __PACKAGE__->register_method ({
     code => sub {
        my ($param) = @_;
 
+       PVE::Cluster::check_corosync_conf_exists();
+
        my $cmd = ['corosync-quorumtool', '-siH'];
 
        exec (@$cmd);
@@ -509,6 +648,8 @@ __PACKAGE__->register_method ({
     code => sub {
        my ($param) = @_;
 
+       PVE::Cluster::check_corosync_conf_exists();
+
        my $cmd = ['corosync-quorumtool', '-l'];
 
        exec (@$cmd);
@@ -536,6 +677,8 @@ __PACKAGE__->register_method ({
     code => sub {
        my ($param) = @_;
 
+       PVE::Cluster::check_corosync_conf_exists();
+
        my $cmd = ['corosync-quorumtool', '-e', $param->{expected}];
 
        exec (@$cmd);
@@ -691,6 +834,35 @@ __PACKAGE__->register_method ({
        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']],
@@ -702,26 +874,7 @@ our $cmddef = {
     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
-
-