X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=PVE%2FCLI%2Fqm.pm;h=b105830fa6f558e0efae0eb6efe66a764cc6b066;hb=HEAD;hp=66feeccebea89ac16e0dbece53663daad4b4e62b;hpb=192bbfda82f82ce828179e5601a9b1c50ac2821d;p=qemu-server.git diff --git a/PVE/CLI/qm.pm b/PVE/CLI/qm.pm index 66feecc..b105830 100755 --- a/PVE/CLI/qm.pm +++ b/PVE/CLI/qm.pm @@ -24,7 +24,7 @@ use PVE::JSONSchema qw(get_standard_option); use PVE::Network; use PVE::RPCEnvironment; use PVE::SafeSyslog; -use PVE::Tools qw(extract_param); +use PVE::Tools qw(extract_param file_get_contents); use PVE::API2::Qemu::Agent; use PVE::API2::Qemu; @@ -251,7 +251,7 @@ __PACKAGE__->register_method({ my $check_remote_storage = sub { my ($storage) = @_; my $found = [ grep { $_->{storage} eq $storage } @$storages ]; - die "remote: storage '$storage' does not exist!\n" + die "remote: storage '$storage' does not exist (or missing permission)!\n" if !@$found; $found = @$found[0]; @@ -426,6 +426,8 @@ __PACKAGE__->register_method ({ last; } elsif ($line =~ /^resume (\d+)$/) { my $vmid = $1; + # check_running and vm_resume with nocheck, since local node + # might not have processed config move/rename yet if (PVE::QemuServer::check_running($vmid, 1)) { eval { PVE::QemuServer::vm_resume($vmid, 1, 1); }; if ($@) { @@ -486,7 +488,7 @@ __PACKAGE__->register_method ({ name => 'monitor', path => 'monitor', method => 'POST', - description => "Enter Qemu Monitor interface.", + description => "Enter QEMU Monitor interface.", parameters => { additionalProperties => 0, properties => { @@ -501,7 +503,7 @@ __PACKAGE__->register_method ({ my $conf = PVE::QemuConfig->load_config ($vmid); # check if VM exists - print "Entering Qemu Monitor for VM $vmid - type 'help' for help\n"; + print "Entering QEMU Monitor for VM $vmid - type 'help' for help\n"; my $term = Term::ReadLine->new('qm'); @@ -913,7 +915,8 @@ __PACKAGE__->register_method({ my $storecfg = PVE::Storage::config(); warn "Starting cleanup for $vmid\n"; - PVE::QemuConfig->lock_config($vmid, sub { + # mdev cleanup can take a while, so wait up to 60 seconds + PVE::QemuConfig->lock_config_full($vmid, 60, sub { my $conf = PVE::QemuConfig->load_config ($vmid); my $pid = PVE::QemuServer::check_running ($vmid); die "vm still running\n" if $pid; @@ -951,6 +954,102 @@ __PACKAGE__->register_method({ return; }}); +__PACKAGE__->register_method({ + name => 'vm_import', + path => 'vm-import', + description => "Import a foreign virtual guest from a supported import source, such as an ESXi storage.", + parameters => { + additionalProperties => 0, + properties => PVE::QemuServer::json_config_properties({ + vmid => get_standard_option('pve-vmid', { completion => \&PVE::Cluster::complete_next_vmid }), + 'source' => { + type => 'string', + description => 'The import source volume id.', + }, + storage => get_standard_option('pve-storage-id', { + description => "Default storage.", + completion => \&PVE::QemuServer::complete_storage, + }), + 'live-import' => { + type => 'boolean', + optional => 1, + default => 0, + description => "Immediately start the VM and copy the data in the background.", + }, + 'dryrun' => { + type => 'boolean', + optional => 1, + default => 0, + description => "Show the create command and exit without doing anything.", + }, + delete => { + type => 'string', format => 'pve-configid-list', + description => "A list of settings you want to delete.", + optional => 1, + }, + format => { + type => 'string', + description => 'Target format', + enum => [ 'raw', 'qcow2', 'vmdk' ], + optional => 1, + }, + }), + }, + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + my ($vmid, $source, $storage, $format, $live_import, $dryrun, $delete) = + delete $param->@{qw(vmid source storage format live-import dryrun delete)}; + + if (defined($format)) { + $format = ",format=$format"; + } else { + $format = ''; + } + + my $storecfg = PVE::Storage::config(); + my $metadata = PVE::Storage::get_import_metadata($storecfg, $source); + + my $create_args = $metadata->{'create-args'}; + if (my $netdevs = $metadata->{net}) { + for my $net (keys $netdevs->%*) { + my $value = $netdevs->{$net}; + $create_args->{$net} = join(',', map { $_ . '=' . $value->{$_} } sort keys %$value); + } + } + if (my $disks = $metadata->{disks}) { + if (delete $disks->{efidisk0}) { + $create_args->{efidisk0} = "$storage:1$format,efitype=4m"; + } + for my $disk (keys $disks->%*) { + my $value = $disks->{$disk}->{volid}; + $create_args->{$disk} = "$storage:0${format},import-from=$value"; + } + } + + $create_args->{'live-restore'} = 1 if $live_import; + + $create_args->{$_} = $param->{$_} for keys $param->%*; + delete $create_args->{$_} for PVE::Tools::split_list($delete); + + if ($dryrun) { + print("# dry-run – the resulting create command for the import would be:\n"); + print("qm create $vmid \\\n "); + print(join(" \\\n ", map { "--$_ $create_args->{$_}" } sort keys $create_args->%*)); + print("\n"); + return; + } + + PVE::API2::Qemu->create_vm({ + %node, + vmid => $vmid, + %$create_args, + }); + return; + } +}); + my $print_agent_result = sub { my ($data) = @_; @@ -977,7 +1076,7 @@ sub param_mapping { my ($name) = @_; my $ssh_key_map = ['sshkeys', sub { - return URI::Escape::uri_escape(PVE::Tools::file_get_contents($_[0])); + return URI::Escape::uri_escape(file_get_contents($_[0])); }]; my $cipassword_map = PVE::CLIHandler::get_standard_mapping('pve-password', { name => 'cipassword' }); my $password_map = PVE::CLIHandler::get_standard_mapping('pve-password'); @@ -1101,6 +1200,7 @@ our $cmddef = { update => [ "PVE::API2::Qemu", 'cloudinit_update', ['vmid'], { node => $nodename }], }, + import => [ __PACKAGE__, 'vm_import', ['vmid', 'source']], }; 1;