1 package PVE
::API2
::Qemu
;
11 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
14 use PVE
::Tools
qw(extract_param);
15 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
17 use PVE
::JSONSchema
qw(get_standard_option);
19 use PVE
::ReplicationConfig
;
20 use PVE
::GuestHelpers
;
23 use PVE
::QemuServer
::Drive
;
24 use PVE
::QemuServer
::Monitor
qw(mon_cmd);
26 use PVE
::RPCEnvironment
;
27 use PVE
::AccessControl
;
31 use PVE
::API2
::Firewall
::VM
;
32 use PVE
::API2
::Qemu
::Agent
;
33 use PVE
::VZDump
::Plugin
;
34 use PVE
::DataCenterConfig
;
38 if (!$ENV{PVE_GENERATING_DOCS
}) {
39 require PVE
::HA
::Env
::PVE2
;
40 import PVE
::HA
::Env
::PVE2
;
41 require PVE
::HA
::Config
;
42 import PVE
::HA
::Config
;
46 use Data
::Dumper
; # fixme: remove
48 use base
qw(PVE::RESTHandler);
50 my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal.";
52 my $resolve_cdrom_alias = sub {
55 if (my $value = $param->{cdrom
}) {
56 $value .= ",media=cdrom" if $value !~ m/media=/;
57 $param->{ide2
} = $value;
58 delete $param->{cdrom
};
62 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
63 my $check_storage_access = sub {
64 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
66 PVE
::QemuServer
::foreach_drive
($settings, sub {
67 my ($ds, $drive) = @_;
69 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
71 my $volid = $drive->{file
};
72 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
74 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit' || (defined($volname) && $volname eq 'cloudinit'))) {
76 } elsif ($isCDROM && ($volid eq 'cdrom')) {
77 $rpcenv->check($authuser, "/", ['Sys.Console']);
78 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
79 my ($storeid, $size) = ($2 || $default_storage, $3);
80 die "no storage ID specified (and no default storage)\n" if !$storeid;
81 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
82 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
83 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
84 if !$scfg->{content
}->{images
};
86 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
90 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
91 if defined($settings->{vmstatestorage
});
94 my $check_storage_access_clone = sub {
95 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
99 PVE
::QemuServer
::foreach_drive
($conf, sub {
100 my ($ds, $drive) = @_;
102 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
104 my $volid = $drive->{file
};
106 return if !$volid || $volid eq 'none';
109 if ($volid eq 'cdrom') {
110 $rpcenv->check($authuser, "/", ['Sys.Console']);
112 # we simply allow access
113 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
114 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
115 $sharedvm = 0 if !$scfg->{shared
};
119 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
120 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
121 $sharedvm = 0 if !$scfg->{shared
};
123 $sid = $storage if $storage;
124 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
128 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
129 if defined($conf->{vmstatestorage
});
134 # Note: $pool is only needed when creating a VM, because pool permissions
135 # are automatically inherited if VM already exists inside a pool.
136 my $create_disks = sub {
137 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
144 my ($ds, $disk) = @_;
146 my $volid = $disk->{file
};
147 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
149 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
150 delete $disk->{size
};
151 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
152 } elsif (defined($volname) && $volname eq 'cloudinit') {
153 $storeid = $storeid // $default_storage;
154 die "no storage ID specified (and no default storage)\n" if !$storeid;
155 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
156 my $name = "vm-$vmid-cloudinit";
160 $fmt = $disk->{format
} // "qcow2";
163 $fmt = $disk->{format
} // "raw";
166 # Initial disk created with 4 MB and aligned to 4MB on regeneration
167 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
168 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
169 $disk->{file
} = $volid;
170 $disk->{media
} = 'cdrom';
171 push @$vollist, $volid;
172 delete $disk->{format
}; # no longer needed
173 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
174 } elsif ($volid =~ $NEW_DISK_RE) {
175 my ($storeid, $size) = ($2 || $default_storage, $3);
176 die "no storage ID specified (and no default storage)\n" if !$storeid;
177 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
178 my $fmt = $disk->{format
} || $defformat;
180 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
183 if ($ds eq 'efidisk0') {
184 ($volid, $size) = PVE
::QemuServer
::create_efidisk
($storecfg, $storeid, $vmid, $fmt, $arch);
186 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
188 push @$vollist, $volid;
189 $disk->{file
} = $volid;
190 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
191 delete $disk->{format
}; # no longer needed
192 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
195 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
197 my $volid_is_new = 1;
200 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
201 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
206 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
208 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
210 die "volume $volid does not exist\n" if !$size;
212 $disk->{size
} = $size;
215 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
219 eval { PVE
::QemuServer
::foreach_drive
($settings, $code); };
221 # free allocated images on error
223 syslog
('err', "VM $vmid creating disks failed");
224 foreach my $volid (@$vollist) {
225 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
231 # modify vm config if everything went well
232 foreach my $ds (keys %$res) {
233 $conf->{$ds} = $res->{$ds};
250 my $memoryoptions = {
256 my $hwtypeoptions = {
269 my $generaloptions = {
276 'migrate_downtime' => 1,
277 'migrate_speed' => 1,
290 my $vmpoweroptions = {
297 'vmstatestorage' => 1,
300 my $cloudinitoptions = {
310 my $check_vm_modify_config_perm = sub {
311 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
313 return 1 if $authuser eq 'root@pam';
315 foreach my $opt (@$key_list) {
316 # some checks (e.g., disk, serial port, usb) need to be done somewhere
317 # else, as there the permission can be value dependend
318 next if PVE
::QemuServer
::is_valid_drivename
($opt);
319 next if $opt eq 'cdrom';
320 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
323 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
324 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
325 } elsif ($memoryoptions->{$opt}) {
326 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
327 } elsif ($hwtypeoptions->{$opt}) {
328 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
329 } elsif ($generaloptions->{$opt}) {
330 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
331 # special case for startup since it changes host behaviour
332 if ($opt eq 'startup') {
333 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
335 } elsif ($vmpoweroptions->{$opt}) {
336 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
337 } elsif ($diskoptions->{$opt}) {
338 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
339 } elsif ($cloudinitoptions->{$opt} || ($opt =~ m/^(?:net|ipconfig)\d+$/)) {
340 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
341 } elsif ($opt eq 'vmstate') {
342 # the user needs Disk and PowerMgmt privileges to change the vmstate
343 # also needs privileges on the storage, that will be checked later
344 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt' ]);
346 # catches hostpci\d+, args, lock, etc.
347 # new options will be checked here
348 die "only root can set '$opt' config\n";
355 __PACKAGE__-
>register_method({
359 description
=> "Virtual machine index (per node).",
361 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
365 protected
=> 1, # qemu pid files are only readable by root
367 additionalProperties
=> 0,
369 node
=> get_standard_option
('pve-node'),
373 description
=> "Determine the full status of active VMs.",
381 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
383 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
388 my $rpcenv = PVE
::RPCEnvironment
::get
();
389 my $authuser = $rpcenv->get_user();
391 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
394 foreach my $vmid (keys %$vmstatus) {
395 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
397 my $data = $vmstatus->{$vmid};
404 my $parse_restore_archive = sub {
405 my ($storecfg, $archive) = @_;
407 my ($archive_storeid, $archive_volname) = PVE
::Storage
::parse_volume_id
($archive);
409 if (defined($archive_storeid)) {
410 my $scfg = PVE
::Storage
::storage_config
($storecfg, $archive_storeid);
411 if ($scfg->{type
} eq 'pbs') {
418 my $path = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
426 __PACKAGE__-
>register_method({
430 description
=> "Create or restore a virtual machine.",
432 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
433 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
434 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
435 user
=> 'all', # check inside
440 additionalProperties
=> 0,
441 properties
=> PVE
::QemuServer
::json_config_properties
(
443 node
=> get_standard_option
('pve-node'),
444 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
446 description
=> "The backup archive. Either the file system path to a .tar or .vma file (use '-' to pipe data from stdin) or a proxmox storage backup volume identifier.",
450 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
452 storage
=> get_standard_option
('pve-storage-id', {
453 description
=> "Default storage.",
455 completion
=> \
&PVE
::QemuServer
::complete_storage
,
460 description
=> "Allow to overwrite existing VM.",
461 requires
=> 'archive',
466 description
=> "Assign a unique random ethernet address.",
467 requires
=> 'archive',
471 type
=> 'string', format
=> 'pve-poolid',
472 description
=> "Add the VM to the specified pool.",
475 description
=> "Override I/O bandwidth limit (in KiB/s).",
479 default => 'restore limit from datacenter or storage config',
485 description
=> "Start VM after it was created successfully.",
495 my $rpcenv = PVE
::RPCEnvironment
::get
();
496 my $authuser = $rpcenv->get_user();
498 my $node = extract_param
($param, 'node');
499 my $vmid = extract_param
($param, 'vmid');
501 my $archive = extract_param
($param, 'archive');
502 my $is_restore = !!$archive;
504 my $bwlimit = extract_param
($param, 'bwlimit');
505 my $force = extract_param
($param, 'force');
506 my $pool = extract_param
($param, 'pool');
507 my $start_after_create = extract_param
($param, 'start');
508 my $storage = extract_param
($param, 'storage');
509 my $unique = extract_param
($param, 'unique');
511 if (defined(my $ssh_keys = $param->{sshkeys
})) {
512 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
513 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
516 PVE
::Cluster
::check_cfs_quorum
();
518 my $filename = PVE
::QemuConfig-
>config_file($vmid);
519 my $storecfg = PVE
::Storage
::config
();
521 if (defined($pool)) {
522 $rpcenv->check_pool_exist($pool);
525 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
526 if defined($storage);
528 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
530 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
532 } elsif ($archive && $force && (-f
$filename) &&
533 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
534 # OK: user has VM.Backup permissions, and want to restore an existing VM
540 &$resolve_cdrom_alias($param);
542 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
544 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
546 foreach my $opt (keys %$param) {
547 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
548 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
549 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
551 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
552 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
556 PVE
::QemuServer
::add_random_macs
($param);
558 my $keystr = join(' ', keys %$param);
559 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
561 if ($archive eq '-') {
562 die "pipe requires cli environment\n"
563 if $rpcenv->{type
} ne 'cli';
564 $archive = { type
=> 'pipe' };
566 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
568 $archive = $parse_restore_archive->($storecfg, $archive);
572 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
574 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
575 die "$emsg $@" if $@;
577 my $restorefn = sub {
578 my $conf = PVE
::QemuConfig-
>load_config($vmid);
580 PVE
::QemuConfig-
>check_protection($conf, $emsg);
582 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
585 my $restore_options = {
591 if ($archive->{type
} eq 'file' || $archive->{type
} eq 'pipe') {
592 PVE
::QemuServer
::restore_file_archive
($archive->{path
} // '-', $vmid, $authuser, $restore_options);
593 } elsif ($archive->{type
} eq 'pbs') {
594 PVE
::QemuServer
::restore_proxmox_backup_archive
($archive->{volid
}, $vmid, $authuser, $restore_options);
596 die "unknown backup archive type\n";
598 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
599 # Convert restored VM to template if backup was VM template
600 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
601 warn "Convert to template.\n";
602 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
606 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
609 # ensure no old replication state are exists
610 PVE
::ReplicationState
::delete_guest_states
($vmid);
612 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
614 if ($start_after_create) {
615 print "Execute autostart\n";
616 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
622 # ensure no old replication state are exists
623 PVE
::ReplicationState
::delete_guest_states
($vmid);
627 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
631 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
633 if (!$conf->{bootdisk
}) {
634 my $firstdisk = PVE
::QemuServer
::Drive
::resolve_first_disk
($conf);
635 $conf->{bootdisk
} = $firstdisk if $firstdisk;
638 # auto generate uuid if user did not specify smbios1 option
639 if (!$conf->{smbios1
}) {
640 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
643 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
644 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
647 PVE
::QemuConfig-
>write_config($vmid, $conf);
653 foreach my $volid (@$vollist) {
654 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
660 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
663 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
665 if ($start_after_create) {
666 print "Execute autostart\n";
667 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
672 my ($code, $worker_name);
674 $worker_name = 'qmrestore';
676 eval { $restorefn->() };
678 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
684 $worker_name = 'qmcreate';
686 eval { $createfn->() };
689 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
690 unlink($conffile) or die "failed to remove config file: $!\n";
698 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
701 __PACKAGE__-
>register_method({
706 description
=> "Directory index",
711 additionalProperties
=> 0,
713 node
=> get_standard_option
('pve-node'),
714 vmid
=> get_standard_option
('pve-vmid'),
722 subdir
=> { type
=> 'string' },
725 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
731 { subdir
=> 'config' },
732 { subdir
=> 'pending' },
733 { subdir
=> 'status' },
734 { subdir
=> 'unlink' },
735 { subdir
=> 'vncproxy' },
736 { subdir
=> 'termproxy' },
737 { subdir
=> 'migrate' },
738 { subdir
=> 'resize' },
739 { subdir
=> 'move' },
741 { subdir
=> 'rrddata' },
742 { subdir
=> 'monitor' },
743 { subdir
=> 'agent' },
744 { subdir
=> 'snapshot' },
745 { subdir
=> 'spiceproxy' },
746 { subdir
=> 'sendkey' },
747 { subdir
=> 'firewall' },
753 __PACKAGE__-
>register_method ({
754 subclass
=> "PVE::API2::Firewall::VM",
755 path
=> '{vmid}/firewall',
758 __PACKAGE__-
>register_method ({
759 subclass
=> "PVE::API2::Qemu::Agent",
760 path
=> '{vmid}/agent',
763 __PACKAGE__-
>register_method({
765 path
=> '{vmid}/rrd',
767 protected
=> 1, # fixme: can we avoid that?
769 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
771 description
=> "Read VM RRD statistics (returns PNG)",
773 additionalProperties
=> 0,
775 node
=> get_standard_option
('pve-node'),
776 vmid
=> get_standard_option
('pve-vmid'),
778 description
=> "Specify the time frame you are interested in.",
780 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
783 description
=> "The list of datasources you want to display.",
784 type
=> 'string', format
=> 'pve-configid-list',
787 description
=> "The RRD consolidation function",
789 enum
=> [ 'AVERAGE', 'MAX' ],
797 filename
=> { type
=> 'string' },
803 return PVE
::RRD
::create_rrd_graph
(
804 "pve2-vm/$param->{vmid}", $param->{timeframe
},
805 $param->{ds
}, $param->{cf
});
809 __PACKAGE__-
>register_method({
811 path
=> '{vmid}/rrddata',
813 protected
=> 1, # fixme: can we avoid that?
815 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
817 description
=> "Read VM RRD statistics",
819 additionalProperties
=> 0,
821 node
=> get_standard_option
('pve-node'),
822 vmid
=> get_standard_option
('pve-vmid'),
824 description
=> "Specify the time frame you are interested in.",
826 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
829 description
=> "The RRD consolidation function",
831 enum
=> [ 'AVERAGE', 'MAX' ],
846 return PVE
::RRD
::create_rrd_data
(
847 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
851 __PACKAGE__-
>register_method({
853 path
=> '{vmid}/config',
856 description
=> "Get the virtual machine configuration with pending configuration " .
857 "changes applied. Set the 'current' parameter to get the current configuration instead.",
859 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
862 additionalProperties
=> 0,
864 node
=> get_standard_option
('pve-node'),
865 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
867 description
=> "Get current values (instead of pending values).",
872 snapshot
=> get_standard_option
('pve-snapshot-name', {
873 description
=> "Fetch config values from given snapshot.",
876 my ($cmd, $pname, $cur, $args) = @_;
877 PVE
::QemuConfig-
>snapshot_list($args->[0]);
883 description
=> "The VM configuration.",
885 properties
=> PVE
::QemuServer
::json_config_properties
({
888 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
895 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
896 current
=> "cannot use 'snapshot' parameter with 'current'"})
897 if ($param->{snapshot
} && $param->{current
});
900 if ($param->{snapshot
}) {
901 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
903 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
905 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
910 __PACKAGE__-
>register_method({
911 name
=> 'vm_pending',
912 path
=> '{vmid}/pending',
915 description
=> "Get the virtual machine configuration with both current and pending values.",
917 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
920 additionalProperties
=> 0,
922 node
=> get_standard_option
('pve-node'),
923 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
932 description
=> "Configuration option name.",
936 description
=> "Current value.",
941 description
=> "Pending value.",
946 description
=> "Indicates a pending delete request if present and not 0. " .
947 "The value 2 indicates a force-delete request.",
959 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
961 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
963 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
964 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
966 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
969 # POST/PUT {vmid}/config implementation
971 # The original API used PUT (idempotent) an we assumed that all operations
972 # are fast. But it turned out that almost any configuration change can
973 # involve hot-plug actions, or disk alloc/free. Such actions can take long
974 # time to complete and have side effects (not idempotent).
976 # The new implementation uses POST and forks a worker process. We added
977 # a new option 'background_delay'. If specified we wait up to
978 # 'background_delay' second for the worker task to complete. It returns null
979 # if the task is finished within that time, else we return the UPID.
981 my $update_vm_api = sub {
982 my ($param, $sync) = @_;
984 my $rpcenv = PVE
::RPCEnvironment
::get
();
986 my $authuser = $rpcenv->get_user();
988 my $node = extract_param
($param, 'node');
990 my $vmid = extract_param
($param, 'vmid');
992 my $digest = extract_param
($param, 'digest');
994 my $background_delay = extract_param
($param, 'background_delay');
996 if (defined(my $cipassword = $param->{cipassword
})) {
997 # Same logic as in cloud-init (but with the regex fixed...)
998 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
999 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1002 my @paramarr = (); # used for log message
1003 foreach my $key (sort keys %$param) {
1004 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1005 push @paramarr, "-$key", $value;
1008 my $skiplock = extract_param
($param, 'skiplock');
1009 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1010 if $skiplock && $authuser ne 'root@pam';
1012 my $delete_str = extract_param
($param, 'delete');
1014 my $revert_str = extract_param
($param, 'revert');
1016 my $force = extract_param
($param, 'force');
1018 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1019 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1020 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1023 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1025 my $storecfg = PVE
::Storage
::config
();
1027 my $defaults = PVE
::QemuServer
::load_defaults
();
1029 &$resolve_cdrom_alias($param);
1031 # now try to verify all parameters
1034 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1035 if (!PVE
::QemuServer
::option_exists
($opt)) {
1036 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1039 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1040 "-revert $opt' at the same time" })
1041 if defined($param->{$opt});
1043 $revert->{$opt} = 1;
1047 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1048 $opt = 'ide2' if $opt eq 'cdrom';
1050 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1051 "-delete $opt' at the same time" })
1052 if defined($param->{$opt});
1054 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1055 "-revert $opt' at the same time" })
1058 if (!PVE
::QemuServer
::option_exists
($opt)) {
1059 raise_param_exc
({ delete => "unknown option '$opt'" });
1065 my $repl_conf = PVE
::ReplicationConfig-
>new();
1066 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1067 my $check_replication = sub {
1069 return if !$is_replicated;
1070 my $volid = $drive->{file
};
1071 return if !$volid || !($drive->{replicate
}//1);
1072 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1074 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1075 return if $volname eq 'cloudinit';
1078 if ($volid =~ $NEW_DISK_RE) {
1080 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1082 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1084 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1085 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1086 return if $scfg->{shared
};
1087 die "cannot add non-replicatable volume to a replicated VM\n";
1090 foreach my $opt (keys %$param) {
1091 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1092 # cleanup drive path
1093 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1094 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1095 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1096 $check_replication->($drive);
1097 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
1098 } elsif ($opt =~ m/^net(\d+)$/) {
1100 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1101 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1102 } elsif ($opt eq 'vmgenid') {
1103 if ($param->{$opt} eq '1') {
1104 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1106 } elsif ($opt eq 'hookscript') {
1107 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1108 raise_param_exc
({ $opt => $@ }) if $@;
1112 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1114 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1116 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1118 my $updatefn = sub {
1120 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1122 die "checksum missmatch (file change by other user?)\n"
1123 if $digest && $digest ne $conf->{digest
};
1125 # FIXME: 'suspended' lock should probabyl be a state or "weak" lock?!
1126 if (scalar(@delete) && grep { $_ eq 'vmstate'} @delete) {
1127 if (defined($conf->{lock}) && $conf->{lock} eq 'suspended') {
1128 delete $conf->{lock}; # for check lock check, not written out
1129 push @delete, 'lock'; # this is the real deal to write it out
1131 push @delete, 'runningmachine' if $conf->{runningmachine
};
1134 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1136 foreach my $opt (keys %$revert) {
1137 if (defined($conf->{$opt})) {
1138 $param->{$opt} = $conf->{$opt};
1139 } elsif (defined($conf->{pending
}->{$opt})) {
1144 if ($param->{memory
} || defined($param->{balloon
})) {
1145 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1146 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1148 die "balloon value too large (must be smaller than assigned memory)\n"
1149 if $balloon && $balloon > $maxmem;
1152 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1156 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1158 # write updates to pending section
1160 my $modified = {}; # record what $option we modify
1162 foreach my $opt (@delete) {
1163 $modified->{$opt} = 1;
1164 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1166 # value of what we want to delete, independent if pending or not
1167 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1168 if (!defined($val)) {
1169 warn "cannot delete '$opt' - not set in current configuration!\n";
1170 $modified->{$opt} = 0;
1173 my $is_pending_val = defined($conf->{pending
}->{$opt});
1174 delete $conf->{pending
}->{$opt};
1176 if ($opt =~ m/^unused/) {
1177 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1178 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1179 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1180 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1181 delete $conf->{$opt};
1182 PVE
::QemuConfig-
>write_config($vmid, $conf);
1184 } elsif ($opt eq 'vmstate') {
1185 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1186 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1187 delete $conf->{$opt};
1188 PVE
::QemuConfig-
>write_config($vmid, $conf);
1190 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1191 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1192 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1193 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1195 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1196 PVE
::QemuConfig-
>write_config($vmid, $conf);
1197 } elsif ($opt =~ m/^serial\d+$/) {
1198 if ($val eq 'socket') {
1199 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1200 } elsif ($authuser ne 'root@pam') {
1201 die "only root can delete '$opt' config for real devices\n";
1203 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1204 PVE
::QemuConfig-
>write_config($vmid, $conf);
1205 } elsif ($opt =~ m/^usb\d+$/) {
1206 if ($val =~ m/spice/) {
1207 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1208 } elsif ($authuser ne 'root@pam') {
1209 die "only root can delete '$opt' config for real devices\n";
1211 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1212 PVE
::QemuConfig-
>write_config($vmid, $conf);
1214 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1215 PVE
::QemuConfig-
>write_config($vmid, $conf);
1219 foreach my $opt (keys %$param) { # add/change
1220 $modified->{$opt} = 1;
1221 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1222 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1224 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1226 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1227 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1228 # FIXME: cloudinit: CDROM or Disk?
1229 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1230 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1232 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1234 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1235 if defined($conf->{pending
}->{$opt});
1237 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1238 } elsif ($opt =~ m/^serial\d+/) {
1239 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1240 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1241 } elsif ($authuser ne 'root@pam') {
1242 die "only root can modify '$opt' config for real devices\n";
1244 $conf->{pending
}->{$opt} = $param->{$opt};
1245 } elsif ($opt =~ m/^usb\d+/) {
1246 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1247 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1248 } elsif ($authuser ne 'root@pam') {
1249 die "only root can modify '$opt' config for real devices\n";
1251 $conf->{pending
}->{$opt} = $param->{$opt};
1253 $conf->{pending
}->{$opt} = $param->{$opt};
1255 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1256 PVE
::QemuConfig-
>write_config($vmid, $conf);
1259 # remove pending changes when nothing changed
1260 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1261 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1262 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1264 return if !scalar(keys %{$conf->{pending
}});
1266 my $running = PVE
::QemuServer
::check_running
($vmid);
1268 # apply pending changes
1270 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1274 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1276 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running, $errors);
1278 raise_param_exc
($errors) if scalar(keys %$errors);
1287 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1289 if ($background_delay) {
1291 # Note: It would be better to do that in the Event based HTTPServer
1292 # to avoid blocking call to sleep.
1294 my $end_time = time() + $background_delay;
1296 my $task = PVE
::Tools
::upid_decode
($upid);
1299 while (time() < $end_time) {
1300 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1302 sleep(1); # this gets interrupted when child process ends
1306 my $status = PVE
::Tools
::upid_read_status
($upid);
1307 return undef if $status eq 'OK';
1316 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1319 my $vm_config_perm_list = [
1324 'VM.Config.Network',
1326 'VM.Config.Options',
1329 __PACKAGE__-
>register_method({
1330 name
=> 'update_vm_async',
1331 path
=> '{vmid}/config',
1335 description
=> "Set virtual machine options (asynchrounous API).",
1337 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1340 additionalProperties
=> 0,
1341 properties
=> PVE
::QemuServer
::json_config_properties
(
1343 node
=> get_standard_option
('pve-node'),
1344 vmid
=> get_standard_option
('pve-vmid'),
1345 skiplock
=> get_standard_option
('skiplock'),
1347 type
=> 'string', format
=> 'pve-configid-list',
1348 description
=> "A list of settings you want to delete.",
1352 type
=> 'string', format
=> 'pve-configid-list',
1353 description
=> "Revert a pending change.",
1358 description
=> $opt_force_description,
1360 requires
=> 'delete',
1364 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1368 background_delay
=> {
1370 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1381 code
=> $update_vm_api,
1384 __PACKAGE__-
>register_method({
1385 name
=> 'update_vm',
1386 path
=> '{vmid}/config',
1390 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1392 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1395 additionalProperties
=> 0,
1396 properties
=> PVE
::QemuServer
::json_config_properties
(
1398 node
=> get_standard_option
('pve-node'),
1399 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1400 skiplock
=> get_standard_option
('skiplock'),
1402 type
=> 'string', format
=> 'pve-configid-list',
1403 description
=> "A list of settings you want to delete.",
1407 type
=> 'string', format
=> 'pve-configid-list',
1408 description
=> "Revert a pending change.",
1413 description
=> $opt_force_description,
1415 requires
=> 'delete',
1419 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1425 returns
=> { type
=> 'null' },
1428 &$update_vm_api($param, 1);
1433 __PACKAGE__-
>register_method({
1434 name
=> 'destroy_vm',
1439 description
=> "Destroy the vm (also delete all used/owned volumes).",
1441 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1444 additionalProperties
=> 0,
1446 node
=> get_standard_option
('pve-node'),
1447 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1448 skiplock
=> get_standard_option
('skiplock'),
1451 description
=> "Remove vmid from backup cron jobs.",
1462 my $rpcenv = PVE
::RPCEnvironment
::get
();
1463 my $authuser = $rpcenv->get_user();
1464 my $vmid = $param->{vmid
};
1466 my $skiplock = $param->{skiplock
};
1467 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1468 if $skiplock && $authuser ne 'root@pam';
1471 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1472 my $storecfg = PVE
::Storage
::config
();
1473 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1475 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("vm:$vmid");
1477 if (!$param->{purge
}) {
1478 die "unable to remove VM $vmid - used in HA resources and purge parameter not set.\n"
1480 # don't allow destroy if with replication jobs but no purge param
1481 my $repl_conf = PVE
::ReplicationConfig-
>new();
1482 $repl_conf->check_for_existing_jobs($vmid);
1485 # early tests (repeat after locking)
1486 die "VM $vmid is running - destroy failed\n"
1487 if PVE
::QemuServer
::check_running
($vmid);
1492 syslog
('info', "destroy VM $vmid: $upid\n");
1493 PVE
::QemuConfig-
>lock_config($vmid, sub {
1494 die "VM $vmid is running - destroy failed\n"
1495 if (PVE
::QemuServer
::check_running
($vmid));
1497 PVE
::QemuServer
::destroy_vm
($storecfg, $vmid, $skiplock, { lock => 'destroyed' });
1499 PVE
::AccessControl
::remove_vm_access
($vmid);
1500 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1501 if ($param->{purge
}) {
1502 print "purging VM $vmid from related configurations..\n";
1503 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1504 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1507 PVE
::HA
::Config
::delete_service_from_config
("vm:$vmid");
1508 print "NOTE: removed VM $vmid from HA resource configuration.\n";
1512 # only now remove the zombie config, else we can have reuse race
1513 PVE
::QemuConfig-
>destroy_config($vmid);
1517 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1520 __PACKAGE__-
>register_method({
1522 path
=> '{vmid}/unlink',
1526 description
=> "Unlink/delete disk images.",
1528 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1531 additionalProperties
=> 0,
1533 node
=> get_standard_option
('pve-node'),
1534 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1536 type
=> 'string', format
=> 'pve-configid-list',
1537 description
=> "A list of disk IDs you want to delete.",
1541 description
=> $opt_force_description,
1546 returns
=> { type
=> 'null'},
1550 $param->{delete} = extract_param
($param, 'idlist');
1552 __PACKAGE__-
>update_vm($param);
1559 __PACKAGE__-
>register_method({
1561 path
=> '{vmid}/vncproxy',
1565 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1567 description
=> "Creates a TCP VNC proxy connections.",
1569 additionalProperties
=> 0,
1571 node
=> get_standard_option
('pve-node'),
1572 vmid
=> get_standard_option
('pve-vmid'),
1576 description
=> "starts websockify instead of vncproxy",
1581 additionalProperties
=> 0,
1583 user
=> { type
=> 'string' },
1584 ticket
=> { type
=> 'string' },
1585 cert
=> { type
=> 'string' },
1586 port
=> { type
=> 'integer' },
1587 upid
=> { type
=> 'string' },
1593 my $rpcenv = PVE
::RPCEnvironment
::get
();
1595 my $authuser = $rpcenv->get_user();
1597 my $vmid = $param->{vmid
};
1598 my $node = $param->{node
};
1599 my $websocket = $param->{websocket
};
1601 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1602 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1604 my $authpath = "/vms/$vmid";
1606 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1608 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1614 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1615 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1616 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1617 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1618 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1620 $family = PVE
::Tools
::get_host_address_family
($node);
1623 my $port = PVE
::Tools
::next_vnc_port
($family);
1630 syslog
('info', "starting vnc proxy $upid\n");
1636 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1638 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1639 '-timeout', $timeout, '-authpath', $authpath,
1640 '-perm', 'Sys.Console'];
1642 if ($param->{websocket
}) {
1643 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1644 push @$cmd, '-notls', '-listen', 'localhost';
1647 push @$cmd, '-c', @$remcmd, @$termcmd;
1649 PVE
::Tools
::run_command
($cmd);
1653 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1655 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1657 my $sock = IO
::Socket
::IP-
>new(
1662 GetAddrInfoFlags
=> 0,
1663 ) or die "failed to create socket: $!\n";
1664 # Inside the worker we shouldn't have any previous alarms
1665 # running anyway...:
1667 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1669 accept(my $cli, $sock) or die "connection failed: $!\n";
1672 if (PVE
::Tools
::run_command
($cmd,
1673 output
=> '>&'.fileno($cli),
1674 input
=> '<&'.fileno($cli),
1677 die "Failed to run vncproxy.\n";
1684 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1686 PVE
::Tools
::wait_for_vnc_port
($port);
1697 __PACKAGE__-
>register_method({
1698 name
=> 'termproxy',
1699 path
=> '{vmid}/termproxy',
1703 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1705 description
=> "Creates a TCP proxy connections.",
1707 additionalProperties
=> 0,
1709 node
=> get_standard_option
('pve-node'),
1710 vmid
=> get_standard_option
('pve-vmid'),
1714 enum
=> [qw(serial0 serial1 serial2 serial3)],
1715 description
=> "opens a serial terminal (defaults to display)",
1720 additionalProperties
=> 0,
1722 user
=> { type
=> 'string' },
1723 ticket
=> { type
=> 'string' },
1724 port
=> { type
=> 'integer' },
1725 upid
=> { type
=> 'string' },
1731 my $rpcenv = PVE
::RPCEnvironment
::get
();
1733 my $authuser = $rpcenv->get_user();
1735 my $vmid = $param->{vmid
};
1736 my $node = $param->{node
};
1737 my $serial = $param->{serial
};
1739 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1741 if (!defined($serial)) {
1742 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1743 $serial = $conf->{vga
};
1747 my $authpath = "/vms/$vmid";
1749 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1754 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1755 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1756 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1757 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1758 push @$remcmd, '--';
1760 $family = PVE
::Tools
::get_host_address_family
($node);
1763 my $port = PVE
::Tools
::next_vnc_port
($family);
1765 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1766 push @$termcmd, '-iface', $serial if $serial;
1771 syslog
('info', "starting qemu termproxy $upid\n");
1773 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1774 '--perm', 'VM.Console', '--'];
1775 push @$cmd, @$remcmd, @$termcmd;
1777 PVE
::Tools
::run_command
($cmd);
1780 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1782 PVE
::Tools
::wait_for_vnc_port
($port);
1792 __PACKAGE__-
>register_method({
1793 name
=> 'vncwebsocket',
1794 path
=> '{vmid}/vncwebsocket',
1797 description
=> "You also need to pass a valid ticket (vncticket).",
1798 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1800 description
=> "Opens a weksocket for VNC traffic.",
1802 additionalProperties
=> 0,
1804 node
=> get_standard_option
('pve-node'),
1805 vmid
=> get_standard_option
('pve-vmid'),
1807 description
=> "Ticket from previous call to vncproxy.",
1812 description
=> "Port number returned by previous vncproxy call.",
1822 port
=> { type
=> 'string' },
1828 my $rpcenv = PVE
::RPCEnvironment
::get
();
1830 my $authuser = $rpcenv->get_user();
1832 my $vmid = $param->{vmid
};
1833 my $node = $param->{node
};
1835 my $authpath = "/vms/$vmid";
1837 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1839 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1841 # Note: VNC ports are acessible from outside, so we do not gain any
1842 # security if we verify that $param->{port} belongs to VM $vmid. This
1843 # check is done by verifying the VNC ticket (inside VNC protocol).
1845 my $port = $param->{port
};
1847 return { port
=> $port };
1850 __PACKAGE__-
>register_method({
1851 name
=> 'spiceproxy',
1852 path
=> '{vmid}/spiceproxy',
1857 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1859 description
=> "Returns a SPICE configuration to connect to the VM.",
1861 additionalProperties
=> 0,
1863 node
=> get_standard_option
('pve-node'),
1864 vmid
=> get_standard_option
('pve-vmid'),
1865 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1868 returns
=> get_standard_option
('remote-viewer-config'),
1872 my $rpcenv = PVE
::RPCEnvironment
::get
();
1874 my $authuser = $rpcenv->get_user();
1876 my $vmid = $param->{vmid
};
1877 my $node = $param->{node
};
1878 my $proxy = $param->{proxy
};
1880 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1881 my $title = "VM $vmid";
1882 $title .= " - ". $conf->{name
} if $conf->{name
};
1884 my $port = PVE
::QemuServer
::spice_port
($vmid);
1886 my ($ticket, undef, $remote_viewer_config) =
1887 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1889 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1890 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1892 return $remote_viewer_config;
1895 __PACKAGE__-
>register_method({
1897 path
=> '{vmid}/status',
1900 description
=> "Directory index",
1905 additionalProperties
=> 0,
1907 node
=> get_standard_option
('pve-node'),
1908 vmid
=> get_standard_option
('pve-vmid'),
1916 subdir
=> { type
=> 'string' },
1919 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1925 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1928 { subdir
=> 'current' },
1929 { subdir
=> 'start' },
1930 { subdir
=> 'stop' },
1931 { subdir
=> 'reset' },
1932 { subdir
=> 'shutdown' },
1933 { subdir
=> 'suspend' },
1934 { subdir
=> 'reboot' },
1940 __PACKAGE__-
>register_method({
1941 name
=> 'vm_status',
1942 path
=> '{vmid}/status/current',
1945 protected
=> 1, # qemu pid files are only readable by root
1946 description
=> "Get virtual machine status.",
1948 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1951 additionalProperties
=> 0,
1953 node
=> get_standard_option
('pve-node'),
1954 vmid
=> get_standard_option
('pve-vmid'),
1960 %$PVE::QemuServer
::vmstatus_return_properties
,
1962 description
=> "HA manager service status.",
1966 description
=> "Qemu VGA configuration supports spice.",
1971 description
=> "Qemu GuestAgent enabled in config.",
1981 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1983 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1984 my $status = $vmstatus->{$param->{vmid
}};
1986 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1988 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1989 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1994 __PACKAGE__-
>register_method({
1996 path
=> '{vmid}/status/start',
2000 description
=> "Start virtual machine.",
2002 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2005 additionalProperties
=> 0,
2007 node
=> get_standard_option
('pve-node'),
2008 vmid
=> get_standard_option
('pve-vmid',
2009 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2010 skiplock
=> get_standard_option
('skiplock'),
2011 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2012 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2015 enum
=> ['secure', 'insecure'],
2016 description
=> "Migration traffic is encrypted using an SSH " .
2017 "tunnel by default. On secure, completely private networks " .
2018 "this can be disabled to increase performance.",
2021 migration_network
=> {
2022 type
=> 'string', format
=> 'CIDR',
2023 description
=> "CIDR of the (sub) network that is used for migration.",
2026 machine
=> get_standard_option
('pve-qemu-machine'),
2028 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
2033 description
=> "Wait maximal timeout seconds.",
2036 default => 'max(30, vm memory in GiB)',
2047 my $rpcenv = PVE
::RPCEnvironment
::get
();
2048 my $authuser = $rpcenv->get_user();
2050 my $node = extract_param
($param, 'node');
2051 my $vmid = extract_param
($param, 'vmid');
2052 my $timeout = extract_param
($param, 'timeout');
2054 my $machine = extract_param
($param, 'machine');
2056 my $get_root_param = sub {
2057 my $value = extract_param
($param, $_[0]);
2058 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2059 if $value && $authuser ne 'root@pam';
2063 my $stateuri = $get_root_param->('stateuri');
2064 my $skiplock = $get_root_param->('skiplock');
2065 my $migratedfrom = $get_root_param->('migratedfrom');
2066 my $migration_type = $get_root_param->('migration_type');
2067 my $migration_network = $get_root_param->('migration_network');
2068 my $targetstorage = $get_root_param->('targetstorage');
2070 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2071 if $targetstorage && !$migratedfrom;
2073 # read spice ticket from STDIN
2075 my $nbd_protocol_version = 0;
2076 my $replicated_volumes = {};
2077 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2078 while (defined(my $line = <STDIN
>)) {
2080 if ($line =~ m/^spice_ticket: (.+)$/) {
2082 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2083 $nbd_protocol_version = $1;
2084 } elsif ($line =~ m/^replicated_volume: (.*)$/) {
2085 $replicated_volumes->{$1} = 1;
2087 # fallback for old source node
2088 $spice_ticket = $line;
2093 PVE
::Cluster
::check_cfs_quorum
();
2095 my $storecfg = PVE
::Storage
::config
();
2097 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2101 print "Requesting HA start for VM $vmid\n";
2103 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2104 PVE
::Tools
::run_command
($cmd);
2108 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2115 syslog
('info', "start VM $vmid: $upid\n");
2117 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef, $machine,
2118 $spice_ticket, $migration_network, $migration_type, $targetstorage, $timeout,
2119 $nbd_protocol_version, $replicated_volumes);
2123 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2127 __PACKAGE__-
>register_method({
2129 path
=> '{vmid}/status/stop',
2133 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2134 "is akin to pulling the power plug of a running computer and may damage the VM data",
2136 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2139 additionalProperties
=> 0,
2141 node
=> get_standard_option
('pve-node'),
2142 vmid
=> get_standard_option
('pve-vmid',
2143 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2144 skiplock
=> get_standard_option
('skiplock'),
2145 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2147 description
=> "Wait maximal timeout seconds.",
2153 description
=> "Do not deactivate storage volumes.",
2166 my $rpcenv = PVE
::RPCEnvironment
::get
();
2167 my $authuser = $rpcenv->get_user();
2169 my $node = extract_param
($param, 'node');
2170 my $vmid = extract_param
($param, 'vmid');
2172 my $skiplock = extract_param
($param, 'skiplock');
2173 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2174 if $skiplock && $authuser ne 'root@pam';
2176 my $keepActive = extract_param
($param, 'keepActive');
2177 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2178 if $keepActive && $authuser ne 'root@pam';
2180 my $migratedfrom = extract_param
($param, 'migratedfrom');
2181 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2182 if $migratedfrom && $authuser ne 'root@pam';
2185 my $storecfg = PVE
::Storage
::config
();
2187 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2192 print "Requesting HA stop for VM $vmid\n";
2194 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2195 PVE
::Tools
::run_command
($cmd);
2199 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2205 syslog
('info', "stop VM $vmid: $upid\n");
2207 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2208 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2212 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2216 __PACKAGE__-
>register_method({
2218 path
=> '{vmid}/status/reset',
2222 description
=> "Reset virtual machine.",
2224 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2227 additionalProperties
=> 0,
2229 node
=> get_standard_option
('pve-node'),
2230 vmid
=> get_standard_option
('pve-vmid',
2231 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2232 skiplock
=> get_standard_option
('skiplock'),
2241 my $rpcenv = PVE
::RPCEnvironment
::get
();
2243 my $authuser = $rpcenv->get_user();
2245 my $node = extract_param
($param, 'node');
2247 my $vmid = extract_param
($param, 'vmid');
2249 my $skiplock = extract_param
($param, 'skiplock');
2250 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2251 if $skiplock && $authuser ne 'root@pam';
2253 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2258 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2263 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2266 __PACKAGE__-
>register_method({
2267 name
=> 'vm_shutdown',
2268 path
=> '{vmid}/status/shutdown',
2272 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2273 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2275 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2278 additionalProperties
=> 0,
2280 node
=> get_standard_option
('pve-node'),
2281 vmid
=> get_standard_option
('pve-vmid',
2282 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2283 skiplock
=> get_standard_option
('skiplock'),
2285 description
=> "Wait maximal timeout seconds.",
2291 description
=> "Make sure the VM stops.",
2297 description
=> "Do not deactivate storage volumes.",
2310 my $rpcenv = PVE
::RPCEnvironment
::get
();
2311 my $authuser = $rpcenv->get_user();
2313 my $node = extract_param
($param, 'node');
2314 my $vmid = extract_param
($param, 'vmid');
2316 my $skiplock = extract_param
($param, 'skiplock');
2317 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2318 if $skiplock && $authuser ne 'root@pam';
2320 my $keepActive = extract_param
($param, 'keepActive');
2321 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2322 if $keepActive && $authuser ne 'root@pam';
2324 my $storecfg = PVE
::Storage
::config
();
2328 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2329 # otherwise, we will infer a shutdown command, but run into the timeout,
2330 # then when the vm is resumed, it will instantly shutdown
2332 # checking the qmp status here to get feedback to the gui/cli/api
2333 # and the status query should not take too long
2334 my $qmpstatus = eval {
2335 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2336 mon_cmd
($vmid, "query-status");
2340 if (!$err && $qmpstatus->{status
} eq "paused") {
2341 if ($param->{forceStop
}) {
2342 warn "VM is paused - stop instead of shutdown\n";
2345 die "VM is paused - cannot shutdown\n";
2349 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2351 my $timeout = $param->{timeout
} // 60;
2355 print "Requesting HA stop for VM $vmid\n";
2357 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2358 PVE
::Tools
::run_command
($cmd);
2362 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2369 syslog
('info', "shutdown VM $vmid: $upid\n");
2371 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2372 $shutdown, $param->{forceStop
}, $keepActive);
2376 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2380 __PACKAGE__-
>register_method({
2381 name
=> 'vm_reboot',
2382 path
=> '{vmid}/status/reboot',
2386 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2388 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2391 additionalProperties
=> 0,
2393 node
=> get_standard_option
('pve-node'),
2394 vmid
=> get_standard_option
('pve-vmid',
2395 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2397 description
=> "Wait maximal timeout seconds for the shutdown.",
2410 my $rpcenv = PVE
::RPCEnvironment
::get
();
2411 my $authuser = $rpcenv->get_user();
2413 my $node = extract_param
($param, 'node');
2414 my $vmid = extract_param
($param, 'vmid');
2416 my $qmpstatus = eval {
2417 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2418 mon_cmd
($vmid, "query-status");
2422 if (!$err && $qmpstatus->{status
} eq "paused") {
2423 die "VM is paused - cannot shutdown\n";
2426 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2431 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2432 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2436 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2439 __PACKAGE__-
>register_method({
2440 name
=> 'vm_suspend',
2441 path
=> '{vmid}/status/suspend',
2445 description
=> "Suspend virtual machine.",
2447 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2448 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2449 " on the storage for the vmstate.",
2450 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2453 additionalProperties
=> 0,
2455 node
=> get_standard_option
('pve-node'),
2456 vmid
=> get_standard_option
('pve-vmid',
2457 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2458 skiplock
=> get_standard_option
('skiplock'),
2463 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2465 statestorage
=> get_standard_option
('pve-storage-id', {
2466 description
=> "The storage for the VM state",
2467 requires
=> 'todisk',
2469 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2479 my $rpcenv = PVE
::RPCEnvironment
::get
();
2480 my $authuser = $rpcenv->get_user();
2482 my $node = extract_param
($param, 'node');
2483 my $vmid = extract_param
($param, 'vmid');
2485 my $todisk = extract_param
($param, 'todisk') // 0;
2487 my $statestorage = extract_param
($param, 'statestorage');
2489 my $skiplock = extract_param
($param, 'skiplock');
2490 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2491 if $skiplock && $authuser ne 'root@pam';
2493 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2495 die "Cannot suspend HA managed VM to disk\n"
2496 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2498 # early check for storage permission, for better user feedback
2500 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2502 if (!$statestorage) {
2503 # get statestorage from config if none is given
2504 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2505 my $storecfg = PVE
::Storage
::config
();
2506 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2509 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2515 syslog
('info', "suspend VM $vmid: $upid\n");
2517 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2522 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2523 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2526 __PACKAGE__-
>register_method({
2527 name
=> 'vm_resume',
2528 path
=> '{vmid}/status/resume',
2532 description
=> "Resume virtual machine.",
2534 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2537 additionalProperties
=> 0,
2539 node
=> get_standard_option
('pve-node'),
2540 vmid
=> get_standard_option
('pve-vmid',
2541 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2542 skiplock
=> get_standard_option
('skiplock'),
2543 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2553 my $rpcenv = PVE
::RPCEnvironment
::get
();
2555 my $authuser = $rpcenv->get_user();
2557 my $node = extract_param
($param, 'node');
2559 my $vmid = extract_param
($param, 'vmid');
2561 my $skiplock = extract_param
($param, 'skiplock');
2562 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2563 if $skiplock && $authuser ne 'root@pam';
2565 my $nocheck = extract_param
($param, 'nocheck');
2567 my $to_disk_suspended;
2569 PVE
::QemuConfig-
>lock_config($vmid, sub {
2570 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2571 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2575 die "VM $vmid not running\n"
2576 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2581 syslog
('info', "resume VM $vmid: $upid\n");
2583 if (!$to_disk_suspended) {
2584 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2586 my $storecfg = PVE
::Storage
::config
();
2587 PVE
::QemuServer
::vm_start
($storecfg, $vmid, undef, $skiplock);
2593 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2596 __PACKAGE__-
>register_method({
2597 name
=> 'vm_sendkey',
2598 path
=> '{vmid}/sendkey',
2602 description
=> "Send key event to virtual machine.",
2604 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2607 additionalProperties
=> 0,
2609 node
=> get_standard_option
('pve-node'),
2610 vmid
=> get_standard_option
('pve-vmid',
2611 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2612 skiplock
=> get_standard_option
('skiplock'),
2614 description
=> "The key (qemu monitor encoding).",
2619 returns
=> { type
=> 'null'},
2623 my $rpcenv = PVE
::RPCEnvironment
::get
();
2625 my $authuser = $rpcenv->get_user();
2627 my $node = extract_param
($param, 'node');
2629 my $vmid = extract_param
($param, 'vmid');
2631 my $skiplock = extract_param
($param, 'skiplock');
2632 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2633 if $skiplock && $authuser ne 'root@pam';
2635 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2640 __PACKAGE__-
>register_method({
2641 name
=> 'vm_feature',
2642 path
=> '{vmid}/feature',
2646 description
=> "Check if feature for virtual machine is available.",
2648 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2651 additionalProperties
=> 0,
2653 node
=> get_standard_option
('pve-node'),
2654 vmid
=> get_standard_option
('pve-vmid'),
2656 description
=> "Feature to check.",
2658 enum
=> [ 'snapshot', 'clone', 'copy' ],
2660 snapname
=> get_standard_option
('pve-snapshot-name', {
2668 hasFeature
=> { type
=> 'boolean' },
2671 items
=> { type
=> 'string' },
2678 my $node = extract_param
($param, 'node');
2680 my $vmid = extract_param
($param, 'vmid');
2682 my $snapname = extract_param
($param, 'snapname');
2684 my $feature = extract_param
($param, 'feature');
2686 my $running = PVE
::QemuServer
::check_running
($vmid);
2688 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2691 my $snap = $conf->{snapshots
}->{$snapname};
2692 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2695 my $storecfg = PVE
::Storage
::config
();
2697 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2698 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2701 hasFeature
=> $hasFeature,
2702 nodes
=> [ keys %$nodelist ],
2706 __PACKAGE__-
>register_method({
2708 path
=> '{vmid}/clone',
2712 description
=> "Create a copy of virtual machine/template.",
2714 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2715 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2716 "'Datastore.AllocateSpace' on any used storage.",
2719 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2721 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2722 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2727 additionalProperties
=> 0,
2729 node
=> get_standard_option
('pve-node'),
2730 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2731 newid
=> get_standard_option
('pve-vmid', {
2732 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2733 description
=> 'VMID for the clone.' }),
2736 type
=> 'string', format
=> 'dns-name',
2737 description
=> "Set a name for the new VM.",
2742 description
=> "Description for the new VM.",
2746 type
=> 'string', format
=> 'pve-poolid',
2747 description
=> "Add the new VM to the specified pool.",
2749 snapname
=> get_standard_option
('pve-snapshot-name', {
2752 storage
=> get_standard_option
('pve-storage-id', {
2753 description
=> "Target storage for full clone.",
2757 description
=> "Target format for file storage. Only valid for full clone.",
2760 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2765 description
=> "Create a full copy of all disks. This is always done when " .
2766 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2768 target
=> get_standard_option
('pve-node', {
2769 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2773 description
=> "Override I/O bandwidth limit (in KiB/s).",
2777 default => 'clone limit from datacenter or storage config',
2787 my $rpcenv = PVE
::RPCEnvironment
::get
();
2788 my $authuser = $rpcenv->get_user();
2790 my $node = extract_param
($param, 'node');
2791 my $vmid = extract_param
($param, 'vmid');
2792 my $newid = extract_param
($param, 'newid');
2793 my $pool = extract_param
($param, 'pool');
2794 $rpcenv->check_pool_exist($pool) if defined($pool);
2796 my $snapname = extract_param
($param, 'snapname');
2797 my $storage = extract_param
($param, 'storage');
2798 my $format = extract_param
($param, 'format');
2799 my $target = extract_param
($param, 'target');
2801 my $localnode = PVE
::INotify
::nodename
();
2803 if ($target && ($target eq $localnode || $target eq 'localhost')) {
2807 PVE
::Cluster
::check_node_exists
($target) if $target;
2809 my $storecfg = PVE
::Storage
::config
();
2812 # check if storage is enabled on local node
2813 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2815 # check if storage is available on target node
2816 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2817 # clone only works if target storage is shared
2818 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2819 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2823 PVE
::Cluster
::check_cfs_quorum
();
2825 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2827 # exclusive lock if VM is running - else shared lock is enough;
2828 my $shared_lock = $running ?
0 : 1;
2831 # do all tests after lock but before forking worker - if possible
2833 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2834 PVE
::QemuConfig-
>check_lock($conf);
2836 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2837 die "unexpected state change\n" if $verify_running != $running;
2839 die "snapshot '$snapname' does not exist\n"
2840 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2842 my $full = extract_param
($param, 'full') // !PVE
::QemuConfig-
>is_template($conf);
2844 die "parameter 'storage' not allowed for linked clones\n"
2845 if defined($storage) && !$full;
2847 die "parameter 'format' not allowed for linked clones\n"
2848 if defined($format) && !$full;
2850 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2852 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2854 die "can't clone VM to node '$target' (VM uses local storage)\n"
2855 if $target && !$sharedvm;
2857 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2858 die "unable to create VM $newid: config file already exists\n"
2861 my $newconf = { lock => 'clone' };
2866 foreach my $opt (keys %$oldconf) {
2867 my $value = $oldconf->{$opt};
2869 # do not copy snapshot related info
2870 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2871 $opt eq 'vmstate' || $opt eq 'snapstate';
2873 # no need to copy unused images, because VMID(owner) changes anyways
2874 next if $opt =~ m/^unused\d+$/;
2876 # always change MAC! address
2877 if ($opt =~ m/^net(\d+)$/) {
2878 my $net = PVE
::QemuServer
::parse_net
($value);
2879 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2880 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2881 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2882 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2883 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2884 die "unable to parse drive options for '$opt'\n" if !$drive;
2885 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2886 $newconf->{$opt} = $value; # simply copy configuration
2888 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2889 die "Full clone feature is not supported for drive '$opt'\n"
2890 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2891 $fullclone->{$opt} = 1;
2893 # not full means clone instead of copy
2894 die "Linked clone feature is not supported for drive '$opt'\n"
2895 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2897 $drives->{$opt} = $drive;
2898 push @$vollist, $drive->{file
};
2901 # copy everything else
2902 $newconf->{$opt} = $value;
2906 # auto generate a new uuid
2907 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2908 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2909 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2910 # auto generate a new vmgenid only if the option was set for template
2911 if ($newconf->{vmgenid
}) {
2912 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2915 delete $newconf->{template
};
2917 if ($param->{name
}) {
2918 $newconf->{name
} = $param->{name
};
2920 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
2923 if ($param->{description
}) {
2924 $newconf->{description
} = $param->{description
};
2927 # create empty/temp config - this fails if VM already exists on other node
2928 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
2929 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2934 my $newvollist = [];
2941 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2943 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2945 my $bwlimit = extract_param
($param, 'bwlimit');
2947 my $total_jobs = scalar(keys %{$drives});
2950 foreach my $opt (keys %$drives) {
2951 my $drive = $drives->{$opt};
2952 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2953 my $completion = $skipcomplete ?
'skip' : 'complete';
2955 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
2956 my $storage_list = [ $src_sid ];
2957 push @$storage_list, $storage if defined($storage);
2958 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
2960 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2961 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2962 $jobs, $completion, $oldconf->{agent
}, $clonelimit, $oldconf);
2964 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
2966 PVE
::QemuConfig-
>write_config($newid, $newconf);
2970 delete $newconf->{lock};
2972 # do not write pending changes
2973 if (my @changes = keys %{$newconf->{pending
}}) {
2974 my $pending = join(',', @changes);
2975 warn "found pending changes for '$pending', discarding for clone\n";
2976 delete $newconf->{pending
};
2979 PVE
::QemuConfig-
>write_config($newid, $newconf);
2982 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2983 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2984 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2986 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2987 die "Failed to move config to node '$target' - rename failed: $!\n"
2988 if !rename($conffile, $newconffile);
2991 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2994 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2995 sleep 1; # some storage like rbd need to wait before release volume - really?
2997 foreach my $volid (@$newvollist) {
2998 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3002 PVE
::Firewall
::remove_vmfw_conf
($newid);
3004 unlink $conffile; # avoid races -> last thing before die
3006 die "clone failed: $err";
3012 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3014 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
3017 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
3018 # Aquire exclusive lock lock for $newid
3019 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3024 __PACKAGE__-
>register_method({
3025 name
=> 'move_vm_disk',
3026 path
=> '{vmid}/move_disk',
3030 description
=> "Move volume to different storage.",
3032 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
3034 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3035 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
3039 additionalProperties
=> 0,
3041 node
=> get_standard_option
('pve-node'),
3042 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3045 description
=> "The disk you want to move.",
3046 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3048 storage
=> get_standard_option
('pve-storage-id', {
3049 description
=> "Target storage.",
3050 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3054 description
=> "Target Format.",
3055 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3060 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3066 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3071 description
=> "Override I/O bandwidth limit (in KiB/s).",
3075 default => 'move limit from datacenter or storage config',
3081 description
=> "the task ID.",
3086 my $rpcenv = PVE
::RPCEnvironment
::get
();
3087 my $authuser = $rpcenv->get_user();
3089 my $node = extract_param
($param, 'node');
3090 my $vmid = extract_param
($param, 'vmid');
3091 my $digest = extract_param
($param, 'digest');
3092 my $disk = extract_param
($param, 'disk');
3093 my $storeid = extract_param
($param, 'storage');
3094 my $format = extract_param
($param, 'format');
3096 my $storecfg = PVE
::Storage
::config
();
3098 my $updatefn = sub {
3099 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3100 PVE
::QemuConfig-
>check_lock($conf);
3102 die "VM config checksum missmatch (file change by other user?)\n"
3103 if $digest && $digest ne $conf->{digest
};
3105 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3107 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3109 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3110 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3112 my $old_volid = $drive->{file
};
3114 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3115 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3119 die "you can't move to the same storage with same format\n" if $oldstoreid eq $storeid &&
3120 (!$format || !$oldfmt || $oldfmt eq $format);
3122 # this only checks snapshots because $disk is passed!
3123 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3124 die "you can't move a disk with snapshots and delete the source\n"
3125 if $snapshotted && $param->{delete};
3127 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3129 my $running = PVE
::QemuServer
::check_running
($vmid);
3131 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3134 my $newvollist = [];
3140 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3142 warn "moving disk with snapshots, snapshots will not be moved!\n"
3145 my $bwlimit = extract_param
($param, 'bwlimit');
3146 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3148 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3149 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit, $conf);
3151 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3153 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3155 # convert moved disk to base if part of template
3156 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3157 if PVE
::QemuConfig-
>is_template($conf);
3159 PVE
::QemuConfig-
>write_config($vmid, $conf);
3161 my $do_trim = PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
};
3162 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3163 eval { mon_cmd
($vmid, "guest-fstrim") };
3167 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3168 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3174 foreach my $volid (@$newvollist) {
3175 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3178 die "storage migration failed: $err";
3181 if ($param->{delete}) {
3183 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3184 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3190 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3193 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3196 my $check_vm_disks_local = sub {
3197 my ($storecfg, $vmconf, $vmid) = @_;
3199 my $local_disks = {};
3201 # add some more information to the disks e.g. cdrom
3202 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3203 my ($volid, $attr) = @_;
3205 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3207 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3208 return if $scfg->{shared
};
3210 # The shared attr here is just a special case where the vdisk
3211 # is marked as shared manually
3212 return if $attr->{shared
};
3213 return if $attr->{cdrom
} and $volid eq "none";
3215 if (exists $local_disks->{$volid}) {
3216 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3218 $local_disks->{$volid} = $attr;
3219 # ensure volid is present in case it's needed
3220 $local_disks->{$volid}->{volid
} = $volid;
3224 return $local_disks;
3227 __PACKAGE__-
>register_method({
3228 name
=> 'migrate_vm_precondition',
3229 path
=> '{vmid}/migrate',
3233 description
=> "Get preconditions for migration.",
3235 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3238 additionalProperties
=> 0,
3240 node
=> get_standard_option
('pve-node'),
3241 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3242 target
=> get_standard_option
('pve-node', {
3243 description
=> "Target node.",
3244 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3252 running
=> { type
=> 'boolean' },
3256 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3258 not_allowed_nodes
=> {
3261 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3265 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3267 local_resources
=> {
3269 description
=> "List local resources e.g. pci, usb"
3276 my $rpcenv = PVE
::RPCEnvironment
::get
();
3278 my $authuser = $rpcenv->get_user();
3280 PVE
::Cluster
::check_cfs_quorum
();
3284 my $vmid = extract_param
($param, 'vmid');
3285 my $target = extract_param
($param, 'target');
3286 my $localnode = PVE
::INotify
::nodename
();
3290 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3291 my $storecfg = PVE
::Storage
::config
();
3294 # try to detect errors early
3295 PVE
::QemuConfig-
>check_lock($vmconf);
3297 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3299 # if vm is not running, return target nodes where local storage is available
3300 # for offline migration
3301 if (!$res->{running
}) {
3302 $res->{allowed_nodes
} = [];
3303 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3304 delete $checked_nodes->{$localnode};
3306 foreach my $node (keys %$checked_nodes) {
3307 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3308 push @{$res->{allowed_nodes
}}, $node;
3312 $res->{not_allowed_nodes
} = $checked_nodes;
3316 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3317 $res->{local_disks
} = [ values %$local_disks ];;
3319 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3321 $res->{local_resources
} = $local_resources;
3328 __PACKAGE__-
>register_method({
3329 name
=> 'migrate_vm',
3330 path
=> '{vmid}/migrate',
3334 description
=> "Migrate virtual machine. Creates a new migration task.",
3336 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3339 additionalProperties
=> 0,
3341 node
=> get_standard_option
('pve-node'),
3342 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3343 target
=> get_standard_option
('pve-node', {
3344 description
=> "Target node.",
3345 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3349 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3354 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3359 enum
=> ['secure', 'insecure'],
3360 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3363 migration_network
=> {
3364 type
=> 'string', format
=> 'CIDR',
3365 description
=> "CIDR of the (sub) network that is used for migration.",
3368 "with-local-disks" => {
3370 description
=> "Enable live storage migration for local disk",
3373 targetstorage
=> get_standard_option
('pve-storage-id', {
3374 description
=> "Default target storage.",
3376 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3379 description
=> "Override I/O bandwidth limit (in KiB/s).",
3383 default => 'migrate limit from datacenter or storage config',
3389 description
=> "the task ID.",
3394 my $rpcenv = PVE
::RPCEnvironment
::get
();
3395 my $authuser = $rpcenv->get_user();
3397 my $target = extract_param
($param, 'target');
3399 my $localnode = PVE
::INotify
::nodename
();
3400 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3402 PVE
::Cluster
::check_cfs_quorum
();
3404 PVE
::Cluster
::check_node_exists
($target);
3406 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3408 my $vmid = extract_param
($param, 'vmid');
3410 raise_param_exc
({ force
=> "Only root may use this option." })
3411 if $param->{force
} && $authuser ne 'root@pam';
3413 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3414 if $param->{migration_type
} && $authuser ne 'root@pam';
3416 # allow root only until better network permissions are available
3417 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3418 if $param->{migration_network
} && $authuser ne 'root@pam';
3421 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3423 # try to detect errors early
3425 PVE
::QemuConfig-
>check_lock($conf);
3427 if (PVE
::QemuServer
::check_running
($vmid)) {
3428 die "can't migrate running VM without --online\n" if !$param->{online
};
3430 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
3431 $param->{online
} = 0;
3434 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3435 if !$param->{online
} && $param->{targetstorage
};
3437 my $storecfg = PVE
::Storage
::config
();
3439 if( $param->{targetstorage
}) {
3440 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3442 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3445 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3450 print "Requesting HA migration for VM $vmid to node $target\n";
3452 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3453 PVE
::Tools
::run_command
($cmd);
3457 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3462 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3466 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3469 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3474 __PACKAGE__-
>register_method({
3476 path
=> '{vmid}/monitor',
3480 description
=> "Execute Qemu monitor commands.",
3482 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3483 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3486 additionalProperties
=> 0,
3488 node
=> get_standard_option
('pve-node'),
3489 vmid
=> get_standard_option
('pve-vmid'),
3492 description
=> "The monitor command.",
3496 returns
=> { type
=> 'string'},
3500 my $rpcenv = PVE
::RPCEnvironment
::get
();
3501 my $authuser = $rpcenv->get_user();
3504 my $command = shift;
3505 return $command =~ m/^\s*info(\s+|$)/
3506 || $command =~ m/^\s*help\s*$/;
3509 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3510 if !&$is_ro($param->{command
});
3512 my $vmid = $param->{vmid
};
3514 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3518 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3520 $res = "ERROR: $@" if $@;
3525 __PACKAGE__-
>register_method({
3526 name
=> 'resize_vm',
3527 path
=> '{vmid}/resize',
3531 description
=> "Extend volume size.",
3533 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3536 additionalProperties
=> 0,
3538 node
=> get_standard_option
('pve-node'),
3539 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3540 skiplock
=> get_standard_option
('skiplock'),
3543 description
=> "The disk you want to resize.",
3544 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3548 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3549 description
=> "The new size. With the `+` sign the value is added to the actual size of the volume and without it, the value is taken as an absolute one. Shrinking disk size is not supported.",
3553 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3559 returns
=> { type
=> 'null'},
3563 my $rpcenv = PVE
::RPCEnvironment
::get
();
3565 my $authuser = $rpcenv->get_user();
3567 my $node = extract_param
($param, 'node');
3569 my $vmid = extract_param
($param, 'vmid');
3571 my $digest = extract_param
($param, 'digest');
3573 my $disk = extract_param
($param, 'disk');
3575 my $sizestr = extract_param
($param, 'size');
3577 my $skiplock = extract_param
($param, 'skiplock');
3578 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3579 if $skiplock && $authuser ne 'root@pam';
3581 my $storecfg = PVE
::Storage
::config
();
3583 my $updatefn = sub {
3585 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3587 die "checksum missmatch (file change by other user?)\n"
3588 if $digest && $digest ne $conf->{digest
};
3589 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3591 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3593 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3595 my (undef, undef, undef, undef, undef, undef, $format) =
3596 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3598 die "can't resize volume: $disk if snapshot exists\n"
3599 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3601 my $volid = $drive->{file
};
3603 die "disk '$disk' has no associated volume\n" if !$volid;
3605 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3607 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3609 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3611 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3612 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3614 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3616 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3617 my ($ext, $newsize, $unit) = ($1, $2, $4);
3620 $newsize = $newsize * 1024;
3621 } elsif ($unit eq 'M') {
3622 $newsize = $newsize * 1024 * 1024;
3623 } elsif ($unit eq 'G') {
3624 $newsize = $newsize * 1024 * 1024 * 1024;
3625 } elsif ($unit eq 'T') {
3626 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3629 $newsize += $size if $ext;
3630 $newsize = int($newsize);
3632 die "shrinking disks is not supported\n" if $newsize < $size;
3634 return if $size == $newsize;
3636 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3638 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3640 my $effective_size = eval { PVE
::Storage
::volume_size_info
($storecfg, $volid, 3); };
3641 $drive->{size
} = $effective_size // $newsize;
3642 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
3644 PVE
::QemuConfig-
>write_config($vmid, $conf);
3647 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3651 __PACKAGE__-
>register_method({
3652 name
=> 'snapshot_list',
3653 path
=> '{vmid}/snapshot',
3655 description
=> "List all snapshots.",
3657 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3660 protected
=> 1, # qemu pid files are only readable by root
3662 additionalProperties
=> 0,
3664 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3665 node
=> get_standard_option
('pve-node'),
3674 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3678 description
=> "Snapshot includes RAM.",
3683 description
=> "Snapshot description.",
3687 description
=> "Snapshot creation time",
3689 renderer
=> 'timestamp',
3693 description
=> "Parent snapshot identifier.",
3699 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3704 my $vmid = $param->{vmid
};
3706 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3707 my $snaphash = $conf->{snapshots
} || {};
3711 foreach my $name (keys %$snaphash) {
3712 my $d = $snaphash->{$name};
3715 snaptime
=> $d->{snaptime
} || 0,
3716 vmstate
=> $d->{vmstate
} ?
1 : 0,
3717 description
=> $d->{description
} || '',
3719 $item->{parent
} = $d->{parent
} if $d->{parent
};
3720 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3724 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3727 digest
=> $conf->{digest
},
3728 running
=> $running,
3729 description
=> "You are here!",
3731 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3733 push @$res, $current;
3738 __PACKAGE__-
>register_method({
3740 path
=> '{vmid}/snapshot',
3744 description
=> "Snapshot a VM.",
3746 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3749 additionalProperties
=> 0,
3751 node
=> get_standard_option
('pve-node'),
3752 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3753 snapname
=> get_standard_option
('pve-snapshot-name'),
3757 description
=> "Save the vmstate",
3762 description
=> "A textual description or comment.",
3768 description
=> "the task ID.",
3773 my $rpcenv = PVE
::RPCEnvironment
::get
();
3775 my $authuser = $rpcenv->get_user();
3777 my $node = extract_param
($param, 'node');
3779 my $vmid = extract_param
($param, 'vmid');
3781 my $snapname = extract_param
($param, 'snapname');
3783 die "unable to use snapshot name 'current' (reserved name)\n"
3784 if $snapname eq 'current';
3786 die "unable to use snapshot name 'pending' (reserved name)\n"
3787 if lc($snapname) eq 'pending';
3790 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3791 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3792 $param->{description
});
3795 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3798 __PACKAGE__-
>register_method({
3799 name
=> 'snapshot_cmd_idx',
3800 path
=> '{vmid}/snapshot/{snapname}',
3807 additionalProperties
=> 0,
3809 vmid
=> get_standard_option
('pve-vmid'),
3810 node
=> get_standard_option
('pve-node'),
3811 snapname
=> get_standard_option
('pve-snapshot-name'),
3820 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3827 push @$res, { cmd
=> 'rollback' };
3828 push @$res, { cmd
=> 'config' };
3833 __PACKAGE__-
>register_method({
3834 name
=> 'update_snapshot_config',
3835 path
=> '{vmid}/snapshot/{snapname}/config',
3839 description
=> "Update snapshot metadata.",
3841 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3844 additionalProperties
=> 0,
3846 node
=> get_standard_option
('pve-node'),
3847 vmid
=> get_standard_option
('pve-vmid'),
3848 snapname
=> get_standard_option
('pve-snapshot-name'),
3852 description
=> "A textual description or comment.",
3856 returns
=> { type
=> 'null' },
3860 my $rpcenv = PVE
::RPCEnvironment
::get
();
3862 my $authuser = $rpcenv->get_user();
3864 my $vmid = extract_param
($param, 'vmid');
3866 my $snapname = extract_param
($param, 'snapname');
3868 return undef if !defined($param->{description
});
3870 my $updatefn = sub {
3872 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3874 PVE
::QemuConfig-
>check_lock($conf);
3876 my $snap = $conf->{snapshots
}->{$snapname};
3878 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3880 $snap->{description
} = $param->{description
} if defined($param->{description
});
3882 PVE
::QemuConfig-
>write_config($vmid, $conf);
3885 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3890 __PACKAGE__-
>register_method({
3891 name
=> 'get_snapshot_config',
3892 path
=> '{vmid}/snapshot/{snapname}/config',
3895 description
=> "Get snapshot configuration",
3897 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
3900 additionalProperties
=> 0,
3902 node
=> get_standard_option
('pve-node'),
3903 vmid
=> get_standard_option
('pve-vmid'),
3904 snapname
=> get_standard_option
('pve-snapshot-name'),
3907 returns
=> { type
=> "object" },
3911 my $rpcenv = PVE
::RPCEnvironment
::get
();
3913 my $authuser = $rpcenv->get_user();
3915 my $vmid = extract_param
($param, 'vmid');
3917 my $snapname = extract_param
($param, 'snapname');
3919 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3921 my $snap = $conf->{snapshots
}->{$snapname};
3923 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3928 __PACKAGE__-
>register_method({
3930 path
=> '{vmid}/snapshot/{snapname}/rollback',
3934 description
=> "Rollback VM state to specified snapshot.",
3936 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3939 additionalProperties
=> 0,
3941 node
=> get_standard_option
('pve-node'),
3942 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3943 snapname
=> get_standard_option
('pve-snapshot-name'),
3948 description
=> "the task ID.",
3953 my $rpcenv = PVE
::RPCEnvironment
::get
();
3955 my $authuser = $rpcenv->get_user();
3957 my $node = extract_param
($param, 'node');
3959 my $vmid = extract_param
($param, 'vmid');
3961 my $snapname = extract_param
($param, 'snapname');
3964 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3965 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3969 # hold migration lock, this makes sure that nobody create replication snapshots
3970 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3973 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3976 __PACKAGE__-
>register_method({
3977 name
=> 'delsnapshot',
3978 path
=> '{vmid}/snapshot/{snapname}',
3982 description
=> "Delete a VM snapshot.",
3984 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3987 additionalProperties
=> 0,
3989 node
=> get_standard_option
('pve-node'),
3990 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3991 snapname
=> get_standard_option
('pve-snapshot-name'),
3995 description
=> "For removal from config file, even if removing disk snapshots fails.",
4001 description
=> "the task ID.",
4006 my $rpcenv = PVE
::RPCEnvironment
::get
();
4008 my $authuser = $rpcenv->get_user();
4010 my $node = extract_param
($param, 'node');
4012 my $vmid = extract_param
($param, 'vmid');
4014 my $snapname = extract_param
($param, 'snapname');
4017 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
4018 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
4021 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
4024 __PACKAGE__-
>register_method({
4026 path
=> '{vmid}/template',
4030 description
=> "Create a Template.",
4032 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4033 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4036 additionalProperties
=> 0,
4038 node
=> get_standard_option
('pve-node'),
4039 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4043 description
=> "If you want to convert only 1 disk to base image.",
4044 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4049 returns
=> { type
=> 'null'},
4053 my $rpcenv = PVE
::RPCEnvironment
::get
();
4055 my $authuser = $rpcenv->get_user();
4057 my $node = extract_param
($param, 'node');
4059 my $vmid = extract_param
($param, 'vmid');
4061 my $disk = extract_param
($param, 'disk');
4063 my $updatefn = sub {
4065 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4067 PVE
::QemuConfig-
>check_lock($conf);
4069 die "unable to create template, because VM contains snapshots\n"
4070 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4072 die "you can't convert a template to a template\n"
4073 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4075 die "you can't convert a VM to template if VM is running\n"
4076 if PVE
::QemuServer
::check_running
($vmid);
4079 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4082 $conf->{template
} = 1;
4083 PVE
::QemuConfig-
>write_config($vmid, $conf);
4085 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4088 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4092 __PACKAGE__-
>register_method({
4093 name
=> 'cloudinit_generated_config_dump',
4094 path
=> '{vmid}/cloudinit/dump',
4097 description
=> "Get automatically generated cloudinit config.",
4099 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4102 additionalProperties
=> 0,
4104 node
=> get_standard_option
('pve-node'),
4105 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4107 description
=> 'Config type.',
4109 enum
=> ['user', 'network', 'meta'],
4119 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4121 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});