]> git.proxmox.com Git - pmg-api.git/blobdiff - PMG/CLI/pmgcm.pm
rewrite config log followup: move common log message out in closure
[pmg-api.git] / PMG / CLI / pmgcm.pm
index b24af92ec1ad6fbe6e0d5370fc30642c46750aaa..c41c24d5d012905f10d7fd9e2d24213058f76f5b 100644 (file)
@@ -3,19 +3,42 @@ package PMG::CLI::pmgcm;
 use strict;
 use warnings;
 use Data::Dumper;
+use Term::ReadLine;
+use POSIX qw(strftime);
+use JSON;
 
 use PVE::SafeSyslog;
 use PVE::Tools qw(extract_param);
 use PVE::INotify;
 use PVE::CLIHandler;
 
+use PMG::Utils;
+use PMG::Ticket;
+use PMG::RESTEnvironment;
 use PMG::DBTools;
+use PMG::RuleDB;
+use PMG::RuleCache;
 use PMG::Cluster;
 use PMG::ClusterConfig;
 use PMG::API2::Cluster;
 
 use base qw(PVE::CLIHandler);
 
+sub setup_environment {
+    PMG::RESTEnvironment->setup_default_cli_env();
+
+    my $rpcenv = PMG::RESTEnvironment->get();
+    # API /config/cluster/nodes need a ticket to connect to other nodes
+    my $ticket = PMG::Ticket::assemble_ticket('root@pam');
+    $rpcenv->set_ticket($ticket);
+}
+
+my $upid_exit = sub {
+    my $upid = shift;
+    my $status = PVE::Tools::upid_read_status($upid);
+    exit($status eq 'OK' ? 0 : -1);
+};
+
 my $format_nodelist = sub {
     my $res = shift;
 
@@ -26,11 +49,33 @@ my $format_nodelist = sub {
 
     print "NAME(CID)--------------IPADDRESS----ROLE-STATE---------UPTIME---LOAD----MEM---DISK\n";
     foreach my $ni (@$res) {
-       my $state = '?';
+       my $state = 'A';
+       $state = 'S' if !$ni->{insync};
+
+       if (my $err = $ni->{conn_error}) {
+           $err =~ s/\n/ /g;
+           $state = "ERROR: $err";
+       }
+
+       my $uptime = $ni->{uptime} ? PMG::Utils::format_uptime($ni->{uptime}) : '-';
+
+       my $loadavg1 = '-';
+       if (my $d = $ni->{loadavg}) {
+           $loadavg1 = $d->[0];
+       }
+
+       my $mem = '-';
+       if (my $d = $ni->{memory}) {
+           $mem = int(0.5 + ($d->{used}*100/$d->{total}));
+       }
+       my $disk = '-';
+       if (my $d = $ni->{rootfs}) {
+           $disk = int(0.5 + ($d->{used}*100/$d->{total}));
+       }
 
        printf "%-20s %-15s %-6s %1s %15s %6s %5s%% %5s%%\n",
        "$ni->{name}($ni->{cid})", $ni->{ip}, $ni->{type},
-       $state, '-', '-', '-', '-';
+       $state, $uptime, $loadavg1, $mem, $disk;
     }
 };
 
@@ -38,7 +83,7 @@ __PACKAGE__->register_method({
     name => 'join_cmd',
     path => 'join_cmd',
     method => 'GET',
-    description => "Print information about cluster master node.",
+    description => "Prints the command for joining an new node to the cluster. You need to execute the command on the new node.",
     parameters => {
        additionalProperties => 0,
        properties => {},
@@ -47,14 +92,14 @@ __PACKAGE__->register_method({
     code => sub {
        my ($param) = @_;
 
-       my $cfg = PVE::INotify::read_file('cluster.conf');
+       my $cinfo = PMG::ClusterConfig->new();
 
-       if (scalar(keys %{$cfg->{ids}})) {
+       if (scalar(keys %{$cinfo->{ids}})) {
 
-           my $master = $cfg->{master} ||
+           my $master = $cinfo->{master} ||
                die "no master found\n";
 
-           print "pmgcm join --master_ip $master->{ip} --fingerprint $master->{fingerprint}\n";
+           print "pmgcm join $master->{ip} --fingerprint $master->{fingerprint}\n";
 
        } else {
            die "no cluster defined\n";
@@ -63,11 +108,205 @@ __PACKAGE__->register_method({
        return undef;
     }});
 
+__PACKAGE__->register_method({
+    name => 'delete',
+    path => 'delete',
+    method => 'GET',
+    description => "Remove a node from the cluster.",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           cid => {
+               description => "Cluster Node ID.",
+               type => 'integer',
+               minimum => 1,
+           },
+       },
+    },
+    returns => { type => 'null' },
+    code => sub {
+       my ($param) = @_;
+
+       my $code = sub {
+           my $cinfo = PMG::ClusterConfig->new();
+
+           die "no cluster defined\n" if !scalar(keys %{$cinfo->{ids}});
+
+           my $master = $cinfo->{master} || die "unable to lookup master node\n";
+
+           die "operation not permitted (not master)\n"
+               if $cinfo->{local}->{cid} != $master->{cid};
+
+           my $cid = $param->{cid};
+
+           die "unable to delete master node\n"
+               if $cinfo->{local}->{cid} == $cid;
+
+           die "no such node (cid == $cid does not exists)\n" if !$cinfo->{ids}->{$cid};
+
+           delete $cinfo->{ids}->{$cid};
+
+           $cinfo->write();
+       };
+
+       PMG::ClusterConfig::lock_config($code, "delete cluster node failed");
+
+       return undef;
+    }});
+
+__PACKAGE__->register_method({
+    name => 'join',
+    path => 'join',
+    method => 'GET',
+    description => "Join a new node to an existing cluster.",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           master_ip => {
+               description => "IP address.",
+               type => 'string', format => 'ip',
+           },
+           fingerprint => {
+               description => "SSL certificate fingerprint.",
+               type => 'string',
+               pattern => '^(:?[A-Z0-9][A-Z0-9]:){31}[A-Z0-9][A-Z0-9]$',
+               optional => 1,
+           },
+       },
+    },
+    returns => { type => 'null' },
+    code => sub {
+       my ($param) = @_;
+
+       my $code = sub {
+           my $cinfo = PMG::ClusterConfig->new();
+
+           die "cluster already defined\n" if scalar(keys %{$cinfo->{ids}});
+
+           my $term = new Term::ReadLine ('pmgcm');
+           my $attribs = $term->Attribs;
+           $attribs->{redisplay_function} = $attribs->{shadow_redisplay};
+           my $password = $term->readline('Enter password: ');
+
+           my $setup = {
+               username => 'root@pam',
+               password => $password,
+               cookie_name => 'PMGAuthCookie',
+               host => $param->{master_ip},
+           };
+           if ($param->{fingerprint}) {
+               $setup->{cached_fingerprints} = {
+                   $param->{fingerprint} => 1,
+               };
+           } else {
+               # allow manual fingerprint verification
+               $setup->{manual_verification} = 1;
+           }
+
+           PMG::API2::Cluster::cluster_join($cinfo, $setup);
+       };
+
+       PMG::ClusterConfig::lock_config($code, "cluster join failed");
+
+       return undef;
+    }});
+
+__PACKAGE__->register_method({
+    name => 'sync',
+    path => 'sync',
+    method => 'GET',
+    description => "Synchronize cluster configuration.",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           master_ip => {
+               description => 'Optional IP address for master node.',
+               type => 'string', format => 'ip',
+               optional => 1,
+           }
+       },
+    },
+    returns => { type => 'null' },
+    code => sub {
+       my ($param) = @_;
+
+       my $cinfo = PMG::ClusterConfig->new();
+
+        my $master_name = undef;
+       my $master_ip = $param->{master_ip};
+
+       if (!$master_ip && $cinfo->{master}) {
+           $master_ip = $cinfo->{master}->{ip};
+           $master_name = $cinfo->{master}->{name};
+       }
+
+       die "no master IP specified (use option --master_ip)\n" if !$master_ip;
+
+       if ($cinfo->{local}->{ip} eq $master_ip) {
+           print STDERR "local node is master - nothing to do\n";
+           return undef;
+       }
+
+       print STDERR "syncing master configuration from '${master_ip}'\n";
+
+       PMG::Cluster::sync_config_from_master($master_name, $master_ip);
+
+       my $cfg = PMG::Config->new();
+
+       $cfg->rewrite_config(undef, 1);
+
+       return undef;
+    }});
+
+__PACKAGE__->register_method({
+    name => 'promote',
+    path => 'promote',
+    method => 'POST',
+    description => "Promote current node to become the new master.",
+    parameters => {
+       additionalProperties => 0,
+       properties => {},
+    },
+    returns => { type => 'null' },
+    code => sub {
+       my ($param) = @_;
+
+       my $code = sub {
+           my $cinfo = PMG::ClusterConfig->new();
+
+           die "no cluster defined\n" if !scalar(keys %{$cinfo->{ids}});
+
+           my $master = $cinfo->{master} || die "unable to lookup master node\n";
+
+           die "this node is already master\n"
+               if $cinfo->{local}->{cid} == $master->{cid};
+
+           my $maxcid = $master->{maxcid};
+           $master->{type} = 'node';
+
+           my $newmaster = $cinfo->{local};
+
+           $newmaster->{maxcid} = $maxcid;
+           $newmaster->{type} = 'master';
+
+           $cinfo->{master} = $newmaster;
+
+           $cinfo->write();
+       };
+
+       PMG::ClusterConfig::lock_config($code, "promote new master failed");
+
+       return undef;
+    }});
+
 our $cmddef = {
-    nodes => [ 'PMG::API2::Cluster', 'nodes', [], {}, $format_nodelist],
-    create => [ 'PMG::API2::Cluster', 'create', []],
-    join => [ 'PMG::API2::Cluster', 'join', ['master_ip', 'fingerprint']],
+    status => [ 'PMG::API2::Cluster', 'status', [], {}, $format_nodelist],
+    create => [ 'PMG::API2::Cluster', 'create', [], {}, $upid_exit],
+    delete => [ __PACKAGE__, 'delete', ['cid']],
+    join => [ __PACKAGE__, 'join', ['master_ip']],
     join_cmd => [ __PACKAGE__, 'join_cmd', []],
+    sync => [ __PACKAGE__, 'sync', []],
+    promote => [ __PACKAGE__, 'promote', []],
 };
 
 1;