1 package PVE
::API2
::Qemu
;
11 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
14 use PVE
::Tools
qw(extract_param);
15 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
17 use PVE
::JSONSchema
qw(get_standard_option);
19 use PVE
::ReplicationConfig
;
20 use PVE
::GuestHelpers
;
23 use PVE
::QemuServer
::Drive
;
24 use PVE
::QemuServer
::Monitor
qw(mon_cmd);
26 use PVE
::RPCEnvironment
;
27 use PVE
::AccessControl
;
31 use PVE
::API2
::Firewall
::VM
;
32 use PVE
::API2
::Qemu
::Agent
;
33 use PVE
::VZDump
::Plugin
;
34 use PVE
::DataCenterConfig
;
38 if (!$ENV{PVE_GENERATING_DOCS
}) {
39 require PVE
::HA
::Env
::PVE2
;
40 import PVE
::HA
::Env
::PVE2
;
41 require PVE
::HA
::Config
;
42 import PVE
::HA
::Config
;
46 use Data
::Dumper
; # fixme: remove
48 use base
qw(PVE::RESTHandler);
50 my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal.";
52 my $resolve_cdrom_alias = sub {
55 if (my $value = $param->{cdrom
}) {
56 $value .= ",media=cdrom" if $value !~ m/media=/;
57 $param->{ide2
} = $value;
58 delete $param->{cdrom
};
62 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
63 my $check_storage_access = sub {
64 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
66 PVE
::QemuServer
::foreach_drive
($settings, sub {
67 my ($ds, $drive) = @_;
69 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
71 my $volid = $drive->{file
};
72 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
74 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit' || (defined($volname) && $volname eq 'cloudinit'))) {
76 } elsif ($isCDROM && ($volid eq 'cdrom')) {
77 $rpcenv->check($authuser, "/", ['Sys.Console']);
78 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
79 my ($storeid, $size) = ($2 || $default_storage, $3);
80 die "no storage ID specified (and no default storage)\n" if !$storeid;
81 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
82 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
83 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
84 if !$scfg->{content
}->{images
};
86 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
90 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
91 if defined($settings->{vmstatestorage
});
94 my $check_storage_access_clone = sub {
95 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
99 PVE
::QemuServer
::foreach_drive
($conf, sub {
100 my ($ds, $drive) = @_;
102 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
104 my $volid = $drive->{file
};
106 return if !$volid || $volid eq 'none';
109 if ($volid eq 'cdrom') {
110 $rpcenv->check($authuser, "/", ['Sys.Console']);
112 # we simply allow access
113 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
114 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
115 $sharedvm = 0 if !$scfg->{shared
};
119 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
120 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
121 $sharedvm = 0 if !$scfg->{shared
};
123 $sid = $storage if $storage;
124 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
128 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
129 if defined($conf->{vmstatestorage
});
134 # Note: $pool is only needed when creating a VM, because pool permissions
135 # are automatically inherited if VM already exists inside a pool.
136 my $create_disks = sub {
137 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
144 my ($ds, $disk) = @_;
146 my $volid = $disk->{file
};
147 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
149 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
150 delete $disk->{size
};
151 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
152 } elsif (defined($volname) && $volname eq 'cloudinit') {
153 $storeid = $storeid // $default_storage;
154 die "no storage ID specified (and no default storage)\n" if !$storeid;
155 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
156 my $name = "vm-$vmid-cloudinit";
160 $fmt = $disk->{format
} // "qcow2";
163 $fmt = $disk->{format
} // "raw";
166 # Initial disk created with 4 MB and aligned to 4MB on regeneration
167 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
168 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
169 $disk->{file
} = $volid;
170 $disk->{media
} = 'cdrom';
171 push @$vollist, $volid;
172 delete $disk->{format
}; # no longer needed
173 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
174 } elsif ($volid =~ $NEW_DISK_RE) {
175 my ($storeid, $size) = ($2 || $default_storage, $3);
176 die "no storage ID specified (and no default storage)\n" if !$storeid;
177 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
178 my $fmt = $disk->{format
} || $defformat;
180 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
183 if ($ds eq 'efidisk0') {
184 ($volid, $size) = PVE
::QemuServer
::create_efidisk
($storecfg, $storeid, $vmid, $fmt, $arch);
186 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
188 push @$vollist, $volid;
189 $disk->{file
} = $volid;
190 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
191 delete $disk->{format
}; # no longer needed
192 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
195 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
197 my $volid_is_new = 1;
200 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
201 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
206 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
208 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
210 die "volume $volid does not exist\n" if !$size;
212 $disk->{size
} = $size;
215 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
219 eval { PVE
::QemuServer
::foreach_drive
($settings, $code); };
221 # free allocated images on error
223 syslog
('err', "VM $vmid creating disks failed");
224 foreach my $volid (@$vollist) {
225 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
231 # modify vm config if everything went well
232 foreach my $ds (keys %$res) {
233 $conf->{$ds} = $res->{$ds};
250 my $memoryoptions = {
256 my $hwtypeoptions = {
269 my $generaloptions = {
276 'migrate_downtime' => 1,
277 'migrate_speed' => 1,
290 my $vmpoweroptions = {
297 'vmstatestorage' => 1,
300 my $cloudinitoptions = {
310 my $check_vm_modify_config_perm = sub {
311 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
313 return 1 if $authuser eq 'root@pam';
315 foreach my $opt (@$key_list) {
316 # some checks (e.g., disk, serial port, usb) need to be done somewhere
317 # else, as there the permission can be value dependend
318 next if PVE
::QemuServer
::is_valid_drivename
($opt);
319 next if $opt eq 'cdrom';
320 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
323 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
324 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
325 } elsif ($memoryoptions->{$opt}) {
326 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
327 } elsif ($hwtypeoptions->{$opt}) {
328 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
329 } elsif ($generaloptions->{$opt}) {
330 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
331 # special case for startup since it changes host behaviour
332 if ($opt eq 'startup') {
333 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
335 } elsif ($vmpoweroptions->{$opt}) {
336 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
337 } elsif ($diskoptions->{$opt}) {
338 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
339 } elsif ($cloudinitoptions->{$opt} || ($opt =~ m/^(?:net|ipconfig)\d+$/)) {
340 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
341 } elsif ($opt eq 'vmstate') {
342 # the user needs Disk and PowerMgmt privileges to change the vmstate
343 # also needs privileges on the storage, that will be checked later
344 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt' ]);
346 # catches hostpci\d+, args, lock, etc.
347 # new options will be checked here
348 die "only root can set '$opt' config\n";
355 __PACKAGE__-
>register_method({
359 description
=> "Virtual machine index (per node).",
361 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
365 protected
=> 1, # qemu pid files are only readable by root
367 additionalProperties
=> 0,
369 node
=> get_standard_option
('pve-node'),
373 description
=> "Determine the full status of active VMs.",
381 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
383 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
388 my $rpcenv = PVE
::RPCEnvironment
::get
();
389 my $authuser = $rpcenv->get_user();
391 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
394 foreach my $vmid (keys %$vmstatus) {
395 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
397 my $data = $vmstatus->{$vmid};
404 my $parse_restore_archive = sub {
405 my ($storecfg, $archive) = @_;
407 my ($archive_storeid, $archive_volname) = PVE
::Storage
::parse_volume_id
($archive);
409 if (defined($archive_storeid)) {
410 my $scfg = PVE
::Storage
::storage_config
($storecfg, $archive_storeid);
411 if ($scfg->{type
} eq 'pbs') {
418 my $path = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
426 __PACKAGE__-
>register_method({
430 description
=> "Create or restore a virtual machine.",
432 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
433 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
434 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
435 user
=> 'all', # check inside
440 additionalProperties
=> 0,
441 properties
=> PVE
::QemuServer
::json_config_properties
(
443 node
=> get_standard_option
('pve-node'),
444 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
446 description
=> "The backup archive. Either the file system path to a .tar or .vma file (use '-' to pipe data from stdin) or a proxmox storage backup volume identifier.",
450 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
452 storage
=> get_standard_option
('pve-storage-id', {
453 description
=> "Default storage.",
455 completion
=> \
&PVE
::QemuServer
::complete_storage
,
460 description
=> "Allow to overwrite existing VM.",
461 requires
=> 'archive',
466 description
=> "Assign a unique random ethernet address.",
467 requires
=> 'archive',
471 type
=> 'string', format
=> 'pve-poolid',
472 description
=> "Add the VM to the specified pool.",
475 description
=> "Override I/O bandwidth limit (in KiB/s).",
479 default => 'restore limit from datacenter or storage config',
485 description
=> "Start VM after it was created successfully.",
495 my $rpcenv = PVE
::RPCEnvironment
::get
();
496 my $authuser = $rpcenv->get_user();
498 my $node = extract_param
($param, 'node');
499 my $vmid = extract_param
($param, 'vmid');
501 my $archive = extract_param
($param, 'archive');
502 my $is_restore = !!$archive;
504 my $bwlimit = extract_param
($param, 'bwlimit');
505 my $force = extract_param
($param, 'force');
506 my $pool = extract_param
($param, 'pool');
507 my $start_after_create = extract_param
($param, 'start');
508 my $storage = extract_param
($param, 'storage');
509 my $unique = extract_param
($param, 'unique');
511 if (defined(my $ssh_keys = $param->{sshkeys
})) {
512 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
513 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
516 PVE
::Cluster
::check_cfs_quorum
();
518 my $filename = PVE
::QemuConfig-
>config_file($vmid);
519 my $storecfg = PVE
::Storage
::config
();
521 if (defined($pool)) {
522 $rpcenv->check_pool_exist($pool);
525 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
526 if defined($storage);
528 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
530 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
532 } elsif ($archive && $force && (-f
$filename) &&
533 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
534 # OK: user has VM.Backup permissions, and want to restore an existing VM
540 &$resolve_cdrom_alias($param);
542 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
544 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
546 foreach my $opt (keys %$param) {
547 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
548 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
549 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
551 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
552 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
556 PVE
::QemuServer
::add_random_macs
($param);
558 my $keystr = join(' ', keys %$param);
559 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
561 if ($archive eq '-') {
562 die "pipe requires cli environment\n"
563 if $rpcenv->{type
} ne 'cli';
564 $archive = { type
=> 'pipe' };
566 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
568 $archive = $parse_restore_archive->($storecfg, $archive);
572 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
574 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
575 die "$emsg $@" if $@;
577 my $restorefn = sub {
578 my $conf = PVE
::QemuConfig-
>load_config($vmid);
580 PVE
::QemuConfig-
>check_protection($conf, $emsg);
582 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
585 my $restore_options = {
591 if ($archive->{type
} eq 'file' || $archive->{type
} eq 'pipe') {
592 PVE
::QemuServer
::restore_file_archive
($archive->{path
} // '-', $vmid, $authuser, $restore_options);
593 } elsif ($archive->{type
} eq 'pbs') {
594 PVE
::QemuServer
::restore_proxmox_backup_archive
($archive->{volid
}, $vmid, $authuser, $restore_options);
596 die "unknown backup archive type\n";
598 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
599 # Convert restored VM to template if backup was VM template
600 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
601 warn "Convert to template.\n";
602 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
606 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
609 # ensure no old replication state are exists
610 PVE
::ReplicationState
::delete_guest_states
($vmid);
612 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
614 if ($start_after_create) {
615 print "Execute autostart\n";
616 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
622 # ensure no old replication state are exists
623 PVE
::ReplicationState
::delete_guest_states
($vmid);
627 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
631 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
633 if (!$conf->{bootdisk
}) {
634 my $firstdisk = PVE
::QemuServer
::Drive
::resolve_first_disk
($conf);
635 $conf->{bootdisk
} = $firstdisk if $firstdisk;
638 # auto generate uuid if user did not specify smbios1 option
639 if (!$conf->{smbios1
}) {
640 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
643 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
644 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
647 PVE
::QemuConfig-
>write_config($vmid, $conf);
653 foreach my $volid (@$vollist) {
654 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
660 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
663 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
665 if ($start_after_create) {
666 print "Execute autostart\n";
667 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
672 my ($code, $worker_name);
674 $worker_name = 'qmrestore';
676 eval { $restorefn->() };
678 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
684 $worker_name = 'qmcreate';
686 eval { $createfn->() };
689 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
690 unlink($conffile) or die "failed to remove config file: $!\n";
698 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
701 __PACKAGE__-
>register_method({
706 description
=> "Directory index",
711 additionalProperties
=> 0,
713 node
=> get_standard_option
('pve-node'),
714 vmid
=> get_standard_option
('pve-vmid'),
722 subdir
=> { type
=> 'string' },
725 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
731 { subdir
=> 'config' },
732 { subdir
=> 'pending' },
733 { subdir
=> 'status' },
734 { subdir
=> 'unlink' },
735 { subdir
=> 'vncproxy' },
736 { subdir
=> 'termproxy' },
737 { subdir
=> 'migrate' },
738 { subdir
=> 'resize' },
739 { subdir
=> 'move' },
741 { subdir
=> 'rrddata' },
742 { subdir
=> 'monitor' },
743 { subdir
=> 'agent' },
744 { subdir
=> 'snapshot' },
745 { subdir
=> 'spiceproxy' },
746 { subdir
=> 'sendkey' },
747 { subdir
=> 'firewall' },
753 __PACKAGE__-
>register_method ({
754 subclass
=> "PVE::API2::Firewall::VM",
755 path
=> '{vmid}/firewall',
758 __PACKAGE__-
>register_method ({
759 subclass
=> "PVE::API2::Qemu::Agent",
760 path
=> '{vmid}/agent',
763 __PACKAGE__-
>register_method({
765 path
=> '{vmid}/rrd',
767 protected
=> 1, # fixme: can we avoid that?
769 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
771 description
=> "Read VM RRD statistics (returns PNG)",
773 additionalProperties
=> 0,
775 node
=> get_standard_option
('pve-node'),
776 vmid
=> get_standard_option
('pve-vmid'),
778 description
=> "Specify the time frame you are interested in.",
780 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
783 description
=> "The list of datasources you want to display.",
784 type
=> 'string', format
=> 'pve-configid-list',
787 description
=> "The RRD consolidation function",
789 enum
=> [ 'AVERAGE', 'MAX' ],
797 filename
=> { type
=> 'string' },
803 return PVE
::RRD
::create_rrd_graph
(
804 "pve2-vm/$param->{vmid}", $param->{timeframe
},
805 $param->{ds
}, $param->{cf
});
809 __PACKAGE__-
>register_method({
811 path
=> '{vmid}/rrddata',
813 protected
=> 1, # fixme: can we avoid that?
815 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
817 description
=> "Read VM RRD statistics",
819 additionalProperties
=> 0,
821 node
=> get_standard_option
('pve-node'),
822 vmid
=> get_standard_option
('pve-vmid'),
824 description
=> "Specify the time frame you are interested in.",
826 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
829 description
=> "The RRD consolidation function",
831 enum
=> [ 'AVERAGE', 'MAX' ],
846 return PVE
::RRD
::create_rrd_data
(
847 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
851 __PACKAGE__-
>register_method({
853 path
=> '{vmid}/config',
856 description
=> "Get the virtual machine configuration with pending configuration " .
857 "changes applied. Set the 'current' parameter to get the current configuration instead.",
859 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
862 additionalProperties
=> 0,
864 node
=> get_standard_option
('pve-node'),
865 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
867 description
=> "Get current values (instead of pending values).",
872 snapshot
=> get_standard_option
('pve-snapshot-name', {
873 description
=> "Fetch config values from given snapshot.",
876 my ($cmd, $pname, $cur, $args) = @_;
877 PVE
::QemuConfig-
>snapshot_list($args->[0]);
883 description
=> "The VM configuration.",
885 properties
=> PVE
::QemuServer
::json_config_properties
({
888 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
895 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
896 current
=> "cannot use 'snapshot' parameter with 'current'"})
897 if ($param->{snapshot
} && $param->{current
});
900 if ($param->{snapshot
}) {
901 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
903 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
905 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
910 __PACKAGE__-
>register_method({
911 name
=> 'vm_pending',
912 path
=> '{vmid}/pending',
915 description
=> "Get the virtual machine configuration with both current and pending values.",
917 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
920 additionalProperties
=> 0,
922 node
=> get_standard_option
('pve-node'),
923 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
932 description
=> "Configuration option name.",
936 description
=> "Current value.",
941 description
=> "Pending value.",
946 description
=> "Indicates a pending delete request if present and not 0. " .
947 "The value 2 indicates a force-delete request.",
959 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
961 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
963 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
964 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
966 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
969 # POST/PUT {vmid}/config implementation
971 # The original API used PUT (idempotent) an we assumed that all operations
972 # are fast. But it turned out that almost any configuration change can
973 # involve hot-plug actions, or disk alloc/free. Such actions can take long
974 # time to complete and have side effects (not idempotent).
976 # The new implementation uses POST and forks a worker process. We added
977 # a new option 'background_delay'. If specified we wait up to
978 # 'background_delay' second for the worker task to complete. It returns null
979 # if the task is finished within that time, else we return the UPID.
981 my $update_vm_api = sub {
982 my ($param, $sync) = @_;
984 my $rpcenv = PVE
::RPCEnvironment
::get
();
986 my $authuser = $rpcenv->get_user();
988 my $node = extract_param
($param, 'node');
990 my $vmid = extract_param
($param, 'vmid');
992 my $digest = extract_param
($param, 'digest');
994 my $background_delay = extract_param
($param, 'background_delay');
996 if (defined(my $cipassword = $param->{cipassword
})) {
997 # Same logic as in cloud-init (but with the regex fixed...)
998 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
999 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1002 my @paramarr = (); # used for log message
1003 foreach my $key (sort keys %$param) {
1004 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1005 push @paramarr, "-$key", $value;
1008 my $skiplock = extract_param
($param, 'skiplock');
1009 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1010 if $skiplock && $authuser ne 'root@pam';
1012 my $delete_str = extract_param
($param, 'delete');
1014 my $revert_str = extract_param
($param, 'revert');
1016 my $force = extract_param
($param, 'force');
1018 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1019 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1020 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1023 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1025 my $storecfg = PVE
::Storage
::config
();
1027 my $defaults = PVE
::QemuServer
::load_defaults
();
1029 &$resolve_cdrom_alias($param);
1031 # now try to verify all parameters
1034 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1035 if (!PVE
::QemuServer
::option_exists
($opt)) {
1036 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1039 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1040 "-revert $opt' at the same time" })
1041 if defined($param->{$opt});
1043 $revert->{$opt} = 1;
1047 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1048 $opt = 'ide2' if $opt eq 'cdrom';
1050 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1051 "-delete $opt' at the same time" })
1052 if defined($param->{$opt});
1054 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1055 "-revert $opt' at the same time" })
1058 if (!PVE
::QemuServer
::option_exists
($opt)) {
1059 raise_param_exc
({ delete => "unknown option '$opt'" });
1065 my $repl_conf = PVE
::ReplicationConfig-
>new();
1066 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1067 my $check_replication = sub {
1069 return if !$is_replicated;
1070 my $volid = $drive->{file
};
1071 return if !$volid || !($drive->{replicate
}//1);
1072 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1074 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1075 return if $volname eq 'cloudinit';
1078 if ($volid =~ $NEW_DISK_RE) {
1080 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1082 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1084 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1085 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1086 return if $scfg->{shared
};
1087 die "cannot add non-replicatable volume to a replicated VM\n";
1090 foreach my $opt (keys %$param) {
1091 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1092 # cleanup drive path
1093 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1094 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1095 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1096 $check_replication->($drive);
1097 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
1098 } elsif ($opt =~ m/^net(\d+)$/) {
1100 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1101 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1102 } elsif ($opt eq 'vmgenid') {
1103 if ($param->{$opt} eq '1') {
1104 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1106 } elsif ($opt eq 'hookscript') {
1107 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1108 raise_param_exc
({ $opt => $@ }) if $@;
1112 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1114 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1116 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1118 my $updatefn = sub {
1120 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1122 die "checksum missmatch (file change by other user?)\n"
1123 if $digest && $digest ne $conf->{digest
};
1125 # FIXME: 'suspended' lock should probabyl be a state or "weak" lock?!
1126 if (scalar(@delete) && grep { $_ eq 'vmstate'} @delete) {
1127 if (defined($conf->{lock}) && $conf->{lock} eq 'suspended') {
1128 delete $conf->{lock}; # for check lock check, not written out
1129 push @delete, 'lock'; # this is the real deal to write it out
1131 push @delete, 'runningmachine' if $conf->{runningmachine
};
1134 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1136 foreach my $opt (keys %$revert) {
1137 if (defined($conf->{$opt})) {
1138 $param->{$opt} = $conf->{$opt};
1139 } elsif (defined($conf->{pending
}->{$opt})) {
1144 if ($param->{memory
} || defined($param->{balloon
})) {
1145 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1146 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1148 die "balloon value too large (must be smaller than assigned memory)\n"
1149 if $balloon && $balloon > $maxmem;
1152 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1156 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1158 # write updates to pending section
1160 my $modified = {}; # record what $option we modify
1162 foreach my $opt (@delete) {
1163 $modified->{$opt} = 1;
1164 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1166 # value of what we want to delete, independent if pending or not
1167 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1168 if (!defined($val)) {
1169 warn "cannot delete '$opt' - not set in current configuration!\n";
1170 $modified->{$opt} = 0;
1173 my $is_pending_val = defined($conf->{pending
}->{$opt});
1174 delete $conf->{pending
}->{$opt};
1176 if ($opt =~ m/^unused/) {
1177 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1178 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1179 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1180 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1181 delete $conf->{$opt};
1182 PVE
::QemuConfig-
>write_config($vmid, $conf);
1184 } elsif ($opt eq 'vmstate') {
1185 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1186 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1187 delete $conf->{$opt};
1188 PVE
::QemuConfig-
>write_config($vmid, $conf);
1190 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1191 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1192 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1193 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1195 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1196 PVE
::QemuConfig-
>write_config($vmid, $conf);
1197 } elsif ($opt =~ m/^serial\d+$/) {
1198 if ($val eq 'socket') {
1199 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1200 } elsif ($authuser ne 'root@pam') {
1201 die "only root can delete '$opt' config for real devices\n";
1203 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1204 PVE
::QemuConfig-
>write_config($vmid, $conf);
1205 } elsif ($opt =~ m/^usb\d+$/) {
1206 if ($val =~ m/spice/) {
1207 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1208 } elsif ($authuser ne 'root@pam') {
1209 die "only root can delete '$opt' config for real devices\n";
1211 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1212 PVE
::QemuConfig-
>write_config($vmid, $conf);
1214 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1215 PVE
::QemuConfig-
>write_config($vmid, $conf);
1219 foreach my $opt (keys %$param) { # add/change
1220 $modified->{$opt} = 1;
1221 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1222 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1224 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1226 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1227 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1228 # FIXME: cloudinit: CDROM or Disk?
1229 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1230 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1232 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1234 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1235 if defined($conf->{pending
}->{$opt});
1237 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1238 } elsif ($opt =~ m/^serial\d+/) {
1239 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1240 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1241 } elsif ($authuser ne 'root@pam') {
1242 die "only root can modify '$opt' config for real devices\n";
1244 $conf->{pending
}->{$opt} = $param->{$opt};
1245 } elsif ($opt =~ m/^usb\d+/) {
1246 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1247 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1248 } elsif ($authuser ne 'root@pam') {
1249 die "only root can modify '$opt' config for real devices\n";
1251 $conf->{pending
}->{$opt} = $param->{$opt};
1253 $conf->{pending
}->{$opt} = $param->{$opt};
1255 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1256 PVE
::QemuConfig-
>write_config($vmid, $conf);
1259 # remove pending changes when nothing changed
1260 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1261 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1262 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1264 return if !scalar(keys %{$conf->{pending
}});
1266 my $running = PVE
::QemuServer
::check_running
($vmid);
1268 # apply pending changes
1270 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1274 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1276 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running, $errors);
1278 raise_param_exc
($errors) if scalar(keys %$errors);
1287 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1289 if ($background_delay) {
1291 # Note: It would be better to do that in the Event based HTTPServer
1292 # to avoid blocking call to sleep.
1294 my $end_time = time() + $background_delay;
1296 my $task = PVE
::Tools
::upid_decode
($upid);
1299 while (time() < $end_time) {
1300 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1302 sleep(1); # this gets interrupted when child process ends
1306 my $status = PVE
::Tools
::upid_read_status
($upid);
1307 return undef if $status eq 'OK';
1316 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1319 my $vm_config_perm_list = [
1324 'VM.Config.Network',
1326 'VM.Config.Options',
1329 __PACKAGE__-
>register_method({
1330 name
=> 'update_vm_async',
1331 path
=> '{vmid}/config',
1335 description
=> "Set virtual machine options (asynchrounous API).",
1337 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1340 additionalProperties
=> 0,
1341 properties
=> PVE
::QemuServer
::json_config_properties
(
1343 node
=> get_standard_option
('pve-node'),
1344 vmid
=> get_standard_option
('pve-vmid'),
1345 skiplock
=> get_standard_option
('skiplock'),
1347 type
=> 'string', format
=> 'pve-configid-list',
1348 description
=> "A list of settings you want to delete.",
1352 type
=> 'string', format
=> 'pve-configid-list',
1353 description
=> "Revert a pending change.",
1358 description
=> $opt_force_description,
1360 requires
=> 'delete',
1364 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1368 background_delay
=> {
1370 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1381 code
=> $update_vm_api,
1384 __PACKAGE__-
>register_method({
1385 name
=> 'update_vm',
1386 path
=> '{vmid}/config',
1390 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1392 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1395 additionalProperties
=> 0,
1396 properties
=> PVE
::QemuServer
::json_config_properties
(
1398 node
=> get_standard_option
('pve-node'),
1399 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1400 skiplock
=> get_standard_option
('skiplock'),
1402 type
=> 'string', format
=> 'pve-configid-list',
1403 description
=> "A list of settings you want to delete.",
1407 type
=> 'string', format
=> 'pve-configid-list',
1408 description
=> "Revert a pending change.",
1413 description
=> $opt_force_description,
1415 requires
=> 'delete',
1419 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1425 returns
=> { type
=> 'null' },
1428 &$update_vm_api($param, 1);
1433 __PACKAGE__-
>register_method({
1434 name
=> 'destroy_vm',
1439 description
=> "Destroy the vm (also delete all used/owned volumes).",
1441 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1444 additionalProperties
=> 0,
1446 node
=> get_standard_option
('pve-node'),
1447 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1448 skiplock
=> get_standard_option
('skiplock'),
1451 description
=> "Remove vmid from backup cron jobs.",
1462 my $rpcenv = PVE
::RPCEnvironment
::get
();
1463 my $authuser = $rpcenv->get_user();
1464 my $vmid = $param->{vmid
};
1466 my $skiplock = $param->{skiplock
};
1467 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1468 if $skiplock && $authuser ne 'root@pam';
1471 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1472 my $storecfg = PVE
::Storage
::config
();
1473 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1475 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("vm:$vmid");
1477 if (!$param->{purge
}) {
1478 die "unable to remove VM $vmid - used in HA resources and purge parameter not set.\n"
1480 # don't allow destroy if with replication jobs but no purge param
1481 my $repl_conf = PVE
::ReplicationConfig-
>new();
1482 $repl_conf->check_for_existing_jobs($vmid);
1485 # early tests (repeat after locking)
1486 die "VM $vmid is running - destroy failed\n"
1487 if PVE
::QemuServer
::check_running
($vmid);
1492 syslog
('info', "destroy VM $vmid: $upid\n");
1493 PVE
::QemuConfig-
>lock_config($vmid, sub {
1494 die "VM $vmid is running - destroy failed\n"
1495 if (PVE
::QemuServer
::check_running
($vmid));
1497 PVE
::QemuServer
::destroy_vm
($storecfg, $vmid, $skiplock, { lock => 'destroyed' });
1499 PVE
::AccessControl
::remove_vm_access
($vmid);
1500 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1501 if ($param->{purge
}) {
1502 print "purging VM $vmid from related configurations..\n";
1503 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1504 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1507 PVE
::HA
::Config
::delete_service_from_config
("vm:$vmid");
1508 print "NOTE: removed VM $vmid from HA resource configuration.\n";
1512 # only now remove the zombie config, else we can have reuse race
1513 PVE
::QemuConfig-
>destroy_config($vmid);
1517 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1520 __PACKAGE__-
>register_method({
1522 path
=> '{vmid}/unlink',
1526 description
=> "Unlink/delete disk images.",
1528 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1531 additionalProperties
=> 0,
1533 node
=> get_standard_option
('pve-node'),
1534 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1536 type
=> 'string', format
=> 'pve-configid-list',
1537 description
=> "A list of disk IDs you want to delete.",
1541 description
=> $opt_force_description,
1546 returns
=> { type
=> 'null'},
1550 $param->{delete} = extract_param
($param, 'idlist');
1552 __PACKAGE__-
>update_vm($param);
1559 __PACKAGE__-
>register_method({
1561 path
=> '{vmid}/vncproxy',
1565 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1567 description
=> "Creates a TCP VNC proxy connections.",
1569 additionalProperties
=> 0,
1571 node
=> get_standard_option
('pve-node'),
1572 vmid
=> get_standard_option
('pve-vmid'),
1576 description
=> "starts websockify instead of vncproxy",
1581 additionalProperties
=> 0,
1583 user
=> { type
=> 'string' },
1584 ticket
=> { type
=> 'string' },
1585 cert
=> { type
=> 'string' },
1586 port
=> { type
=> 'integer' },
1587 upid
=> { type
=> 'string' },
1593 my $rpcenv = PVE
::RPCEnvironment
::get
();
1595 my $authuser = $rpcenv->get_user();
1597 my $vmid = $param->{vmid
};
1598 my $node = $param->{node
};
1599 my $websocket = $param->{websocket
};
1601 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1602 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1604 my $authpath = "/vms/$vmid";
1606 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1608 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1614 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1615 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1616 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1617 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1618 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1620 $family = PVE
::Tools
::get_host_address_family
($node);
1623 my $port = PVE
::Tools
::next_vnc_port
($family);
1630 syslog
('info', "starting vnc proxy $upid\n");
1636 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1638 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1639 '-timeout', $timeout, '-authpath', $authpath,
1640 '-perm', 'Sys.Console'];
1642 if ($param->{websocket
}) {
1643 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1644 push @$cmd, '-notls', '-listen', 'localhost';
1647 push @$cmd, '-c', @$remcmd, @$termcmd;
1649 PVE
::Tools
::run_command
($cmd);
1653 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1655 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1657 my $sock = IO
::Socket
::IP-
>new(
1662 GetAddrInfoFlags
=> 0,
1663 ) or die "failed to create socket: $!\n";
1664 # Inside the worker we shouldn't have any previous alarms
1665 # running anyway...:
1667 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1669 accept(my $cli, $sock) or die "connection failed: $!\n";
1672 if (PVE
::Tools
::run_command
($cmd,
1673 output
=> '>&'.fileno($cli),
1674 input
=> '<&'.fileno($cli),
1677 die "Failed to run vncproxy.\n";
1684 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1686 PVE
::Tools
::wait_for_vnc_port
($port);
1697 __PACKAGE__-
>register_method({
1698 name
=> 'termproxy',
1699 path
=> '{vmid}/termproxy',
1703 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1705 description
=> "Creates a TCP proxy connections.",
1707 additionalProperties
=> 0,
1709 node
=> get_standard_option
('pve-node'),
1710 vmid
=> get_standard_option
('pve-vmid'),
1714 enum
=> [qw(serial0 serial1 serial2 serial3)],
1715 description
=> "opens a serial terminal (defaults to display)",
1720 additionalProperties
=> 0,
1722 user
=> { type
=> 'string' },
1723 ticket
=> { type
=> 'string' },
1724 port
=> { type
=> 'integer' },
1725 upid
=> { type
=> 'string' },
1731 my $rpcenv = PVE
::RPCEnvironment
::get
();
1733 my $authuser = $rpcenv->get_user();
1735 my $vmid = $param->{vmid
};
1736 my $node = $param->{node
};
1737 my $serial = $param->{serial
};
1739 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1741 if (!defined($serial)) {
1742 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1743 $serial = $conf->{vga
};
1747 my $authpath = "/vms/$vmid";
1749 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1754 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1755 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1756 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1757 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1758 push @$remcmd, '--';
1760 $family = PVE
::Tools
::get_host_address_family
($node);
1763 my $port = PVE
::Tools
::next_vnc_port
($family);
1765 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1766 push @$termcmd, '-iface', $serial if $serial;
1771 syslog
('info', "starting qemu termproxy $upid\n");
1773 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1774 '--perm', 'VM.Console', '--'];
1775 push @$cmd, @$remcmd, @$termcmd;
1777 PVE
::Tools
::run_command
($cmd);
1780 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1782 PVE
::Tools
::wait_for_vnc_port
($port);
1792 __PACKAGE__-
>register_method({
1793 name
=> 'vncwebsocket',
1794 path
=> '{vmid}/vncwebsocket',
1797 description
=> "You also need to pass a valid ticket (vncticket).",
1798 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1800 description
=> "Opens a weksocket for VNC traffic.",
1802 additionalProperties
=> 0,
1804 node
=> get_standard_option
('pve-node'),
1805 vmid
=> get_standard_option
('pve-vmid'),
1807 description
=> "Ticket from previous call to vncproxy.",
1812 description
=> "Port number returned by previous vncproxy call.",
1822 port
=> { type
=> 'string' },
1828 my $rpcenv = PVE
::RPCEnvironment
::get
();
1830 my $authuser = $rpcenv->get_user();
1832 my $vmid = $param->{vmid
};
1833 my $node = $param->{node
};
1835 my $authpath = "/vms/$vmid";
1837 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1839 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1841 # Note: VNC ports are acessible from outside, so we do not gain any
1842 # security if we verify that $param->{port} belongs to VM $vmid. This
1843 # check is done by verifying the VNC ticket (inside VNC protocol).
1845 my $port = $param->{port
};
1847 return { port
=> $port };
1850 __PACKAGE__-
>register_method({
1851 name
=> 'spiceproxy',
1852 path
=> '{vmid}/spiceproxy',
1857 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1859 description
=> "Returns a SPICE configuration to connect to the VM.",
1861 additionalProperties
=> 0,
1863 node
=> get_standard_option
('pve-node'),
1864 vmid
=> get_standard_option
('pve-vmid'),
1865 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1868 returns
=> get_standard_option
('remote-viewer-config'),
1872 my $rpcenv = PVE
::RPCEnvironment
::get
();
1874 my $authuser = $rpcenv->get_user();
1876 my $vmid = $param->{vmid
};
1877 my $node = $param->{node
};
1878 my $proxy = $param->{proxy
};
1880 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1881 my $title = "VM $vmid";
1882 $title .= " - ". $conf->{name
} if $conf->{name
};
1884 my $port = PVE
::QemuServer
::spice_port
($vmid);
1886 my ($ticket, undef, $remote_viewer_config) =
1887 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1889 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1890 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1892 return $remote_viewer_config;
1895 __PACKAGE__-
>register_method({
1897 path
=> '{vmid}/status',
1900 description
=> "Directory index",
1905 additionalProperties
=> 0,
1907 node
=> get_standard_option
('pve-node'),
1908 vmid
=> get_standard_option
('pve-vmid'),
1916 subdir
=> { type
=> 'string' },
1919 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1925 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1928 { subdir
=> 'current' },
1929 { subdir
=> 'start' },
1930 { subdir
=> 'stop' },
1931 { subdir
=> 'reset' },
1932 { subdir
=> 'shutdown' },
1933 { subdir
=> 'suspend' },
1934 { subdir
=> 'reboot' },
1940 __PACKAGE__-
>register_method({
1941 name
=> 'vm_status',
1942 path
=> '{vmid}/status/current',
1945 protected
=> 1, # qemu pid files are only readable by root
1946 description
=> "Get virtual machine status.",
1948 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1951 additionalProperties
=> 0,
1953 node
=> get_standard_option
('pve-node'),
1954 vmid
=> get_standard_option
('pve-vmid'),
1960 %$PVE::QemuServer
::vmstatus_return_properties
,
1962 description
=> "HA manager service status.",
1966 description
=> "Qemu VGA configuration supports spice.",
1971 description
=> "Qemu GuestAgent enabled in config.",
1981 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1983 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1984 my $status = $vmstatus->{$param->{vmid
}};
1986 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1988 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1989 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1994 __PACKAGE__-
>register_method({
1996 path
=> '{vmid}/status/start',
2000 description
=> "Start virtual machine.",
2002 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2005 additionalProperties
=> 0,
2007 node
=> get_standard_option
('pve-node'),
2008 vmid
=> get_standard_option
('pve-vmid',
2009 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2010 skiplock
=> get_standard_option
('skiplock'),
2011 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2012 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2015 enum
=> ['secure', 'insecure'],
2016 description
=> "Migration traffic is encrypted using an SSH " .
2017 "tunnel by default. On secure, completely private networks " .
2018 "this can be disabled to increase performance.",
2021 migration_network
=> {
2022 type
=> 'string', format
=> 'CIDR',
2023 description
=> "CIDR of the (sub) network that is used for migration.",
2026 machine
=> get_standard_option
('pve-qemu-machine'),
2028 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
2033 description
=> "Wait maximal timeout seconds.",
2036 default => 'max(30, vm memory in GiB)',
2047 my $rpcenv = PVE
::RPCEnvironment
::get
();
2048 my $authuser = $rpcenv->get_user();
2050 my $node = extract_param
($param, 'node');
2051 my $vmid = extract_param
($param, 'vmid');
2052 my $timeout = extract_param
($param, 'timeout');
2054 my $machine = extract_param
($param, 'machine');
2056 my $get_root_param = sub {
2057 my $value = extract_param
($param, $_[0]);
2058 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2059 if $value && $authuser ne 'root@pam';
2063 my $stateuri = $get_root_param->('stateuri');
2064 my $skiplock = $get_root_param->('skiplock');
2065 my $migratedfrom = $get_root_param->('migratedfrom');
2066 my $migration_type = $get_root_param->('migration_type');
2067 my $migration_network = $get_root_param->('migration_network');
2068 my $targetstorage = $get_root_param->('targetstorage');
2070 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2071 if $targetstorage && !$migratedfrom;
2073 # read spice ticket from STDIN
2075 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2076 if (defined(my $line = <STDIN
>)) {
2078 $spice_ticket = $line;
2082 PVE
::Cluster
::check_cfs_quorum
();
2084 my $storecfg = PVE
::Storage
::config
();
2086 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2090 print "Requesting HA start for VM $vmid\n";
2092 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2093 PVE
::Tools
::run_command
($cmd);
2097 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2104 syslog
('info', "start VM $vmid: $upid\n");
2106 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef, $machine,
2107 $spice_ticket, $migration_network, $migration_type, $targetstorage, $timeout);
2111 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2115 __PACKAGE__-
>register_method({
2117 path
=> '{vmid}/status/stop',
2121 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2122 "is akin to pulling the power plug of a running computer and may damage the VM data",
2124 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2127 additionalProperties
=> 0,
2129 node
=> get_standard_option
('pve-node'),
2130 vmid
=> get_standard_option
('pve-vmid',
2131 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2132 skiplock
=> get_standard_option
('skiplock'),
2133 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2135 description
=> "Wait maximal timeout seconds.",
2141 description
=> "Do not deactivate storage volumes.",
2154 my $rpcenv = PVE
::RPCEnvironment
::get
();
2155 my $authuser = $rpcenv->get_user();
2157 my $node = extract_param
($param, 'node');
2158 my $vmid = extract_param
($param, 'vmid');
2160 my $skiplock = extract_param
($param, 'skiplock');
2161 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2162 if $skiplock && $authuser ne 'root@pam';
2164 my $keepActive = extract_param
($param, 'keepActive');
2165 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2166 if $keepActive && $authuser ne 'root@pam';
2168 my $migratedfrom = extract_param
($param, 'migratedfrom');
2169 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2170 if $migratedfrom && $authuser ne 'root@pam';
2173 my $storecfg = PVE
::Storage
::config
();
2175 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2180 print "Requesting HA stop for VM $vmid\n";
2182 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2183 PVE
::Tools
::run_command
($cmd);
2187 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2193 syslog
('info', "stop VM $vmid: $upid\n");
2195 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2196 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2200 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2204 __PACKAGE__-
>register_method({
2206 path
=> '{vmid}/status/reset',
2210 description
=> "Reset virtual machine.",
2212 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2215 additionalProperties
=> 0,
2217 node
=> get_standard_option
('pve-node'),
2218 vmid
=> get_standard_option
('pve-vmid',
2219 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2220 skiplock
=> get_standard_option
('skiplock'),
2229 my $rpcenv = PVE
::RPCEnvironment
::get
();
2231 my $authuser = $rpcenv->get_user();
2233 my $node = extract_param
($param, 'node');
2235 my $vmid = extract_param
($param, 'vmid');
2237 my $skiplock = extract_param
($param, 'skiplock');
2238 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2239 if $skiplock && $authuser ne 'root@pam';
2241 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2246 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2251 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2254 __PACKAGE__-
>register_method({
2255 name
=> 'vm_shutdown',
2256 path
=> '{vmid}/status/shutdown',
2260 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2261 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2263 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2266 additionalProperties
=> 0,
2268 node
=> get_standard_option
('pve-node'),
2269 vmid
=> get_standard_option
('pve-vmid',
2270 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2271 skiplock
=> get_standard_option
('skiplock'),
2273 description
=> "Wait maximal timeout seconds.",
2279 description
=> "Make sure the VM stops.",
2285 description
=> "Do not deactivate storage volumes.",
2298 my $rpcenv = PVE
::RPCEnvironment
::get
();
2299 my $authuser = $rpcenv->get_user();
2301 my $node = extract_param
($param, 'node');
2302 my $vmid = extract_param
($param, 'vmid');
2304 my $skiplock = extract_param
($param, 'skiplock');
2305 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2306 if $skiplock && $authuser ne 'root@pam';
2308 my $keepActive = extract_param
($param, 'keepActive');
2309 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2310 if $keepActive && $authuser ne 'root@pam';
2312 my $storecfg = PVE
::Storage
::config
();
2316 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2317 # otherwise, we will infer a shutdown command, but run into the timeout,
2318 # then when the vm is resumed, it will instantly shutdown
2320 # checking the qmp status here to get feedback to the gui/cli/api
2321 # and the status query should not take too long
2322 my $qmpstatus = eval {
2323 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2324 mon_cmd
($vmid, "query-status");
2328 if (!$err && $qmpstatus->{status
} eq "paused") {
2329 if ($param->{forceStop
}) {
2330 warn "VM is paused - stop instead of shutdown\n";
2333 die "VM is paused - cannot shutdown\n";
2337 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2339 my $timeout = $param->{timeout
} // 60;
2343 print "Requesting HA stop for VM $vmid\n";
2345 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2346 PVE
::Tools
::run_command
($cmd);
2350 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2357 syslog
('info', "shutdown VM $vmid: $upid\n");
2359 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2360 $shutdown, $param->{forceStop
}, $keepActive);
2364 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2368 __PACKAGE__-
>register_method({
2369 name
=> 'vm_reboot',
2370 path
=> '{vmid}/status/reboot',
2374 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2376 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2379 additionalProperties
=> 0,
2381 node
=> get_standard_option
('pve-node'),
2382 vmid
=> get_standard_option
('pve-vmid',
2383 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2385 description
=> "Wait maximal timeout seconds for the shutdown.",
2398 my $rpcenv = PVE
::RPCEnvironment
::get
();
2399 my $authuser = $rpcenv->get_user();
2401 my $node = extract_param
($param, 'node');
2402 my $vmid = extract_param
($param, 'vmid');
2404 my $qmpstatus = eval {
2405 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2406 mon_cmd
($vmid, "query-status");
2410 if (!$err && $qmpstatus->{status
} eq "paused") {
2411 die "VM is paused - cannot shutdown\n";
2414 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2419 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2420 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2424 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2427 __PACKAGE__-
>register_method({
2428 name
=> 'vm_suspend',
2429 path
=> '{vmid}/status/suspend',
2433 description
=> "Suspend virtual machine.",
2435 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2436 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2437 " on the storage for the vmstate.",
2438 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2441 additionalProperties
=> 0,
2443 node
=> get_standard_option
('pve-node'),
2444 vmid
=> get_standard_option
('pve-vmid',
2445 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2446 skiplock
=> get_standard_option
('skiplock'),
2451 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2453 statestorage
=> get_standard_option
('pve-storage-id', {
2454 description
=> "The storage for the VM state",
2455 requires
=> 'todisk',
2457 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2467 my $rpcenv = PVE
::RPCEnvironment
::get
();
2468 my $authuser = $rpcenv->get_user();
2470 my $node = extract_param
($param, 'node');
2471 my $vmid = extract_param
($param, 'vmid');
2473 my $todisk = extract_param
($param, 'todisk') // 0;
2475 my $statestorage = extract_param
($param, 'statestorage');
2477 my $skiplock = extract_param
($param, 'skiplock');
2478 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2479 if $skiplock && $authuser ne 'root@pam';
2481 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2483 die "Cannot suspend HA managed VM to disk\n"
2484 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2486 # early check for storage permission, for better user feedback
2488 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2490 if (!$statestorage) {
2491 # get statestorage from config if none is given
2492 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2493 my $storecfg = PVE
::Storage
::config
();
2494 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2497 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2503 syslog
('info', "suspend VM $vmid: $upid\n");
2505 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2510 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2511 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2514 __PACKAGE__-
>register_method({
2515 name
=> 'vm_resume',
2516 path
=> '{vmid}/status/resume',
2520 description
=> "Resume virtual machine.",
2522 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2525 additionalProperties
=> 0,
2527 node
=> get_standard_option
('pve-node'),
2528 vmid
=> get_standard_option
('pve-vmid',
2529 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2530 skiplock
=> get_standard_option
('skiplock'),
2531 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2541 my $rpcenv = PVE
::RPCEnvironment
::get
();
2543 my $authuser = $rpcenv->get_user();
2545 my $node = extract_param
($param, 'node');
2547 my $vmid = extract_param
($param, 'vmid');
2549 my $skiplock = extract_param
($param, 'skiplock');
2550 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2551 if $skiplock && $authuser ne 'root@pam';
2553 my $nocheck = extract_param
($param, 'nocheck');
2555 my $to_disk_suspended;
2557 PVE
::QemuConfig-
>lock_config($vmid, sub {
2558 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2559 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2563 die "VM $vmid not running\n"
2564 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2569 syslog
('info', "resume VM $vmid: $upid\n");
2571 if (!$to_disk_suspended) {
2572 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2574 my $storecfg = PVE
::Storage
::config
();
2575 PVE
::QemuServer
::vm_start
($storecfg, $vmid, undef, $skiplock);
2581 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2584 __PACKAGE__-
>register_method({
2585 name
=> 'vm_sendkey',
2586 path
=> '{vmid}/sendkey',
2590 description
=> "Send key event to virtual machine.",
2592 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2595 additionalProperties
=> 0,
2597 node
=> get_standard_option
('pve-node'),
2598 vmid
=> get_standard_option
('pve-vmid',
2599 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2600 skiplock
=> get_standard_option
('skiplock'),
2602 description
=> "The key (qemu monitor encoding).",
2607 returns
=> { type
=> 'null'},
2611 my $rpcenv = PVE
::RPCEnvironment
::get
();
2613 my $authuser = $rpcenv->get_user();
2615 my $node = extract_param
($param, 'node');
2617 my $vmid = extract_param
($param, 'vmid');
2619 my $skiplock = extract_param
($param, 'skiplock');
2620 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2621 if $skiplock && $authuser ne 'root@pam';
2623 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2628 __PACKAGE__-
>register_method({
2629 name
=> 'vm_feature',
2630 path
=> '{vmid}/feature',
2634 description
=> "Check if feature for virtual machine is available.",
2636 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2639 additionalProperties
=> 0,
2641 node
=> get_standard_option
('pve-node'),
2642 vmid
=> get_standard_option
('pve-vmid'),
2644 description
=> "Feature to check.",
2646 enum
=> [ 'snapshot', 'clone', 'copy' ],
2648 snapname
=> get_standard_option
('pve-snapshot-name', {
2656 hasFeature
=> { type
=> 'boolean' },
2659 items
=> { type
=> 'string' },
2666 my $node = extract_param
($param, 'node');
2668 my $vmid = extract_param
($param, 'vmid');
2670 my $snapname = extract_param
($param, 'snapname');
2672 my $feature = extract_param
($param, 'feature');
2674 my $running = PVE
::QemuServer
::check_running
($vmid);
2676 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2679 my $snap = $conf->{snapshots
}->{$snapname};
2680 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2683 my $storecfg = PVE
::Storage
::config
();
2685 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2686 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2689 hasFeature
=> $hasFeature,
2690 nodes
=> [ keys %$nodelist ],
2694 __PACKAGE__-
>register_method({
2696 path
=> '{vmid}/clone',
2700 description
=> "Create a copy of virtual machine/template.",
2702 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2703 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2704 "'Datastore.AllocateSpace' on any used storage.",
2707 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2709 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2710 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2715 additionalProperties
=> 0,
2717 node
=> get_standard_option
('pve-node'),
2718 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2719 newid
=> get_standard_option
('pve-vmid', {
2720 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2721 description
=> 'VMID for the clone.' }),
2724 type
=> 'string', format
=> 'dns-name',
2725 description
=> "Set a name for the new VM.",
2730 description
=> "Description for the new VM.",
2734 type
=> 'string', format
=> 'pve-poolid',
2735 description
=> "Add the new VM to the specified pool.",
2737 snapname
=> get_standard_option
('pve-snapshot-name', {
2740 storage
=> get_standard_option
('pve-storage-id', {
2741 description
=> "Target storage for full clone.",
2745 description
=> "Target format for file storage. Only valid for full clone.",
2748 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2753 description
=> "Create a full copy of all disks. This is always done when " .
2754 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2756 target
=> get_standard_option
('pve-node', {
2757 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2761 description
=> "Override I/O bandwidth limit (in KiB/s).",
2765 default => 'clone limit from datacenter or storage config',
2775 my $rpcenv = PVE
::RPCEnvironment
::get
();
2776 my $authuser = $rpcenv->get_user();
2778 my $node = extract_param
($param, 'node');
2779 my $vmid = extract_param
($param, 'vmid');
2780 my $newid = extract_param
($param, 'newid');
2781 my $pool = extract_param
($param, 'pool');
2782 $rpcenv->check_pool_exist($pool) if defined($pool);
2784 my $snapname = extract_param
($param, 'snapname');
2785 my $storage = extract_param
($param, 'storage');
2786 my $format = extract_param
($param, 'format');
2787 my $target = extract_param
($param, 'target');
2789 my $localnode = PVE
::INotify
::nodename
();
2791 if ($target && ($target eq $localnode || $target eq 'localhost')) {
2795 PVE
::Cluster
::check_node_exists
($target) if $target;
2797 my $storecfg = PVE
::Storage
::config
();
2800 # check if storage is enabled on local node
2801 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2803 # check if storage is available on target node
2804 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2805 # clone only works if target storage is shared
2806 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2807 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2811 PVE
::Cluster
::check_cfs_quorum
();
2813 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2815 # exclusive lock if VM is running - else shared lock is enough;
2816 my $shared_lock = $running ?
0 : 1;
2819 # do all tests after lock but before forking worker - if possible
2821 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2822 PVE
::QemuConfig-
>check_lock($conf);
2824 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2825 die "unexpected state change\n" if $verify_running != $running;
2827 die "snapshot '$snapname' does not exist\n"
2828 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2830 my $full = extract_param
($param, 'full') // !PVE
::QemuConfig-
>is_template($conf);
2832 die "parameter 'storage' not allowed for linked clones\n"
2833 if defined($storage) && !$full;
2835 die "parameter 'format' not allowed for linked clones\n"
2836 if defined($format) && !$full;
2838 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2840 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2842 die "can't clone VM to node '$target' (VM uses local storage)\n"
2843 if $target && !$sharedvm;
2845 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2846 die "unable to create VM $newid: config file already exists\n"
2849 my $newconf = { lock => 'clone' };
2854 foreach my $opt (keys %$oldconf) {
2855 my $value = $oldconf->{$opt};
2857 # do not copy snapshot related info
2858 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2859 $opt eq 'vmstate' || $opt eq 'snapstate';
2861 # no need to copy unused images, because VMID(owner) changes anyways
2862 next if $opt =~ m/^unused\d+$/;
2864 # always change MAC! address
2865 if ($opt =~ m/^net(\d+)$/) {
2866 my $net = PVE
::QemuServer
::parse_net
($value);
2867 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2868 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2869 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2870 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2871 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2872 die "unable to parse drive options for '$opt'\n" if !$drive;
2873 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2874 $newconf->{$opt} = $value; # simply copy configuration
2876 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2877 die "Full clone feature is not supported for drive '$opt'\n"
2878 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2879 $fullclone->{$opt} = 1;
2881 # not full means clone instead of copy
2882 die "Linked clone feature is not supported for drive '$opt'\n"
2883 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2885 $drives->{$opt} = $drive;
2886 push @$vollist, $drive->{file
};
2889 # copy everything else
2890 $newconf->{$opt} = $value;
2894 # auto generate a new uuid
2895 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2896 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2897 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2898 # auto generate a new vmgenid only if the option was set for template
2899 if ($newconf->{vmgenid
}) {
2900 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2903 delete $newconf->{template
};
2905 if ($param->{name
}) {
2906 $newconf->{name
} = $param->{name
};
2908 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
2911 if ($param->{description
}) {
2912 $newconf->{description
} = $param->{description
};
2915 # create empty/temp config - this fails if VM already exists on other node
2916 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
2917 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2922 my $newvollist = [];
2929 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2931 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2933 my $bwlimit = extract_param
($param, 'bwlimit');
2935 my $total_jobs = scalar(keys %{$drives});
2938 foreach my $opt (keys %$drives) {
2939 my $drive = $drives->{$opt};
2940 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2942 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
2943 my $storage_list = [ $src_sid ];
2944 push @$storage_list, $storage if defined($storage);
2945 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
2947 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2948 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2949 $jobs, $skipcomplete, $oldconf->{agent
}, $clonelimit);
2951 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
2953 PVE
::QemuConfig-
>write_config($newid, $newconf);
2957 delete $newconf->{lock};
2959 # do not write pending changes
2960 if (my @changes = keys %{$newconf->{pending
}}) {
2961 my $pending = join(',', @changes);
2962 warn "found pending changes for '$pending', discarding for clone\n";
2963 delete $newconf->{pending
};
2966 PVE
::QemuConfig-
>write_config($newid, $newconf);
2969 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2970 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2971 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2973 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2974 die "Failed to move config to node '$target' - rename failed: $!\n"
2975 if !rename($conffile, $newconffile);
2978 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2981 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2982 sleep 1; # some storage like rbd need to wait before release volume - really?
2984 foreach my $volid (@$newvollist) {
2985 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2989 PVE
::Firewall
::remove_vmfw_conf
($newid);
2991 unlink $conffile; # avoid races -> last thing before die
2993 die "clone failed: $err";
2999 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3001 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
3004 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
3005 # Aquire exclusive lock lock for $newid
3006 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3011 __PACKAGE__-
>register_method({
3012 name
=> 'move_vm_disk',
3013 path
=> '{vmid}/move_disk',
3017 description
=> "Move volume to different storage.",
3019 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
3021 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3022 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
3026 additionalProperties
=> 0,
3028 node
=> get_standard_option
('pve-node'),
3029 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3032 description
=> "The disk you want to move.",
3033 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3035 storage
=> get_standard_option
('pve-storage-id', {
3036 description
=> "Target storage.",
3037 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3041 description
=> "Target Format.",
3042 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3047 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3053 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3058 description
=> "Override I/O bandwidth limit (in KiB/s).",
3062 default => 'move limit from datacenter or storage config',
3068 description
=> "the task ID.",
3073 my $rpcenv = PVE
::RPCEnvironment
::get
();
3074 my $authuser = $rpcenv->get_user();
3076 my $node = extract_param
($param, 'node');
3077 my $vmid = extract_param
($param, 'vmid');
3078 my $digest = extract_param
($param, 'digest');
3079 my $disk = extract_param
($param, 'disk');
3080 my $storeid = extract_param
($param, 'storage');
3081 my $format = extract_param
($param, 'format');
3083 my $storecfg = PVE
::Storage
::config
();
3085 my $updatefn = sub {
3086 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3087 PVE
::QemuConfig-
>check_lock($conf);
3089 die "VM config checksum missmatch (file change by other user?)\n"
3090 if $digest && $digest ne $conf->{digest
};
3092 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3094 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3096 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3097 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3099 my $old_volid = $drive->{file
};
3101 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3102 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3106 die "you can't move to the same storage with same format\n" if $oldstoreid eq $storeid &&
3107 (!$format || !$oldfmt || $oldfmt eq $format);
3109 # this only checks snapshots because $disk is passed!
3110 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3111 die "you can't move a disk with snapshots and delete the source\n"
3112 if $snapshotted && $param->{delete};
3114 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3116 my $running = PVE
::QemuServer
::check_running
($vmid);
3118 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3121 my $newvollist = [];
3127 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3129 warn "moving disk with snapshots, snapshots will not be moved!\n"
3132 my $bwlimit = extract_param
($param, 'bwlimit');
3133 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3135 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3136 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit);
3138 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3140 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3142 # convert moved disk to base if part of template
3143 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3144 if PVE
::QemuConfig-
>is_template($conf);
3146 PVE
::QemuConfig-
>write_config($vmid, $conf);
3148 my $do_trim = PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
};
3149 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3150 eval { mon_cmd
($vmid, "guest-fstrim") };
3154 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3155 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3161 foreach my $volid (@$newvollist) {
3162 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3165 die "storage migration failed: $err";
3168 if ($param->{delete}) {
3170 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3171 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3177 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3180 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3183 my $check_vm_disks_local = sub {
3184 my ($storecfg, $vmconf, $vmid) = @_;
3186 my $local_disks = {};
3188 # add some more information to the disks e.g. cdrom
3189 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3190 my ($volid, $attr) = @_;
3192 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3194 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3195 return if $scfg->{shared
};
3197 # The shared attr here is just a special case where the vdisk
3198 # is marked as shared manually
3199 return if $attr->{shared
};
3200 return if $attr->{cdrom
} and $volid eq "none";
3202 if (exists $local_disks->{$volid}) {
3203 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3205 $local_disks->{$volid} = $attr;
3206 # ensure volid is present in case it's needed
3207 $local_disks->{$volid}->{volid
} = $volid;
3211 return $local_disks;
3214 __PACKAGE__-
>register_method({
3215 name
=> 'migrate_vm_precondition',
3216 path
=> '{vmid}/migrate',
3220 description
=> "Get preconditions for migration.",
3222 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3225 additionalProperties
=> 0,
3227 node
=> get_standard_option
('pve-node'),
3228 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3229 target
=> get_standard_option
('pve-node', {
3230 description
=> "Target node.",
3231 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3239 running
=> { type
=> 'boolean' },
3243 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3245 not_allowed_nodes
=> {
3248 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3252 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3254 local_resources
=> {
3256 description
=> "List local resources e.g. pci, usb"
3263 my $rpcenv = PVE
::RPCEnvironment
::get
();
3265 my $authuser = $rpcenv->get_user();
3267 PVE
::Cluster
::check_cfs_quorum
();
3271 my $vmid = extract_param
($param, 'vmid');
3272 my $target = extract_param
($param, 'target');
3273 my $localnode = PVE
::INotify
::nodename
();
3277 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3278 my $storecfg = PVE
::Storage
::config
();
3281 # try to detect errors early
3282 PVE
::QemuConfig-
>check_lock($vmconf);
3284 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3286 # if vm is not running, return target nodes where local storage is available
3287 # for offline migration
3288 if (!$res->{running
}) {
3289 $res->{allowed_nodes
} = [];
3290 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3291 delete $checked_nodes->{$localnode};
3293 foreach my $node (keys %$checked_nodes) {
3294 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3295 push @{$res->{allowed_nodes
}}, $node;
3299 $res->{not_allowed_nodes
} = $checked_nodes;
3303 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3304 $res->{local_disks
} = [ values %$local_disks ];;
3306 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3308 $res->{local_resources
} = $local_resources;
3315 __PACKAGE__-
>register_method({
3316 name
=> 'migrate_vm',
3317 path
=> '{vmid}/migrate',
3321 description
=> "Migrate virtual machine. Creates a new migration task.",
3323 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3326 additionalProperties
=> 0,
3328 node
=> get_standard_option
('pve-node'),
3329 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3330 target
=> get_standard_option
('pve-node', {
3331 description
=> "Target node.",
3332 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3336 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3341 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3346 enum
=> ['secure', 'insecure'],
3347 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3350 migration_network
=> {
3351 type
=> 'string', format
=> 'CIDR',
3352 description
=> "CIDR of the (sub) network that is used for migration.",
3355 "with-local-disks" => {
3357 description
=> "Enable live storage migration for local disk",
3360 targetstorage
=> get_standard_option
('pve-storage-id', {
3361 description
=> "Default target storage.",
3363 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3366 description
=> "Override I/O bandwidth limit (in KiB/s).",
3370 default => 'migrate limit from datacenter or storage config',
3376 description
=> "the task ID.",
3381 my $rpcenv = PVE
::RPCEnvironment
::get
();
3382 my $authuser = $rpcenv->get_user();
3384 my $target = extract_param
($param, 'target');
3386 my $localnode = PVE
::INotify
::nodename
();
3387 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3389 PVE
::Cluster
::check_cfs_quorum
();
3391 PVE
::Cluster
::check_node_exists
($target);
3393 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3395 my $vmid = extract_param
($param, 'vmid');
3397 raise_param_exc
({ force
=> "Only root may use this option." })
3398 if $param->{force
} && $authuser ne 'root@pam';
3400 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3401 if $param->{migration_type
} && $authuser ne 'root@pam';
3403 # allow root only until better network permissions are available
3404 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3405 if $param->{migration_network
} && $authuser ne 'root@pam';
3408 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3410 # try to detect errors early
3412 PVE
::QemuConfig-
>check_lock($conf);
3414 if (PVE
::QemuServer
::check_running
($vmid)) {
3415 die "can't migrate running VM without --online\n" if !$param->{online
};
3417 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
3418 $param->{online
} = 0;
3421 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3422 if !$param->{online
} && $param->{targetstorage
};
3424 my $storecfg = PVE
::Storage
::config
();
3426 if( $param->{targetstorage
}) {
3427 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3429 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3432 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3437 print "Requesting HA migration for VM $vmid to node $target\n";
3439 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3440 PVE
::Tools
::run_command
($cmd);
3444 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3449 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3453 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3456 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3461 __PACKAGE__-
>register_method({
3463 path
=> '{vmid}/monitor',
3467 description
=> "Execute Qemu monitor commands.",
3469 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3470 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3473 additionalProperties
=> 0,
3475 node
=> get_standard_option
('pve-node'),
3476 vmid
=> get_standard_option
('pve-vmid'),
3479 description
=> "The monitor command.",
3483 returns
=> { type
=> 'string'},
3487 my $rpcenv = PVE
::RPCEnvironment
::get
();
3488 my $authuser = $rpcenv->get_user();
3491 my $command = shift;
3492 return $command =~ m/^\s*info(\s+|$)/
3493 || $command =~ m/^\s*help\s*$/;
3496 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3497 if !&$is_ro($param->{command
});
3499 my $vmid = $param->{vmid
};
3501 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3505 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3507 $res = "ERROR: $@" if $@;
3512 __PACKAGE__-
>register_method({
3513 name
=> 'resize_vm',
3514 path
=> '{vmid}/resize',
3518 description
=> "Extend volume size.",
3520 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3523 additionalProperties
=> 0,
3525 node
=> get_standard_option
('pve-node'),
3526 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3527 skiplock
=> get_standard_option
('skiplock'),
3530 description
=> "The disk you want to resize.",
3531 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3535 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3536 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.",
3540 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3546 returns
=> { type
=> 'null'},
3550 my $rpcenv = PVE
::RPCEnvironment
::get
();
3552 my $authuser = $rpcenv->get_user();
3554 my $node = extract_param
($param, 'node');
3556 my $vmid = extract_param
($param, 'vmid');
3558 my $digest = extract_param
($param, 'digest');
3560 my $disk = extract_param
($param, 'disk');
3562 my $sizestr = extract_param
($param, 'size');
3564 my $skiplock = extract_param
($param, 'skiplock');
3565 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3566 if $skiplock && $authuser ne 'root@pam';
3568 my $storecfg = PVE
::Storage
::config
();
3570 my $updatefn = sub {
3572 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3574 die "checksum missmatch (file change by other user?)\n"
3575 if $digest && $digest ne $conf->{digest
};
3576 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3578 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3580 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3582 my (undef, undef, undef, undef, undef, undef, $format) =
3583 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3585 die "can't resize volume: $disk if snapshot exists\n"
3586 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3588 my $volid = $drive->{file
};
3590 die "disk '$disk' has no associated volume\n" if !$volid;
3592 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3594 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3596 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3598 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3599 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3601 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3603 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3604 my ($ext, $newsize, $unit) = ($1, $2, $4);
3607 $newsize = $newsize * 1024;
3608 } elsif ($unit eq 'M') {
3609 $newsize = $newsize * 1024 * 1024;
3610 } elsif ($unit eq 'G') {
3611 $newsize = $newsize * 1024 * 1024 * 1024;
3612 } elsif ($unit eq 'T') {
3613 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3616 $newsize += $size if $ext;
3617 $newsize = int($newsize);
3619 die "shrinking disks is not supported\n" if $newsize < $size;
3621 return if $size == $newsize;
3623 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3625 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3627 my $effective_size = eval { PVE
::Storage
::volume_size_info
($storecfg, $volid, 3); };
3628 $drive->{size
} = $effective_size // $newsize;
3629 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
3631 PVE
::QemuConfig-
>write_config($vmid, $conf);
3634 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3638 __PACKAGE__-
>register_method({
3639 name
=> 'snapshot_list',
3640 path
=> '{vmid}/snapshot',
3642 description
=> "List all snapshots.",
3644 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3647 protected
=> 1, # qemu pid files are only readable by root
3649 additionalProperties
=> 0,
3651 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3652 node
=> get_standard_option
('pve-node'),
3661 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3665 description
=> "Snapshot includes RAM.",
3670 description
=> "Snapshot description.",
3674 description
=> "Snapshot creation time",
3676 renderer
=> 'timestamp',
3680 description
=> "Parent snapshot identifier.",
3686 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3691 my $vmid = $param->{vmid
};
3693 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3694 my $snaphash = $conf->{snapshots
} || {};
3698 foreach my $name (keys %$snaphash) {
3699 my $d = $snaphash->{$name};
3702 snaptime
=> $d->{snaptime
} || 0,
3703 vmstate
=> $d->{vmstate
} ?
1 : 0,
3704 description
=> $d->{description
} || '',
3706 $item->{parent
} = $d->{parent
} if $d->{parent
};
3707 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3711 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3714 digest
=> $conf->{digest
},
3715 running
=> $running,
3716 description
=> "You are here!",
3718 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3720 push @$res, $current;
3725 __PACKAGE__-
>register_method({
3727 path
=> '{vmid}/snapshot',
3731 description
=> "Snapshot a VM.",
3733 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3736 additionalProperties
=> 0,
3738 node
=> get_standard_option
('pve-node'),
3739 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3740 snapname
=> get_standard_option
('pve-snapshot-name'),
3744 description
=> "Save the vmstate",
3749 description
=> "A textual description or comment.",
3755 description
=> "the task ID.",
3760 my $rpcenv = PVE
::RPCEnvironment
::get
();
3762 my $authuser = $rpcenv->get_user();
3764 my $node = extract_param
($param, 'node');
3766 my $vmid = extract_param
($param, 'vmid');
3768 my $snapname = extract_param
($param, 'snapname');
3770 die "unable to use snapshot name 'current' (reserved name)\n"
3771 if $snapname eq 'current';
3773 die "unable to use snapshot name 'pending' (reserved name)\n"
3774 if lc($snapname) eq 'pending';
3777 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3778 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3779 $param->{description
});
3782 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3785 __PACKAGE__-
>register_method({
3786 name
=> 'snapshot_cmd_idx',
3787 path
=> '{vmid}/snapshot/{snapname}',
3794 additionalProperties
=> 0,
3796 vmid
=> get_standard_option
('pve-vmid'),
3797 node
=> get_standard_option
('pve-node'),
3798 snapname
=> get_standard_option
('pve-snapshot-name'),
3807 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3814 push @$res, { cmd
=> 'rollback' };
3815 push @$res, { cmd
=> 'config' };
3820 __PACKAGE__-
>register_method({
3821 name
=> 'update_snapshot_config',
3822 path
=> '{vmid}/snapshot/{snapname}/config',
3826 description
=> "Update snapshot metadata.",
3828 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3831 additionalProperties
=> 0,
3833 node
=> get_standard_option
('pve-node'),
3834 vmid
=> get_standard_option
('pve-vmid'),
3835 snapname
=> get_standard_option
('pve-snapshot-name'),
3839 description
=> "A textual description or comment.",
3843 returns
=> { type
=> 'null' },
3847 my $rpcenv = PVE
::RPCEnvironment
::get
();
3849 my $authuser = $rpcenv->get_user();
3851 my $vmid = extract_param
($param, 'vmid');
3853 my $snapname = extract_param
($param, 'snapname');
3855 return undef if !defined($param->{description
});
3857 my $updatefn = sub {
3859 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3861 PVE
::QemuConfig-
>check_lock($conf);
3863 my $snap = $conf->{snapshots
}->{$snapname};
3865 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3867 $snap->{description
} = $param->{description
} if defined($param->{description
});
3869 PVE
::QemuConfig-
>write_config($vmid, $conf);
3872 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3877 __PACKAGE__-
>register_method({
3878 name
=> 'get_snapshot_config',
3879 path
=> '{vmid}/snapshot/{snapname}/config',
3882 description
=> "Get snapshot configuration",
3884 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
3887 additionalProperties
=> 0,
3889 node
=> get_standard_option
('pve-node'),
3890 vmid
=> get_standard_option
('pve-vmid'),
3891 snapname
=> get_standard_option
('pve-snapshot-name'),
3894 returns
=> { type
=> "object" },
3898 my $rpcenv = PVE
::RPCEnvironment
::get
();
3900 my $authuser = $rpcenv->get_user();
3902 my $vmid = extract_param
($param, 'vmid');
3904 my $snapname = extract_param
($param, 'snapname');
3906 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3908 my $snap = $conf->{snapshots
}->{$snapname};
3910 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3915 __PACKAGE__-
>register_method({
3917 path
=> '{vmid}/snapshot/{snapname}/rollback',
3921 description
=> "Rollback VM state to specified snapshot.",
3923 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3926 additionalProperties
=> 0,
3928 node
=> get_standard_option
('pve-node'),
3929 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3930 snapname
=> get_standard_option
('pve-snapshot-name'),
3935 description
=> "the task ID.",
3940 my $rpcenv = PVE
::RPCEnvironment
::get
();
3942 my $authuser = $rpcenv->get_user();
3944 my $node = extract_param
($param, 'node');
3946 my $vmid = extract_param
($param, 'vmid');
3948 my $snapname = extract_param
($param, 'snapname');
3951 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3952 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3956 # hold migration lock, this makes sure that nobody create replication snapshots
3957 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3960 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3963 __PACKAGE__-
>register_method({
3964 name
=> 'delsnapshot',
3965 path
=> '{vmid}/snapshot/{snapname}',
3969 description
=> "Delete a VM snapshot.",
3971 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3974 additionalProperties
=> 0,
3976 node
=> get_standard_option
('pve-node'),
3977 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3978 snapname
=> get_standard_option
('pve-snapshot-name'),
3982 description
=> "For removal from config file, even if removing disk snapshots fails.",
3988 description
=> "the task ID.",
3993 my $rpcenv = PVE
::RPCEnvironment
::get
();
3995 my $authuser = $rpcenv->get_user();
3997 my $node = extract_param
($param, 'node');
3999 my $vmid = extract_param
($param, 'vmid');
4001 my $snapname = extract_param
($param, 'snapname');
4004 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
4005 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
4008 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
4011 __PACKAGE__-
>register_method({
4013 path
=> '{vmid}/template',
4017 description
=> "Create a Template.",
4019 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4020 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4023 additionalProperties
=> 0,
4025 node
=> get_standard_option
('pve-node'),
4026 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4030 description
=> "If you want to convert only 1 disk to base image.",
4031 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4036 returns
=> { type
=> 'null'},
4040 my $rpcenv = PVE
::RPCEnvironment
::get
();
4042 my $authuser = $rpcenv->get_user();
4044 my $node = extract_param
($param, 'node');
4046 my $vmid = extract_param
($param, 'vmid');
4048 my $disk = extract_param
($param, 'disk');
4050 my $updatefn = sub {
4052 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4054 PVE
::QemuConfig-
>check_lock($conf);
4056 die "unable to create template, because VM contains snapshots\n"
4057 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4059 die "you can't convert a template to a template\n"
4060 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4062 die "you can't convert a VM to template if VM is running\n"
4063 if PVE
::QemuServer
::check_running
($vmid);
4066 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4069 $conf->{template
} = 1;
4070 PVE
::QemuConfig-
>write_config($vmid, $conf);
4072 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4075 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4079 __PACKAGE__-
>register_method({
4080 name
=> 'cloudinit_generated_config_dump',
4081 path
=> '{vmid}/cloudinit/dump',
4084 description
=> "Get automatically generated cloudinit config.",
4086 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4089 additionalProperties
=> 0,
4091 node
=> get_standard_option
('pve-node'),
4092 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4094 description
=> 'Config type.',
4096 enum
=> ['user', 'network', 'meta'],
4106 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4108 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});