1 package PVE
::API2
::Qemu
;
11 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
14 use PVE
::Tools
qw(extract_param);
15 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
17 use PVE
::JSONSchema
qw(get_standard_option);
19 use PVE
::ReplicationConfig
;
20 use PVE
::GuestHelpers
;
23 use PVE
::QemuServer
::Drive
;
24 use PVE
::QemuServer
::Monitor
qw(mon_cmd);
26 use PVE
::RPCEnvironment
;
27 use PVE
::AccessControl
;
31 use PVE
::API2
::Firewall
::VM
;
32 use PVE
::API2
::Qemu
::Agent
;
33 use PVE
::VZDump
::Plugin
;
34 use PVE
::DataCenterConfig
;
38 if (!$ENV{PVE_GENERATING_DOCS
}) {
39 require PVE
::HA
::Env
::PVE2
;
40 import PVE
::HA
::Env
::PVE2
;
41 require PVE
::HA
::Config
;
42 import PVE
::HA
::Config
;
46 use Data
::Dumper
; # fixme: remove
48 use base
qw(PVE::RESTHandler);
50 my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal.";
52 my $resolve_cdrom_alias = sub {
55 if (my $value = $param->{cdrom
}) {
56 $value .= ",media=cdrom" if $value !~ m/media=/;
57 $param->{ide2
} = $value;
58 delete $param->{cdrom
};
62 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
63 my $check_storage_access = sub {
64 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
66 PVE
::QemuServer
::foreach_drive
($settings, sub {
67 my ($ds, $drive) = @_;
69 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
71 my $volid = $drive->{file
};
72 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
74 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit' || (defined($volname) && $volname eq 'cloudinit'))) {
76 } elsif ($isCDROM && ($volid eq 'cdrom')) {
77 $rpcenv->check($authuser, "/", ['Sys.Console']);
78 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
79 my ($storeid, $size) = ($2 || $default_storage, $3);
80 die "no storage ID specified (and no default storage)\n" if !$storeid;
81 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
82 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
83 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
84 if !$scfg->{content
}->{images
};
86 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
90 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
91 if defined($settings->{vmstatestorage
});
94 my $check_storage_access_clone = sub {
95 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
99 PVE
::QemuServer
::foreach_drive
($conf, sub {
100 my ($ds, $drive) = @_;
102 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
104 my $volid = $drive->{file
};
106 return if !$volid || $volid eq 'none';
109 if ($volid eq 'cdrom') {
110 $rpcenv->check($authuser, "/", ['Sys.Console']);
112 # we simply allow access
113 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
114 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
115 $sharedvm = 0 if !$scfg->{shared
};
119 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
120 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
121 $sharedvm = 0 if !$scfg->{shared
};
123 $sid = $storage if $storage;
124 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
128 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
129 if defined($conf->{vmstatestorage
});
134 # Note: $pool is only needed when creating a VM, because pool permissions
135 # are automatically inherited if VM already exists inside a pool.
136 my $create_disks = sub {
137 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
144 my ($ds, $disk) = @_;
146 my $volid = $disk->{file
};
147 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
149 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
150 delete $disk->{size
};
151 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
152 } elsif (defined($volname) && $volname eq 'cloudinit') {
153 $storeid = $storeid // $default_storage;
154 die "no storage ID specified (and no default storage)\n" if !$storeid;
155 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
156 my $name = "vm-$vmid-cloudinit";
160 $fmt = $disk->{format
} // "qcow2";
163 $fmt = $disk->{format
} // "raw";
166 # Initial disk created with 4 MB and aligned to 4MB on regeneration
167 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
168 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
169 $disk->{file
} = $volid;
170 $disk->{media
} = 'cdrom';
171 push @$vollist, $volid;
172 delete $disk->{format
}; # no longer needed
173 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
174 } elsif ($volid =~ $NEW_DISK_RE) {
175 my ($storeid, $size) = ($2 || $default_storage, $3);
176 die "no storage ID specified (and no default storage)\n" if !$storeid;
177 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
178 my $fmt = $disk->{format
} || $defformat;
180 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
183 if ($ds eq 'efidisk0') {
184 ($volid, $size) = PVE
::QemuServer
::create_efidisk
($storecfg, $storeid, $vmid, $fmt, $arch);
186 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
188 push @$vollist, $volid;
189 $disk->{file
} = $volid;
190 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
191 delete $disk->{format
}; # no longer needed
192 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
195 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
197 my $volid_is_new = 1;
200 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
201 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
206 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
208 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
210 die "volume $volid does not exist\n" if !$size;
212 $disk->{size
} = $size;
215 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
219 eval { PVE
::QemuServer
::foreach_drive
($settings, $code); };
221 # free allocated images on error
223 syslog
('err', "VM $vmid creating disks failed");
224 foreach my $volid (@$vollist) {
225 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
231 # modify vm config if everything went well
232 foreach my $ds (keys %$res) {
233 $conf->{$ds} = $res->{$ds};
250 my $memoryoptions = {
256 my $hwtypeoptions = {
269 my $generaloptions = {
276 'migrate_downtime' => 1,
277 'migrate_speed' => 1,
290 my $vmpoweroptions = {
297 'vmstatestorage' => 1,
300 my $cloudinitoptions = {
310 my $check_vm_modify_config_perm = sub {
311 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
313 return 1 if $authuser eq 'root@pam';
315 foreach my $opt (@$key_list) {
316 # some checks (e.g., disk, serial port, usb) need to be done somewhere
317 # else, as there the permission can be value dependend
318 next if PVE
::QemuServer
::is_valid_drivename
($opt);
319 next if $opt eq 'cdrom';
320 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
323 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
324 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
325 } elsif ($memoryoptions->{$opt}) {
326 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
327 } elsif ($hwtypeoptions->{$opt}) {
328 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
329 } elsif ($generaloptions->{$opt}) {
330 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
331 # special case for startup since it changes host behaviour
332 if ($opt eq 'startup') {
333 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
335 } elsif ($vmpoweroptions->{$opt}) {
336 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
337 } elsif ($diskoptions->{$opt}) {
338 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
339 } elsif ($cloudinitoptions->{$opt} || ($opt =~ m/^(?:net|ipconfig)\d+$/)) {
340 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
341 } elsif ($opt eq 'vmstate') {
342 # the user needs Disk and PowerMgmt privileges to change the vmstate
343 # also needs privileges on the storage, that will be checked later
344 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt' ]);
346 # catches hostpci\d+, args, lock, etc.
347 # new options will be checked here
348 die "only root can set '$opt' config\n";
355 __PACKAGE__-
>register_method({
359 description
=> "Virtual machine index (per node).",
361 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
365 protected
=> 1, # qemu pid files are only readable by root
367 additionalProperties
=> 0,
369 node
=> get_standard_option
('pve-node'),
373 description
=> "Determine the full status of active VMs.",
381 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
383 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
388 my $rpcenv = PVE
::RPCEnvironment
::get
();
389 my $authuser = $rpcenv->get_user();
391 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
394 foreach my $vmid (keys %$vmstatus) {
395 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
397 my $data = $vmstatus->{$vmid};
404 my $parse_restore_archive = sub {
405 my ($storecfg, $archive) = @_;
407 my ($archive_storeid, $archive_volname) = PVE
::Storage
::parse_volume_id
($archive);
409 if (defined($archive_storeid)) {
410 my $scfg = PVE
::Storage
::storage_config
($storecfg, $archive_storeid);
411 if ($scfg->{type
} eq 'pbs') {
418 my $path = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
426 __PACKAGE__-
>register_method({
430 description
=> "Create or restore a virtual machine.",
432 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
433 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
434 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
435 user
=> 'all', # check inside
440 additionalProperties
=> 0,
441 properties
=> PVE
::QemuServer
::json_config_properties
(
443 node
=> get_standard_option
('pve-node'),
444 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
446 description
=> "The backup archive. Either the file system path to a .tar or .vma file (use '-' to pipe data from stdin) or a proxmox storage backup volume identifier.",
450 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
452 storage
=> get_standard_option
('pve-storage-id', {
453 description
=> "Default storage.",
455 completion
=> \
&PVE
::QemuServer
::complete_storage
,
460 description
=> "Allow to overwrite existing VM.",
461 requires
=> 'archive',
466 description
=> "Assign a unique random ethernet address.",
467 requires
=> 'archive',
471 type
=> 'string', format
=> 'pve-poolid',
472 description
=> "Add the VM to the specified pool.",
475 description
=> "Override I/O bandwidth limit (in KiB/s).",
479 default => 'restore limit from datacenter or storage config',
485 description
=> "Start VM after it was created successfully.",
495 my $rpcenv = PVE
::RPCEnvironment
::get
();
496 my $authuser = $rpcenv->get_user();
498 my $node = extract_param
($param, 'node');
499 my $vmid = extract_param
($param, 'vmid');
501 my $archive = extract_param
($param, 'archive');
502 my $is_restore = !!$archive;
504 my $bwlimit = extract_param
($param, 'bwlimit');
505 my $force = extract_param
($param, 'force');
506 my $pool = extract_param
($param, 'pool');
507 my $start_after_create = extract_param
($param, 'start');
508 my $storage = extract_param
($param, 'storage');
509 my $unique = extract_param
($param, 'unique');
511 if (defined(my $ssh_keys = $param->{sshkeys
})) {
512 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
513 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
516 PVE
::Cluster
::check_cfs_quorum
();
518 my $filename = PVE
::QemuConfig-
>config_file($vmid);
519 my $storecfg = PVE
::Storage
::config
();
521 if (defined($pool)) {
522 $rpcenv->check_pool_exist($pool);
525 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
526 if defined($storage);
528 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
530 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
532 } elsif ($archive && $force && (-f
$filename) &&
533 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
534 # OK: user has VM.Backup permissions, and want to restore an existing VM
540 &$resolve_cdrom_alias($param);
542 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
544 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
546 foreach my $opt (keys %$param) {
547 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
548 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
549 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
551 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
552 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
556 PVE
::QemuServer
::add_random_macs
($param);
558 my $keystr = join(' ', keys %$param);
559 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
561 if ($archive eq '-') {
562 die "pipe requires cli environment\n"
563 if $rpcenv->{type
} ne 'cli';
564 $archive = { type
=> 'pipe' };
566 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
568 $archive = $parse_restore_archive->($storecfg, $archive);
572 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
574 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
575 die "$emsg $@" if $@;
577 my $restorefn = sub {
578 my $conf = PVE
::QemuConfig-
>load_config($vmid);
580 PVE
::QemuConfig-
>check_protection($conf, $emsg);
582 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
585 my $restore_options = {
591 if ($archive->{type
} eq 'file' || $archive->{type
} eq 'pipe') {
592 PVE
::QemuServer
::restore_file_archive
($archive->{path
} // '-', $vmid, $authuser, $restore_options);
593 } elsif ($archive->{type
} eq 'pbs') {
594 PVE
::QemuServer
::restore_proxmox_backup_archive
($archive->{volid
}, $vmid, $authuser, $restore_options);
596 die "unknown backup archive type\n";
598 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
599 # Convert restored VM to template if backup was VM template
600 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
601 warn "Convert to template.\n";
602 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
606 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
609 # ensure no old replication state are exists
610 PVE
::ReplicationState
::delete_guest_states
($vmid);
612 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
614 if ($start_after_create) {
615 print "Execute autostart\n";
616 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
622 # ensure no old replication state are exists
623 PVE
::ReplicationState
::delete_guest_states
($vmid);
627 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
631 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
633 if (!$conf->{bootdisk
}) {
634 my $firstdisk = PVE
::QemuServer
::Drive
::resolve_first_disk
($conf);
635 $conf->{bootdisk
} = $firstdisk if $firstdisk;
638 # auto generate uuid if user did not specify smbios1 option
639 if (!$conf->{smbios1
}) {
640 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
643 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
644 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
647 PVE
::QemuConfig-
>write_config($vmid, $conf);
653 foreach my $volid (@$vollist) {
654 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
660 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
663 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
665 if ($start_after_create) {
666 print "Execute autostart\n";
667 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
672 my ($code, $worker_name);
674 $worker_name = 'qmrestore';
676 eval { $restorefn->() };
678 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
684 $worker_name = 'qmcreate';
686 eval { $createfn->() };
689 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
690 unlink($conffile) or die "failed to remove config file: $!\n";
698 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
701 __PACKAGE__-
>register_method({
706 description
=> "Directory index",
711 additionalProperties
=> 0,
713 node
=> get_standard_option
('pve-node'),
714 vmid
=> get_standard_option
('pve-vmid'),
722 subdir
=> { type
=> 'string' },
725 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
731 { subdir
=> 'config' },
732 { subdir
=> 'pending' },
733 { subdir
=> 'status' },
734 { subdir
=> 'unlink' },
735 { subdir
=> 'vncproxy' },
736 { subdir
=> 'termproxy' },
737 { subdir
=> 'migrate' },
738 { subdir
=> 'resize' },
739 { subdir
=> 'move' },
741 { subdir
=> 'rrddata' },
742 { subdir
=> 'monitor' },
743 { subdir
=> 'agent' },
744 { subdir
=> 'snapshot' },
745 { subdir
=> 'spiceproxy' },
746 { subdir
=> 'sendkey' },
747 { subdir
=> 'firewall' },
753 __PACKAGE__-
>register_method ({
754 subclass
=> "PVE::API2::Firewall::VM",
755 path
=> '{vmid}/firewall',
758 __PACKAGE__-
>register_method ({
759 subclass
=> "PVE::API2::Qemu::Agent",
760 path
=> '{vmid}/agent',
763 __PACKAGE__-
>register_method({
765 path
=> '{vmid}/rrd',
767 protected
=> 1, # fixme: can we avoid that?
769 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
771 description
=> "Read VM RRD statistics (returns PNG)",
773 additionalProperties
=> 0,
775 node
=> get_standard_option
('pve-node'),
776 vmid
=> get_standard_option
('pve-vmid'),
778 description
=> "Specify the time frame you are interested in.",
780 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
783 description
=> "The list of datasources you want to display.",
784 type
=> 'string', format
=> 'pve-configid-list',
787 description
=> "The RRD consolidation function",
789 enum
=> [ 'AVERAGE', 'MAX' ],
797 filename
=> { type
=> 'string' },
803 return PVE
::RRD
::create_rrd_graph
(
804 "pve2-vm/$param->{vmid}", $param->{timeframe
},
805 $param->{ds
}, $param->{cf
});
809 __PACKAGE__-
>register_method({
811 path
=> '{vmid}/rrddata',
813 protected
=> 1, # fixme: can we avoid that?
815 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
817 description
=> "Read VM RRD statistics",
819 additionalProperties
=> 0,
821 node
=> get_standard_option
('pve-node'),
822 vmid
=> get_standard_option
('pve-vmid'),
824 description
=> "Specify the time frame you are interested in.",
826 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
829 description
=> "The RRD consolidation function",
831 enum
=> [ 'AVERAGE', 'MAX' ],
846 return PVE
::RRD
::create_rrd_data
(
847 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
851 __PACKAGE__-
>register_method({
853 path
=> '{vmid}/config',
856 description
=> "Get the virtual machine configuration with pending configuration " .
857 "changes applied. Set the 'current' parameter to get the current configuration instead.",
859 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
862 additionalProperties
=> 0,
864 node
=> get_standard_option
('pve-node'),
865 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
867 description
=> "Get current values (instead of pending values).",
872 snapshot
=> get_standard_option
('pve-snapshot-name', {
873 description
=> "Fetch config values from given snapshot.",
876 my ($cmd, $pname, $cur, $args) = @_;
877 PVE
::QemuConfig-
>snapshot_list($args->[0]);
883 description
=> "The VM configuration.",
885 properties
=> PVE
::QemuServer
::json_config_properties
({
888 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
895 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
896 current
=> "cannot use 'snapshot' parameter with 'current'"})
897 if ($param->{snapshot
} && $param->{current
});
900 if ($param->{snapshot
}) {
901 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
903 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
905 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
910 __PACKAGE__-
>register_method({
911 name
=> 'vm_pending',
912 path
=> '{vmid}/pending',
915 description
=> "Get the virtual machine configuration with both current and pending values.",
917 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
920 additionalProperties
=> 0,
922 node
=> get_standard_option
('pve-node'),
923 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
932 description
=> "Configuration option name.",
936 description
=> "Current value.",
941 description
=> "Pending value.",
946 description
=> "Indicates a pending delete request if present and not 0. " .
947 "The value 2 indicates a force-delete request.",
959 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
961 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
963 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
964 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
966 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
969 # POST/PUT {vmid}/config implementation
971 # The original API used PUT (idempotent) an we assumed that all operations
972 # are fast. But it turned out that almost any configuration change can
973 # involve hot-plug actions, or disk alloc/free. Such actions can take long
974 # time to complete and have side effects (not idempotent).
976 # The new implementation uses POST and forks a worker process. We added
977 # a new option 'background_delay'. If specified we wait up to
978 # 'background_delay' second for the worker task to complete. It returns null
979 # if the task is finished within that time, else we return the UPID.
981 my $update_vm_api = sub {
982 my ($param, $sync) = @_;
984 my $rpcenv = PVE
::RPCEnvironment
::get
();
986 my $authuser = $rpcenv->get_user();
988 my $node = extract_param
($param, 'node');
990 my $vmid = extract_param
($param, 'vmid');
992 my $digest = extract_param
($param, 'digest');
994 my $background_delay = extract_param
($param, 'background_delay');
996 if (defined(my $cipassword = $param->{cipassword
})) {
997 # Same logic as in cloud-init (but with the regex fixed...)
998 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
999 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1002 my @paramarr = (); # used for log message
1003 foreach my $key (sort keys %$param) {
1004 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1005 push @paramarr, "-$key", $value;
1008 my $skiplock = extract_param
($param, 'skiplock');
1009 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1010 if $skiplock && $authuser ne 'root@pam';
1012 my $delete_str = extract_param
($param, 'delete');
1014 my $revert_str = extract_param
($param, 'revert');
1016 my $force = extract_param
($param, 'force');
1018 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1019 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1020 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1023 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1025 my $storecfg = PVE
::Storage
::config
();
1027 my $defaults = PVE
::QemuServer
::load_defaults
();
1029 &$resolve_cdrom_alias($param);
1031 # now try to verify all parameters
1034 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1035 if (!PVE
::QemuServer
::option_exists
($opt)) {
1036 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1039 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1040 "-revert $opt' at the same time" })
1041 if defined($param->{$opt});
1043 $revert->{$opt} = 1;
1047 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1048 $opt = 'ide2' if $opt eq 'cdrom';
1050 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1051 "-delete $opt' at the same time" })
1052 if defined($param->{$opt});
1054 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1055 "-revert $opt' at the same time" })
1058 if (!PVE
::QemuServer
::option_exists
($opt)) {
1059 raise_param_exc
({ delete => "unknown option '$opt'" });
1065 my $repl_conf = PVE
::ReplicationConfig-
>new();
1066 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1067 my $check_replication = sub {
1069 return if !$is_replicated;
1070 my $volid = $drive->{file
};
1071 return if !$volid || !($drive->{replicate
}//1);
1072 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1074 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1075 return if $volname eq 'cloudinit';
1078 if ($volid =~ $NEW_DISK_RE) {
1080 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1082 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1084 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1085 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1086 return if $scfg->{shared
};
1087 die "cannot add non-replicatable volume to a replicated VM\n";
1090 foreach my $opt (keys %$param) {
1091 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1092 # cleanup drive path
1093 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1094 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1095 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1096 $check_replication->($drive);
1097 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
1098 } elsif ($opt =~ m/^net(\d+)$/) {
1100 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1101 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1102 } elsif ($opt eq 'vmgenid') {
1103 if ($param->{$opt} eq '1') {
1104 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1106 } elsif ($opt eq 'hookscript') {
1107 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1108 raise_param_exc
({ $opt => $@ }) if $@;
1112 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1114 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1116 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1118 my $updatefn = sub {
1120 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1122 die "checksum missmatch (file change by other user?)\n"
1123 if $digest && $digest ne $conf->{digest
};
1125 # FIXME: 'suspended' lock should probabyl be a state or "weak" lock?!
1126 if (scalar(@delete) && grep { $_ eq 'vmstate'} @delete) {
1127 if (defined($conf->{lock}) && $conf->{lock} eq 'suspended') {
1128 delete $conf->{lock}; # for check lock check, not written out
1129 push @delete, 'lock'; # this is the real deal to write it out
1131 push @delete, 'runningmachine' if $conf->{runningmachine
};
1134 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1136 foreach my $opt (keys %$revert) {
1137 if (defined($conf->{$opt})) {
1138 $param->{$opt} = $conf->{$opt};
1139 } elsif (defined($conf->{pending
}->{$opt})) {
1144 if ($param->{memory
} || defined($param->{balloon
})) {
1145 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1146 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1148 die "balloon value too large (must be smaller than assigned memory)\n"
1149 if $balloon && $balloon > $maxmem;
1152 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1156 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1158 # write updates to pending section
1160 my $modified = {}; # record what $option we modify
1162 foreach my $opt (@delete) {
1163 $modified->{$opt} = 1;
1164 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1166 # value of what we want to delete, independent if pending or not
1167 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1168 if (!defined($val)) {
1169 warn "cannot delete '$opt' - not set in current configuration!\n";
1170 $modified->{$opt} = 0;
1173 my $is_pending_val = defined($conf->{pending
}->{$opt});
1174 delete $conf->{pending
}->{$opt};
1176 if ($opt =~ m/^unused/) {
1177 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1178 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1179 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1180 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1181 delete $conf->{$opt};
1182 PVE
::QemuConfig-
>write_config($vmid, $conf);
1184 } elsif ($opt eq 'vmstate') {
1185 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1186 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1187 delete $conf->{$opt};
1188 PVE
::QemuConfig-
>write_config($vmid, $conf);
1190 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1191 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1192 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1193 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1195 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1196 PVE
::QemuConfig-
>write_config($vmid, $conf);
1197 } elsif ($opt =~ m/^serial\d+$/) {
1198 if ($val eq 'socket') {
1199 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1200 } elsif ($authuser ne 'root@pam') {
1201 die "only root can delete '$opt' config for real devices\n";
1203 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1204 PVE
::QemuConfig-
>write_config($vmid, $conf);
1205 } elsif ($opt =~ m/^usb\d+$/) {
1206 if ($val =~ m/spice/) {
1207 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1208 } elsif ($authuser ne 'root@pam') {
1209 die "only root can delete '$opt' config for real devices\n";
1211 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1212 PVE
::QemuConfig-
>write_config($vmid, $conf);
1214 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1215 PVE
::QemuConfig-
>write_config($vmid, $conf);
1219 foreach my $opt (keys %$param) { # add/change
1220 $modified->{$opt} = 1;
1221 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1222 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1224 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1226 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1227 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1228 # FIXME: cloudinit: CDROM or Disk?
1229 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1230 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1232 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1234 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1235 if defined($conf->{pending
}->{$opt});
1237 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1238 } elsif ($opt =~ m/^serial\d+/) {
1239 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1240 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1241 } elsif ($authuser ne 'root@pam') {
1242 die "only root can modify '$opt' config for real devices\n";
1244 $conf->{pending
}->{$opt} = $param->{$opt};
1245 } elsif ($opt =~ m/^usb\d+/) {
1246 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1247 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1248 } elsif ($authuser ne 'root@pam') {
1249 die "only root can modify '$opt' config for real devices\n";
1251 $conf->{pending
}->{$opt} = $param->{$opt};
1253 $conf->{pending
}->{$opt} = $param->{$opt};
1255 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1256 PVE
::QemuConfig-
>write_config($vmid, $conf);
1259 # remove pending changes when nothing changed
1260 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1261 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1262 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1264 return if !scalar(keys %{$conf->{pending
}});
1266 my $running = PVE
::QemuServer
::check_running
($vmid);
1268 # apply pending changes
1270 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1274 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1276 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running, $errors);
1278 raise_param_exc
($errors) if scalar(keys %$errors);
1287 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1289 if ($background_delay) {
1291 # Note: It would be better to do that in the Event based HTTPServer
1292 # to avoid blocking call to sleep.
1294 my $end_time = time() + $background_delay;
1296 my $task = PVE
::Tools
::upid_decode
($upid);
1299 while (time() < $end_time) {
1300 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1302 sleep(1); # this gets interrupted when child process ends
1306 my $status = PVE
::Tools
::upid_read_status
($upid);
1307 return undef if $status eq 'OK';
1316 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1319 my $vm_config_perm_list = [
1324 'VM.Config.Network',
1326 'VM.Config.Options',
1329 __PACKAGE__-
>register_method({
1330 name
=> 'update_vm_async',
1331 path
=> '{vmid}/config',
1335 description
=> "Set virtual machine options (asynchrounous API).",
1337 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1340 additionalProperties
=> 0,
1341 properties
=> PVE
::QemuServer
::json_config_properties
(
1343 node
=> get_standard_option
('pve-node'),
1344 vmid
=> get_standard_option
('pve-vmid'),
1345 skiplock
=> get_standard_option
('skiplock'),
1347 type
=> 'string', format
=> 'pve-configid-list',
1348 description
=> "A list of settings you want to delete.",
1352 type
=> 'string', format
=> 'pve-configid-list',
1353 description
=> "Revert a pending change.",
1358 description
=> $opt_force_description,
1360 requires
=> 'delete',
1364 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1368 background_delay
=> {
1370 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1381 code
=> $update_vm_api,
1384 __PACKAGE__-
>register_method({
1385 name
=> 'update_vm',
1386 path
=> '{vmid}/config',
1390 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1392 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1395 additionalProperties
=> 0,
1396 properties
=> PVE
::QemuServer
::json_config_properties
(
1398 node
=> get_standard_option
('pve-node'),
1399 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1400 skiplock
=> get_standard_option
('skiplock'),
1402 type
=> 'string', format
=> 'pve-configid-list',
1403 description
=> "A list of settings you want to delete.",
1407 type
=> 'string', format
=> 'pve-configid-list',
1408 description
=> "Revert a pending change.",
1413 description
=> $opt_force_description,
1415 requires
=> 'delete',
1419 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1425 returns
=> { type
=> 'null' },
1428 &$update_vm_api($param, 1);
1433 __PACKAGE__-
>register_method({
1434 name
=> 'destroy_vm',
1439 description
=> "Destroy the vm (also delete all used/owned volumes).",
1441 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1444 additionalProperties
=> 0,
1446 node
=> get_standard_option
('pve-node'),
1447 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1448 skiplock
=> get_standard_option
('skiplock'),
1451 description
=> "Remove vmid from backup cron jobs.",
1462 my $rpcenv = PVE
::RPCEnvironment
::get
();
1463 my $authuser = $rpcenv->get_user();
1464 my $vmid = $param->{vmid
};
1466 my $skiplock = $param->{skiplock
};
1467 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1468 if $skiplock && $authuser ne 'root@pam';
1471 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1472 my $storecfg = PVE
::Storage
::config
();
1473 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1475 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("vm:$vmid");
1477 if (!$param->{purge
}) {
1478 die "unable to remove VM $vmid - used in HA resources and purge parameter not set.\n"
1480 # don't allow destroy if with replication jobs but no purge param
1481 my $repl_conf = PVE
::ReplicationConfig-
>new();
1482 $repl_conf->check_for_existing_jobs($vmid);
1485 # early tests (repeat after locking)
1486 die "VM $vmid is running - destroy failed\n"
1487 if PVE
::QemuServer
::check_running
($vmid);
1492 syslog
('info', "destroy VM $vmid: $upid\n");
1493 PVE
::QemuConfig-
>lock_config($vmid, sub {
1494 die "VM $vmid is running - destroy failed\n"
1495 if (PVE
::QemuServer
::check_running
($vmid));
1497 PVE
::QemuServer
::destroy_vm
($storecfg, $vmid, $skiplock, { lock => 'destroyed' });
1499 PVE
::AccessControl
::remove_vm_access
($vmid);
1500 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1501 if ($param->{purge
}) {
1502 print "purging VM $vmid from related configurations..\n";
1503 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1504 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1507 PVE
::HA
::Config
::delete_service_from_config
("vm:$vmid");
1508 print "NOTE: removed VM $vmid from HA resource configuration.\n";
1512 # only now remove the zombie config, else we can have reuse race
1513 PVE
::QemuConfig-
>destroy_config($vmid);
1517 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1520 __PACKAGE__-
>register_method({
1522 path
=> '{vmid}/unlink',
1526 description
=> "Unlink/delete disk images.",
1528 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1531 additionalProperties
=> 0,
1533 node
=> get_standard_option
('pve-node'),
1534 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1536 type
=> 'string', format
=> 'pve-configid-list',
1537 description
=> "A list of disk IDs you want to delete.",
1541 description
=> $opt_force_description,
1546 returns
=> { type
=> 'null'},
1550 $param->{delete} = extract_param
($param, 'idlist');
1552 __PACKAGE__-
>update_vm($param);
1559 __PACKAGE__-
>register_method({
1561 path
=> '{vmid}/vncproxy',
1565 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1567 description
=> "Creates a TCP VNC proxy connections.",
1569 additionalProperties
=> 0,
1571 node
=> get_standard_option
('pve-node'),
1572 vmid
=> get_standard_option
('pve-vmid'),
1576 description
=> "starts websockify instead of vncproxy",
1581 additionalProperties
=> 0,
1583 user
=> { type
=> 'string' },
1584 ticket
=> { type
=> 'string' },
1585 cert
=> { type
=> 'string' },
1586 port
=> { type
=> 'integer' },
1587 upid
=> { type
=> 'string' },
1593 my $rpcenv = PVE
::RPCEnvironment
::get
();
1595 my $authuser = $rpcenv->get_user();
1597 my $vmid = $param->{vmid
};
1598 my $node = $param->{node
};
1599 my $websocket = $param->{websocket
};
1601 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1602 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1604 my $authpath = "/vms/$vmid";
1606 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1608 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1614 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1615 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1616 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1617 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1618 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1620 $family = PVE
::Tools
::get_host_address_family
($node);
1623 my $port = PVE
::Tools
::next_vnc_port
($family);
1630 syslog
('info', "starting vnc proxy $upid\n");
1636 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1638 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1639 '-timeout', $timeout, '-authpath', $authpath,
1640 '-perm', 'Sys.Console'];
1642 if ($param->{websocket
}) {
1643 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1644 push @$cmd, '-notls', '-listen', 'localhost';
1647 push @$cmd, '-c', @$remcmd, @$termcmd;
1649 PVE
::Tools
::run_command
($cmd);
1653 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1655 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1657 my $sock = IO
::Socket
::IP-
>new(
1662 GetAddrInfoFlags
=> 0,
1663 ) or die "failed to create socket: $!\n";
1664 # Inside the worker we shouldn't have any previous alarms
1665 # running anyway...:
1667 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1669 accept(my $cli, $sock) or die "connection failed: $!\n";
1672 if (PVE
::Tools
::run_command
($cmd,
1673 output
=> '>&'.fileno($cli),
1674 input
=> '<&'.fileno($cli),
1677 die "Failed to run vncproxy.\n";
1684 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1686 PVE
::Tools
::wait_for_vnc_port
($port);
1697 __PACKAGE__-
>register_method({
1698 name
=> 'termproxy',
1699 path
=> '{vmid}/termproxy',
1703 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1705 description
=> "Creates a TCP proxy connections.",
1707 additionalProperties
=> 0,
1709 node
=> get_standard_option
('pve-node'),
1710 vmid
=> get_standard_option
('pve-vmid'),
1714 enum
=> [qw(serial0 serial1 serial2 serial3)],
1715 description
=> "opens a serial terminal (defaults to display)",
1720 additionalProperties
=> 0,
1722 user
=> { type
=> 'string' },
1723 ticket
=> { type
=> 'string' },
1724 port
=> { type
=> 'integer' },
1725 upid
=> { type
=> 'string' },
1731 my $rpcenv = PVE
::RPCEnvironment
::get
();
1733 my $authuser = $rpcenv->get_user();
1735 my $vmid = $param->{vmid
};
1736 my $node = $param->{node
};
1737 my $serial = $param->{serial
};
1739 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1741 if (!defined($serial)) {
1742 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1743 $serial = $conf->{vga
};
1747 my $authpath = "/vms/$vmid";
1749 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1754 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1755 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1756 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1757 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1758 push @$remcmd, '--';
1760 $family = PVE
::Tools
::get_host_address_family
($node);
1763 my $port = PVE
::Tools
::next_vnc_port
($family);
1765 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1766 push @$termcmd, '-iface', $serial if $serial;
1771 syslog
('info', "starting qemu termproxy $upid\n");
1773 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1774 '--perm', 'VM.Console', '--'];
1775 push @$cmd, @$remcmd, @$termcmd;
1777 PVE
::Tools
::run_command
($cmd);
1780 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1782 PVE
::Tools
::wait_for_vnc_port
($port);
1792 __PACKAGE__-
>register_method({
1793 name
=> 'vncwebsocket',
1794 path
=> '{vmid}/vncwebsocket',
1797 description
=> "You also need to pass a valid ticket (vncticket).",
1798 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1800 description
=> "Opens a weksocket for VNC traffic.",
1802 additionalProperties
=> 0,
1804 node
=> get_standard_option
('pve-node'),
1805 vmid
=> get_standard_option
('pve-vmid'),
1807 description
=> "Ticket from previous call to vncproxy.",
1812 description
=> "Port number returned by previous vncproxy call.",
1822 port
=> { type
=> 'string' },
1828 my $rpcenv = PVE
::RPCEnvironment
::get
();
1830 my $authuser = $rpcenv->get_user();
1832 my $vmid = $param->{vmid
};
1833 my $node = $param->{node
};
1835 my $authpath = "/vms/$vmid";
1837 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1839 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1841 # Note: VNC ports are acessible from outside, so we do not gain any
1842 # security if we verify that $param->{port} belongs to VM $vmid. This
1843 # check is done by verifying the VNC ticket (inside VNC protocol).
1845 my $port = $param->{port
};
1847 return { port
=> $port };
1850 __PACKAGE__-
>register_method({
1851 name
=> 'spiceproxy',
1852 path
=> '{vmid}/spiceproxy',
1857 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1859 description
=> "Returns a SPICE configuration to connect to the VM.",
1861 additionalProperties
=> 0,
1863 node
=> get_standard_option
('pve-node'),
1864 vmid
=> get_standard_option
('pve-vmid'),
1865 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1868 returns
=> get_standard_option
('remote-viewer-config'),
1872 my $rpcenv = PVE
::RPCEnvironment
::get
();
1874 my $authuser = $rpcenv->get_user();
1876 my $vmid = $param->{vmid
};
1877 my $node = $param->{node
};
1878 my $proxy = $param->{proxy
};
1880 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1881 my $title = "VM $vmid";
1882 $title .= " - ". $conf->{name
} if $conf->{name
};
1884 my $port = PVE
::QemuServer
::spice_port
($vmid);
1886 my ($ticket, undef, $remote_viewer_config) =
1887 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1889 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1890 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1892 return $remote_viewer_config;
1895 __PACKAGE__-
>register_method({
1897 path
=> '{vmid}/status',
1900 description
=> "Directory index",
1905 additionalProperties
=> 0,
1907 node
=> get_standard_option
('pve-node'),
1908 vmid
=> get_standard_option
('pve-vmid'),
1916 subdir
=> { type
=> 'string' },
1919 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1925 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1928 { subdir
=> 'current' },
1929 { subdir
=> 'start' },
1930 { subdir
=> 'stop' },
1931 { subdir
=> 'reset' },
1932 { subdir
=> 'shutdown' },
1933 { subdir
=> 'suspend' },
1934 { subdir
=> 'reboot' },
1940 __PACKAGE__-
>register_method({
1941 name
=> 'vm_status',
1942 path
=> '{vmid}/status/current',
1945 protected
=> 1, # qemu pid files are only readable by root
1946 description
=> "Get virtual machine status.",
1948 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1951 additionalProperties
=> 0,
1953 node
=> get_standard_option
('pve-node'),
1954 vmid
=> get_standard_option
('pve-vmid'),
1960 %$PVE::QemuServer
::vmstatus_return_properties
,
1962 description
=> "HA manager service status.",
1966 description
=> "Qemu VGA configuration supports spice.",
1971 description
=> "Qemu GuestAgent enabled in config.",
1981 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1983 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1984 my $status = $vmstatus->{$param->{vmid
}};
1986 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1988 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1989 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1994 __PACKAGE__-
>register_method({
1996 path
=> '{vmid}/status/start',
2000 description
=> "Start virtual machine.",
2002 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2005 additionalProperties
=> 0,
2007 node
=> get_standard_option
('pve-node'),
2008 vmid
=> get_standard_option
('pve-vmid',
2009 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2010 skiplock
=> get_standard_option
('skiplock'),
2011 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2012 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2015 enum
=> ['secure', 'insecure'],
2016 description
=> "Migration traffic is encrypted using an SSH " .
2017 "tunnel by default. On secure, completely private networks " .
2018 "this can be disabled to increase performance.",
2021 migration_network
=> {
2022 type
=> 'string', format
=> 'CIDR',
2023 description
=> "CIDR of the (sub) network that is used for migration.",
2026 machine
=> get_standard_option
('pve-qemu-machine'),
2028 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
2033 description
=> "Wait maximal timeout seconds.",
2036 default => 'max(30, vm memory in GiB)',
2047 my $rpcenv = PVE
::RPCEnvironment
::get
();
2048 my $authuser = $rpcenv->get_user();
2050 my $node = extract_param
($param, 'node');
2051 my $vmid = extract_param
($param, 'vmid');
2052 my $timeout = extract_param
($param, 'timeout');
2054 my $machine = extract_param
($param, 'machine');
2056 my $get_root_param = sub {
2057 my $value = extract_param
($param, $_[0]);
2058 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2059 if $value && $authuser ne 'root@pam';
2063 my $stateuri = $get_root_param->('stateuri');
2064 my $skiplock = $get_root_param->('skiplock');
2065 my $migratedfrom = $get_root_param->('migratedfrom');
2066 my $migration_type = $get_root_param->('migration_type');
2067 my $migration_network = $get_root_param->('migration_network');
2068 my $targetstorage = $get_root_param->('targetstorage');
2070 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2071 if $targetstorage && !$migratedfrom;
2073 # read spice ticket from STDIN
2075 my $nbd_protocol_version = 0;
2076 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2077 while (defined(my $line = <STDIN
>)) {
2079 if ($line =~ m/^spice_ticket: (.+)$/) {
2081 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2082 $nbd_protocol_version = $1;
2084 # fallback for old source node
2085 $spice_ticket = $line;
2090 PVE
::Cluster
::check_cfs_quorum
();
2092 my $storecfg = PVE
::Storage
::config
();
2094 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2098 print "Requesting HA start for VM $vmid\n";
2100 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2101 PVE
::Tools
::run_command
($cmd);
2105 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2112 syslog
('info', "start VM $vmid: $upid\n");
2114 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef, $machine,
2115 $spice_ticket, $migration_network, $migration_type, $targetstorage, $timeout,
2116 $nbd_protocol_version);
2120 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2124 __PACKAGE__-
>register_method({
2126 path
=> '{vmid}/status/stop',
2130 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2131 "is akin to pulling the power plug of a running computer and may damage the VM data",
2133 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2136 additionalProperties
=> 0,
2138 node
=> get_standard_option
('pve-node'),
2139 vmid
=> get_standard_option
('pve-vmid',
2140 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2141 skiplock
=> get_standard_option
('skiplock'),
2142 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2144 description
=> "Wait maximal timeout seconds.",
2150 description
=> "Do not deactivate storage volumes.",
2163 my $rpcenv = PVE
::RPCEnvironment
::get
();
2164 my $authuser = $rpcenv->get_user();
2166 my $node = extract_param
($param, 'node');
2167 my $vmid = extract_param
($param, 'vmid');
2169 my $skiplock = extract_param
($param, 'skiplock');
2170 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2171 if $skiplock && $authuser ne 'root@pam';
2173 my $keepActive = extract_param
($param, 'keepActive');
2174 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2175 if $keepActive && $authuser ne 'root@pam';
2177 my $migratedfrom = extract_param
($param, 'migratedfrom');
2178 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2179 if $migratedfrom && $authuser ne 'root@pam';
2182 my $storecfg = PVE
::Storage
::config
();
2184 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2189 print "Requesting HA stop for VM $vmid\n";
2191 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2192 PVE
::Tools
::run_command
($cmd);
2196 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2202 syslog
('info', "stop VM $vmid: $upid\n");
2204 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2205 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2209 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2213 __PACKAGE__-
>register_method({
2215 path
=> '{vmid}/status/reset',
2219 description
=> "Reset virtual machine.",
2221 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2224 additionalProperties
=> 0,
2226 node
=> get_standard_option
('pve-node'),
2227 vmid
=> get_standard_option
('pve-vmid',
2228 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2229 skiplock
=> get_standard_option
('skiplock'),
2238 my $rpcenv = PVE
::RPCEnvironment
::get
();
2240 my $authuser = $rpcenv->get_user();
2242 my $node = extract_param
($param, 'node');
2244 my $vmid = extract_param
($param, 'vmid');
2246 my $skiplock = extract_param
($param, 'skiplock');
2247 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2248 if $skiplock && $authuser ne 'root@pam';
2250 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2255 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2260 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2263 __PACKAGE__-
>register_method({
2264 name
=> 'vm_shutdown',
2265 path
=> '{vmid}/status/shutdown',
2269 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2270 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2272 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2275 additionalProperties
=> 0,
2277 node
=> get_standard_option
('pve-node'),
2278 vmid
=> get_standard_option
('pve-vmid',
2279 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2280 skiplock
=> get_standard_option
('skiplock'),
2282 description
=> "Wait maximal timeout seconds.",
2288 description
=> "Make sure the VM stops.",
2294 description
=> "Do not deactivate storage volumes.",
2307 my $rpcenv = PVE
::RPCEnvironment
::get
();
2308 my $authuser = $rpcenv->get_user();
2310 my $node = extract_param
($param, 'node');
2311 my $vmid = extract_param
($param, 'vmid');
2313 my $skiplock = extract_param
($param, 'skiplock');
2314 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2315 if $skiplock && $authuser ne 'root@pam';
2317 my $keepActive = extract_param
($param, 'keepActive');
2318 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2319 if $keepActive && $authuser ne 'root@pam';
2321 my $storecfg = PVE
::Storage
::config
();
2325 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2326 # otherwise, we will infer a shutdown command, but run into the timeout,
2327 # then when the vm is resumed, it will instantly shutdown
2329 # checking the qmp status here to get feedback to the gui/cli/api
2330 # and the status query should not take too long
2331 my $qmpstatus = eval {
2332 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2333 mon_cmd
($vmid, "query-status");
2337 if (!$err && $qmpstatus->{status
} eq "paused") {
2338 if ($param->{forceStop
}) {
2339 warn "VM is paused - stop instead of shutdown\n";
2342 die "VM is paused - cannot shutdown\n";
2346 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2348 my $timeout = $param->{timeout
} // 60;
2352 print "Requesting HA stop for VM $vmid\n";
2354 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2355 PVE
::Tools
::run_command
($cmd);
2359 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2366 syslog
('info', "shutdown VM $vmid: $upid\n");
2368 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2369 $shutdown, $param->{forceStop
}, $keepActive);
2373 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2377 __PACKAGE__-
>register_method({
2378 name
=> 'vm_reboot',
2379 path
=> '{vmid}/status/reboot',
2383 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2385 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2388 additionalProperties
=> 0,
2390 node
=> get_standard_option
('pve-node'),
2391 vmid
=> get_standard_option
('pve-vmid',
2392 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2394 description
=> "Wait maximal timeout seconds for the shutdown.",
2407 my $rpcenv = PVE
::RPCEnvironment
::get
();
2408 my $authuser = $rpcenv->get_user();
2410 my $node = extract_param
($param, 'node');
2411 my $vmid = extract_param
($param, 'vmid');
2413 my $qmpstatus = eval {
2414 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2415 mon_cmd
($vmid, "query-status");
2419 if (!$err && $qmpstatus->{status
} eq "paused") {
2420 die "VM is paused - cannot shutdown\n";
2423 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2428 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2429 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2433 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2436 __PACKAGE__-
>register_method({
2437 name
=> 'vm_suspend',
2438 path
=> '{vmid}/status/suspend',
2442 description
=> "Suspend virtual machine.",
2444 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2445 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2446 " on the storage for the vmstate.",
2447 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2450 additionalProperties
=> 0,
2452 node
=> get_standard_option
('pve-node'),
2453 vmid
=> get_standard_option
('pve-vmid',
2454 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2455 skiplock
=> get_standard_option
('skiplock'),
2460 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2462 statestorage
=> get_standard_option
('pve-storage-id', {
2463 description
=> "The storage for the VM state",
2464 requires
=> 'todisk',
2466 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2476 my $rpcenv = PVE
::RPCEnvironment
::get
();
2477 my $authuser = $rpcenv->get_user();
2479 my $node = extract_param
($param, 'node');
2480 my $vmid = extract_param
($param, 'vmid');
2482 my $todisk = extract_param
($param, 'todisk') // 0;
2484 my $statestorage = extract_param
($param, 'statestorage');
2486 my $skiplock = extract_param
($param, 'skiplock');
2487 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2488 if $skiplock && $authuser ne 'root@pam';
2490 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2492 die "Cannot suspend HA managed VM to disk\n"
2493 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2495 # early check for storage permission, for better user feedback
2497 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2499 if (!$statestorage) {
2500 # get statestorage from config if none is given
2501 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2502 my $storecfg = PVE
::Storage
::config
();
2503 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2506 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2512 syslog
('info', "suspend VM $vmid: $upid\n");
2514 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2519 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2520 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2523 __PACKAGE__-
>register_method({
2524 name
=> 'vm_resume',
2525 path
=> '{vmid}/status/resume',
2529 description
=> "Resume virtual machine.",
2531 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2534 additionalProperties
=> 0,
2536 node
=> get_standard_option
('pve-node'),
2537 vmid
=> get_standard_option
('pve-vmid',
2538 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2539 skiplock
=> get_standard_option
('skiplock'),
2540 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2550 my $rpcenv = PVE
::RPCEnvironment
::get
();
2552 my $authuser = $rpcenv->get_user();
2554 my $node = extract_param
($param, 'node');
2556 my $vmid = extract_param
($param, 'vmid');
2558 my $skiplock = extract_param
($param, 'skiplock');
2559 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2560 if $skiplock && $authuser ne 'root@pam';
2562 my $nocheck = extract_param
($param, 'nocheck');
2564 my $to_disk_suspended;
2566 PVE
::QemuConfig-
>lock_config($vmid, sub {
2567 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2568 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2572 die "VM $vmid not running\n"
2573 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2578 syslog
('info', "resume VM $vmid: $upid\n");
2580 if (!$to_disk_suspended) {
2581 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2583 my $storecfg = PVE
::Storage
::config
();
2584 PVE
::QemuServer
::vm_start
($storecfg, $vmid, undef, $skiplock);
2590 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2593 __PACKAGE__-
>register_method({
2594 name
=> 'vm_sendkey',
2595 path
=> '{vmid}/sendkey',
2599 description
=> "Send key event to virtual machine.",
2601 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2604 additionalProperties
=> 0,
2606 node
=> get_standard_option
('pve-node'),
2607 vmid
=> get_standard_option
('pve-vmid',
2608 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2609 skiplock
=> get_standard_option
('skiplock'),
2611 description
=> "The key (qemu monitor encoding).",
2616 returns
=> { type
=> 'null'},
2620 my $rpcenv = PVE
::RPCEnvironment
::get
();
2622 my $authuser = $rpcenv->get_user();
2624 my $node = extract_param
($param, 'node');
2626 my $vmid = extract_param
($param, 'vmid');
2628 my $skiplock = extract_param
($param, 'skiplock');
2629 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2630 if $skiplock && $authuser ne 'root@pam';
2632 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2637 __PACKAGE__-
>register_method({
2638 name
=> 'vm_feature',
2639 path
=> '{vmid}/feature',
2643 description
=> "Check if feature for virtual machine is available.",
2645 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2648 additionalProperties
=> 0,
2650 node
=> get_standard_option
('pve-node'),
2651 vmid
=> get_standard_option
('pve-vmid'),
2653 description
=> "Feature to check.",
2655 enum
=> [ 'snapshot', 'clone', 'copy' ],
2657 snapname
=> get_standard_option
('pve-snapshot-name', {
2665 hasFeature
=> { type
=> 'boolean' },
2668 items
=> { type
=> 'string' },
2675 my $node = extract_param
($param, 'node');
2677 my $vmid = extract_param
($param, 'vmid');
2679 my $snapname = extract_param
($param, 'snapname');
2681 my $feature = extract_param
($param, 'feature');
2683 my $running = PVE
::QemuServer
::check_running
($vmid);
2685 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2688 my $snap = $conf->{snapshots
}->{$snapname};
2689 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2692 my $storecfg = PVE
::Storage
::config
();
2694 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2695 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2698 hasFeature
=> $hasFeature,
2699 nodes
=> [ keys %$nodelist ],
2703 __PACKAGE__-
>register_method({
2705 path
=> '{vmid}/clone',
2709 description
=> "Create a copy of virtual machine/template.",
2711 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2712 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2713 "'Datastore.AllocateSpace' on any used storage.",
2716 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2718 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2719 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2724 additionalProperties
=> 0,
2726 node
=> get_standard_option
('pve-node'),
2727 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2728 newid
=> get_standard_option
('pve-vmid', {
2729 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2730 description
=> 'VMID for the clone.' }),
2733 type
=> 'string', format
=> 'dns-name',
2734 description
=> "Set a name for the new VM.",
2739 description
=> "Description for the new VM.",
2743 type
=> 'string', format
=> 'pve-poolid',
2744 description
=> "Add the new VM to the specified pool.",
2746 snapname
=> get_standard_option
('pve-snapshot-name', {
2749 storage
=> get_standard_option
('pve-storage-id', {
2750 description
=> "Target storage for full clone.",
2754 description
=> "Target format for file storage. Only valid for full clone.",
2757 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2762 description
=> "Create a full copy of all disks. This is always done when " .
2763 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2765 target
=> get_standard_option
('pve-node', {
2766 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2770 description
=> "Override I/O bandwidth limit (in KiB/s).",
2774 default => 'clone limit from datacenter or storage config',
2784 my $rpcenv = PVE
::RPCEnvironment
::get
();
2785 my $authuser = $rpcenv->get_user();
2787 my $node = extract_param
($param, 'node');
2788 my $vmid = extract_param
($param, 'vmid');
2789 my $newid = extract_param
($param, 'newid');
2790 my $pool = extract_param
($param, 'pool');
2791 $rpcenv->check_pool_exist($pool) if defined($pool);
2793 my $snapname = extract_param
($param, 'snapname');
2794 my $storage = extract_param
($param, 'storage');
2795 my $format = extract_param
($param, 'format');
2796 my $target = extract_param
($param, 'target');
2798 my $localnode = PVE
::INotify
::nodename
();
2800 if ($target && ($target eq $localnode || $target eq 'localhost')) {
2804 PVE
::Cluster
::check_node_exists
($target) if $target;
2806 my $storecfg = PVE
::Storage
::config
();
2809 # check if storage is enabled on local node
2810 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2812 # check if storage is available on target node
2813 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2814 # clone only works if target storage is shared
2815 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2816 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2820 PVE
::Cluster
::check_cfs_quorum
();
2822 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2824 # exclusive lock if VM is running - else shared lock is enough;
2825 my $shared_lock = $running ?
0 : 1;
2828 # do all tests after lock but before forking worker - if possible
2830 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2831 PVE
::QemuConfig-
>check_lock($conf);
2833 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2834 die "unexpected state change\n" if $verify_running != $running;
2836 die "snapshot '$snapname' does not exist\n"
2837 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2839 my $full = extract_param
($param, 'full') // !PVE
::QemuConfig-
>is_template($conf);
2841 die "parameter 'storage' not allowed for linked clones\n"
2842 if defined($storage) && !$full;
2844 die "parameter 'format' not allowed for linked clones\n"
2845 if defined($format) && !$full;
2847 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2849 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2851 die "can't clone VM to node '$target' (VM uses local storage)\n"
2852 if $target && !$sharedvm;
2854 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2855 die "unable to create VM $newid: config file already exists\n"
2858 my $newconf = { lock => 'clone' };
2863 foreach my $opt (keys %$oldconf) {
2864 my $value = $oldconf->{$opt};
2866 # do not copy snapshot related info
2867 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2868 $opt eq 'vmstate' || $opt eq 'snapstate';
2870 # no need to copy unused images, because VMID(owner) changes anyways
2871 next if $opt =~ m/^unused\d+$/;
2873 # always change MAC! address
2874 if ($opt =~ m/^net(\d+)$/) {
2875 my $net = PVE
::QemuServer
::parse_net
($value);
2876 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2877 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2878 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2879 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2880 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2881 die "unable to parse drive options for '$opt'\n" if !$drive;
2882 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2883 $newconf->{$opt} = $value; # simply copy configuration
2885 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2886 die "Full clone feature is not supported for drive '$opt'\n"
2887 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2888 $fullclone->{$opt} = 1;
2890 # not full means clone instead of copy
2891 die "Linked clone feature is not supported for drive '$opt'\n"
2892 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2894 $drives->{$opt} = $drive;
2895 push @$vollist, $drive->{file
};
2898 # copy everything else
2899 $newconf->{$opt} = $value;
2903 # auto generate a new uuid
2904 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2905 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2906 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2907 # auto generate a new vmgenid only if the option was set for template
2908 if ($newconf->{vmgenid
}) {
2909 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2912 delete $newconf->{template
};
2914 if ($param->{name
}) {
2915 $newconf->{name
} = $param->{name
};
2917 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
2920 if ($param->{description
}) {
2921 $newconf->{description
} = $param->{description
};
2924 # create empty/temp config - this fails if VM already exists on other node
2925 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
2926 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2931 my $newvollist = [];
2938 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2940 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2942 my $bwlimit = extract_param
($param, 'bwlimit');
2944 my $total_jobs = scalar(keys %{$drives});
2947 foreach my $opt (keys %$drives) {
2948 my $drive = $drives->{$opt};
2949 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2950 my $completion = $skipcomplete ?
'skip' : 'wait';
2952 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
2953 my $storage_list = [ $src_sid ];
2954 push @$storage_list, $storage if defined($storage);
2955 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
2957 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2958 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2959 $jobs, $completion, $oldconf->{agent
}, $clonelimit);
2961 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
2963 PVE
::QemuConfig-
>write_config($newid, $newconf);
2967 delete $newconf->{lock};
2969 # do not write pending changes
2970 if (my @changes = keys %{$newconf->{pending
}}) {
2971 my $pending = join(',', @changes);
2972 warn "found pending changes for '$pending', discarding for clone\n";
2973 delete $newconf->{pending
};
2976 PVE
::QemuConfig-
>write_config($newid, $newconf);
2979 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2980 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2981 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2983 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2984 die "Failed to move config to node '$target' - rename failed: $!\n"
2985 if !rename($conffile, $newconffile);
2988 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2991 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2992 sleep 1; # some storage like rbd need to wait before release volume - really?
2994 foreach my $volid (@$newvollist) {
2995 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2999 PVE
::Firewall
::remove_vmfw_conf
($newid);
3001 unlink $conffile; # avoid races -> last thing before die
3003 die "clone failed: $err";
3009 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3011 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
3014 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
3015 # Aquire exclusive lock lock for $newid
3016 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3021 __PACKAGE__-
>register_method({
3022 name
=> 'move_vm_disk',
3023 path
=> '{vmid}/move_disk',
3027 description
=> "Move volume to different storage.",
3029 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
3031 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3032 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
3036 additionalProperties
=> 0,
3038 node
=> get_standard_option
('pve-node'),
3039 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3042 description
=> "The disk you want to move.",
3043 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3045 storage
=> get_standard_option
('pve-storage-id', {
3046 description
=> "Target storage.",
3047 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3051 description
=> "Target Format.",
3052 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3057 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3063 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3068 description
=> "Override I/O bandwidth limit (in KiB/s).",
3072 default => 'move limit from datacenter or storage config',
3078 description
=> "the task ID.",
3083 my $rpcenv = PVE
::RPCEnvironment
::get
();
3084 my $authuser = $rpcenv->get_user();
3086 my $node = extract_param
($param, 'node');
3087 my $vmid = extract_param
($param, 'vmid');
3088 my $digest = extract_param
($param, 'digest');
3089 my $disk = extract_param
($param, 'disk');
3090 my $storeid = extract_param
($param, 'storage');
3091 my $format = extract_param
($param, 'format');
3093 my $storecfg = PVE
::Storage
::config
();
3095 my $updatefn = sub {
3096 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3097 PVE
::QemuConfig-
>check_lock($conf);
3099 die "VM config checksum missmatch (file change by other user?)\n"
3100 if $digest && $digest ne $conf->{digest
};
3102 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3104 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3106 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3107 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3109 my $old_volid = $drive->{file
};
3111 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3112 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3116 die "you can't move to the same storage with same format\n" if $oldstoreid eq $storeid &&
3117 (!$format || !$oldfmt || $oldfmt eq $format);
3119 # this only checks snapshots because $disk is passed!
3120 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3121 die "you can't move a disk with snapshots and delete the source\n"
3122 if $snapshotted && $param->{delete};
3124 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3126 my $running = PVE
::QemuServer
::check_running
($vmid);
3128 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3131 my $newvollist = [];
3137 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3139 warn "moving disk with snapshots, snapshots will not be moved!\n"
3142 my $bwlimit = extract_param
($param, 'bwlimit');
3143 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3145 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3146 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit);
3148 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3150 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3152 # convert moved disk to base if part of template
3153 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3154 if PVE
::QemuConfig-
>is_template($conf);
3156 PVE
::QemuConfig-
>write_config($vmid, $conf);
3158 my $do_trim = PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
};
3159 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3160 eval { mon_cmd
($vmid, "guest-fstrim") };
3164 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3165 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3171 foreach my $volid (@$newvollist) {
3172 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3175 die "storage migration failed: $err";
3178 if ($param->{delete}) {
3180 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3181 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3187 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3190 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3193 my $check_vm_disks_local = sub {
3194 my ($storecfg, $vmconf, $vmid) = @_;
3196 my $local_disks = {};
3198 # add some more information to the disks e.g. cdrom
3199 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3200 my ($volid, $attr) = @_;
3202 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3204 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3205 return if $scfg->{shared
};
3207 # The shared attr here is just a special case where the vdisk
3208 # is marked as shared manually
3209 return if $attr->{shared
};
3210 return if $attr->{cdrom
} and $volid eq "none";
3212 if (exists $local_disks->{$volid}) {
3213 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3215 $local_disks->{$volid} = $attr;
3216 # ensure volid is present in case it's needed
3217 $local_disks->{$volid}->{volid
} = $volid;
3221 return $local_disks;
3224 __PACKAGE__-
>register_method({
3225 name
=> 'migrate_vm_precondition',
3226 path
=> '{vmid}/migrate',
3230 description
=> "Get preconditions for migration.",
3232 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3235 additionalProperties
=> 0,
3237 node
=> get_standard_option
('pve-node'),
3238 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3239 target
=> get_standard_option
('pve-node', {
3240 description
=> "Target node.",
3241 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3249 running
=> { type
=> 'boolean' },
3253 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3255 not_allowed_nodes
=> {
3258 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3262 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3264 local_resources
=> {
3266 description
=> "List local resources e.g. pci, usb"
3273 my $rpcenv = PVE
::RPCEnvironment
::get
();
3275 my $authuser = $rpcenv->get_user();
3277 PVE
::Cluster
::check_cfs_quorum
();
3281 my $vmid = extract_param
($param, 'vmid');
3282 my $target = extract_param
($param, 'target');
3283 my $localnode = PVE
::INotify
::nodename
();
3287 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3288 my $storecfg = PVE
::Storage
::config
();
3291 # try to detect errors early
3292 PVE
::QemuConfig-
>check_lock($vmconf);
3294 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3296 # if vm is not running, return target nodes where local storage is available
3297 # for offline migration
3298 if (!$res->{running
}) {
3299 $res->{allowed_nodes
} = [];
3300 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3301 delete $checked_nodes->{$localnode};
3303 foreach my $node (keys %$checked_nodes) {
3304 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3305 push @{$res->{allowed_nodes
}}, $node;
3309 $res->{not_allowed_nodes
} = $checked_nodes;
3313 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3314 $res->{local_disks
} = [ values %$local_disks ];;
3316 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3318 $res->{local_resources
} = $local_resources;
3325 __PACKAGE__-
>register_method({
3326 name
=> 'migrate_vm',
3327 path
=> '{vmid}/migrate',
3331 description
=> "Migrate virtual machine. Creates a new migration task.",
3333 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3336 additionalProperties
=> 0,
3338 node
=> get_standard_option
('pve-node'),
3339 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3340 target
=> get_standard_option
('pve-node', {
3341 description
=> "Target node.",
3342 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3346 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3351 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3356 enum
=> ['secure', 'insecure'],
3357 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3360 migration_network
=> {
3361 type
=> 'string', format
=> 'CIDR',
3362 description
=> "CIDR of the (sub) network that is used for migration.",
3365 "with-local-disks" => {
3367 description
=> "Enable live storage migration for local disk",
3370 targetstorage
=> get_standard_option
('pve-storage-id', {
3371 description
=> "Default target storage.",
3373 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3376 description
=> "Override I/O bandwidth limit (in KiB/s).",
3380 default => 'migrate limit from datacenter or storage config',
3386 description
=> "the task ID.",
3391 my $rpcenv = PVE
::RPCEnvironment
::get
();
3392 my $authuser = $rpcenv->get_user();
3394 my $target = extract_param
($param, 'target');
3396 my $localnode = PVE
::INotify
::nodename
();
3397 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3399 PVE
::Cluster
::check_cfs_quorum
();
3401 PVE
::Cluster
::check_node_exists
($target);
3403 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3405 my $vmid = extract_param
($param, 'vmid');
3407 raise_param_exc
({ force
=> "Only root may use this option." })
3408 if $param->{force
} && $authuser ne 'root@pam';
3410 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3411 if $param->{migration_type
} && $authuser ne 'root@pam';
3413 # allow root only until better network permissions are available
3414 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3415 if $param->{migration_network
} && $authuser ne 'root@pam';
3418 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3420 # try to detect errors early
3422 PVE
::QemuConfig-
>check_lock($conf);
3424 if (PVE
::QemuServer
::check_running
($vmid)) {
3425 die "can't migrate running VM without --online\n" if !$param->{online
};
3427 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
3428 $param->{online
} = 0;
3431 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3432 if !$param->{online
} && $param->{targetstorage
};
3434 my $storecfg = PVE
::Storage
::config
();
3436 if( $param->{targetstorage
}) {
3437 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3439 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3442 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3447 print "Requesting HA migration for VM $vmid to node $target\n";
3449 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3450 PVE
::Tools
::run_command
($cmd);
3454 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3459 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3463 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3466 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3471 __PACKAGE__-
>register_method({
3473 path
=> '{vmid}/monitor',
3477 description
=> "Execute Qemu monitor commands.",
3479 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3480 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3483 additionalProperties
=> 0,
3485 node
=> get_standard_option
('pve-node'),
3486 vmid
=> get_standard_option
('pve-vmid'),
3489 description
=> "The monitor command.",
3493 returns
=> { type
=> 'string'},
3497 my $rpcenv = PVE
::RPCEnvironment
::get
();
3498 my $authuser = $rpcenv->get_user();
3501 my $command = shift;
3502 return $command =~ m/^\s*info(\s+|$)/
3503 || $command =~ m/^\s*help\s*$/;
3506 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3507 if !&$is_ro($param->{command
});
3509 my $vmid = $param->{vmid
};
3511 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3515 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3517 $res = "ERROR: $@" if $@;
3522 __PACKAGE__-
>register_method({
3523 name
=> 'resize_vm',
3524 path
=> '{vmid}/resize',
3528 description
=> "Extend volume size.",
3530 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3533 additionalProperties
=> 0,
3535 node
=> get_standard_option
('pve-node'),
3536 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3537 skiplock
=> get_standard_option
('skiplock'),
3540 description
=> "The disk you want to resize.",
3541 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3545 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3546 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.",
3550 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3556 returns
=> { type
=> 'null'},
3560 my $rpcenv = PVE
::RPCEnvironment
::get
();
3562 my $authuser = $rpcenv->get_user();
3564 my $node = extract_param
($param, 'node');
3566 my $vmid = extract_param
($param, 'vmid');
3568 my $digest = extract_param
($param, 'digest');
3570 my $disk = extract_param
($param, 'disk');
3572 my $sizestr = extract_param
($param, 'size');
3574 my $skiplock = extract_param
($param, 'skiplock');
3575 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3576 if $skiplock && $authuser ne 'root@pam';
3578 my $storecfg = PVE
::Storage
::config
();
3580 my $updatefn = sub {
3582 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3584 die "checksum missmatch (file change by other user?)\n"
3585 if $digest && $digest ne $conf->{digest
};
3586 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3588 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3590 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3592 my (undef, undef, undef, undef, undef, undef, $format) =
3593 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3595 die "can't resize volume: $disk if snapshot exists\n"
3596 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3598 my $volid = $drive->{file
};
3600 die "disk '$disk' has no associated volume\n" if !$volid;
3602 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3604 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3606 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3608 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3609 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3611 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3613 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3614 my ($ext, $newsize, $unit) = ($1, $2, $4);
3617 $newsize = $newsize * 1024;
3618 } elsif ($unit eq 'M') {
3619 $newsize = $newsize * 1024 * 1024;
3620 } elsif ($unit eq 'G') {
3621 $newsize = $newsize * 1024 * 1024 * 1024;
3622 } elsif ($unit eq 'T') {
3623 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3626 $newsize += $size if $ext;
3627 $newsize = int($newsize);
3629 die "shrinking disks is not supported\n" if $newsize < $size;
3631 return if $size == $newsize;
3633 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3635 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3637 my $effective_size = eval { PVE
::Storage
::volume_size_info
($storecfg, $volid, 3); };
3638 $drive->{size
} = $effective_size // $newsize;
3639 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
3641 PVE
::QemuConfig-
>write_config($vmid, $conf);
3644 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3648 __PACKAGE__-
>register_method({
3649 name
=> 'snapshot_list',
3650 path
=> '{vmid}/snapshot',
3652 description
=> "List all snapshots.",
3654 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3657 protected
=> 1, # qemu pid files are only readable by root
3659 additionalProperties
=> 0,
3661 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3662 node
=> get_standard_option
('pve-node'),
3671 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3675 description
=> "Snapshot includes RAM.",
3680 description
=> "Snapshot description.",
3684 description
=> "Snapshot creation time",
3686 renderer
=> 'timestamp',
3690 description
=> "Parent snapshot identifier.",
3696 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3701 my $vmid = $param->{vmid
};
3703 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3704 my $snaphash = $conf->{snapshots
} || {};
3708 foreach my $name (keys %$snaphash) {
3709 my $d = $snaphash->{$name};
3712 snaptime
=> $d->{snaptime
} || 0,
3713 vmstate
=> $d->{vmstate
} ?
1 : 0,
3714 description
=> $d->{description
} || '',
3716 $item->{parent
} = $d->{parent
} if $d->{parent
};
3717 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3721 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3724 digest
=> $conf->{digest
},
3725 running
=> $running,
3726 description
=> "You are here!",
3728 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3730 push @$res, $current;
3735 __PACKAGE__-
>register_method({
3737 path
=> '{vmid}/snapshot',
3741 description
=> "Snapshot a VM.",
3743 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3746 additionalProperties
=> 0,
3748 node
=> get_standard_option
('pve-node'),
3749 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3750 snapname
=> get_standard_option
('pve-snapshot-name'),
3754 description
=> "Save the vmstate",
3759 description
=> "A textual description or comment.",
3765 description
=> "the task ID.",
3770 my $rpcenv = PVE
::RPCEnvironment
::get
();
3772 my $authuser = $rpcenv->get_user();
3774 my $node = extract_param
($param, 'node');
3776 my $vmid = extract_param
($param, 'vmid');
3778 my $snapname = extract_param
($param, 'snapname');
3780 die "unable to use snapshot name 'current' (reserved name)\n"
3781 if $snapname eq 'current';
3783 die "unable to use snapshot name 'pending' (reserved name)\n"
3784 if lc($snapname) eq 'pending';
3787 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3788 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3789 $param->{description
});
3792 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3795 __PACKAGE__-
>register_method({
3796 name
=> 'snapshot_cmd_idx',
3797 path
=> '{vmid}/snapshot/{snapname}',
3804 additionalProperties
=> 0,
3806 vmid
=> get_standard_option
('pve-vmid'),
3807 node
=> get_standard_option
('pve-node'),
3808 snapname
=> get_standard_option
('pve-snapshot-name'),
3817 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3824 push @$res, { cmd
=> 'rollback' };
3825 push @$res, { cmd
=> 'config' };
3830 __PACKAGE__-
>register_method({
3831 name
=> 'update_snapshot_config',
3832 path
=> '{vmid}/snapshot/{snapname}/config',
3836 description
=> "Update snapshot metadata.",
3838 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3841 additionalProperties
=> 0,
3843 node
=> get_standard_option
('pve-node'),
3844 vmid
=> get_standard_option
('pve-vmid'),
3845 snapname
=> get_standard_option
('pve-snapshot-name'),
3849 description
=> "A textual description or comment.",
3853 returns
=> { type
=> 'null' },
3857 my $rpcenv = PVE
::RPCEnvironment
::get
();
3859 my $authuser = $rpcenv->get_user();
3861 my $vmid = extract_param
($param, 'vmid');
3863 my $snapname = extract_param
($param, 'snapname');
3865 return undef if !defined($param->{description
});
3867 my $updatefn = sub {
3869 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3871 PVE
::QemuConfig-
>check_lock($conf);
3873 my $snap = $conf->{snapshots
}->{$snapname};
3875 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3877 $snap->{description
} = $param->{description
} if defined($param->{description
});
3879 PVE
::QemuConfig-
>write_config($vmid, $conf);
3882 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3887 __PACKAGE__-
>register_method({
3888 name
=> 'get_snapshot_config',
3889 path
=> '{vmid}/snapshot/{snapname}/config',
3892 description
=> "Get snapshot configuration",
3894 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
3897 additionalProperties
=> 0,
3899 node
=> get_standard_option
('pve-node'),
3900 vmid
=> get_standard_option
('pve-vmid'),
3901 snapname
=> get_standard_option
('pve-snapshot-name'),
3904 returns
=> { type
=> "object" },
3908 my $rpcenv = PVE
::RPCEnvironment
::get
();
3910 my $authuser = $rpcenv->get_user();
3912 my $vmid = extract_param
($param, 'vmid');
3914 my $snapname = extract_param
($param, 'snapname');
3916 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3918 my $snap = $conf->{snapshots
}->{$snapname};
3920 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3925 __PACKAGE__-
>register_method({
3927 path
=> '{vmid}/snapshot/{snapname}/rollback',
3931 description
=> "Rollback VM state to specified snapshot.",
3933 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3936 additionalProperties
=> 0,
3938 node
=> get_standard_option
('pve-node'),
3939 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3940 snapname
=> get_standard_option
('pve-snapshot-name'),
3945 description
=> "the task ID.",
3950 my $rpcenv = PVE
::RPCEnvironment
::get
();
3952 my $authuser = $rpcenv->get_user();
3954 my $node = extract_param
($param, 'node');
3956 my $vmid = extract_param
($param, 'vmid');
3958 my $snapname = extract_param
($param, 'snapname');
3961 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3962 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3966 # hold migration lock, this makes sure that nobody create replication snapshots
3967 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3970 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3973 __PACKAGE__-
>register_method({
3974 name
=> 'delsnapshot',
3975 path
=> '{vmid}/snapshot/{snapname}',
3979 description
=> "Delete a VM snapshot.",
3981 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3984 additionalProperties
=> 0,
3986 node
=> get_standard_option
('pve-node'),
3987 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3988 snapname
=> get_standard_option
('pve-snapshot-name'),
3992 description
=> "For removal from config file, even if removing disk snapshots fails.",
3998 description
=> "the task ID.",
4003 my $rpcenv = PVE
::RPCEnvironment
::get
();
4005 my $authuser = $rpcenv->get_user();
4007 my $node = extract_param
($param, 'node');
4009 my $vmid = extract_param
($param, 'vmid');
4011 my $snapname = extract_param
($param, 'snapname');
4014 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
4015 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
4018 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
4021 __PACKAGE__-
>register_method({
4023 path
=> '{vmid}/template',
4027 description
=> "Create a Template.",
4029 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4030 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4033 additionalProperties
=> 0,
4035 node
=> get_standard_option
('pve-node'),
4036 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4040 description
=> "If you want to convert only 1 disk to base image.",
4041 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4046 returns
=> { type
=> 'null'},
4050 my $rpcenv = PVE
::RPCEnvironment
::get
();
4052 my $authuser = $rpcenv->get_user();
4054 my $node = extract_param
($param, 'node');
4056 my $vmid = extract_param
($param, 'vmid');
4058 my $disk = extract_param
($param, 'disk');
4060 my $updatefn = sub {
4062 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4064 PVE
::QemuConfig-
>check_lock($conf);
4066 die "unable to create template, because VM contains snapshots\n"
4067 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4069 die "you can't convert a template to a template\n"
4070 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4072 die "you can't convert a VM to template if VM is running\n"
4073 if PVE
::QemuServer
::check_running
($vmid);
4076 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4079 $conf->{template
} = 1;
4080 PVE
::QemuConfig-
>write_config($vmid, $conf);
4082 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4085 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4089 __PACKAGE__-
>register_method({
4090 name
=> 'cloudinit_generated_config_dump',
4091 path
=> '{vmid}/cloudinit/dump',
4094 description
=> "Get automatically generated cloudinit config.",
4096 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4099 additionalProperties
=> 0,
4101 node
=> get_standard_option
('pve-node'),
4102 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4104 description
=> 'Config type.',
4106 enum
=> ['user', 'network', 'meta'],
4116 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4118 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});