]> git.proxmox.com Git - pve-manager.git/blobdiff - PVE/API2/Nodes.pm
use login instead of bash
[pve-manager.git] / PVE / API2 / Nodes.pm
index 03521f5dcee5aadd55c1fedc128480d92d7f6462..c85c9f57715ab00bdb452509b4ffc8789b2a17cf 100644 (file)
@@ -2,28 +2,47 @@ package PVE::API2::Nodes::Nodeinfo;
 
 use strict;
 use warnings;
-use POSIX;
+use POSIX qw(LONG_MAX);
 use Filesys::Df;
 use Time::Local qw(timegm_nocheck);
+use HTTP::Status qw(:constants);
 use PVE::pvecfg;
 use PVE::Tools;
+use PVE::API2Tools;
 use PVE::ProcFSTools;
 use PVE::SafeSyslog;
-use PVE::Cluster;
+use PVE::Cluster qw(cfs_read_file);
 use PVE::INotify;
+use PVE::Exception qw(raise raise_perm_exc raise_param_exc);
 use PVE::RESTHandler;
 use PVE::RPCEnvironment;
 use PVE::JSONSchema qw(get_standard_option);
 use PVE::AccessControl;
-use PVE::OpenVZ;
+use PVE::Storage;
+use PVE::Firewall;
+use PVE::LXC;
+use PVE::APLInfo;
+use PVE::Report;
+use PVE::HA::Env::PVE2;
+use PVE::HA::Config;
+use PVE::QemuConfig;
+use PVE::QemuServer;
+use PVE::API2::Subscription;
 use PVE::API2::Services;
 use PVE::API2::Network;
 use PVE::API2::Tasks;
 use PVE::API2::Storage::Scan;
 use PVE::API2::Storage::Status;
 use PVE::API2::Qemu;
-use PVE::API2::OpenVZ;
+use PVE::API2::LXC;
+use PVE::API2::LXC::Status;
 use PVE::API2::VZDump;
+use PVE::API2::APT;
+use PVE::API2::Ceph;
+use PVE::API2::Firewall::Host;
+use Digest::MD5;
+use Digest::SHA;
+use PVE::API2::Disks;
 use JSON;
 
 use base qw(PVE::RESTHandler);
