]> git.proxmox.com Git - pve-manager.git/blobdiff - PVE/API2/Nodes.pm
api: nodes: simplify appliance download code
[pve-manager.git] / PVE / API2 / Nodes.pm
index 6224cb719654755ab441c9b72b2c83d8eb4d9da7..08d03cede2d74a70222978478b00bf177ed2e97f 100644 (file)
@@ -2,55 +2,60 @@ package PVE::API2::Nodes::Nodeinfo;
 
 use strict;
 use warnings;
-use POSIX qw(LONG_MAX);
+
+use Digest::MD5;
+use Digest::SHA;
 use Filesys::Df;
-use Time::Local qw(timegm_nocheck);
 use HTTP::Status qw(:constants);
-use PVE::pvecfg;
-use PVE::Tools;
+use JSON;
+use POSIX qw(LONG_MAX);
+use Time::Local qw(timegm_nocheck);
+use Socket;
+
 use PVE::API2Tools;
-use PVE::ProcFSTools;
-use PVE::SafeSyslog;
+use PVE::APLInfo;
+use PVE::AccessControl;
 use PVE::Cluster qw(cfs_read_file);
-use PVE::INotify;
+use PVE::DataCenterConfig;
 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::Storage;
 use PVE::Firewall;
-use PVE::LXC;
-use PVE::APLInfo;
-use PVE::Report;
-use PVE::HA::Env::PVE2;
 use PVE::HA::Config;
+use PVE::HA::Env::PVE2;
+use PVE::INotify;
+use PVE::JSONSchema qw(get_standard_option);
+use PVE::LXC;
+use PVE::ProcFSTools;
 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::Scan;
-use PVE::API2::Storage::Status;
-use PVE::API2::Qemu;
-use PVE::API2::LXC;
-use PVE::API2::LXC::Status;
-use PVE::API2::VZDump;
+use PVE::RESTHandler;
+use PVE::RPCEnvironment;
+use PVE::RRD;
+use PVE::Report;
+use PVE::SafeSyslog;
+use PVE::Storage;
+use PVE::Tools;
+use PVE::pvecfg;
+
 use PVE::API2::APT;
+use PVE::API2::Capabilities;
 use PVE::API2::Ceph;
-use PVE::API2::Firewall::Host;
-use PVE::API2::Replication;
 use PVE::API2::Certificates;
-use PVE::API2::NodeConfig;
-use PVE::API2::Hardware;
-use Digest::MD5;
-use Digest::SHA;
 use PVE::API2::Disks;
