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