@@ -34,8 +53,13 @@ __PACKAGE__->register_method ({
 });
 
 __PACKAGE__->register_method ({
-    subclass => "PVE::API2::OpenVZ",  
-    path => 'openvz',
+    subclass => "PVE::API2::LXC",  
+    path => 'lxc',
+});
+
+__PACKAGE__->register_method ({
+    subclass => "PVE::API2::Ceph",  
+    path => 'ceph',
 });
 
 __PACKAGE__->register_method ({
@@ -48,6 +72,11 @@ __PACKAGE__->register_method ({
     path => 'services',
 });
 
+__PACKAGE__->register_method ({
+    subclass => "PVE::API2::Subscription",  
+    path => 'subscription',
+});
+
 __PACKAGE__->register_method ({
     subclass => "PVE::API2::Network",  
     path => 'network',
@@ -68,6 +97,21 @@ __PACKAGE__->register_method ({
     path => 'storage',
 });
 
+__PACKAGE__->register_method ({
+   subclass => "PVE::API2::Disks",
+   path => 'disks',
+});
+
+__PACKAGE__->register_method ({
+    subclass => "PVE::API2::APT",  
+    path => 'apt',
+});
+
+__PACKAGE__->register_method ({
+    subclass => "PVE::API2::Firewall::Host",  
+    path => 'firewall',
+});
+
 __PACKAGE__->register_method ({
     name => 'index', 
     path => '', 
@@ -92,25 +136,33 @@ __PACKAGE__->register_method ({
        my ($param) = @_;
     
        my $result = [
+           { name => 'ceph' },
+           { name => 'disks' },
+           { name => 'apt' },
            { name => 'version' },
            { name => 'syslog' },
            { name => 'status' },
+           { name => 'subscription' },
+           { name => 'report' },
            { name => 'tasks' },
            { name => 'rrd' }, # fixme: remove?
            { name => 'rrddata' },# fixme: remove?
            { name => 'vncshell' },
+           { name => 'spiceshell' },
            { name => 'time' },
            { name => 'dns' },
            { name => 'services' },
            { name => 'scan' },
            { name => 'storage' },
-           { name => 'upload' },
            { name => 'qemu' },
-           { name => 'openvz' },
+           { name => 'lxc' },
            { name => 'vzdump' },
-           { name => 'ubcfailcnt' },
            { name => 'network' },
-           { name => 'network_changes' },
+           { name => 'aplinfo' },
+           { name => 'startall' },
+           { name => 'stopall' },
+           { name => 'netstat' },
+           { name => 'firewall' },
            ];
 
        return $result;
@@ -143,105 +195,12 @@ __PACKAGE__->register_method ({
        return PVE::pvecfg::version_info();
     }});
 
-__PACKAGE__->register_method({
-    name => 'beancounters_failcnt', 
-    path => 'ubcfailcnt',
-    permissions => {
-       path => '/nodes/{node}',
-       privs => [ 'Sys.Audit' ],
-    },
-    method => 'GET',
-    proxyto => 'node',
-    protected => 1, # openvz /proc entries are only readable by root
-    description => "Get user_beancounters failcnt for all active containers.",
-    parameters => {
-       additionalProperties => 0,
-       properties => {
-           node => get_standard_option('pve-node'),
-       },
-    },
-    returns => {
-       type => 'array',
-       items => {
-           type => "object",
-           properties => {
-               id => { type => 'string' },
-               failcnt => { type => 'number' },
-           },
-       },
-    },
-    code => sub {
-       my ($param) = @_;
-
-       my $ubchash = PVE::OpenVZ::read_user_beancounters();
-
-       my $res = [];
-       foreach my $vmid (keys %$ubchash) {
-           next if !$vmid;
-           push @$res, { id => $vmid, failcnt => $ubchash->{$vmid}->{failcntsum} };
-
-       }
-       return $res;
-    }});
-
-__PACKAGE__->register_method({
-    name => 'network_changes', 
-    path => 'network_changes', 
-    method => 'GET',
-    permissions => {
-       path => '/nodes/{node}',
-       privs => [ 'Sys.Audit' ],
-    },
-    description => "Get network configuration changes (diff) since last boot.",
-    proxyto => 'node',
-    parameters => {
-       additionalProperties => 0,
-       properties => {
-           node => get_standard_option('pve-node'),
-       },
-    },
-    returns => { type => "string" },
-    code => sub {
-       my ($param) = @_;
-
-       my $res = PVE::INotify::read_file('interfaces', 1);
-
-       return $res->{changes} || '';
-   }});
-
-__PACKAGE__->register_method({
-    name => 'revert_network_changes', 
-    path => 'network_changes', 
-    method => 'DELETE',
-    permissions => {
-       path => '/nodes/{node}',
-       privs => [ 'Sys.Modify' ],
-    },
-    protected => 1,
-    description => "Revert network configuration changes.",
-    proxyto => 'node',
-    parameters => {
-       additionalProperties => 0,
-       properties => {
-           node => get_standard_option('pve-node'),
-       },
-    },
-    returns => { type => "null" },
-    code => sub {
-       my ($param) = @_;
-
-       unlink "/etc/network/interfaces.new";
-
-       return undef;
-   }});
-
 __PACKAGE__->register_method({
     name => 'status', 
     path => 'status', 
     method => 'GET',
     permissions => {
-       path => '/nodes/{node}',
-       privs => [ 'Sys.Audit' ],
+       check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
     },
     description => "Read node status",
     proxyto => 'node',
@@ -287,6 +246,11 @@ __PACKAGE__->register_method({
            total => $meminfo->{memtotal},
            used => $meminfo->{memused},
        };
+       
+       $res->{ksm} = {
+           shared => $meminfo->{memshared},
+       };
+
        $res->{swap} = {
            free => $meminfo->{swapfree},
            total => $meminfo->{swaptotal},
@@ -308,13 +272,141 @@ __PACKAGE__->register_method({
        return $res;
     }});
 
+__PACKAGE__->register_method({
+    name => 'netstat',
+    path => 'netstat',
+    method => 'GET',
+    permissions => {
+       check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
+    },
+    description => "Read tap/vm network device interface counters",
+    proxyto => 'node',
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           node => get_standard_option('pve-node'),
+       },
+    },
+    returns => {
+        type => "array",
+        items => {
+            type => "object",
+            properties => {},
+        },
+    },
+    code => sub {
+       my ($param) = @_;
+
+       my $res = [ ];
+
+       my $netdev = PVE::ProcFSTools::read_proc_net_dev();
+       foreach my $dev (keys %$netdev) {
+               next if $dev !~ m/^(?:tap|veth)([1-9]\d*)i(\d+)$/;
+               my $vmid = $1;
+               my $netid = $2;
+
+                push(
+                    @$res,
+                    {
+                        vmid => $vmid,
+                        dev  => "net$netid",
+                        in   => $netdev->{$dev}->{transmit},
+                        out  => $netdev->{$dev}->{receive},
+                    }
+                );
+       }
+
+       return $res;
+    }});
+
+__PACKAGE__->register_method({
+    name => 'execute',
+    path => 'execute',
+    method => 'POST',
+    permissions => {
+       check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
+    },
+    description => "Execute multiple commands in order.",
+    proxyto => 'node',
+    protected => 1, # avoid problems with proxy code
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           node => get_standard_option('pve-node'),
+           commands => {
+               description => "JSON encoded array of commands.",
+               type => "string",
+           }
+       },
+    },
+    returns => {
+       type => 'array',
+       properties => {
+
+       },
+    },
+    code => sub {
+       my ($param) = @_;
+       my $res = [];
+
+       my $rpcenv = PVE::RPCEnvironment::get();
+       my $user = $rpcenv->get_user();
+
+       my $commands = eval { decode_json($param->{commands}); };
+
+       die "commands param did not contain valid JSON: $@" if $@;
+       die "commands is not an array" if ref($commands) ne "ARRAY";
+
+        foreach my $cmd (@$commands) {
+           eval {
+               die "$cmd is not a valid command" if (ref($cmd) ne "HASH" || !$cmd->{path} || !$cmd->{method});
+           
+               $cmd->{args} //= {};
+
+               my $path = "nodes/$param->{node}/$cmd->{path}";
+               
+               my $uri_param = {};
+               my ($handler, $info) = PVE::API2->find_handler($cmd->{method}, $path, $uri_param);
+               if (!$handler || !$info) {
+                   die "no handler for '$path'\n";
+               }
+
+               foreach my $p (keys %{$cmd->{args}}) {
+                   raise_param_exc({ $p => "duplicate parameter" }) if defined($uri_param->{$p});
+                   $uri_param->{$p} = $cmd->{args}->{$p};
+               }
+
+               # check access permissions
+               $rpcenv->check_api2_permissions($info->{permissions}, $user, $uri_param);
+
+               push @$res, {
+                   status => HTTP_OK,
+                   data => $handler->handle($info, $uri_param),
+               };
+           };
+           if (my $err = $@) {
+               my $resp = { status => HTTP_INTERNAL_SERVER_ERROR };
+               if (ref($err) eq "PVE::Exception") {
+                   $resp->{status} = $err->{code} if $err->{code};
+                   $resp->{errors} = $err->{errors} if $err->{errors};
+                   $resp->{message} = $err->{msg};
+               } else {
+                   $resp->{message} = $err;
+               }
+               push @$res, $resp;
+           }
+       }
+
+       return $res;
+    }});
+
+
 __PACKAGE__->register_method({
     name => 'node_cmd', 
     path => 'status', 
     method => 'POST',
     permissions => {
-       path => '/nodes/{node}',
-       privs => [ 'Sys.PowerMgmt' ],
+       check => ['perm', '/nodes/{node}', [ 'Sys.PowerMgmt' ]],
     },
     protected => 1,
     description => "Reboot or shutdown a node.",
@@ -350,8 +442,7 @@ __PACKAGE__->register_method({
     method => 'GET',
     protected => 1, # fixme: can we avoid that?
     permissions => {
-       path => '/nodes/{node}',
-       privs => [ 'Sys.Audit' ],
+       check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
     },
     description => "Read node RRD statistics (returns PNG)",
     parameters => {
@@ -396,8 +487,7 @@ __PACKAGE__->register_method({
     method => 'GET',
     protected => 1, # fixme: can we avoid that?
     permissions => {
-       path => '/nodes/{node}',
-       privs => [ 'Sys.Audit' ],
+       check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
     },
     description => "Read node RRD statistics",
     parameters => {
@@ -438,8 +528,7 @@ __PACKAGE__->register_method({
     description => "Read system log",
     proxyto => 'node',
     permissions => {
-       path => '/nodes/{node}',
-       privs => [ 'Sys.Syslog' ],
+       check => ['perm', '/nodes/{node}', [ 'Sys.Syslog' ]],
     },
     protected => 1,
     parameters => {
@@ -456,6 +545,18 @@ __PACKAGE__->register_method({
                minimum => 0,
                optional => 1,
            },
+           since => {
+               type=> 'string',
+               pattern => '^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$',
+               description => "Display all log since this date-time string.",
+               optional => 1,
+           },
+           until => {
+               type=> 'string',
+               pattern => '^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$',
+               description => "Display all log until this date-time string.",
+               optional => 1,
+           },
        },
     },
     returns => {
@@ -481,11 +582,12 @@ __PACKAGE__->register_method({
        my $user = $rpcenv->get_user();
        my $node = $param->{node};
 
-       my ($count, $lines) = PVE::Tools::dump_logfile("/var/log/syslog", $param->{start}, $param->{limit});
+       my ($count, $lines) = PVE::Tools::dump_journal($param->{start}, $param->{limit},
+                                                      $param->{since}, $param->{until});
 
        $rpcenv->set_result_attrib('total', $count);
-           
-       return $lines; 
+
+       return $lines;
     }});
 
 my $sslcert;
@@ -496,14 +598,25 @@ __PACKAGE__->register_method ({
     method => 'POST',
     protected => 1,
     permissions => {
-       path => '/nodes/{node}',
-       privs => [ 'Sys.Console' ],
+       description => "Restricted to users on realm 'pam'",
+       check => ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
     },
     description => "Creates a VNC Shell proxy.",
     parameters => {
        additionalProperties => 0,
        properties => {
            node => get_standard_option('pve-node'),
+           upgrade => {
+               type => 'boolean',
+               description => "Run 'apt-get dist-upgrade' instead of normal shell.",
+               optional => 1,
+               default => 0,
+           },
+           websocket => {
+               optional => 1,
+               type => 'boolean',
+               description => "use websocket instead of standard vnc.",
+           },
        },
     },
     returns => { 
@@ -521,56 +634,91 @@ __PACKAGE__->register_method ({
 
        my $rpcenv = PVE::RPCEnvironment::get();
 
-       my $user = $rpcenv->get_user();
+       my ($user, undef, $realm) = PVE::AccessControl::verify_username($rpcenv->get_user());
+
+       raise_perm_exc("realm != pam") if $realm ne 'pam'; 
 
-       my $ticket = PVE::AccessControl::assemble_ticket($user);
+       raise_perm_exc('user != root@pam') if $param->{upgrade} && $user ne 'root@pam';
 
        my $node = $param->{node};
 
+       my $authpath = "/nodes/$node";
+
+       my $ticket = PVE::AccessControl::assemble_vnc_ticket($user, $authpath);
+
        $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192)
            if !$sslcert;
 
-       my $port = PVE::Tools::next_vnc_port();
+       my ($remip, $family);
 
-       my $remip;
-       
        if ($node ne PVE::INotify::nodename()) {
-           $remip = PVE::Cluster::remote_node_ip($node);
+           ($remip, $family) = PVE::Cluster::remote_node_ip($node);
+       } else {
+           $family = PVE::Tools::get_host_address_family($node);
        }
 
+       my $port = PVE::Tools::next_vnc_port($family);
+
        # NOTE: vncterm VNC traffic is already TLS encrypted,
        # so we select the fastest chipher here (or 'none'?)
        my $remcmd = $remip ? 
-           ['/usr/bin/ssh', '-c', 'blowfish-cbc', '-t', $remip] : [];
+           ['/usr/bin/ssh', '-t', $remip] : [];
 
-       my $shcmd = $user eq 'root@pam' ? [ "/bin/bash", "-l" ] : [ "/bin/login" ];
+       my $shcmd;
+
+       if ($user eq 'root@pam') {
+           if ($param->{upgrade}) {
+               my $upgradecmd = "pveupgrade --shell";
+               $upgradecmd = PVE::Tools::shellquote($upgradecmd) if $remip;
+               $shcmd = [ '/bin/bash', '-c', $upgradecmd ];
+           } else {
+               $shcmd = [ '/bin/login', '-f', 'root' ];
+           }
+       } else {
+           $shcmd = [ '/bin/login' ];
+       }
 
        my $timeout = 10; 
 
-       # fixme: do we want to require special auth permissions?
-       # example "-perm Shell"
-       my @cmd = ('/usr/bin/vncterm', '-rfbport', $port,
-                  '-timeout', $timeout, '-authpath', "/nodes/$node", 
-                  '-perm', 'Sys.Console', '-c', @$remcmd, @$shcmd);
+       my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
+                  '-timeout', $timeout, '-authpath', $authpath, 
+                  '-perm', 'Sys.Console'];
+
+       if ($param->{websocket}) {
+           $ENV{PVE_VNC_TICKET} = $ticket; # pass ticket to vncterm 
+           push @$cmd, '-notls', '-listen', 'localhost';
+       }
+
+       push @$cmd, '-c', @$remcmd, @$shcmd;
 
        my $realcmd = sub {
            my $upid = shift;
 
            syslog ('info', "starting vnc proxy $upid\n");
 
-           my $cmdstr = join (' ', @cmd);
+           my $cmdstr = join (' ', @$cmd);
            syslog ('info', "launch command: $cmdstr");
 
-           if (system(@cmd) != 0) {
-               my $msg = "vncterm failed - $?";
-               syslog ('err', $msg);
-               return;
+           eval { 
+               foreach my $k (keys %ENV) {
+                   next if $k eq 'PVE_VNC_TICKET';
+                   next if $k eq 'PATH' || $k eq 'TERM' || $k eq 'USER' || $k eq 'HOME' || $k eq 'LANG' || $k eq 'LANGUAGE';
+                   delete $ENV{$k};
+               }
+               $ENV{PWD} = '/';
+
+               PVE::Tools::run_command($cmd, errmsg => "vncterm failed", keeplocale => 1);
+           };
+           if (my $err = $@) {
+               syslog ('err', $err);
            }
 
            return;
        };
 
        my $upid = $rpcenv->fork_worker('vncshell', "", $user, $realcmd);
+       
+       PVE::Tools::wait_for_vnc_port($port);
 
        return {
            user => $user,
@@ -581,13 +729,123 @@ __PACKAGE__->register_method ({
        };
     }});
 
+__PACKAGE__->register_method({
+    name => 'vncwebsocket',
+    path => 'vncwebsocket',
+    method => 'GET',
+    permissions => { 
+       description => "Restricted to users on realm 'pam'. You also need to pass a valid ticket (vncticket).",
+       check => ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
+    },
+    description => "Opens a weksocket for VNC traffic.",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           node => get_standard_option('pve-node'),
+           vncticket => {
+               description => "Ticket from previous call to vncproxy.",
+               type => 'string',
+               maxLength => 512,
+           },
+           port => {
+               description => "Port number returned by previous vncproxy call.",
+               type => 'integer',
+               minimum => 5900,
+               maximum => 5999,
+           },
+       },
+    },
+    returns => {
+       type => "object",
+       properties => {
+           port => { type => 'string' },
+       },
+    },
+    code => sub {
+       my ($param) = @_;
+
+       my $rpcenv = PVE::RPCEnvironment::get();
+
+       my ($user, undef, $realm) = PVE::AccessControl::verify_username($rpcenv->get_user());
+
+       raise_perm_exc("realm != pam") if $realm ne 'pam'; 
+
+       my $authpath = "/nodes/$param->{node}";
+
+       PVE::AccessControl::verify_vnc_ticket($param->{vncticket}, $user, $authpath);
+
+       my $port = $param->{port};
+       
+       return { port => $port };
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'spiceshell', 
+    path => 'spiceshell',  
+    method => 'POST',
+    protected => 1,
+    proxyto => 'node',
+    permissions => {
+       description => "Restricted to users on realm 'pam'",
+       check => ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
+    },
+    description => "Creates a SPICE shell.",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           node => get_standard_option('pve-node'),
+           proxy => get_standard_option('spice-proxy', { optional => 1 }),
+           upgrade => {
+               type => 'boolean',
+               description => "Run 'apt-get dist-upgrade' instead of normal shell.",
+               optional => 1,
+               default => 0,
+           },
+       },
+    },
+    returns => get_standard_option('remote-viewer-config'),
+    code => sub {
+       my ($param) = @_;
+
+       my $rpcenv = PVE::RPCEnvironment::get();
+       my $authuser = $rpcenv->get_user();
+
+       my ($user, undef, $realm) = PVE::AccessControl::verify_username($authuser);
+
+       raise_perm_exc("realm != pam") if $realm ne 'pam'; 
+
+       raise_perm_exc('user != root@pam') if $param->{upgrade} && $user ne 'root@pam';
+
+       my $node = $param->{node};
+       my $proxy = $param->{proxy};
+
+       my $authpath = "/nodes/$node";
+       my $permissions = 'Sys.Console';
+
+       my $shcmd;
+
+       if ($user eq 'root@pam') {
+           if ($param->{upgrade}) {
+               my $upgradecmd = "pveupgrade --shell";
+               $shcmd = [ '/bin/bash', '-c', $upgradecmd ];
+           } else {
+               $shcmd = [ '/bin/login', '-f', 'root' ];
+           }
+       } else {
+           $shcmd = [ '/bin/login' ];
+       }
+
+       my $title = "Shell on '$node'";
+
+       return PVE::API2Tools::run_spiceterm($authpath, $permissions, 0, $node, $proxy, $title, $shcmd);
+    }});
+
 __PACKAGE__->register_method({
     name => 'dns', 
     path => 'dns', 
     method => 'GET',
     permissions => {
-       path => '/nodes/{node}',
-       privs => [ 'Sys.Audit' ],
+       check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
     },
     description => "Read DNS settings.",
     proxyto => 'node',
@@ -636,6 +894,9 @@ __PACKAGE__->register_method({
     path => 'dns', 
     method => 'PUT',
     description => "Write DNS settings.",
+    permissions => {
+       check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
+    },
     proxyto => 'node',
     protected => 1,
     parameters => {
@@ -648,17 +909,17 @@ __PACKAGE__->register_method({
            },
            dns1 => {
                description => 'First name server IP address.',
-               type => 'string', format => 'ipv4',
+               type => 'string', format => 'ip',
                optional => 1,
            },          
            dns2 => {
                description => 'Second name server IP address.',
-               type => 'string', format => 'ipv4',
+               type => 'string', format => 'ip',
                optional => 1,
            },          
            dns3 => {
                description => 'Third name server IP address.',
-               type => 'string', format => 'ipv4',
+               type => 'string', format => 'ip',
                optional => 1,
            },          
        },
@@ -677,9 +938,8 @@ __PACKAGE__->register_method({
     path => 'time', 
     method => 'GET',
     permissions => {
-       path => '/nodes/{node}',
-       privs => [ 'Sys.Audit' ],
-    },
+       check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
+   },
     description => "Read server time and time zone settings.",
     proxyto => 'node',
     parameters => {
@@ -727,6 +987,9 @@ __PACKAGE__->register_method({
     path => 'time', 
     method => 'PUT',
     description => "Set time zone.",
+    permissions => {
+       check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
+    },
     proxyto => 'node',
     protected => 1,
     parameters => {
@@ -748,79 +1011,635 @@ __PACKAGE__->register_method({
        return undef;
     }});
 
-__PACKAGE__->register_method ({
-    name => 'upload', 
-    path => 'upload',
-    method => 'POST',
+__PACKAGE__->register_method({
+    name => 'aplinfo', 
+    path => 'aplinfo', 
+    method => 'GET',
     permissions => {
-       path => '/storage/{storage}',
-       privs => [ 'Datastore.AllocateSpace' ],
+       user => 'all',
     },
-    description => "Upload content.",
+    description => "Get list of appliances.",
+    proxyto => 'node',
     parameters => {
        additionalProperties => 0,
-       properties => { 
+       properties => {
            node => get_standard_option('pve-node'),
-           storage => get_standard_option('pve-storage-id'),
-           filename => { 
-               description => "The name of the file to create/upload.",
-               type => 'string',
-           },
-           vmid => get_standard_option
-               ('pve-vmid', { 
-                   description => "Specify owner VM",
-                   optional => 1,
-                }),
        },
     },
     returns => {
-       description => "Volume identifier",
-       type => 'string',
+       type => 'array',
+       items => {
+           type => "object",
+           properties => {},
+       },
     },
     code => sub {
        my ($param) = @_;
 
-       # todo: can we proxy file uploads to remote nodes?
-       if ($param->{node} ne PVE::INotify::nodename()) {
-           raise_param_exc({ node => "can't upload content to remote node" });
+       my $res = [];
+
+       my $list = PVE::APLInfo::load_data();
+
+       foreach my $template (keys %{$list->{all}}) {
+           my $pd = $list->{all}->{$template};
+           next if $pd->{'package'} eq 'pve-web-news';
+           push @$res, $pd;
        }
 
+       return $res;
+    }});
+
+__PACKAGE__->register_method({
+    name => 'apl_download', 
+    path => 'aplinfo', 
+    method => 'POST',
+    permissions => {
+       check => ['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']],
+    },
+    description => "Download appliance templates.",
+    proxyto => 'node',
+    protected => 1,
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           node => get_standard_option('pve-node'),
+           storage => get_standard_option('pve-storage-id', {
+               description => "The storage where the template will be stored",
+               completion => \&PVE::Storage::complete_storage_enabled,
+           }),
+           template => { type => 'string',
+                         description => "The template wich will downloaded",
+                         maxLength => 255,
+                         completion => \&complete_templet_repo,
+           },
+       },
+    },
+    returns => { type => "string" },
+    code => sub {
+       my ($param) = @_;
+
+       my $rpcenv = PVE::RPCEnvironment::get();
+
+       my $user = $rpcenv->get_user();
+
        my $node = $param->{node};
-       my $storeid = $param->{storage};
-       my $name = $param->{filename};
 
-       my $fh = CGI::upload('filename') || die "unable to get file handle\n";
+       my $list = PVE::APLInfo::load_data();
 
-       syslog ('info', "UPLOAD $name to $node $storeid");
-       
-       # fixme:
-       die "upload not implemented\n";
+       my $template = $param->{template};
+       my $pd = $list->{all}->{$template};
+
+       raise_param_exc({ template => "no such template"}) if !$pd;
+
+       my $cfg = PVE::Storage::config();
+       my $scfg = PVE::Storage::storage_check_enabled($cfg, $param->{storage}, $node);
+
+       die "unknown template type '$pd->{type}'\n"
+           if !($pd->{type} eq 'openvz' || $pd->{type} eq 'lxc');
 
-       my $buffer = "";
-       my $tmpname = "/tmp/proxmox_upload-$$.bin";
+       die "storage '$param->{storage}' does not support templates\n" 
+           if !$scfg->{content}->{vztmpl};
+
+       my $src = $pd->{location};
+       my $tmpldir = PVE::Storage::get_vztmpl_dir($cfg, $param->{storage});
+       my $dest = "$tmpldir/$template";
+       my $tmpdest = "$tmpldir/${template}.tmp.$$";
+
+       my $worker = sub  {
+           my $upid = shift;
+           
+           print "starting template download from: $src\n";
+           print "target file: $dest\n";
+
+           my $check_hash = sub {
+               my ($template_info, $filename, $noerr) = @_;
+
+               my $digest;
+               my $expected;
+
+               eval {
+                   open(my $fh, '<', $filename) or die "Can't open '$filename': $!";
+                   binmode($fh);
+                   if (defined($template_info->{sha512sum})) {
+                       $expected = $template_info->{sha512sum};
+                       $digest = Digest::SHA->new(512)->addfile($fh)->hexdigest;
+                   } elsif (defined($template_info->{md5sum})) {
+                       #fallback to MD5
+                       $expected = $template_info->{md5sum};
+                       $digest = Digest::MD5->new->addfile($fh)->hexdigest;
+                   } else {
+                       die "no expected checksum defined";
+                   }
+                   close($fh);
+               };
+
+               die "checking hash failed - $@\n" if $@ && !$noerr;
+
+               return ($digest, $digest ? lc($digest) eq lc($expected) : 0);
+           };
+
+           eval {
+               if (-f $dest) {
+                   my ($hash, $correct) = &$check_hash($pd, $dest, 1);
+
+                   if ($hash && $correct) {
+                       print "file already exists $hash - no need to download\n";
+                       return;
+                   }
+               }
+
+               local %ENV;
+               my $dccfg = PVE::Cluster::cfs_read_file('datacenter.cfg');
+               if ($dccfg->{http_proxy}) {
+                   $ENV{http_proxy} = $dccfg->{http_proxy};
+               }
+
+               my @cmd = ('/usr/bin/wget', '--progress=dot:mega', '-O', $tmpdest, $src);
+               if (system (@cmd) != 0) {
+                   die "download failed - $!\n";
+               }
+
+               my ($hash, $correct) = &$check_hash($pd, $tmpdest);
+
+               die "could not calculate checksum\n" if !$hash;
+               
+               if (!$correct) {
+                   my $expected = $pd->{sha512sum} // $pd->{md5sum};
+                   die "wrong checksum: $hash != $expected\n";
+               }
+
+               if (!rename($tmpdest, $dest)) {
+                   die "unable to save file - $!\n";
+               }
+           };
+           my $err = $@;
+
+           unlink $tmpdest;
+
+           if ($err) {
+               print "\n";
+               die $err if $err;
+           }
+
+           print "download finished\n";
+       };
+
+       return $rpcenv->fork_worker('download', undef, $user, $worker);
+    }});
+
+__PACKAGE__->register_method({
+    name => 'report',
+    path => 'report',
+    method => 'GET',
+    permissions => {
+       check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
+    },
+    protected => 1,
+    description => "Gather various systems information about a node",
+    proxyto => 'node',
+    parameters => {
+    additionalProperties => 0,
+       properties => {
+           node => get_standard_option('pve-node'),
+       },
+    },
+    returns => {
+       type => 'string',
+    },
+    code => sub {
+       return PVE::Report::generate();
+    }});
+
+# returns a list of VMIDs, those can be filtered by
+# * current parent node
+# * vmid whitelist
+# * guest is a template (default: skip)
+# * guest is HA manged (default: skip)
+my $get_filtered_vmlist = sub {
+    my ($nodename, $vmfilter, $templates, $ha_managed) = @_;
+
+    my $vmlist = PVE::Cluster::get_vmlist();
+
+    my $vms_allowed = {};
+    if (defined($vmfilter)) {
+       foreach my $vmid (PVE::Tools::split_list($vmfilter)) {
+           $vms_allowed->{$vmid} = 1;
+       }
+    }
+
+    my $res = {};
+    foreach my $vmid (keys %{$vmlist->{ids}}) {
+       next if %$vms_allowed && !$vms_allowed->{$vmid};
+
+       my $d = $vmlist->{ids}->{$vmid};
+       next if $nodename && $d->{node} ne $nodename;
 
        eval {
-           open FILE, ">$tmpname" || die "can't open temporary file '$tmpname' - $!\n";
-           while (read($fh, $buffer, 32768)) {
-               die "write failed - $!" unless print FILE $buffer;
+           my $class;
+           if ($d->{type} eq 'lxc') {
+               $class = 'PVE::LXC::Config';
+           } elsif ($d->{type} eq 'qemu') {
+               $class = 'PVE::QemuConfig';
+           } else {
+               die "unknown VM type '$d->{type}'\n";
            }
-           close FILE || die " can't close temporary file '$tmpname' - $!\n";
+
+           my $conf = $class->load_config($vmid);
+           return if !$templates && $class->is_template($conf);
+           return if !$ha_managed && PVE::HA::Config::vm_is_ha_managed($vmid);
+
+           $res->{$vmid} = $conf;
+           $res->{$vmid}->{type} = $d->{type};
        };
-       my $err = $@;
+       warn $@ if $@;
+    }
+
+    return $res;
+};
+
+# return all VMs which should get started/stopped on power up/down
+my $get_start_stop_list = sub {
+    my ($nodename, $autostart, $vmfilter) = @_;
+
+    my $vmlist = &$get_filtered_vmlist($nodename, $vmfilter);
 
-       if ($err) {
-           unlink $tmpname;
-           die $err;
+    my $resList = {};
+    foreach my $vmid (keys %$vmlist) {
+       my $conf = $vmlist->{$vmid};
+
+       next if $autostart && !$conf->{onboot};
+
+       my $startup = {};
+       if ($conf->{startup}) {
+           $startup =  PVE::JSONSchema::pve_parse_startup_order($conf->{startup});
        }
 
-       unlink $tmpname; # fixme: proxy to local host import
+       $startup->{order} = LONG_MAX if !defined($startup->{order});
 
-       # fixme: return volid
+       $resList->{$startup->{order}}->{$vmid} = $startup;
+       $resList->{$startup->{order}}->{$vmid}->{type} = $conf->{type};
+    }
 
-       return undef;
+    return $resList;
+};
+
+__PACKAGE__->register_method ({
+    name => 'startall', 
+    path => 'startall', 
+    method => 'POST',
+    protected => 1,
+    permissions => {
+       check => ['perm', '/', [ 'VM.PowerMgmt' ]],
+    },
+    proxyto => 'node',
+    description => "Start all VMs and containers (when onboot=1).",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           node => get_standard_option('pve-node'),
+           force => {
+               optional => 1,
+               type => 'boolean',
+               description => "force if onboot=0.",
+           },
+           vms => {
+               description => "Only consider Guests with these IDs.",
+               type => 'string',  format => 'pve-vmid-list',
+               optional => 1,
+           },
+       },
+    },
+    returns => {
+       type => 'string',
+    },
+    code => sub {
+       my ($param) = @_;
 
+       my $rpcenv = PVE::RPCEnvironment::get();
+       my $authuser = $rpcenv->get_user();
+
+       my $nodename = $param->{node};
+       $nodename = PVE::INotify::nodename() if $nodename eq 'localhost';
+
+       my $force = $param->{force};
+
+       my $code = sub {
+
+           $rpcenv->{type} = 'priv'; # to start tasks in background
+
+           if (!PVE::Cluster::check_cfs_quorum(1)) {
+               print "waiting for quorum ...\n";
+               do {
+                   sleep(1);
+               } while (!PVE::Cluster::check_cfs_quorum(1));
+               print "got quorum\n";
+           }
+           my $autostart = $force ? undef : 1;
+           my $startList = &$get_start_stop_list($nodename, $autostart, $param->{vms});
+
+           # Note: use numeric sorting with <=>
+           foreach my $order (sort {$a <=> $b} keys %$startList) {
+               my $vmlist = $startList->{$order};
+
+               foreach my $vmid (sort {$a <=> $b} keys %$vmlist) {
+                   my $d = $vmlist->{$vmid};
+
+                   PVE::Cluster::check_cfs_quorum(); # abort when we loose quorum
+
+                   eval {
+                       my $default_delay = 0;
+                       my $upid;
+
+                       if ($d->{type} eq 'lxc') {
+                           return if PVE::LXC::check_running($vmid);
+                           print STDERR "Starting CT $vmid\n";
+                           $upid = PVE::API2::LXC::Status->vm_start({node => $nodename, vmid => $vmid });
+                       } elsif ($d->{type} eq 'qemu') {
+                           $default_delay = 3; # to reduce load
+                           return if PVE::QemuServer::check_running($vmid, 1);
+                           print STDERR "Starting VM $vmid\n";
+                           $upid = PVE::API2::Qemu->vm_start({node => $nodename, vmid => $vmid });
+                       } else {
+                           die "unknown VM type '$d->{type}'\n";
+                       }
+
+                       my $res = PVE::Tools::upid_decode($upid);
+                       while (PVE::ProcFSTools::check_process_running($res->{pid})) {
+                           sleep(1);
+                       }
+
+                       my $status = PVE::Tools::upid_read_status($upid);
+                       if ($status eq 'OK') {
+                           # use default delay to reduce load
+                           my $delay = defined($d->{up}) ? int($d->{up}) : $default_delay;
+                           if ($delay > 0) {
+                               print STDERR "Waiting for $delay seconds (startup delay)\n" if $d->{up};
+                               for (my $i = 0; $i < $delay; $i++) {
+                                   sleep(1);
+                               }
+                           }
+                       } else {
+                           if ($d->{type} eq 'lxc') {
+                               print STDERR "Starting CT $vmid failed: $status\n";
+                           } elsif ($d->{type} eq 'qemu') {
+                               print STDERR "Starting VM $vmid failed: status\n";
+                           }
+                       }
+                   };
+                   warn $@ if $@;
+               }
+           }
+           return;
+       };
+
+       return $rpcenv->fork_worker('startall', undef, $authuser, $code);
     }});
 
+my $create_stop_worker = sub {
+    my ($nodename, $type, $vmid, $down_timeout) = @_;
+
+    my $upid;
+    if ($type eq 'lxc') {
+       return if !PVE::LXC::check_running($vmid);
+       my $timeout =  defined($down_timeout) ? int($down_timeout) : 60;
+       print STDERR "Stopping CT $vmid (timeout = $timeout seconds)\n";
+       $upid = PVE::API2::LXC::Status->vm_shutdown({node => $nodename, vmid => $vmid,
+                                            timeout => $timeout, forceStop => 1 });
+    } elsif ($type eq 'qemu') {
+       return if !PVE::QemuServer::check_running($vmid, 1);
+       my $timeout =  defined($down_timeout) ? int($down_timeout) : 60*3;
+       print STDERR "Stopping VM $vmid (timeout = $timeout seconds)\n";
+       $upid = PVE::API2::Qemu->vm_shutdown({node => $nodename, vmid => $vmid, 
+                                             timeout => $timeout, forceStop => 1 });
+    } else {
+       die "unknown VM type '$type'\n";
+    }
+
+    return $upid;
+};
+
+__PACKAGE__->register_method ({
+    name => 'stopall', 
+    path => 'stopall', 
+    method => 'POST',
+    protected => 1,
+    permissions => {
+       check => ['perm', '/', [ 'VM.PowerMgmt' ]],
+    },
+    proxyto => 'node',
+    description => "Stop all VMs and Containers.",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           node => get_standard_option('pve-node'),
+           vms => {
+               description => "Only consider Guests with these IDs.",
+               type => 'string',  format => 'pve-vmid-list',
+               optional => 1,
+           },
+       },
+    },
+    returns => {
+       type => 'string',
+    },
+    code => sub {
+       my ($param) = @_;
+
+       my $rpcenv = PVE::RPCEnvironment::get();
+       my $authuser = $rpcenv->get_user();
+
+       my $nodename = $param->{node};
+       $nodename = PVE::INotify::nodename() if $nodename eq 'localhost';
+
+       my $code = sub {
+
+           $rpcenv->{type} = 'priv'; # to start tasks in background
+
+           my $stopList = &$get_start_stop_list($nodename, undef, $param->{vms});
+
+           my $cpuinfo = PVE::ProcFSTools::read_cpuinfo();
+           my $datacenterconfig = cfs_read_file('datacenter.cfg');
+           # if not set by user spawn max cpu count number of workers
+           my $maxWorkers =  $datacenterconfig->{max_workers} || $cpuinfo->{cpus};
+
+           foreach my $order (sort {$b <=> $a} keys %$stopList) {
+               my $vmlist = $stopList->{$order};
+               my $workers = {};
+
+               my $finish_worker = sub {
+                   my $pid = shift;
+                   my $d = $workers->{$pid};
+                   return if !$d;
+                   delete $workers->{$pid};
+
+                   syslog('info', "end task $d->{upid}");
+               };
+
+               foreach my $vmid (sort {$b <=> $a} keys %$vmlist) {
+                   my $d = $vmlist->{$vmid};
+                   my $upid;
+                   eval { $upid = &$create_stop_worker($nodename, $d->{type}, $vmid, $d->{down}); };
+                   warn $@ if $@;
+                   next if !$upid;
+
+                   my $res = PVE::Tools::upid_decode($upid, 1);
+                   next if !$res;
+
+                   my $pid = $res->{pid};
+
+                   $workers->{$pid} = { type => $d->{type}, upid => $upid, vmid => $vmid };
+                   while (scalar(keys %$workers) >= $maxWorkers) {
+                       foreach my $p (keys %$workers) {
+                           if (!PVE::ProcFSTools::check_process_running($p)) {
+                               &$finish_worker($p);
+                           }
+                       }
+                       sleep(1);
+                   }
+               }
+               while (scalar(keys %$workers)) {
+                   foreach my $p (keys %$workers) {
+                       if (!PVE::ProcFSTools::check_process_running($p)) {
+                           &$finish_worker($p);
+                       }
+                   }
+                   sleep(1);
+               }
+           }
+
+           syslog('info', "all VMs and CTs stopped");
+
+           return;
+       };
+
+       return $rpcenv->fork_worker('stopall', undef, $authuser, $code);
+    }});
+
+my $create_migrate_worker = sub {
+    my ($nodename, $type, $vmid, $target) = @_;
+
+    my $upid;
+    if ($type eq 'lxc') {
+       my $online = PVE::LXC::check_running($vmid) ? 1 : 0;
+       print STDERR "Migrating CT $vmid\n";
+       $upid = PVE::API2::LXC->migrate_vm({node => $nodename, vmid => $vmid, target => $target,
+                                           online => $online });
+    } elsif ($type eq 'qemu') {
+       my $online = PVE::QemuServer::check_running($vmid, 1) ? 1 : 0;
+       print STDERR "Migrating VM $vmid\n";
+       $upid = PVE::API2::Qemu->migrate_vm({node => $nodename, vmid => $vmid, target => $target,
+                                            online => $online });
+    } else {
+       die "unknown VM type '$type'\n";
+    }
+
+    my $res = PVE::Tools::upid_decode($upid);
+
+    return $res->{pid};
+};
+
+__PACKAGE__->register_method ({
+    name => 'migrateall',
+    path => 'migrateall',
+    method => 'POST',
+    proxyto => 'node',
+    protected => 1,
+    permissions => {
+       check => ['perm', '/', [ 'VM.Migrate' ]],
+    },
+    description => "Migrate all VMs and Containers.",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           node => get_standard_option('pve-node'),
+            target => get_standard_option('pve-node', { description => "Target node." }),
+            maxworkers => {
+                description => "Maximal number of parallel migration job." .
+                   " If not set use 'max_workers' from datacenter.cfg," .
+                   " one of both must be set!",
+               optional => 1,
+                type => 'integer',
+                minimum => 1
+            },
+           vms => {
+               description => "Only consider Guests with these IDs.",
+               type => 'string',  format => 'pve-vmid-list',
+               optional => 1,
+           },
+       },
+    },
+    returns => {
+       type => 'string',
+    },
+    code => sub {
+       my ($param) = @_;
+
+       my $rpcenv = PVE::RPCEnvironment::get();
+       my $authuser = $rpcenv->get_user();
+
+       my $nodename = $param->{node};
+       $nodename = PVE::INotify::nodename() if $nodename eq 'localhost';
+
+        my $target = $param->{target};
+
+       my $datacenterconfig = cfs_read_file('datacenter.cfg');
+       # prefer parameter over datacenter cfg settings
+       my $maxWorkers = $param->{maxworkers} || $datacenterconfig->{max_workers} ||
+           die "either 'maxworkers' parameter or max_workers in datacenter.cfg must be set!\n";
+
+       my $code = sub {
+           $rpcenv->{type} = 'priv'; # to start tasks in background
+
+           my $vmlist = &$get_filtered_vmlist($nodename, $param->{vms}, 1, 1);
+
+           my $workers = {};
+           foreach my $vmid (sort keys %$vmlist) {
+               my $d = $vmlist->{$vmid};
+               my $pid;
+               eval { $pid = &$create_migrate_worker($nodename, $d->{type}, $vmid, $target); };
+               warn $@ if $@;
+               next if !$pid;
+
+               $workers->{$pid} = 1;
+               while (scalar(keys %$workers) >= $maxWorkers) {
+                   foreach my $p (keys %$workers) {
+                       if (!PVE::ProcFSTools::check_process_running($p)) {
+                           delete $workers->{$p};
+                       }
+                   }
+                   sleep(1);
+               }
+           }
+           while (scalar(keys %$workers)) {
+               foreach my $p (keys %$workers) {
+                   if (!PVE::ProcFSTools::check_process_running($p)) {
+                       delete $workers->{$p};
+                   }
+               }
+               sleep(1);
+           }
+           return;
+       };
+
+       return $rpcenv->fork_worker('migrateall', undef, $authuser, $code);
+
+    }});
+
+# bash completion helper
+
+sub complete_templet_repo {
+    my ($cmdname, $pname, $cvalue) = @_;
+
+    my $repo = PVE::APLInfo::load_data();
+    my $res = [];
+    foreach my $templ (keys %{$repo->{all}}) {
+       next if $templ !~ m/^$cvalue/;
+       push @$res, $templ;
+    }
+
+    return $res;
+}
+
 package PVE::API2::Nodes;
 
 use strict;
@@ -830,6 +1649,7 @@ use PVE::SafeSyslog;
 use PVE::Cluster;
 use PVE::RESTHandler;
 use PVE::RPCEnvironment;
+use PVE::API2Tools;
 
 use base qw(PVE::RESTHandler);
 
@@ -854,7 +1674,7 @@ __PACKAGE__->register_method ({
            type => "object",
            properties => {},
        },
-       links => [ { rel => 'child', href => "{name}" } ],
+       links => [ { rel => 'child', href => "{node}" } ],
     },
     code => sub {
        my ($param) = @_;
@@ -862,26 +1682,12 @@ __PACKAGE__->register_method ({
        my $clinfo = PVE::Cluster::get_clinfo();
        my $res = [];
 
-       my $nodename = PVE::INotify::nodename();
-       my $nodelist = $clinfo->{nodelist};
-
+       my $nodelist = PVE::Cluster::get_nodelist();
+       my $members = PVE::Cluster::get_members();
        my $rrd = PVE::Cluster::rrd_dump();
 
-       my @nodes = $nodelist ? (keys %$nodelist) : $nodename;
-
-       foreach my $node (@nodes) {
-           my $entry = { name => $node };
-           if (my $d = $rrd->{"pve2-node/$node"}) {
-
-               $entry->{uptime} = $d->[0];
-               $entry->{maxcpu} = $d->[3];
-               $entry->{cpu} = $d->[4];
-               $entry->{maxmem} = $d->[6];
-               $entry->{mem} = $d->[7];
-               $entry->{maxdisk} = $d->[10];
-               $entry->{disk} = $d->[11];
-           }
-
+       foreach my $node (@$nodelist) {
+           my $entry = PVE::API2Tools::extract_node_stats($node, $members, $rrd);
            push @$res, $entry;
        }