-use PVE::DataCenterConfig;
-use PVE::RRD;
-use JSON;
-use Socket;
+use PVE::API2::Firewall::Host;
+use PVE::API2::Hardware;
+use PVE::API2::LXC::Status;
+use PVE::API2::LXC;
+use PVE::API2::Network;
+use PVE::API2::NodeConfig;
+use PVE::API2::Qemu::CPU;
+use PVE::API2::Qemu;
+use PVE::API2::Replication;
+use PVE::API2::Services;
+use PVE::API2::Storage::Scan;
+use PVE::API2::Storage::Status;
+use PVE::API2::Subscription;
+use PVE::API2::Tasks;
+use PVE::API2::VZDump;
 
 my $have_sdn;
 eval {
@@ -101,7 +106,7 @@ __PACKAGE__->register_method ({
 });
 
 __PACKAGE__->register_method ({
-    subclass => "PVE::API2::Scan",
+    subclass => "PVE::API2::Storage::Scan",
     path => 'scan',
 });
 
@@ -110,6 +115,10 @@ __PACKAGE__->register_method ({
     path => 'hardware',
 });
 
+__PACKAGE__->register_method ({
+    subclass => "PVE::API2::Capabilities",
+    path => 'capabilities',
+});
 
 __PACKAGE__->register_method ({
     subclass => "PVE::API2::Storage::Status",
@@ -190,7 +199,7 @@ __PACKAGE__->register_method ({
     permissions => { user => 'all' },
     description => "Node index.",
     parameters => {
-       additionalProperties => 0,
+       additionalProperties => 0,
        properties => {
            node => get_standard_option('pve-node'),
        },
@@ -207,40 +216,41 @@ __PACKAGE__->register_method ({
        my ($param) = @_;
 
        my $result = [
+           { name => 'aplinfo' },
+           { name => 'apt' },
+           { name => 'capabilities' },
            { name => 'ceph' },
+           { name => 'certificates' },
+           { name => 'config' },
            { name => 'disks' },
-           { name => 'apt' },
-           { name => 'version' },
-           { name => 'syslog' },
+           { name => 'dns' },
+           { name => 'firewall' },
+           { name => 'hosts' },
            { name => 'journal' },
-           { name => 'status' },
-           { name => 'wakeonlan' },
-           { name => 'subscription' },
+           { name => 'lxc' },
+           { name => 'netstat' },
+           { name => 'network' },
+           { name => 'qemu' },
+           { name => 'replication' },
            { name => 'report' },
-           { name => 'tasks' },
            { name => 'rrd' }, # fixme: remove?
            { name => 'rrddata' },# fixme: remove?
-           { name => 'replication' },
-           { name => 'vncshell' },
-           { name => 'termproxy' },
-           { name => 'spiceshell' },
-           { name => 'time' },
-           { name => 'dns' },
-           { name => 'services' },
            { name => 'scan' },
-           { name => 'storage' },
-           { name => 'qemu' },
-           { name => 'lxc' },
-           { name => 'vzdump' },
-           { name => 'network' },
-           { name => 'aplinfo' },
+           { name => 'services' },
+           { name => 'spiceshell' },
            { name => 'startall' },
+           { name => 'status' },
            { name => 'stopall' },
-           { name => 'netstat' },
-           { name => 'firewall' },
-           { name => 'certificates' },
-           { name => 'config' },
-           { name => 'hosts' },
+           { name => 'storage' },
+           { name => 'subscription' },
+           { name => 'syslog' },
+           { name => 'tasks' },
+           { name => 'termproxy' },
+           { name => 'time' },
+           { name => 'version' },
+           { name => 'vncshell' },
+           { name => 'vzdump' },
+           { name => 'wakeonlan' },
        ];
 
        push @$result, { name => 'sdn' } if $have_sdn;
@@ -256,7 +266,7 @@ __PACKAGE__->register_method ({
     permissions => { user => 'all' },
     description => "API version details",
     parameters => {
-       additionalProperties => 0,
+       additionalProperties => 0,
        properties => {
            node => get_standard_option('pve-node'),
        },
@@ -294,7 +304,7 @@ __PACKAGE__->register_method({
     description => "Read node status",
     proxyto => 'node',
     parameters => {
-       additionalProperties => 0,
+       additionalProperties => 0,
        properties => {
            node => get_standard_option('pve-node'),
        },
@@ -377,11 +387,11 @@ __PACKAGE__->register_method({
        },
     },
     returns => {
-        type => "array",
-        items => {
-            type => "object",
-            properties => {},
-        },
+       type => "array",
+       items => {
+           type => "object",
+           properties => {},
+       },
     },
     code => sub {
        my ($param) = @_;
@@ -389,20 +399,16 @@ __PACKAGE__->register_method({
        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},
-                    }
-                );
+       foreach my $dev (sort keys %$netdev) {
+           next if $dev !~ m/^(?:tap|veth)([1-9]\d*)i(\d+)$/;
+           my ($vmid, $netid) = ($1, $2);
+
+           push @$res, {
+               vmid => $vmid,
+               dev  => "net$netid",
+               in   => $netdev->{$dev}->{transmit},
+               out  => $netdev->{$dev}->{receive},
+           };
        }
 
        return $res;
@@ -501,7 +507,7 @@ __PACKAGE__->register_method({
     description => "Reboot or shutdown a node.",
     proxyto => 'node',
     parameters => {
-       additionalProperties => 0,
+       additionalProperties => 0,
        properties => {
            node => get_standard_option('pve-node'),
            command => {
@@ -596,7 +602,7 @@ __PACKAGE__->register_method({
     },
     description => "Read node RRD statistics (returns PNG)",
     parameters => {
-       additionalProperties => 0,
+       additionalProperties => 0,
        properties => {
            node => get_standard_option('pve-node'),
            timeframe => {
@@ -641,7 +647,7 @@ __PACKAGE__->register_method({
     },
     description => "Read node RRD statistics",
     parameters => {
-       additionalProperties => 0,
+       additionalProperties => 0,
        properties => {
            node => get_standard_option('pve-node'),
            timeframe => {
@@ -682,7 +688,7 @@ __PACKAGE__->register_method({
     },
     protected => 1,
     parameters => {
-       additionalProperties => 0,
+       additionalProperties => 0,
        properties => {
            node => get_standard_option('pve-node'),
            start => {
@@ -829,25 +835,56 @@ __PACKAGE__->register_method({
 my $sslcert;
 
 my $shell_cmd_map = {
-    'login' => [ '/bin/login', '-f', 'root' ],
-    'upgrade' => [ '/usr/bin/pveupgrade', '--shell' ],
-    'ceph_install' => [ '/usr/bin/pveceph', 'install' ],
+    'login' => {
+       cmd => [ '/bin/login', '-f', 'root' ],
+    },
+    'upgrade' => {
+       cmd => [ '/usr/bin/pveupgrade', '--shell' ],
+    },
+    'ceph_install' => {
+       cmd => [ '/usr/bin/pveceph', 'install' ],
+       allow_args => 1,
+    },
 };
 
 sub get_shell_command  {
-    my ($user, $shellcmd) = @_;
+    my ($user, $shellcmd, $args) = @_;
 
+    my $cmd;
     if ($user eq 'root@pam') {
        if (defined($shellcmd) && exists($shell_cmd_map->{$shellcmd})) {
-           return $shell_cmd_map->{$shellcmd};
+           my $def = $shell_cmd_map->{$shellcmd};
+           $cmd = [ @{$def->{cmd}} ]; # clone
+           if (defined($args) && $def->{allow_args}) {
+               push @$cmd, split("\0", $args);
+           }
        } else {
-           return [ '/bin/login', '-f', 'root' ];
+           $cmd = [ '/bin/login', '-f', 'root' ];
        }
     } else {
-       return [ '/bin/login' ];
+       # non-root must always login for now, we do not have a superuser role!
+       $cmd = [ '/bin/login' ];
     }
+    return $cmd;
 }
 
+my $get_vnc_connection_info = sub {
+    my $node = shift;
+
+    my $remote_cmd = [];
+
+    my ($remip, $family);
+    if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {
+       ($remip, $family) = PVE::Cluster::remote_node_ip($node);
+       $remote_cmd = ['/usr/bin/ssh', '-e', 'none', '-t', $remip , '--'];
+    } else {
+       $family = PVE::Tools::get_host_address_family($node);
+    }
+    my $port = PVE::Tools::next_vnc_port($family);
+
+    return ($port, $remote_cmd);
+};
+
 __PACKAGE__->register_method ({
     name => 'vncshell',
     path => 'vncshell',
@@ -859,15 +896,9 @@ __PACKAGE__->register_method ({
     },
     description => "Creates a VNC Shell proxy.",
     parameters => {
-       additionalProperties => 0,
+       additionalProperties => 0,
        properties => {
            node => get_standard_option('pve-node'),
-           upgrade => {
-               type => 'boolean',
-               description => "Deprecated, use the 'cmd' property instead! Run 'apt-get dist-upgrade' instead of normal shell.",
-               optional => 1,
-               default => 0,
-           },
            cmd => {
                type => 'string',
                description => "Run specific command or default to login.",
@@ -875,6 +906,13 @@ __PACKAGE__->register_method ({
                optional => 1,
                default => 'login',
            },
+           'cmd-opts' => {
+               type => 'string',
+               description => "Add parameters to a command. Encoded as null terminated strings.",
+               requires => 'cmd',
+               optional => 1,
+               default => '',
+           },
            websocket => {
                optional => 1,
                type => 'boolean',
@@ -897,7 +935,7 @@ __PACKAGE__->register_method ({
        },
     },
     returns => {
-       additionalProperties => 0,
+       additionalProperties => 0,
        properties => {
            user => { type => 'string' },
            ticket => { type => 'string' },
@@ -910,56 +948,37 @@ __PACKAGE__->register_method ({
        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';
 
-       raise_perm_exc('user != root@pam') if $param->{upgrade} && $user ne 'root@pam';
+       if (defined($param->{cmd}) && $param->{cmd} eq 'upgrade' && $user ne 'root@pam') {
+           raise_perm_exc('user != 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 ($remip, $family);
+       my ($port, $remcmd) = $get_vnc_connection_info->($node);
 
-       if ($node ne PVE::INotify::nodename()) {
-           ($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', '-e', 'none', '-t', $remip] : [];
-
-       # FIXME: remove with 6.0
-       if ($param->{upgrade}) {
-           $param->{cmd} = 'upgrade';
-       }
-       my $shcmd = get_shell_command($user, $param->{cmd});
+       my $shcmd = get_shell_command($user, $param->{cmd}, $param->{'cmd-opts'});
 
        my $timeout = 10;
 
-       my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
-                  '-timeout', $timeout, '-authpath', $authpath,
-                  '-perm', 'Sys.Console'];
-
-       if ($param->{width}) {
-           push @$cmd, '-width', $param->{width};
-       }
+       my $cmd = ['/usr/bin/vncterm',
+           '-rfbport', $port,
+           '-timeout', $timeout,
+           '-authpath', $authpath,
+           '-perm', 'Sys.Console',
+       ];
 
-       if ($param->{height}) {
-           push @$cmd, '-height', $param->{height};
-       }
+       push @$cmd, '-width', $param->{width} if $param->{width};
+       push @$cmd, '-height', $param->{height} if $param->{height};
 
        if ($param->{websocket}) {
            $ENV{PVE_VNC_TICKET} = $ticket; # pass ticket to vncterm
@@ -1020,12 +1039,6 @@ __PACKAGE__->register_method ({
        additionalProperties => 0,
        properties => {
            node => get_standard_option('pve-node'),
-           upgrade => {
-               type => 'boolean',
-               description => "Deprecated, use the 'cmd' property instead! Run 'apt-get dist-upgrade' instead of normal shell.",
-               optional => 1,
-               default => 0,
-           },
            cmd => {
                type => 'string',
                description => "Run specific command or default to login.",
@@ -1033,6 +1046,13 @@ __PACKAGE__->register_method ({
                optional => 1,
                default => 'login',
            },
+           'cmd-opts' => {
+               type => 'string',
+               description => "Add parameters to a command. Encoded as null terminated strings.",
+               requires => 'cmd',
+               optional => 1,
+               default => '',
+           },
        },
     },
     returns => {
@@ -1048,47 +1068,33 @@ __PACKAGE__->register_method ({
        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';
+       raise_perm_exc("realm $realm != pam") if $realm ne 'pam';
 
        my $node = $param->{node};
-
        my $authpath = "/nodes/$node";
-
        my $ticket = PVE::AccessControl::assemble_vnc_ticket($user, $authpath);
 
-       my ($remip, $family);
-
-       if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {
-           ($remip, $family) = PVE::Cluster::remote_node_ip($node);
-       } else {
-           $family = PVE::Tools::get_host_address_family($node);
-       }
+       my ($port, $remcmd) = $get_vnc_connection_info->($node);
 
-       my $port = PVE::Tools::next_vnc_port($family);
-
-       my $remcmd = $remip ?
-           ['/usr/bin/ssh', '-e', 'none', '-t', $remip , '--'] : [];
-       # FIXME: remove with 6.0
-       if ($param->{upgrade}) {
-           $param->{cmd} = 'upgrade';
-       }
-       my $shcmd = get_shell_command($user, $param->{cmd});
+       my $shcmd = get_shell_command($user, $param->{cmd}, $param->{'cmd-opts'});
 
        my $realcmd = sub {
            my $upid = shift;
 
            syslog ('info', "starting termproxy $upid\n");
 
-           my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
-                      '--perm', 'Sys.Console',  '--'];
+           my $cmd = [
+               '/usr/bin/termproxy',
+               $port,
+               '--path', $authpath,
+               '--perm', 'Sys.Console',
+               '--'
+           ];
            push  @$cmd, @$remcmd, @$shcmd;
 
            PVE::Tools::run_command($cmd);
        };
-
        my $upid = $rpcenv->fork_worker('vncshell', "", $user, $realcmd);
 
        PVE::Tools::wait_for_vnc_port($port);
@@ -1109,9 +1115,9 @@ __PACKAGE__->register_method({
        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.",
+    description => "Opens a websocket for VNC traffic.",
     parameters => {
-       additionalProperties => 0,
+       additionalProperties => 0,
        properties => {
            node => get_standard_option('pve-node'),
            vncticket => {
@@ -1163,16 +1169,10 @@ __PACKAGE__->register_method ({
     },
     description => "Creates a SPICE shell.",
     parameters => {
-       additionalProperties => 0,
+       additionalProperties => 0,
        properties => {
            node => get_standard_option('pve-node'),
            proxy => get_standard_option('spice-proxy', { optional => 1 }),
-           upgrade => {
-               type => 'boolean',
-               description => "Deprecated, use the 'cmd' property instead! Run 'apt-get dist-upgrade' instead of normal shell.",
-               optional => 1,
-               default => 0,
-           },
            cmd => {
                type => 'string',
                description => "Run specific command or default to login.",
@@ -1180,6 +1180,13 @@ __PACKAGE__->register_method ({
                optional => 1,
                default => 'login',
            },
+           'cmd-opts' => {
+               type => 'string',
+               description => "Add parameters to a command. Encoded as null terminated strings.",
+               requires => 'cmd',
+               optional => 1,
+               default => '',
+           },
        },
     },
     returns => get_standard_option('remote-viewer-config'),
@@ -1193,18 +1200,17 @@ __PACKAGE__->register_method ({
 
        raise_perm_exc("realm != pam") if $realm ne 'pam';
 
-       raise_perm_exc('user != root@pam') if $param->{upgrade} && $user ne 'root@pam';
+       if (defined($param->{cmd}) && $param->{cmd} eq 'upgrade' && $user ne 'root@pam') {
+           raise_perm_exc('user != root@pam');
+       }
 
        my $node = $param->{node};
        my $proxy = $param->{proxy};
 
        my $authpath = "/nodes/$node";
        my $permissions = 'Sys.Console';
-       # FIXME: remove with 6.0
-       if ($param->{upgrade}) {
-           $param->{cmd} = 'upgrade';
-       }
-       my $shcmd = get_shell_command($user, $param->{cmd});
+
+       my $shcmd = get_shell_command($user, $param->{cmd}, $param->{'cmd-opts'});
 
        my $title = "Shell on '$node'";
 
@@ -1221,14 +1227,14 @@ __PACKAGE__->register_method({
     description => "Read DNS settings.",
     proxyto => 'node',
     parameters => {
-       additionalProperties => 0,
+       additionalProperties => 0,
        properties => {
            node => get_standard_option('pve-node'),
        },
     },
     returns => {
        type => "object",
-       additionalProperties => 0,
+       additionalProperties => 0,
        properties => {
            search => {
                description => "Search domain for host-name lookup.",
@@ -1271,7 +1277,7 @@ __PACKAGE__->register_method({
     proxyto => 'node',
     protected => 1,
     parameters => {
-       additionalProperties => 0,
+       additionalProperties => 0,
        properties => {
            node => get_standard_option('pve-node'),
            search => {
@@ -1321,7 +1327,7 @@ __PACKAGE__->register_method({
     },
     returns => {
        type => "object",
-       additionalProperties => 0,
+       additionalProperties => 0,
        properties => {
            timezone => {
                description => "Time zone",
@@ -1366,7 +1372,7 @@ __PACKAGE__->register_method({
     proxyto => 'node',
     protected => 1,
     parameters => {
-       additionalProperties => 0,
+       additionalProperties => 0,
        properties => {
            node => get_standard_option('pve-node'),
            timezone => {
@@ -1394,7 +1400,7 @@ __PACKAGE__->register_method({
     description => "Get list of appliances.",
     proxyto => 'node',
     parameters => {
-       additionalProperties => 0,
+       additionalProperties => 0,
        properties => {
            node => get_standard_option('pve-node'),
        },
@@ -1433,17 +1439,18 @@ __PACKAGE__->register_method({
     proxyto => 'node',
     protected => 1,
     parameters => {
-       additionalProperties => 0,
+       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 which will downloaded",
-                         maxLength => 255,
-                         completion => \&complete_templet_repo,
+           template => {
+               type => 'string',
+               description => "The template which will downloaded",
+               maxLength => 255,
+               completion => \&complete_templet_repo,
            },
        },
     },
@@ -1452,112 +1459,40 @@ __PACKAGE__->register_method({
        my ($param) = @_;
 
        my $rpcenv = PVE::RPCEnvironment::get();
-
        my $user = $rpcenv->get_user();
 
        my $node = $param->{node};
-
-       my $list = PVE::APLInfo::load_data();
-
        my $template = $param->{template};
-       my $pd = $list->{all}->{$template};
 
-       raise_param_exc({ template => "no such template"}) if !$pd;
+       my $list = PVE::APLInfo::load_data();
+       my $appliance = $list->{all}->{$template};
+       raise_param_exc({ template => "no such template"}) if !$appliance;
 
        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');
+       die "unknown template type '$appliance->{type}'\n"
+           if !($appliance->{type} eq 'openvz' || $appliance->{type} eq 'lxc');
 
        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);
+       my $worker = sub {
+           my $dccfg = PVE::Cluster::cfs_read_file('datacenter.cfg');
 
-               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";
+           PVE::Tools::download_file_from_url("$tmpldir/$template", $appliance->{location}, {
+               hash_required => 1,
+               sha512sum => $appliance->{sha512sum},
+               md5sum => $appliance->{md5sum},
+               http_proxy => $dccfg->{http_proxy},
+           });
        };
 
-       return $rpcenv->fork_worker('download', undef, $user, $worker);
+       my $upid = $rpcenv->fork_worker('download', $template, $user, $worker);
+
+       return $upid;
     }});
 
 __PACKAGE__->register_method({
@@ -1571,7 +1506,7 @@ __PACKAGE__->register_method({
     description => "Gather various systems information about a node",
     proxyto => 'node',
     parameters => {
-    additionalProperties => 0,
+       additionalProperties => 0,
        properties => {
            node => get_standard_option('pve-node'),
        },
@@ -1691,7 +1626,7 @@ __PACKAGE__->register_method ({
     proxyto => 'node',
     description => "Start all VMs and containers located on this node (by default only those with onboot=1).",
     parameters => {
-       additionalProperties => 0,
+       additionalProperties => 0,
        properties => {
            node => get_standard_option('pve-node'),
            force => {
@@ -1831,7 +1766,7 @@ __PACKAGE__->register_method ({
     proxyto => 'node',
     description => "Stop all VMs and Containers.",
     parameters => {
-       additionalProperties => 0,
+       additionalProperties => 0,
        properties => {
            node => get_standard_option('pve-node'),
            vms => {
@@ -1927,10 +1862,36 @@ my $create_migrate_worker = sub {
        $upid = PVE::API2::LXC->migrate_vm({node => $nodename, vmid => $vmid, target => $target,
                                            restart => $online });
     } elsif ($type eq 'qemu') {
+       print STDERR "Check VM $vmid: ";
+       *STDERR->flush();
        my $online = PVE::QemuServer::check_running($vmid, 1) ? 1 : 0;
+       my $preconditions = PVE::API2::Qemu->migrate_vm_precondition({node => $nodename, vmid => $vmid, target => $target});
+       my $invalidConditions = '';
+       if ($online && !$with_local_disks && scalar @{$preconditions->{local_disks}}) {
+           $invalidConditions .= "\n  Has local disks: " .
+               join(', ', map { $_->{volid} } @{$preconditions->{local_disks}});
+       }
+
+       if (@{$preconditions->{local_resources}}) {
+           $invalidConditions .= "\n  Has local resources: " . join(', ', @{$preconditions->{local_resources}});
+       }
+
+       if ($invalidConditions && $invalidConditions ne '') {
+           print STDERR "skip VM $vmid - precondition check failed:";
+           die "$invalidConditions\n";
+       }
+       print STDERR "precondition check passed\n";
        print STDERR "Migrating VM $vmid\n";
-       $upid = PVE::API2::Qemu->migrate_vm({node => $nodename, vmid => $vmid, target => $target,
-                                            online => $online, 'with-local-disks' => $with_local_disks});
+
+       my $params = {
+           node => $nodename,
+           vmid => $vmid,
+           target => $target,
+           online => $online,
+       };
+       $params->{'with-local-disks'} = $with_local_disks if defined($with_local_disks);
+
+       $upid = PVE::API2::Qemu->migrate_vm($params);
     } else {
        die "unknown VM type '$type'\n";
     }
@@ -2004,8 +1965,13 @@ __PACKAGE__->register_method ({
            $rpcenv->{type} = 'priv'; # to start tasks in background
 
            my $vmlist = &$get_filtered_vmlist($nodename, $param->{vms}, 1, 1);
+           if (!scalar(keys %$vmlist)) {
+               warn "no virtual guests matched, nothing to do..\n";
+               return;
+           }
 
            my $workers = {};
+           my $workers_started = 0;
            foreach my $vmid (sort keys %$vmlist) {
                my $d = $vmlist->{$vmid};
                my $pid;
@@ -2013,6 +1979,7 @@ __PACKAGE__->register_method ({
                warn $@ if $@;
                next if !$pid;
 
+               $workers_started++;
                $workers->{$pid} = 1;
                while (scalar(keys %$workers) >= $maxWorkers) {
                    foreach my $p (keys %$workers) {
@@ -2025,12 +1992,17 @@ __PACKAGE__->register_method ({
            }
            while (scalar(keys %$workers)) {
                foreach my $p (keys %$workers) {
+                   # FIXME: what about PID re-use ?!?!
                    if (!PVE::ProcFSTools::check_process_running($p)) {
                        delete $workers->{$p};
                    }
                }
                sleep(1);
            }
+           if ($workers_started <= 0) {
+               die "no migrations worker started...\n";
+           }
+           print STDERR "All jobs finished, used $workers_started workers in total.\n";
            return;
        };
 
@@ -2151,7 +2123,7 @@ __PACKAGE__->register_method ({
     permissions => { user => 'all' },
     description => "Cluster node index.",
     parameters => {
-       additionalProperties => 0,
+       additionalProperties => 0,
        properties => {},
     },
     returns => {
@@ -2224,7 +2196,10 @@ __PACKAGE__->register_method ({
        foreach my $node (@$nodelist) {
            my $can_audit = $rpcenv->check($authuser, "/nodes/$node", [ 'Sys.Audit' ], 1);
            my $entry = PVE::API2Tools::extract_node_stats($node, $members, $rrd, !$can_audit);
-           $entry->{ssl_fingerprint} = PVE::Cluster::get_node_fingerprint($node);
+
+           $entry->{ssl_fingerprint} = eval { PVE::Cluster::get_node_fingerprint($node) };
+           warn "$@" if $@;
+
            push @$res, $entry;
        }