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, 1);
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 my $migrate_opts = {
2118 migratedfrom
=> $migratedfrom,
2119 spice_ticket
=> $spice_ticket,
2120 network
=> $migration_network,
2121 type
=> $migration_type,
2122 targetstorage
=> $targetstorage,
2123 nbd_proto_version
=> $nbd_protocol_version,
2124 replicated_volumes
=> $replicated_volumes,
2128 statefile
=> $stateuri,
2129 skiplock
=> $skiplock,
2130 forcemachine
=> $machine,
2131 timeout
=> $timeout,
2134 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $params, $migrate_opts);
2138 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2142 __PACKAGE__-
>register_method({
2144 path
=> '{vmid}/status/stop',
2148 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2149 "is akin to pulling the power plug of a running computer and may damage the VM data",
2151 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2154 additionalProperties
=> 0,
2156 node
=> get_standard_option
('pve-node'),
2157 vmid
=> get_standard_option
('pve-vmid',
2158 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2159 skiplock
=> get_standard_option
('skiplock'),
2160 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2162 description
=> "Wait maximal timeout seconds.",
2168 description
=> "Do not deactivate storage volumes.",
2181 my $rpcenv = PVE
::RPCEnvironment
::get
();
2182 my $authuser = $rpcenv->get_user();
2184 my $node = extract_param
($param, 'node');
2185 my $vmid = extract_param
($param, 'vmid');
2187 my $skiplock = extract_param
($param, 'skiplock');
2188 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2189 if $skiplock && $authuser ne 'root@pam';
2191 my $keepActive = extract_param
($param, 'keepActive');
2192 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2193 if $keepActive && $authuser ne 'root@pam';
2195 my $migratedfrom = extract_param
($param, 'migratedfrom');
2196 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2197 if $migratedfrom && $authuser ne 'root@pam';
2200 my $storecfg = PVE
::Storage
::config
();
2202 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2207 print "Requesting HA stop for VM $vmid\n";
2209 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2210 PVE
::Tools
::run_command
($cmd);
2214 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2220 syslog
('info', "stop VM $vmid: $upid\n");
2222 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2223 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2227 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2231 __PACKAGE__-
>register_method({
2233 path
=> '{vmid}/status/reset',
2237 description
=> "Reset virtual machine.",
2239 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2242 additionalProperties
=> 0,
2244 node
=> get_standard_option
('pve-node'),
2245 vmid
=> get_standard_option
('pve-vmid',
2246 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2247 skiplock
=> get_standard_option
('skiplock'),
2256 my $rpcenv = PVE
::RPCEnvironment
::get
();
2258 my $authuser = $rpcenv->get_user();
2260 my $node = extract_param
($param, 'node');
2262 my $vmid = extract_param
($param, 'vmid');
2264 my $skiplock = extract_param
($param, 'skiplock');
2265 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2266 if $skiplock && $authuser ne 'root@pam';
2268 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2273 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2278 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2281 __PACKAGE__-
>register_method({
2282 name
=> 'vm_shutdown',
2283 path
=> '{vmid}/status/shutdown',
2287 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2288 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2290 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2293 additionalProperties
=> 0,
2295 node
=> get_standard_option
('pve-node'),
2296 vmid
=> get_standard_option
('pve-vmid',
2297 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2298 skiplock
=> get_standard_option
('skiplock'),
2300 description
=> "Wait maximal timeout seconds.",
2306 description
=> "Make sure the VM stops.",
2312 description
=> "Do not deactivate storage volumes.",
2325 my $rpcenv = PVE
::RPCEnvironment
::get
();
2326 my $authuser = $rpcenv->get_user();
2328 my $node = extract_param
($param, 'node');
2329 my $vmid = extract_param
($param, 'vmid');
2331 my $skiplock = extract_param
($param, 'skiplock');
2332 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2333 if $skiplock && $authuser ne 'root@pam';
2335 my $keepActive = extract_param
($param, 'keepActive');
2336 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2337 if $keepActive && $authuser ne 'root@pam';
2339 my $storecfg = PVE
::Storage
::config
();
2343 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2344 # otherwise, we will infer a shutdown command, but run into the timeout,
2345 # then when the vm is resumed, it will instantly shutdown
2347 # checking the qmp status here to get feedback to the gui/cli/api
2348 # and the status query should not take too long
2349 my $qmpstatus = eval {
2350 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2351 mon_cmd
($vmid, "query-status");
2355 if (!$err && $qmpstatus->{status
} eq "paused") {
2356 if ($param->{forceStop
}) {
2357 warn "VM is paused - stop instead of shutdown\n";
2360 die "VM is paused - cannot shutdown\n";
2364 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2366 my $timeout = $param->{timeout
} // 60;
2370 print "Requesting HA stop for VM $vmid\n";
2372 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2373 PVE
::Tools
::run_command
($cmd);
2377 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2384 syslog
('info', "shutdown VM $vmid: $upid\n");
2386 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2387 $shutdown, $param->{forceStop
}, $keepActive);
2391 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2395 __PACKAGE__-
>register_method({
2396 name
=> 'vm_reboot',
2397 path
=> '{vmid}/status/reboot',
2401 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2403 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2406 additionalProperties
=> 0,
2408 node
=> get_standard_option
('pve-node'),
2409 vmid
=> get_standard_option
('pve-vmid',
2410 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2412 description
=> "Wait maximal timeout seconds for the shutdown.",
2425 my $rpcenv = PVE
::RPCEnvironment
::get
();
2426 my $authuser = $rpcenv->get_user();
2428 my $node = extract_param
($param, 'node');
2429 my $vmid = extract_param
($param, 'vmid');
2431 my $qmpstatus = eval {
2432 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2433 mon_cmd
($vmid, "query-status");
2437 if (!$err && $qmpstatus->{status
} eq "paused") {
2438 die "VM is paused - cannot shutdown\n";
2441 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2446 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2447 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2451 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2454 __PACKAGE__-
>register_method({
2455 name
=> 'vm_suspend',
2456 path
=> '{vmid}/status/suspend',
2460 description
=> "Suspend virtual machine.",
2462 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2463 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2464 " on the storage for the vmstate.",
2465 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2468 additionalProperties
=> 0,
2470 node
=> get_standard_option
('pve-node'),
2471 vmid
=> get_standard_option
('pve-vmid',
2472 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2473 skiplock
=> get_standard_option
('skiplock'),
2478 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2480 statestorage
=> get_standard_option
('pve-storage-id', {
2481 description
=> "The storage for the VM state",
2482 requires
=> 'todisk',
2484 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2494 my $rpcenv = PVE
::RPCEnvironment
::get
();
2495 my $authuser = $rpcenv->get_user();
2497 my $node = extract_param
($param, 'node');
2498 my $vmid = extract_param
($param, 'vmid');
2500 my $todisk = extract_param
($param, 'todisk') // 0;
2502 my $statestorage = extract_param
($param, 'statestorage');
2504 my $skiplock = extract_param
($param, 'skiplock');
2505 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2506 if $skiplock && $authuser ne 'root@pam';
2508 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2510 die "Cannot suspend HA managed VM to disk\n"
2511 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2513 # early check for storage permission, for better user feedback
2515 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2517 if (!$statestorage) {
2518 # get statestorage from config if none is given
2519 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2520 my $storecfg = PVE
::Storage
::config
();
2521 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2524 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2530 syslog
('info', "suspend VM $vmid: $upid\n");
2532 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2537 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2538 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2541 __PACKAGE__-
>register_method({
2542 name
=> 'vm_resume',
2543 path
=> '{vmid}/status/resume',
2547 description
=> "Resume virtual machine.",
2549 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2552 additionalProperties
=> 0,
2554 node
=> get_standard_option
('pve-node'),
2555 vmid
=> get_standard_option
('pve-vmid',
2556 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2557 skiplock
=> get_standard_option
('skiplock'),
2558 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2568 my $rpcenv = PVE
::RPCEnvironment
::get
();
2570 my $authuser = $rpcenv->get_user();
2572 my $node = extract_param
($param, 'node');
2574 my $vmid = extract_param
($param, 'vmid');
2576 my $skiplock = extract_param
($param, 'skiplock');
2577 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2578 if $skiplock && $authuser ne 'root@pam';
2580 my $nocheck = extract_param
($param, 'nocheck');
2582 my $to_disk_suspended;
2584 PVE
::QemuConfig-
>lock_config($vmid, sub {
2585 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2586 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2590 die "VM $vmid not running\n"
2591 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2596 syslog
('info', "resume VM $vmid: $upid\n");
2598 if (!$to_disk_suspended) {
2599 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2601 my $storecfg = PVE
::Storage
::config
();
2602 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
2608 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2611 __PACKAGE__-
>register_method({
2612 name
=> 'vm_sendkey',
2613 path
=> '{vmid}/sendkey',
2617 description
=> "Send key event to virtual machine.",
2619 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2622 additionalProperties
=> 0,
2624 node
=> get_standard_option
('pve-node'),
2625 vmid
=> get_standard_option
('pve-vmid',
2626 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2627 skiplock
=> get_standard_option
('skiplock'),
2629 description
=> "The key (qemu monitor encoding).",
2634 returns
=> { type
=> 'null'},
2638 my $rpcenv = PVE
::RPCEnvironment
::get
();
2640 my $authuser = $rpcenv->get_user();
2642 my $node = extract_param
($param, 'node');
2644 my $vmid = extract_param
($param, 'vmid');
2646 my $skiplock = extract_param
($param, 'skiplock');
2647 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2648 if $skiplock && $authuser ne 'root@pam';
2650 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2655 __PACKAGE__-
>register_method({
2656 name
=> 'vm_feature',
2657 path
=> '{vmid}/feature',
2661 description
=> "Check if feature for virtual machine is available.",
2663 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2666 additionalProperties
=> 0,
2668 node
=> get_standard_option
('pve-node'),
2669 vmid
=> get_standard_option
('pve-vmid'),
2671 description
=> "Feature to check.",
2673 enum
=> [ 'snapshot', 'clone', 'copy' ],
2675 snapname
=> get_standard_option
('pve-snapshot-name', {
2683 hasFeature
=> { type
=> 'boolean' },
2686 items
=> { type
=> 'string' },
2693 my $node = extract_param
($param, 'node');
2695 my $vmid = extract_param
($param, 'vmid');
2697 my $snapname = extract_param
($param, 'snapname');
2699 my $feature = extract_param
($param, 'feature');
2701 my $running = PVE
::QemuServer
::check_running
($vmid);
2703 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2706 my $snap = $conf->{snapshots
}->{$snapname};
2707 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2710 my $storecfg = PVE
::Storage
::config
();
2712 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2713 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2716 hasFeature
=> $hasFeature,
2717 nodes
=> [ keys %$nodelist ],
2721 __PACKAGE__-
>register_method({
2723 path
=> '{vmid}/clone',
2727 description
=> "Create a copy of virtual machine/template.",
2729 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2730 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2731 "'Datastore.AllocateSpace' on any used storage.",
2734 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2736 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2737 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2742 additionalProperties
=> 0,
2744 node
=> get_standard_option
('pve-node'),
2745 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2746 newid
=> get_standard_option
('pve-vmid', {
2747 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2748 description
=> 'VMID for the clone.' }),
2751 type
=> 'string', format
=> 'dns-name',
2752 description
=> "Set a name for the new VM.",
2757 description
=> "Description for the new VM.",
2761 type
=> 'string', format
=> 'pve-poolid',
2762 description
=> "Add the new VM to the specified pool.",
2764 snapname
=> get_standard_option
('pve-snapshot-name', {
2767 storage
=> get_standard_option
('pve-storage-id', {
2768 description
=> "Target storage for full clone.",
2772 description
=> "Target format for file storage. Only valid for full clone.",
2775 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2780 description
=> "Create a full copy of all disks. This is always done when " .
2781 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2783 target
=> get_standard_option
('pve-node', {
2784 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2788 description
=> "Override I/O bandwidth limit (in KiB/s).",
2792 default => 'clone limit from datacenter or storage config',
2802 my $rpcenv = PVE
::RPCEnvironment
::get
();
2803 my $authuser = $rpcenv->get_user();
2805 my $node = extract_param
($param, 'node');
2806 my $vmid = extract_param
($param, 'vmid');
2807 my $newid = extract_param
($param, 'newid');
2808 my $pool = extract_param
($param, 'pool');
2809 $rpcenv->check_pool_exist($pool) if defined($pool);
2811 my $snapname = extract_param
($param, 'snapname');
2812 my $storage = extract_param
($param, 'storage');
2813 my $format = extract_param
($param, 'format');
2814 my $target = extract_param
($param, 'target');
2816 my $localnode = PVE
::INotify
::nodename
();
2818 if ($target && ($target eq $localnode || $target eq 'localhost')) {
2822 PVE
::Cluster
::check_node_exists
($target) if $target;
2824 my $storecfg = PVE
::Storage
::config
();
2827 # check if storage is enabled on local node
2828 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2830 # check if storage is available on target node
2831 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2832 # clone only works if target storage is shared
2833 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2834 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2838 PVE
::Cluster
::check_cfs_quorum
();
2840 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2842 # exclusive lock if VM is running - else shared lock is enough;
2843 my $shared_lock = $running ?
0 : 1;
2846 # do all tests after lock but before forking worker - if possible
2848 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2849 PVE
::QemuConfig-
>check_lock($conf);
2851 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2852 die "unexpected state change\n" if $verify_running != $running;
2854 die "snapshot '$snapname' does not exist\n"
2855 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2857 my $full = extract_param
($param, 'full') // !PVE
::QemuConfig-
>is_template($conf);
2859 die "parameter 'storage' not allowed for linked clones\n"
2860 if defined($storage) && !$full;
2862 die "parameter 'format' not allowed for linked clones\n"
2863 if defined($format) && !$full;
2865 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2867 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2869 die "can't clone VM to node '$target' (VM uses local storage)\n"
2870 if $target && !$sharedvm;
2872 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2873 die "unable to create VM $newid: config file already exists\n"
2876 my $newconf = { lock => 'clone' };
2881 foreach my $opt (keys %$oldconf) {
2882 my $value = $oldconf->{$opt};
2884 # do not copy snapshot related info
2885 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2886 $opt eq 'vmstate' || $opt eq 'snapstate';
2888 # no need to copy unused images, because VMID(owner) changes anyways
2889 next if $opt =~ m/^unused\d+$/;
2891 # always change MAC! address
2892 if ($opt =~ m/^net(\d+)$/) {
2893 my $net = PVE
::QemuServer
::parse_net
($value);
2894 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2895 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2896 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2897 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2898 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2899 die "unable to parse drive options for '$opt'\n" if !$drive;
2900 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2901 $newconf->{$opt} = $value; # simply copy configuration
2903 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2904 die "Full clone feature is not supported for drive '$opt'\n"
2905 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2906 $fullclone->{$opt} = 1;
2908 # not full means clone instead of copy
2909 die "Linked clone feature is not supported for drive '$opt'\n"
2910 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2912 $drives->{$opt} = $drive;
2913 push @$vollist, $drive->{file
};
2916 # copy everything else
2917 $newconf->{$opt} = $value;
2921 # auto generate a new uuid
2922 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2923 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2924 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2925 # auto generate a new vmgenid only if the option was set for template
2926 if ($newconf->{vmgenid
}) {
2927 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2930 delete $newconf->{template
};
2932 if ($param->{name
}) {
2933 $newconf->{name
} = $param->{name
};
2935 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
2938 if ($param->{description
}) {
2939 $newconf->{description
} = $param->{description
};
2942 # create empty/temp config - this fails if VM already exists on other node
2943 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
2944 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2949 my $newvollist = [];
2956 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2958 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2960 my $bwlimit = extract_param
($param, 'bwlimit');
2962 my $total_jobs = scalar(keys %{$drives});
2965 foreach my $opt (keys %$drives) {
2966 my $drive = $drives->{$opt};
2967 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2968 my $completion = $skipcomplete ?
'skip' : 'complete';
2970 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
2971 my $storage_list = [ $src_sid ];
2972 push @$storage_list, $storage if defined($storage);
2973 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
2975 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2976 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2977 $jobs, $completion, $oldconf->{agent
}, $clonelimit, $oldconf);
2979 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
2981 PVE
::QemuConfig-
>write_config($newid, $newconf);
2985 delete $newconf->{lock};
2987 # do not write pending changes
2988 if (my @changes = keys %{$newconf->{pending
}}) {
2989 my $pending = join(',', @changes);
2990 warn "found pending changes for '$pending', discarding for clone\n";
2991 delete $newconf->{pending
};
2994 PVE
::QemuConfig-
>write_config($newid, $newconf);
2997 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2998 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2999 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3001 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3002 die "Failed to move config to node '$target' - rename failed: $!\n"
3003 if !rename($conffile, $newconffile);
3006 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3009 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3010 sleep 1; # some storage like rbd need to wait before release volume - really?
3012 foreach my $volid (@$newvollist) {
3013 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3017 PVE
::Firewall
::remove_vmfw_conf
($newid);
3019 unlink $conffile; # avoid races -> last thing before die
3021 die "clone failed: $err";
3027 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3029 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
3032 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
3033 # Aquire exclusive lock lock for $newid
3034 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3039 __PACKAGE__-
>register_method({
3040 name
=> 'move_vm_disk',
3041 path
=> '{vmid}/move_disk',
3045 description
=> "Move volume to different storage.",
3047 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
3049 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3050 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
3054 additionalProperties
=> 0,
3056 node
=> get_standard_option
('pve-node'),
3057 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3060 description
=> "The disk you want to move.",
3061 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3063 storage
=> get_standard_option
('pve-storage-id', {
3064 description
=> "Target storage.",
3065 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3069 description
=> "Target Format.",
3070 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3075 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3081 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3086 description
=> "Override I/O bandwidth limit (in KiB/s).",
3090 default => 'move limit from datacenter or storage config',
3096 description
=> "the task ID.",
3101 my $rpcenv = PVE
::RPCEnvironment
::get
();
3102 my $authuser = $rpcenv->get_user();
3104 my $node = extract_param
($param, 'node');
3105 my $vmid = extract_param
($param, 'vmid');
3106 my $digest = extract_param
($param, 'digest');
3107 my $disk = extract_param
($param, 'disk');
3108 my $storeid = extract_param
($param, 'storage');
3109 my $format = extract_param
($param, 'format');
3111 my $storecfg = PVE
::Storage
::config
();
3113 my $updatefn = sub {
3114 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3115 PVE
::QemuConfig-
>check_lock($conf);
3117 die "VM config checksum missmatch (file change by other user?)\n"
3118 if $digest && $digest ne $conf->{digest
};
3120 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3122 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3124 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3125 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3127 my $old_volid = $drive->{file
};
3129 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3130 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3134 die "you can't move to the same storage with same format\n" if $oldstoreid eq $storeid &&
3135 (!$format || !$oldfmt || $oldfmt eq $format);
3137 # this only checks snapshots because $disk is passed!
3138 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3139 die "you can't move a disk with snapshots and delete the source\n"
3140 if $snapshotted && $param->{delete};
3142 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3144 my $running = PVE
::QemuServer
::check_running
($vmid);
3146 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3149 my $newvollist = [];
3155 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3157 warn "moving disk with snapshots, snapshots will not be moved!\n"
3160 my $bwlimit = extract_param
($param, 'bwlimit');
3161 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3163 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3164 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit, $conf);
3166 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3168 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3170 # convert moved disk to base if part of template
3171 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3172 if PVE
::QemuConfig-
>is_template($conf);
3174 PVE
::QemuConfig-
>write_config($vmid, $conf);
3176 my $do_trim = PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
};
3177 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3178 eval { mon_cmd
($vmid, "guest-fstrim") };
3182 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3183 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3189 foreach my $volid (@$newvollist) {
3190 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3193 die "storage migration failed: $err";
3196 if ($param->{delete}) {
3198 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3199 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3205 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3208 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3211 my $check_vm_disks_local = sub {
3212 my ($storecfg, $vmconf, $vmid) = @_;
3214 my $local_disks = {};
3216 # add some more information to the disks e.g. cdrom
3217 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3218 my ($volid, $attr) = @_;
3220 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3222 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3223 return if $scfg->{shared
};
3225 # The shared attr here is just a special case where the vdisk
3226 # is marked as shared manually
3227 return if $attr->{shared
};
3228 return if $attr->{cdrom
} and $volid eq "none";
3230 if (exists $local_disks->{$volid}) {
3231 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3233 $local_disks->{$volid} = $attr;
3234 # ensure volid is present in case it's needed
3235 $local_disks->{$volid}->{volid
} = $volid;
3239 return $local_disks;
3242 __PACKAGE__-
>register_method({
3243 name
=> 'migrate_vm_precondition',
3244 path
=> '{vmid}/migrate',
3248 description
=> "Get preconditions for migration.",
3250 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3253 additionalProperties
=> 0,
3255 node
=> get_standard_option
('pve-node'),
3256 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3257 target
=> get_standard_option
('pve-node', {
3258 description
=> "Target node.",
3259 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3267 running
=> { type
=> 'boolean' },
3271 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3273 not_allowed_nodes
=> {
3276 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3280 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3282 local_resources
=> {
3284 description
=> "List local resources e.g. pci, usb"
3291 my $rpcenv = PVE
::RPCEnvironment
::get
();
3293 my $authuser = $rpcenv->get_user();
3295 PVE
::Cluster
::check_cfs_quorum
();
3299 my $vmid = extract_param
($param, 'vmid');
3300 my $target = extract_param
($param, 'target');
3301 my $localnode = PVE
::INotify
::nodename
();
3305 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3306 my $storecfg = PVE
::Storage
::config
();
3309 # try to detect errors early
3310 PVE
::QemuConfig-
>check_lock($vmconf);
3312 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3314 # if vm is not running, return target nodes where local storage is available
3315 # for offline migration
3316 if (!$res->{running
}) {
3317 $res->{allowed_nodes
} = [];
3318 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3319 delete $checked_nodes->{$localnode};
3321 foreach my $node (keys %$checked_nodes) {
3322 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3323 push @{$res->{allowed_nodes
}}, $node;
3327 $res->{not_allowed_nodes
} = $checked_nodes;
3331 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3332 $res->{local_disks
} = [ values %$local_disks ];;
3334 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3336 $res->{local_resources
} = $local_resources;
3343 __PACKAGE__-
>register_method({
3344 name
=> 'migrate_vm',
3345 path
=> '{vmid}/migrate',
3349 description
=> "Migrate virtual machine. Creates a new migration task.",
3351 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3354 additionalProperties
=> 0,
3356 node
=> get_standard_option
('pve-node'),
3357 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3358 target
=> get_standard_option
('pve-node', {
3359 description
=> "Target node.",
3360 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3364 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3369 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3374 enum
=> ['secure', 'insecure'],
3375 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3378 migration_network
=> {
3379 type
=> 'string', format
=> 'CIDR',
3380 description
=> "CIDR of the (sub) network that is used for migration.",
3383 "with-local-disks" => {
3385 description
=> "Enable live storage migration for local disk",
3388 targetstorage
=> get_standard_option
('pve-storage-id', {
3389 description
=> "Default target storage.",
3391 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3394 description
=> "Override I/O bandwidth limit (in KiB/s).",
3398 default => 'migrate limit from datacenter or storage config',
3404 description
=> "the task ID.",
3409 my $rpcenv = PVE
::RPCEnvironment
::get
();
3410 my $authuser = $rpcenv->get_user();
3412 my $target = extract_param
($param, 'target');
3414 my $localnode = PVE
::INotify
::nodename
();
3415 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3417 PVE
::Cluster
::check_cfs_quorum
();
3419 PVE
::Cluster
::check_node_exists
($target);
3421 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3423 my $vmid = extract_param
($param, 'vmid');
3425 raise_param_exc
({ force
=> "Only root may use this option." })
3426 if $param->{force
} && $authuser ne 'root@pam';
3428 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3429 if $param->{migration_type
} && $authuser ne 'root@pam';
3431 # allow root only until better network permissions are available
3432 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3433 if $param->{migration_network
} && $authuser ne 'root@pam';
3436 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3438 # try to detect errors early
3440 PVE
::QemuConfig-
>check_lock($conf);
3442 if (PVE
::QemuServer
::check_running
($vmid)) {
3443 die "can't migrate running VM without --online\n" if !$param->{online
};
3445 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
3446 $param->{online
} = 0;
3449 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3450 if !$param->{online
} && $param->{targetstorage
};
3452 my $storecfg = PVE
::Storage
::config
();
3454 if( $param->{targetstorage
}) {
3455 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3457 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3460 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3465 print "Requesting HA migration for VM $vmid to node $target\n";
3467 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3468 PVE
::Tools
::run_command
($cmd);
3472 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3477 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3481 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3484 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3489 __PACKAGE__-
>register_method({
3491 path
=> '{vmid}/monitor',
3495 description
=> "Execute Qemu monitor commands.",
3497 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3498 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3501 additionalProperties
=> 0,
3503 node
=> get_standard_option
('pve-node'),
3504 vmid
=> get_standard_option
('pve-vmid'),
3507 description
=> "The monitor command.",
3511 returns
=> { type
=> 'string'},
3515 my $rpcenv = PVE
::RPCEnvironment
::get
();
3516 my $authuser = $rpcenv->get_user();
3519 my $command = shift;
3520 return $command =~ m/^\s*info(\s+|$)/
3521 || $command =~ m/^\s*help\s*$/;
3524 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3525 if !&$is_ro($param->{command
});
3527 my $vmid = $param->{vmid
};
3529 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3533 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3535 $res = "ERROR: $@" if $@;
3540 __PACKAGE__-
>register_method({
3541 name
=> 'resize_vm',
3542 path
=> '{vmid}/resize',
3546 description
=> "Extend volume size.",
3548 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3551 additionalProperties
=> 0,
3553 node
=> get_standard_option
('pve-node'),
3554 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3555 skiplock
=> get_standard_option
('skiplock'),
3558 description
=> "The disk you want to resize.",
3559 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3563 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3564 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.",
3568 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3574 returns
=> { type
=> 'null'},
3578 my $rpcenv = PVE
::RPCEnvironment
::get
();
3580 my $authuser = $rpcenv->get_user();
3582 my $node = extract_param
($param, 'node');
3584 my $vmid = extract_param
($param, 'vmid');
3586 my $digest = extract_param
($param, 'digest');
3588 my $disk = extract_param
($param, 'disk');
3590 my $sizestr = extract_param
($param, 'size');
3592 my $skiplock = extract_param
($param, 'skiplock');
3593 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3594 if $skiplock && $authuser ne 'root@pam';
3596 my $storecfg = PVE
::Storage
::config
();
3598 my $updatefn = sub {
3600 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3602 die "checksum missmatch (file change by other user?)\n"
3603 if $digest && $digest ne $conf->{digest
};
3604 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3606 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3608 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3610 my (undef, undef, undef, undef, undef, undef, $format) =
3611 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3613 die "can't resize volume: $disk if snapshot exists\n"
3614 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3616 my $volid = $drive->{file
};
3618 die "disk '$disk' has no associated volume\n" if !$volid;
3620 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3622 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3624 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3626 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3627 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3629 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3631 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3632 my ($ext, $newsize, $unit) = ($1, $2, $4);
3635 $newsize = $newsize * 1024;
3636 } elsif ($unit eq 'M') {
3637 $newsize = $newsize * 1024 * 1024;
3638 } elsif ($unit eq 'G') {
3639 $newsize = $newsize * 1024 * 1024 * 1024;
3640 } elsif ($unit eq 'T') {
3641 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3644 $newsize += $size if $ext;
3645 $newsize = int($newsize);
3647 die "shrinking disks is not supported\n" if $newsize < $size;
3649 return if $size == $newsize;
3651 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3653 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3655 my $effective_size = eval { PVE
::Storage
::volume_size_info
($storecfg, $volid, 3); };
3656 $drive->{size
} = $effective_size // $newsize;
3657 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
3659 PVE
::QemuConfig-
>write_config($vmid, $conf);
3662 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3666 __PACKAGE__-
>register_method({
3667 name
=> 'snapshot_list',
3668 path
=> '{vmid}/snapshot',
3670 description
=> "List all snapshots.",
3672 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3675 protected
=> 1, # qemu pid files are only readable by root
3677 additionalProperties
=> 0,
3679 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3680 node
=> get_standard_option
('pve-node'),
3689 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3693 description
=> "Snapshot includes RAM.",
3698 description
=> "Snapshot description.",
3702 description
=> "Snapshot creation time",
3704 renderer
=> 'timestamp',
3708 description
=> "Parent snapshot identifier.",
3714 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3719 my $vmid = $param->{vmid
};
3721 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3722 my $snaphash = $conf->{snapshots
} || {};
3726 foreach my $name (keys %$snaphash) {
3727 my $d = $snaphash->{$name};
3730 snaptime
=> $d->{snaptime
} || 0,
3731 vmstate
=> $d->{vmstate
} ?
1 : 0,
3732 description
=> $d->{description
} || '',
3734 $item->{parent
} = $d->{parent
} if $d->{parent
};
3735 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3739 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3742 digest
=> $conf->{digest
},
3743 running
=> $running,
3744 description
=> "You are here!",
3746 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3748 push @$res, $current;
3753 __PACKAGE__-
>register_method({
3755 path
=> '{vmid}/snapshot',
3759 description
=> "Snapshot a VM.",
3761 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3764 additionalProperties
=> 0,
3766 node
=> get_standard_option
('pve-node'),
3767 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3768 snapname
=> get_standard_option
('pve-snapshot-name'),
3772 description
=> "Save the vmstate",
3777 description
=> "A textual description or comment.",
3783 description
=> "the task ID.",
3788 my $rpcenv = PVE
::RPCEnvironment
::get
();
3790 my $authuser = $rpcenv->get_user();
3792 my $node = extract_param
($param, 'node');
3794 my $vmid = extract_param
($param, 'vmid');
3796 my $snapname = extract_param
($param, 'snapname');
3798 die "unable to use snapshot name 'current' (reserved name)\n"
3799 if $snapname eq 'current';
3801 die "unable to use snapshot name 'pending' (reserved name)\n"
3802 if lc($snapname) eq 'pending';
3805 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3806 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3807 $param->{description
});
3810 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3813 __PACKAGE__-
>register_method({
3814 name
=> 'snapshot_cmd_idx',
3815 path
=> '{vmid}/snapshot/{snapname}',
3822 additionalProperties
=> 0,
3824 vmid
=> get_standard_option
('pve-vmid'),
3825 node
=> get_standard_option
('pve-node'),
3826 snapname
=> get_standard_option
('pve-snapshot-name'),
3835 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3842 push @$res, { cmd
=> 'rollback' };
3843 push @$res, { cmd
=> 'config' };
3848 __PACKAGE__-
>register_method({
3849 name
=> 'update_snapshot_config',
3850 path
=> '{vmid}/snapshot/{snapname}/config',
3854 description
=> "Update snapshot metadata.",
3856 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3859 additionalProperties
=> 0,
3861 node
=> get_standard_option
('pve-node'),
3862 vmid
=> get_standard_option
('pve-vmid'),
3863 snapname
=> get_standard_option
('pve-snapshot-name'),
3867 description
=> "A textual description or comment.",
3871 returns
=> { type
=> 'null' },
3875 my $rpcenv = PVE
::RPCEnvironment
::get
();
3877 my $authuser = $rpcenv->get_user();
3879 my $vmid = extract_param
($param, 'vmid');
3881 my $snapname = extract_param
($param, 'snapname');
3883 return undef if !defined($param->{description
});
3885 my $updatefn = sub {
3887 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3889 PVE
::QemuConfig-
>check_lock($conf);
3891 my $snap = $conf->{snapshots
}->{$snapname};
3893 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3895 $snap->{description
} = $param->{description
} if defined($param->{description
});
3897 PVE
::QemuConfig-
>write_config($vmid, $conf);
3900 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3905 __PACKAGE__-
>register_method({
3906 name
=> 'get_snapshot_config',
3907 path
=> '{vmid}/snapshot/{snapname}/config',
3910 description
=> "Get snapshot configuration",
3912 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
3915 additionalProperties
=> 0,
3917 node
=> get_standard_option
('pve-node'),
3918 vmid
=> get_standard_option
('pve-vmid'),
3919 snapname
=> get_standard_option
('pve-snapshot-name'),
3922 returns
=> { type
=> "object" },
3926 my $rpcenv = PVE
::RPCEnvironment
::get
();
3928 my $authuser = $rpcenv->get_user();
3930 my $vmid = extract_param
($param, 'vmid');
3932 my $snapname = extract_param
($param, 'snapname');
3934 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3936 my $snap = $conf->{snapshots
}->{$snapname};
3938 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3943 __PACKAGE__-
>register_method({
3945 path
=> '{vmid}/snapshot/{snapname}/rollback',
3949 description
=> "Rollback VM state to specified snapshot.",
3951 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3954 additionalProperties
=> 0,
3956 node
=> get_standard_option
('pve-node'),
3957 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3958 snapname
=> get_standard_option
('pve-snapshot-name'),
3963 description
=> "the task ID.",
3968 my $rpcenv = PVE
::RPCEnvironment
::get
();
3970 my $authuser = $rpcenv->get_user();
3972 my $node = extract_param
($param, 'node');
3974 my $vmid = extract_param
($param, 'vmid');
3976 my $snapname = extract_param
($param, 'snapname');
3979 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3980 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3984 # hold migration lock, this makes sure that nobody create replication snapshots
3985 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3988 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3991 __PACKAGE__-
>register_method({
3992 name
=> 'delsnapshot',
3993 path
=> '{vmid}/snapshot/{snapname}',
3997 description
=> "Delete a VM snapshot.",
3999 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4002 additionalProperties
=> 0,
4004 node
=> get_standard_option
('pve-node'),
4005 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4006 snapname
=> get_standard_option
('pve-snapshot-name'),
4010 description
=> "For removal from config file, even if removing disk snapshots fails.",
4016 description
=> "the task ID.",
4021 my $rpcenv = PVE
::RPCEnvironment
::get
();
4023 my $authuser = $rpcenv->get_user();
4025 my $node = extract_param
($param, 'node');
4027 my $vmid = extract_param
($param, 'vmid');
4029 my $snapname = extract_param
($param, 'snapname');
4032 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
4033 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
4036 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
4039 __PACKAGE__-
>register_method({
4041 path
=> '{vmid}/template',
4045 description
=> "Create a Template.",
4047 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4048 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4051 additionalProperties
=> 0,
4053 node
=> get_standard_option
('pve-node'),
4054 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4058 description
=> "If you want to convert only 1 disk to base image.",
4059 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4064 returns
=> { type
=> 'null'},
4068 my $rpcenv = PVE
::RPCEnvironment
::get
();
4070 my $authuser = $rpcenv->get_user();
4072 my $node = extract_param
($param, 'node');
4074 my $vmid = extract_param
($param, 'vmid');
4076 my $disk = extract_param
($param, 'disk');
4078 my $updatefn = sub {
4080 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4082 PVE
::QemuConfig-
>check_lock($conf);
4084 die "unable to create template, because VM contains snapshots\n"
4085 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4087 die "you can't convert a template to a template\n"
4088 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4090 die "you can't convert a VM to template if VM is running\n"
4091 if PVE
::QemuServer
::check_running
($vmid);
4094 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4097 $conf->{template
} = 1;
4098 PVE
::QemuConfig-
>write_config($vmid, $conf);
4100 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4103 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4107 __PACKAGE__-
>register_method({
4108 name
=> 'cloudinit_generated_config_dump',
4109 path
=> '{vmid}/cloudinit/dump',
4112 description
=> "Get automatically generated cloudinit config.",
4114 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4117 additionalProperties
=> 0,
4119 node
=> get_standard_option
('pve-node'),
4120 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4122 description
=> 'Config type.',
4124 enum
=> ['user', 'network', 'meta'],
4134 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4136 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});