1 package PVE
::API2
::Qemu
;
9 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
11 use PVE
::Tools
qw(extract_param);
12 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
14 use PVE
::JSONSchema
qw(get_standard_option);
18 use PVE
::RPCEnvironment
;
19 use PVE
::AccessControl
;
22 use PVE
::API2
::Firewall
::VM
;
24 use Data
::Dumper
; # fixme: remove
26 use base
qw(PVE::RESTHandler);
28 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.";
30 my $resolve_cdrom_alias = sub {
33 if (my $value = $param->{cdrom
}) {
34 $value .= ",media=cdrom" if $value !~ m/media=/;
35 $param->{ide2
} = $value;
36 delete $param->{cdrom
};
40 my $test_deallocate_drive = sub {
41 my ($storecfg, $vmid, $key, $drive, $force) = @_;
43 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
44 my $volid = $drive->{file
};
45 if ( PVE
::QemuServer
::vm_is_volid_owner
($storecfg, $vmid, $volid)) {
46 if ($force || $key =~ m/^unused/) {
47 my $sid = PVE
::Storage
::parse_volume_id
($volid);
56 my $check_storage_access = sub {
57 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
59 PVE
::QemuServer
::foreach_drive
($settings, sub {
60 my ($ds, $drive) = @_;
62 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
64 my $volid = $drive->{file
};
66 if (!$volid || $volid eq 'none') {
68 } elsif ($isCDROM && ($volid eq 'cdrom')) {
69 $rpcenv->check($authuser, "/", ['Sys.Console']);
70 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
71 my ($storeid, $size) = ($2 || $default_storage, $3);
72 die "no storage ID specified (and no default storage)\n" if !$storeid;
73 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
75 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
80 my $check_storage_access_clone = sub {
81 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
85 PVE
::QemuServer
::foreach_drive
($conf, sub {
86 my ($ds, $drive) = @_;
88 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
90 my $volid = $drive->{file
};
92 return if !$volid || $volid eq 'none';
95 if ($volid eq 'cdrom') {
96 $rpcenv->check($authuser, "/", ['Sys.Console']);
98 # we simply allow access
99 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
100 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
101 $sharedvm = 0 if !$scfg->{shared
};
105 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
106 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
107 $sharedvm = 0 if !$scfg->{shared
};
109 $sid = $storage if $storage;
110 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
117 # Note: $pool is only needed when creating a VM, because pool permissions
118 # are automatically inherited if VM already exists inside a pool.
119 my $create_disks = sub {
120 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
125 PVE
::QemuServer
::foreach_drive
($settings, sub {
126 my ($ds, $disk) = @_;
128 my $volid = $disk->{file
};
130 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
131 delete $disk->{size
};
132 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
133 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
134 my ($storeid, $size) = ($2 || $default_storage, $3);
135 die "no storage ID specified (and no default storage)\n" if !$storeid;
136 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
137 my $fmt = $disk->{format
} || $defformat;
138 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
139 $fmt, undef, $size*1024*1024);
140 $disk->{file
} = $volid;
141 $disk->{size
} = $size*1024*1024*1024;
142 push @$vollist, $volid;
143 delete $disk->{format
}; # no longer needed
144 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
147 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
149 my $volid_is_new = 1;
152 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
153 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
158 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
160 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
162 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
164 die "volume $volid does not exists\n" if !$size;
166 $disk->{size
} = $size;
169 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
173 # free allocated images on error
175 syslog
('err', "VM $vmid creating disks failed");
176 foreach my $volid (@$vollist) {
177 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
183 # modify vm config if everything went well
184 foreach my $ds (keys %$res) {
185 $conf->{$ds} = $res->{$ds};
191 my $check_vm_modify_config_perm = sub {
192 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
194 return 1 if $authuser eq 'root@pam';
196 foreach my $opt (@$key_list) {
197 # disk checks need to be done somewhere else
198 next if PVE
::QemuServer
::valid_drivename
($opt);
200 if ($opt eq 'sockets' || $opt eq 'cores' ||
201 $opt eq 'cpu' || $opt eq 'smp' ||
202 $opt eq 'cpulimit' || $opt eq 'cpuunits') {
203 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
204 } elsif ($opt eq 'boot' || $opt eq 'bootdisk') {
205 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
206 } elsif ($opt eq 'memory' || $opt eq 'balloon' || $opt eq 'shares') {
207 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
208 } elsif ($opt eq 'args' || $opt eq 'lock') {
209 die "only root can set '$opt' config\n";
210 } elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' || $opt eq 'machine' ||
211 $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet' || $opt eq 'smbios1') {
212 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
213 } elsif ($opt =~ m/^net\d+$/) {
214 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
216 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
223 __PACKAGE__-
>register_method({
227 description
=> "Virtual machine index (per node).",
229 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
233 protected
=> 1, # qemu pid files are only readable by root
235 additionalProperties
=> 0,
237 node
=> get_standard_option
('pve-node'),
246 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
251 my $rpcenv = PVE
::RPCEnvironment
::get
();
252 my $authuser = $rpcenv->get_user();
254 my $vmstatus = PVE
::QemuServer
::vmstatus
();
257 foreach my $vmid (keys %$vmstatus) {
258 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
260 my $data = $vmstatus->{$vmid};
261 $data->{vmid
} = int($vmid);
270 __PACKAGE__-
>register_method({
274 description
=> "Create or restore a virtual machine.",
276 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
277 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
278 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
279 user
=> 'all', # check inside
284 additionalProperties
=> 0,
285 properties
=> PVE
::QemuServer
::json_config_properties
(
287 node
=> get_standard_option
('pve-node'),
288 vmid
=> get_standard_option
('pve-vmid'),
290 description
=> "The backup file.",
295 storage
=> get_standard_option
('pve-storage-id', {
296 description
=> "Default storage.",
302 description
=> "Allow to overwrite existing VM.",
303 requires
=> 'archive',
308 description
=> "Assign a unique random ethernet address.",
309 requires
=> 'archive',
313 type
=> 'string', format
=> 'pve-poolid',
314 description
=> "Add the VM to the specified pool.",
324 my $rpcenv = PVE
::RPCEnvironment
::get
();
326 my $authuser = $rpcenv->get_user();
328 my $node = extract_param
($param, 'node');
330 my $vmid = extract_param
($param, 'vmid');
332 my $archive = extract_param
($param, 'archive');
334 my $storage = extract_param
($param, 'storage');
336 my $force = extract_param
($param, 'force');
338 my $unique = extract_param
($param, 'unique');
340 my $pool = extract_param
($param, 'pool');
342 my $filename = PVE
::QemuServer
::config_file
($vmid);
344 my $storecfg = PVE
::Storage
::config
();
346 PVE
::Cluster
::check_cfs_quorum
();
348 if (defined($pool)) {
349 $rpcenv->check_pool_exist($pool);
352 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
353 if defined($storage);
355 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
357 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
359 } elsif ($archive && $force && (-f
$filename) &&
360 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
361 # OK: user has VM.Backup permissions, and want to restore an existing VM
367 &$resolve_cdrom_alias($param);
369 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
371 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
373 foreach my $opt (keys %$param) {
374 if (PVE
::QemuServer
::valid_drivename
($opt)) {
375 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
376 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
378 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
379 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
383 PVE
::QemuServer
::add_random_macs
($param);
385 my $keystr = join(' ', keys %$param);
386 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
388 if ($archive eq '-') {
389 die "pipe requires cli environment\n"
390 if $rpcenv->{type
} ne 'cli';
392 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
393 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
397 my $restorefn = sub {
399 # fixme: this test does not work if VM exists on other node!
401 die "unable to restore vm $vmid: config file already exists\n"
404 die "unable to restore vm $vmid: vm is running\n"
405 if PVE
::QemuServer
::check_running
($vmid);
409 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
412 unique
=> $unique });
414 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
417 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
423 die "unable to create vm $vmid: config file already exists\n"
434 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
436 # try to be smart about bootdisk
437 my @disks = PVE
::QemuServer
::disknames
();
439 foreach my $ds (reverse @disks) {
440 next if !$conf->{$ds};
441 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
442 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
446 if (!$conf->{bootdisk
} && $firstdisk) {
447 $conf->{bootdisk
} = $firstdisk;
450 # auto generate uuid if user did not specify smbios1 option
451 if (!$conf->{smbios1
}) {
452 my ($uuid, $uuid_str);
453 UUID
::generate
($uuid);
454 UUID
::unparse
($uuid, $uuid_str);
455 $conf->{smbios1
} = "uuid=$uuid_str";
458 PVE
::QemuServer
::update_config_nolock
($vmid, $conf);
464 foreach my $volid (@$vollist) {
465 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
468 die "create failed - $err";
471 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
474 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
477 return PVE
::QemuServer
::lock_config_full
($vmid, 1, $archive ?
$restorefn : $createfn);
480 __PACKAGE__-
>register_method({
485 description
=> "Directory index",
490 additionalProperties
=> 0,
492 node
=> get_standard_option
('pve-node'),
493 vmid
=> get_standard_option
('pve-vmid'),
501 subdir
=> { type
=> 'string' },
504 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
510 { subdir
=> 'config' },
511 { subdir
=> 'status' },
512 { subdir
=> 'unlink' },
513 { subdir
=> 'vncproxy' },
514 { subdir
=> 'migrate' },
515 { subdir
=> 'resize' },
516 { subdir
=> 'move' },
518 { subdir
=> 'rrddata' },
519 { subdir
=> 'monitor' },
520 { subdir
=> 'snapshot' },
521 { subdir
=> 'spiceproxy' },
522 { subdir
=> 'sendkey' },
523 { subdir
=> 'firewall' },
529 __PACKAGE__-
>register_method ({
530 subclass
=> "PVE::API2::Firewall::VM",
531 path
=> '{vmid}/firewall',
534 __PACKAGE__-
>register_method({
536 path
=> '{vmid}/rrd',
538 protected
=> 1, # fixme: can we avoid that?
540 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
542 description
=> "Read VM RRD statistics (returns PNG)",
544 additionalProperties
=> 0,
546 node
=> get_standard_option
('pve-node'),
547 vmid
=> get_standard_option
('pve-vmid'),
549 description
=> "Specify the time frame you are interested in.",
551 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
554 description
=> "The list of datasources you want to display.",
555 type
=> 'string', format
=> 'pve-configid-list',
558 description
=> "The RRD consolidation function",
560 enum
=> [ 'AVERAGE', 'MAX' ],
568 filename
=> { type
=> 'string' },
574 return PVE
::Cluster
::create_rrd_graph
(
575 "pve2-vm/$param->{vmid}", $param->{timeframe
},
576 $param->{ds
}, $param->{cf
});
580 __PACKAGE__-
>register_method({
582 path
=> '{vmid}/rrddata',
584 protected
=> 1, # fixme: can we avoid that?
586 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
588 description
=> "Read VM RRD statistics",
590 additionalProperties
=> 0,
592 node
=> get_standard_option
('pve-node'),
593 vmid
=> get_standard_option
('pve-vmid'),
595 description
=> "Specify the time frame you are interested in.",
597 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
600 description
=> "The RRD consolidation function",
602 enum
=> [ 'AVERAGE', 'MAX' ],
617 return PVE
::Cluster
::create_rrd_data
(
618 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
622 __PACKAGE__-
>register_method({
624 path
=> '{vmid}/config',
627 description
=> "Get virtual machine configuration.",
629 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
632 additionalProperties
=> 0,
634 node
=> get_standard_option
('pve-node'),
635 vmid
=> get_standard_option
('pve-vmid'),
643 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
650 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
652 delete $conf->{snapshots
};
657 my $delete_drive = sub {
658 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
660 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
661 my $volid = $drive->{file
};
663 if (PVE
::QemuServer
::vm_is_volid_owner
($storecfg, $vmid, $volid)) {
664 if ($force || $key =~ m/^unused/) {
666 # check if the disk is really unused
667 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, $key);
668 my $path = PVE
::Storage
::path
($storecfg, $volid);
670 die "unable to delete '$volid' - volume is still in use (snapshot?)\n"
671 if $used_paths->{$path};
673 PVE
::Storage
::vdisk_free
($storecfg, $volid);
677 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
682 delete $conf->{$key};
685 my $vmconfig_delete_option = sub {
686 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
688 return if !defined($conf->{$opt});
690 my $isDisk = PVE
::QemuServer
::valid_drivename
($opt)|| ($opt =~ m/^unused/);
693 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
695 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
696 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
697 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
701 my $unplugwarning = "";
702 if ($conf->{ostype
} && $conf->{ostype
} eq 'l26') {
703 $unplugwarning = "<br>verify that you have acpiphp && pci_hotplug modules loaded in your guest VM";
704 } elsif ($conf->{ostype
} && $conf->{ostype
} eq 'l24') {
705 $unplugwarning = "<br>kernel 2.4 don't support hotplug, please disable hotplug in options";
706 } elsif (!$conf->{ostype
} || ($conf->{ostype
} && $conf->{ostype
} eq 'other')) {
707 $unplugwarning = "<br>verify that your guest support acpi hotplug";
710 if ($opt eq 'tablet') {
711 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
713 die "error hot-unplug $opt $unplugwarning" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
717 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
718 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
720 delete $conf->{$opt};
723 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
726 my $safe_num_ne = sub {
729 return 0 if !defined($a) && !defined($b);
730 return 1 if !defined($a);
731 return 1 if !defined($b);
736 my $vmconfig_update_disk = sub {
737 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
739 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
741 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { #cdrom
742 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
744 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
749 if (my $old_drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt})) {
751 my $media = $drive->{media
} || 'disk';
752 my $oldmedia = $old_drive->{media
} || 'disk';
753 die "unable to change media type\n" if $media ne $oldmedia;
755 if (!PVE
::QemuServer
::drive_is_cdrom
($old_drive) &&
756 ($drive->{file
} ne $old_drive->{file
})) { # delete old disks
758 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
759 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
762 if(&$safe_num_ne($drive->{mbps
}, $old_drive->{mbps
}) ||
763 &$safe_num_ne($drive->{mbps_rd
}, $old_drive->{mbps_rd
}) ||
764 &$safe_num_ne($drive->{mbps_wr
}, $old_drive->{mbps_wr
}) ||
765 &$safe_num_ne($drive->{iops
}, $old_drive->{iops
}) ||
766 &$safe_num_ne($drive->{iops_rd
}, $old_drive->{iops_rd
}) ||
767 &$safe_num_ne($drive->{iops_wr
}, $old_drive->{iops_wr
}) ||
768 &$safe_num_ne($drive->{mbps_max
}, $old_drive->{mbps_max
}) ||
769 &$safe_num_ne($drive->{mbps_rd_max
}, $old_drive->{mbps_rd_max
}) ||
770 &$safe_num_ne($drive->{mbps_wr_max
}, $old_drive->{mbps_wr_max
}) ||
771 &$safe_num_ne($drive->{iops_max
}, $old_drive->{iops_max
}) ||
772 &$safe_num_ne($drive->{iops_rd_max
}, $old_drive->{iops_rd_max
}) ||
773 &$safe_num_ne($drive->{iops_wr_max
}, $old_drive->{iops_wr_max
})) {
774 PVE
::QemuServer
::qemu_block_set_io_throttle
($vmid,"drive-$opt",
775 ($drive->{mbps
} || 0)*1024*1024,
776 ($drive->{mbps_rd
} || 0)*1024*1024,
777 ($drive->{mbps_wr
} || 0)*1024*1024,
779 $drive->{iops_rd
} || 0,
780 $drive->{iops_wr
} || 0,
781 ($drive->{mbps_max
} || 0)*1024*1024,
782 ($drive->{mbps_rd_max
} || 0)*1024*1024,
783 ($drive->{mbps_wr_max
} || 0)*1024*1024,
784 $drive->{iops_max
} || 0,
785 $drive->{iops_rd_max
} || 0,
786 $drive->{iops_wr_max
} || 0)
787 if !PVE
::QemuServer
::drive_is_cdrom
($drive);
792 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
793 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
795 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
796 $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
798 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # cdrom
800 if (PVE
::QemuServer
::check_running
($vmid)) {
801 if ($drive->{file
} eq 'none') {
802 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt");
804 my $path = PVE
::QemuServer
::get_iso_path
($storecfg, $vmid, $drive->{file
});
805 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt"); #force eject if locked
806 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change",device
=> "drive-$opt",target
=> "$path") if $path;
810 } else { # hotplug new disks
812 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $drive);
816 my $vmconfig_update_net = sub {
817 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
819 if ($conf->{$opt} && PVE
::QemuServer
::check_running
($vmid)) {
820 my $oldnet = PVE
::QemuServer
::parse_net
($conf->{$opt});
821 my $newnet = PVE
::QemuServer
::parse_net
($value);
823 if($oldnet->{model
} ne $newnet->{model
}){
824 #if model change, we try to hot-unplug
825 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
828 if($newnet->{bridge
} && $oldnet->{bridge
}){
829 my $iface = "tap".$vmid."i".$1 if $opt =~ m/net(\d+)/;
831 if($newnet->{rate
} ne $oldnet->{rate
}){
832 PVE
::Network
::tap_rate_limit
($iface, $newnet->{rate
});
835 if(($newnet->{bridge
} ne $oldnet->{bridge
}) || ($newnet->{tag
} ne $oldnet->{tag
}) || ($newnet->{firewall
} ne $oldnet->{firewall
})){
836 PVE
::Network
::tap_unplug
($iface);
837 PVE
::Network
::tap_plug
($iface, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
841 #if bridge/nat mode change, we try to hot-unplug
842 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
847 $conf->{$opt} = $value;
848 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
849 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
851 my $net = PVE
::QemuServer
::parse_net
($conf->{$opt});
853 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $net);
856 # POST/PUT {vmid}/config implementation
858 # The original API used PUT (idempotent) an we assumed that all operations
859 # are fast. But it turned out that almost any configuration change can
860 # involve hot-plug actions, or disk alloc/free. Such actions can take long
861 # time to complete and have side effects (not idempotent).
863 # The new implementation uses POST and forks a worker process. We added
864 # a new option 'background_delay'. If specified we wait up to
865 # 'background_delay' second for the worker task to complete. It returns null
866 # if the task is finished within that time, else we return the UPID.
868 my $update_vm_api = sub {
869 my ($param, $sync) = @_;
871 my $rpcenv = PVE
::RPCEnvironment
::get
();
873 my $authuser = $rpcenv->get_user();
875 my $node = extract_param
($param, 'node');
877 my $vmid = extract_param
($param, 'vmid');
879 my $digest = extract_param
($param, 'digest');
881 my $background_delay = extract_param
($param, 'background_delay');
883 my @paramarr = (); # used for log message
884 foreach my $key (keys %$param) {
885 push @paramarr, "-$key", $param->{$key};
888 my $skiplock = extract_param
($param, 'skiplock');
889 raise_param_exc
({ skiplock
=> "Only root may use this option." })
890 if $skiplock && $authuser ne 'root@pam';
892 my $delete_str = extract_param
($param, 'delete');
894 my $force = extract_param
($param, 'force');
896 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
898 my $storecfg = PVE
::Storage
::config
();
900 my $defaults = PVE
::QemuServer
::load_defaults
();
902 &$resolve_cdrom_alias($param);
904 # now try to verify all parameters
907 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
908 $opt = 'ide2' if $opt eq 'cdrom';
909 raise_param_exc
({ delete => "you can't use '-$opt' and " .
910 "-delete $opt' at the same time" })
911 if defined($param->{$opt});
913 if (!PVE
::QemuServer
::option_exists
($opt)) {
914 raise_param_exc
({ delete => "unknown option '$opt'" });
920 foreach my $opt (keys %$param) {
921 if (PVE
::QemuServer
::valid_drivename
($opt)) {
923 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
924 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
925 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
926 } elsif ($opt =~ m/^net(\d+)$/) {
928 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
929 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
933 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
935 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
937 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
941 my $conf = PVE
::QemuServer
::load_config
($vmid);
943 die "checksum missmatch (file change by other user?)\n"
944 if $digest && $digest ne $conf->{digest
};
946 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
948 if ($param->{memory
} || defined($param->{balloon
})) {
949 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
950 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
952 die "balloon value too large (must be smaller than assigned memory)\n"
953 if $balloon && $balloon > $maxmem;
956 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
960 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
962 # write updates to pending section
964 my $modified = {}; # record what $option we modify
966 foreach my $opt (@delete) {
967 $modified->{$opt} = 1;
968 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
969 if ($opt =~ m/^unused/) {
970 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
971 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
972 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
973 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
974 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive);
975 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
977 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
978 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
979 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
980 if defined($conf->{pending
}->{$opt});
981 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt);
982 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
984 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt);
985 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
989 foreach my $opt (keys %$param) { # add/change
990 $modified->{$opt} = 1;
991 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
992 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
994 if (PVE
::QemuServer
::valid_drivename
($opt)) {
995 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
996 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
997 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
999 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1001 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1002 if defined($conf->{pending
}->{$opt});
1004 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1006 $conf->{pending
}->{$opt} = $param->{$opt};
1008 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1009 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
1012 # remove pending changes when nothing changed
1013 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
1014 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1015 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1) if $changes;
1017 return if !scalar(keys %{$conf->{pending
}});
1019 my $running = PVE
::QemuServer
::check_running
($vmid);
1021 # apply pending changes
1023 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
1027 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1028 raise_param_exc
($errors) if scalar(keys %$errors);
1030 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1032 return; # TODO: remove old code below
1034 foreach my $opt (keys %$param) { # add/change
1036 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
1038 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
1040 if (PVE
::QemuServer
::valid_drivename
($opt)) {
1042 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
1043 $opt, $param->{$opt}, $force);
1045 } elsif ($opt =~ m/^net(\d+)$/) { #nics
1047 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
1048 $opt, $param->{$opt});
1052 if($opt eq 'tablet' && $param->{$opt} == 1){
1053 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
1054 } elsif($opt eq 'tablet' && $param->{$opt} == 0){
1055 PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
1058 if($opt eq 'cores' && $conf->{maxcpus
}){
1059 PVE
::QemuServer
::qemu_cpu_hotplug
($vmid, $conf, $param->{$opt});
1062 $conf->{$opt} = $param->{$opt};
1063 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
1067 # allow manual ballooning if shares is set to zero
1068 if ($running && defined($param->{balloon
}) &&
1069 defined($conf->{shares
}) && ($conf->{shares
} == 0)) {
1070 my $balloon = $param->{'balloon'} || $conf->{memory
} || $defaults->{memory
};
1071 PVE
::QemuServer
::vm_mon_cmd
($vmid, "balloon", value
=> $balloon*1024*1024);
1079 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1081 if ($background_delay) {
1083 # Note: It would be better to do that in the Event based HTTPServer
1084 # to avoid blocking call to sleep.
1086 my $end_time = time() + $background_delay;
1088 my $task = PVE
::Tools
::upid_decode
($upid);
1091 while (time() < $end_time) {
1092 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1094 sleep(1); # this gets interrupted when child process ends
1098 my $status = PVE
::Tools
::upid_read_status
($upid);
1099 return undef if $status eq 'OK';
1108 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1111 my $vm_config_perm_list = [
1116 'VM.Config.Network',
1118 'VM.Config.Options',
1121 __PACKAGE__-
>register_method({
1122 name
=> 'update_vm_async',
1123 path
=> '{vmid}/config',
1127 description
=> "Set virtual machine options (asynchrounous API).",
1129 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1132 additionalProperties
=> 0,
1133 properties
=> PVE
::QemuServer
::json_config_properties
(
1135 node
=> get_standard_option
('pve-node'),
1136 vmid
=> get_standard_option
('pve-vmid'),
1137 skiplock
=> get_standard_option
('skiplock'),
1139 type
=> 'string', format
=> 'pve-configid-list',
1140 description
=> "A list of settings you want to delete.",
1145 description
=> $opt_force_description,
1147 requires
=> 'delete',
1151 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1155 background_delay
=> {
1157 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1168 code
=> $update_vm_api,
1171 __PACKAGE__-
>register_method({
1172 name
=> 'update_vm',
1173 path
=> '{vmid}/config',
1177 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1179 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1182 additionalProperties
=> 0,
1183 properties
=> PVE
::QemuServer
::json_config_properties
(
1185 node
=> get_standard_option
('pve-node'),
1186 vmid
=> get_standard_option
('pve-vmid'),
1187 skiplock
=> get_standard_option
('skiplock'),
1189 type
=> 'string', format
=> 'pve-configid-list',
1190 description
=> "A list of settings you want to delete.",
1195 description
=> $opt_force_description,
1197 requires
=> 'delete',
1201 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1207 returns
=> { type
=> 'null' },
1210 &$update_vm_api($param, 1);
1216 __PACKAGE__-
>register_method({
1217 name
=> 'destroy_vm',
1222 description
=> "Destroy the vm (also delete all used/owned volumes).",
1224 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1227 additionalProperties
=> 0,
1229 node
=> get_standard_option
('pve-node'),
1230 vmid
=> get_standard_option
('pve-vmid'),
1231 skiplock
=> get_standard_option
('skiplock'),
1240 my $rpcenv = PVE
::RPCEnvironment
::get
();
1242 my $authuser = $rpcenv->get_user();
1244 my $vmid = $param->{vmid
};
1246 my $skiplock = $param->{skiplock
};
1247 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1248 if $skiplock && $authuser ne 'root@pam';
1251 my $conf = PVE
::QemuServer
::load_config
($vmid);
1253 my $storecfg = PVE
::Storage
::config
();
1255 my $delVMfromPoolFn = sub {
1256 my $usercfg = cfs_read_file
("user.cfg");
1257 if (my $pool = $usercfg->{vms
}->{$vmid}) {
1258 if (my $data = $usercfg->{pools
}->{$pool}) {
1259 delete $data->{vms
}->{$vmid};
1260 delete $usercfg->{vms
}->{$vmid};
1261 cfs_write_file
("user.cfg", $usercfg);
1269 syslog
('info', "destroy VM $vmid: $upid\n");
1271 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1273 PVE
::AccessControl
::remove_vm_from_pool
($vmid);
1276 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1279 __PACKAGE__-
>register_method({
1281 path
=> '{vmid}/unlink',
1285 description
=> "Unlink/delete disk images.",
1287 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1290 additionalProperties
=> 0,
1292 node
=> get_standard_option
('pve-node'),
1293 vmid
=> get_standard_option
('pve-vmid'),
1295 type
=> 'string', format
=> 'pve-configid-list',
1296 description
=> "A list of disk IDs you want to delete.",
1300 description
=> $opt_force_description,
1305 returns
=> { type
=> 'null'},
1309 $param->{delete} = extract_param
($param, 'idlist');
1311 __PACKAGE__-
>update_vm($param);
1318 __PACKAGE__-
>register_method({
1320 path
=> '{vmid}/vncproxy',
1324 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1326 description
=> "Creates a TCP VNC proxy connections.",
1328 additionalProperties
=> 0,
1330 node
=> get_standard_option
('pve-node'),
1331 vmid
=> get_standard_option
('pve-vmid'),
1335 description
=> "starts websockify instead of vncproxy",
1340 additionalProperties
=> 0,
1342 user
=> { type
=> 'string' },
1343 ticket
=> { type
=> 'string' },
1344 cert
=> { type
=> 'string' },
1345 port
=> { type
=> 'integer' },
1346 upid
=> { type
=> 'string' },
1352 my $rpcenv = PVE
::RPCEnvironment
::get
();
1354 my $authuser = $rpcenv->get_user();
1356 my $vmid = $param->{vmid
};
1357 my $node = $param->{node
};
1358 my $websocket = $param->{websocket
};
1360 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # check if VM exists
1362 my $authpath = "/vms/$vmid";
1364 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1366 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1369 my $port = PVE
::Tools
::next_vnc_port
();
1374 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1375 $remip = PVE
::Cluster
::remote_node_ip
($node);
1376 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1377 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1385 syslog
('info', "starting vnc proxy $upid\n");
1389 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1391 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1393 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1394 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1395 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1396 '-timeout', $timeout, '-authpath', $authpath,
1397 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1400 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1402 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1404 my $qmstr = join(' ', @$qmcmd);
1406 # also redirect stderr (else we get RFB protocol errors)
1407 $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1410 PVE
::Tools
::run_command
($cmd);
1415 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1417 PVE
::Tools
::wait_for_vnc_port
($port);
1428 __PACKAGE__-
>register_method({
1429 name
=> 'vncwebsocket',
1430 path
=> '{vmid}/vncwebsocket',
1433 description
=> "You also need to pass a valid ticket (vncticket).",
1434 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1436 description
=> "Opens a weksocket for VNC traffic.",
1438 additionalProperties
=> 0,
1440 node
=> get_standard_option
('pve-node'),
1441 vmid
=> get_standard_option
('pve-vmid'),
1443 description
=> "Ticket from previous call to vncproxy.",
1448 description
=> "Port number returned by previous vncproxy call.",
1458 port
=> { type
=> 'string' },
1464 my $rpcenv = PVE
::RPCEnvironment
::get
();
1466 my $authuser = $rpcenv->get_user();
1468 my $vmid = $param->{vmid
};
1469 my $node = $param->{node
};
1471 my $authpath = "/vms/$vmid";
1473 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1475 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # VM exists ?
1477 # Note: VNC ports are acessible from outside, so we do not gain any
1478 # security if we verify that $param->{port} belongs to VM $vmid. This
1479 # check is done by verifying the VNC ticket (inside VNC protocol).
1481 my $port = $param->{port
};
1483 return { port
=> $port };
1486 __PACKAGE__-
>register_method({
1487 name
=> 'spiceproxy',
1488 path
=> '{vmid}/spiceproxy',
1493 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1495 description
=> "Returns a SPICE configuration to connect to the VM.",
1497 additionalProperties
=> 0,
1499 node
=> get_standard_option
('pve-node'),
1500 vmid
=> get_standard_option
('pve-vmid'),
1501 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1504 returns
=> get_standard_option
('remote-viewer-config'),
1508 my $rpcenv = PVE
::RPCEnvironment
::get
();
1510 my $authuser = $rpcenv->get_user();
1512 my $vmid = $param->{vmid
};
1513 my $node = $param->{node
};
1514 my $proxy = $param->{proxy
};
1516 my $conf = PVE
::QemuServer
::load_config
($vmid, $node);
1517 my $title = "VM $vmid - $conf->{'name'}",
1519 my $port = PVE
::QemuServer
::spice_port
($vmid);
1521 my ($ticket, undef, $remote_viewer_config) =
1522 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1524 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1525 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1527 return $remote_viewer_config;
1530 __PACKAGE__-
>register_method({
1532 path
=> '{vmid}/status',
1535 description
=> "Directory index",
1540 additionalProperties
=> 0,
1542 node
=> get_standard_option
('pve-node'),
1543 vmid
=> get_standard_option
('pve-vmid'),
1551 subdir
=> { type
=> 'string' },
1554 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1560 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1563 { subdir
=> 'current' },
1564 { subdir
=> 'start' },
1565 { subdir
=> 'stop' },
1571 my $vm_is_ha_managed = sub {
1574 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1575 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1581 __PACKAGE__-
>register_method({
1582 name
=> 'vm_status',
1583 path
=> '{vmid}/status/current',
1586 protected
=> 1, # qemu pid files are only readable by root
1587 description
=> "Get virtual machine status.",
1589 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1592 additionalProperties
=> 0,
1594 node
=> get_standard_option
('pve-node'),
1595 vmid
=> get_standard_option
('pve-vmid'),
1598 returns
=> { type
=> 'object' },
1603 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1605 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1606 my $status = $vmstatus->{$param->{vmid
}};
1608 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1610 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1615 __PACKAGE__-
>register_method({
1617 path
=> '{vmid}/status/start',
1621 description
=> "Start virtual machine.",
1623 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1626 additionalProperties
=> 0,
1628 node
=> get_standard_option
('pve-node'),
1629 vmid
=> get_standard_option
('pve-vmid'),
1630 skiplock
=> get_standard_option
('skiplock'),
1631 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1632 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1633 machine
=> get_standard_option
('pve-qm-machine'),
1642 my $rpcenv = PVE
::RPCEnvironment
::get
();
1644 my $authuser = $rpcenv->get_user();
1646 my $node = extract_param
($param, 'node');
1648 my $vmid = extract_param
($param, 'vmid');
1650 my $machine = extract_param
($param, 'machine');
1652 my $stateuri = extract_param
($param, 'stateuri');
1653 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1654 if $stateuri && $authuser ne 'root@pam';
1656 my $skiplock = extract_param
($param, 'skiplock');
1657 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1658 if $skiplock && $authuser ne 'root@pam';
1660 my $migratedfrom = extract_param
($param, 'migratedfrom');
1661 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1662 if $migratedfrom && $authuser ne 'root@pam';
1664 # read spice ticket from STDIN
1666 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1667 if (defined(my $line = <>)) {
1669 $spice_ticket = $line;
1673 my $storecfg = PVE
::Storage
::config
();
1675 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1676 $rpcenv->{type
} ne 'ha') {
1681 my $service = "pvevm:$vmid";
1683 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1685 print "Executing HA start for VM $vmid\n";
1687 PVE
::Tools
::run_command
($cmd);
1692 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1699 syslog
('info', "start VM $vmid: $upid\n");
1701 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1702 $machine, $spice_ticket);
1707 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1711 __PACKAGE__-
>register_method({
1713 path
=> '{vmid}/status/stop',
1717 description
=> "Stop virtual machine.",
1719 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1722 additionalProperties
=> 0,
1724 node
=> get_standard_option
('pve-node'),
1725 vmid
=> get_standard_option
('pve-vmid'),
1726 skiplock
=> get_standard_option
('skiplock'),
1727 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1729 description
=> "Wait maximal timeout seconds.",
1735 description
=> "Do not decativate storage volumes.",
1748 my $rpcenv = PVE
::RPCEnvironment
::get
();
1750 my $authuser = $rpcenv->get_user();
1752 my $node = extract_param
($param, 'node');
1754 my $vmid = extract_param
($param, 'vmid');
1756 my $skiplock = extract_param
($param, 'skiplock');
1757 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1758 if $skiplock && $authuser ne 'root@pam';
1760 my $keepActive = extract_param
($param, 'keepActive');
1761 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1762 if $keepActive && $authuser ne 'root@pam';
1764 my $migratedfrom = extract_param
($param, 'migratedfrom');
1765 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1766 if $migratedfrom && $authuser ne 'root@pam';
1769 my $storecfg = PVE
::Storage
::config
();
1771 if (&$vm_is_ha_managed($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1776 my $service = "pvevm:$vmid";
1778 my $cmd = ['clusvcadm', '-d', $service];
1780 print "Executing HA stop for VM $vmid\n";
1782 PVE
::Tools
::run_command
($cmd);
1787 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1793 syslog
('info', "stop VM $vmid: $upid\n");
1795 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1796 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1801 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1805 __PACKAGE__-
>register_method({
1807 path
=> '{vmid}/status/reset',
1811 description
=> "Reset virtual machine.",
1813 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1816 additionalProperties
=> 0,
1818 node
=> get_standard_option
('pve-node'),
1819 vmid
=> get_standard_option
('pve-vmid'),
1820 skiplock
=> get_standard_option
('skiplock'),
1829 my $rpcenv = PVE
::RPCEnvironment
::get
();
1831 my $authuser = $rpcenv->get_user();
1833 my $node = extract_param
($param, 'node');
1835 my $vmid = extract_param
($param, 'vmid');
1837 my $skiplock = extract_param
($param, 'skiplock');
1838 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1839 if $skiplock && $authuser ne 'root@pam';
1841 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1846 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1851 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1854 __PACKAGE__-
>register_method({
1855 name
=> 'vm_shutdown',
1856 path
=> '{vmid}/status/shutdown',
1860 description
=> "Shutdown virtual machine.",
1862 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1865 additionalProperties
=> 0,
1867 node
=> get_standard_option
('pve-node'),
1868 vmid
=> get_standard_option
('pve-vmid'),
1869 skiplock
=> get_standard_option
('skiplock'),
1871 description
=> "Wait maximal timeout seconds.",
1877 description
=> "Make sure the VM stops.",
1883 description
=> "Do not decativate storage volumes.",
1896 my $rpcenv = PVE
::RPCEnvironment
::get
();
1898 my $authuser = $rpcenv->get_user();
1900 my $node = extract_param
($param, 'node');
1902 my $vmid = extract_param
($param, 'vmid');
1904 my $skiplock = extract_param
($param, 'skiplock');
1905 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1906 if $skiplock && $authuser ne 'root@pam';
1908 my $keepActive = extract_param
($param, 'keepActive');
1909 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1910 if $keepActive && $authuser ne 'root@pam';
1912 my $storecfg = PVE
::Storage
::config
();
1917 syslog
('info', "shutdown VM $vmid: $upid\n");
1919 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1920 1, $param->{forceStop
}, $keepActive);
1925 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1928 __PACKAGE__-
>register_method({
1929 name
=> 'vm_suspend',
1930 path
=> '{vmid}/status/suspend',
1934 description
=> "Suspend virtual machine.",
1936 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1939 additionalProperties
=> 0,
1941 node
=> get_standard_option
('pve-node'),
1942 vmid
=> get_standard_option
('pve-vmid'),
1943 skiplock
=> get_standard_option
('skiplock'),
1952 my $rpcenv = PVE
::RPCEnvironment
::get
();
1954 my $authuser = $rpcenv->get_user();
1956 my $node = extract_param
($param, 'node');
1958 my $vmid = extract_param
($param, 'vmid');
1960 my $skiplock = extract_param
($param, 'skiplock');
1961 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1962 if $skiplock && $authuser ne 'root@pam';
1964 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1969 syslog
('info', "suspend VM $vmid: $upid\n");
1971 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1976 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1979 __PACKAGE__-
>register_method({
1980 name
=> 'vm_resume',
1981 path
=> '{vmid}/status/resume',
1985 description
=> "Resume virtual machine.",
1987 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1990 additionalProperties
=> 0,
1992 node
=> get_standard_option
('pve-node'),
1993 vmid
=> get_standard_option
('pve-vmid'),
1994 skiplock
=> get_standard_option
('skiplock'),
2003 my $rpcenv = PVE
::RPCEnvironment
::get
();
2005 my $authuser = $rpcenv->get_user();
2007 my $node = extract_param
($param, 'node');
2009 my $vmid = extract_param
($param, 'vmid');
2011 my $skiplock = extract_param
($param, 'skiplock');
2012 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2013 if $skiplock && $authuser ne 'root@pam';
2015 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2020 syslog
('info', "resume VM $vmid: $upid\n");
2022 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
2027 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2030 __PACKAGE__-
>register_method({
2031 name
=> 'vm_sendkey',
2032 path
=> '{vmid}/sendkey',
2036 description
=> "Send key event to virtual machine.",
2038 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2041 additionalProperties
=> 0,
2043 node
=> get_standard_option
('pve-node'),
2044 vmid
=> get_standard_option
('pve-vmid'),
2045 skiplock
=> get_standard_option
('skiplock'),
2047 description
=> "The key (qemu monitor encoding).",
2052 returns
=> { type
=> 'null'},
2056 my $rpcenv = PVE
::RPCEnvironment
::get
();
2058 my $authuser = $rpcenv->get_user();
2060 my $node = extract_param
($param, 'node');
2062 my $vmid = extract_param
($param, 'vmid');
2064 my $skiplock = extract_param
($param, 'skiplock');
2065 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2066 if $skiplock && $authuser ne 'root@pam';
2068 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2073 __PACKAGE__-
>register_method({
2074 name
=> 'vm_feature',
2075 path
=> '{vmid}/feature',
2079 description
=> "Check if feature for virtual machine is available.",
2081 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2084 additionalProperties
=> 0,
2086 node
=> get_standard_option
('pve-node'),
2087 vmid
=> get_standard_option
('pve-vmid'),
2089 description
=> "Feature to check.",
2091 enum
=> [ 'snapshot', 'clone', 'copy' ],
2093 snapname
=> get_standard_option
('pve-snapshot-name', {
2101 hasFeature
=> { type
=> 'boolean' },
2104 items
=> { type
=> 'string' },
2111 my $node = extract_param
($param, 'node');
2113 my $vmid = extract_param
($param, 'vmid');
2115 my $snapname = extract_param
($param, 'snapname');
2117 my $feature = extract_param
($param, 'feature');
2119 my $running = PVE
::QemuServer
::check_running
($vmid);
2121 my $conf = PVE
::QemuServer
::load_config
($vmid);
2124 my $snap = $conf->{snapshots
}->{$snapname};
2125 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2128 my $storecfg = PVE
::Storage
::config
();
2130 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2131 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
2134 hasFeature
=> $hasFeature,
2135 nodes
=> [ keys %$nodelist ],
2139 __PACKAGE__-
>register_method({
2141 path
=> '{vmid}/clone',
2145 description
=> "Create a copy of virtual machine/template.",
2147 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2148 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2149 "'Datastore.AllocateSpace' on any used storage.",
2152 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2154 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2155 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2160 additionalProperties
=> 0,
2162 node
=> get_standard_option
('pve-node'),
2163 vmid
=> get_standard_option
('pve-vmid'),
2164 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2167 type
=> 'string', format
=> 'dns-name',
2168 description
=> "Set a name for the new VM.",
2173 description
=> "Description for the new VM.",
2177 type
=> 'string', format
=> 'pve-poolid',
2178 description
=> "Add the new VM to the specified pool.",
2180 snapname
=> get_standard_option
('pve-snapshot-name', {
2183 storage
=> get_standard_option
('pve-storage-id', {
2184 description
=> "Target storage for full clone.",
2189 description
=> "Target format for file storage.",
2193 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2198 description
=> "Create a full copy of all disk. This is always done when " .
2199 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2202 target
=> get_standard_option
('pve-node', {
2203 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2214 my $rpcenv = PVE
::RPCEnvironment
::get
();
2216 my $authuser = $rpcenv->get_user();
2218 my $node = extract_param
($param, 'node');
2220 my $vmid = extract_param
($param, 'vmid');
2222 my $newid = extract_param
($param, 'newid');
2224 my $pool = extract_param
($param, 'pool');
2226 if (defined($pool)) {
2227 $rpcenv->check_pool_exist($pool);
2230 my $snapname = extract_param
($param, 'snapname');
2232 my $storage = extract_param
($param, 'storage');
2234 my $format = extract_param
($param, 'format');
2236 my $target = extract_param
($param, 'target');
2238 my $localnode = PVE
::INotify
::nodename
();
2240 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2242 PVE
::Cluster
::check_node_exists
($target) if $target;
2244 my $storecfg = PVE
::Storage
::config
();
2247 # check if storage is enabled on local node
2248 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2250 # check if storage is available on target node
2251 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2252 # clone only works if target storage is shared
2253 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2254 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2258 PVE
::Cluster
::check_cfs_quorum
();
2260 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2262 # exclusive lock if VM is running - else shared lock is enough;
2263 my $shared_lock = $running ?
0 : 1;
2267 # do all tests after lock
2268 # we also try to do all tests before we fork the worker
2270 my $conf = PVE
::QemuServer
::load_config
($vmid);
2272 PVE
::QemuServer
::check_lock
($conf);
2274 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2276 die "unexpected state change\n" if $verify_running != $running;
2278 die "snapshot '$snapname' does not exist\n"
2279 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2281 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2283 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2285 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2287 my $conffile = PVE
::QemuServer
::config_file
($newid);
2289 die "unable to create VM $newid: config file already exists\n"
2292 my $newconf = { lock => 'clone' };
2296 foreach my $opt (keys %$oldconf) {
2297 my $value = $oldconf->{$opt};
2299 # do not copy snapshot related info
2300 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2301 $opt eq 'vmstate' || $opt eq 'snapstate';
2303 # always change MAC! address
2304 if ($opt =~ m/^net(\d+)$/) {
2305 my $net = PVE
::QemuServer
::parse_net
($value);
2306 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2307 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2308 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
2309 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2310 die "unable to parse drive options for '$opt'\n" if !$drive;
2311 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2312 $newconf->{$opt} = $value; # simply copy configuration
2314 if ($param->{full
}) {
2315 die "Full clone feature is not available"
2316 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2319 # not full means clone instead of copy
2320 die "Linked clone feature is not available"
2321 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2323 $drives->{$opt} = $drive;
2324 push @$vollist, $drive->{file
};
2327 # copy everything else
2328 $newconf->{$opt} = $value;
2332 # auto generate a new uuid
2333 my ($uuid, $uuid_str);
2334 UUID
::generate
($uuid);
2335 UUID
::unparse
($uuid, $uuid_str);
2336 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2337 $smbios1->{uuid
} = $uuid_str;
2338 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2340 delete $newconf->{template
};
2342 if ($param->{name
}) {
2343 $newconf->{name
} = $param->{name
};
2345 if ($oldconf->{name
}) {
2346 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2348 $newconf->{name
} = "Copy-of-VM-$vmid";
2352 if ($param->{description
}) {
2353 $newconf->{description
} = $param->{description
};
2356 # create empty/temp config - this fails if VM already exists on other node
2357 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2362 my $newvollist = [];
2365 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2367 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2369 foreach my $opt (keys %$drives) {
2370 my $drive = $drives->{$opt};
2372 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2373 $newid, $storage, $format, $drive->{full
}, $newvollist);
2375 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2377 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2380 delete $newconf->{lock};
2381 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2384 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2385 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist);
2387 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2388 die "Failed to move config to node '$target' - rename failed: $!\n"
2389 if !rename($conffile, $newconffile);
2392 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2397 sleep 1; # some storage like rbd need to wait before release volume - really?
2399 foreach my $volid (@$newvollist) {
2400 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2403 die "clone failed: $err";
2409 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2412 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2413 # Aquire exclusive lock lock for $newid
2414 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2419 __PACKAGE__-
>register_method({
2420 name
=> 'move_vm_disk',
2421 path
=> '{vmid}/move_disk',
2425 description
=> "Move volume to different storage.",
2427 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2428 "and 'Datastore.AllocateSpace' permissions on the storage.",
2431 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2432 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2436 additionalProperties
=> 0,
2438 node
=> get_standard_option
('pve-node'),
2439 vmid
=> get_standard_option
('pve-vmid'),
2442 description
=> "The disk you want to move.",
2443 enum
=> [ PVE
::QemuServer
::disknames
() ],
2445 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2448 description
=> "Target Format.",
2449 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2454 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2460 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2468 description
=> "the task ID.",
2473 my $rpcenv = PVE
::RPCEnvironment
::get
();
2475 my $authuser = $rpcenv->get_user();
2477 my $node = extract_param
($param, 'node');
2479 my $vmid = extract_param
($param, 'vmid');
2481 my $digest = extract_param
($param, 'digest');
2483 my $disk = extract_param
($param, 'disk');
2485 my $storeid = extract_param
($param, 'storage');
2487 my $format = extract_param
($param, 'format');
2489 my $storecfg = PVE
::Storage
::config
();
2491 my $updatefn = sub {
2493 my $conf = PVE
::QemuServer
::load_config
($vmid);
2495 die "checksum missmatch (file change by other user?)\n"
2496 if $digest && $digest ne $conf->{digest
};
2498 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2500 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2502 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2504 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2507 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2508 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2512 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2513 (!$format || !$oldfmt || $oldfmt eq $format);
2515 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2517 my $running = PVE
::QemuServer
::check_running
($vmid);
2519 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2523 my $newvollist = [];
2526 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2528 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2529 $vmid, $storeid, $format, 1, $newvollist);
2531 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2533 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2535 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2538 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2539 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2546 foreach my $volid (@$newvollist) {
2547 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2550 die "storage migration failed: $err";
2553 if ($param->{delete}) {
2554 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, 1);
2555 my $path = PVE
::Storage
::path
($storecfg, $old_volid);
2556 if ($used_paths->{$path}){
2557 warn "volume $old_volid have snapshots. Can't delete it\n";
2558 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid);
2559 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2561 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2567 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2570 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2573 __PACKAGE__-
>register_method({
2574 name
=> 'migrate_vm',
2575 path
=> '{vmid}/migrate',
2579 description
=> "Migrate virtual machine. Creates a new migration task.",
2581 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2584 additionalProperties
=> 0,
2586 node
=> get_standard_option
('pve-node'),
2587 vmid
=> get_standard_option
('pve-vmid'),
2588 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2591 description
=> "Use online/live migration.",
2596 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2603 description
=> "the task ID.",
2608 my $rpcenv = PVE
::RPCEnvironment
::get
();
2610 my $authuser = $rpcenv->get_user();
2612 my $target = extract_param
($param, 'target');
2614 my $localnode = PVE
::INotify
::nodename
();
2615 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2617 PVE
::Cluster
::check_cfs_quorum
();
2619 PVE
::Cluster
::check_node_exists
($target);
2621 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2623 my $vmid = extract_param
($param, 'vmid');
2625 raise_param_exc
({ force
=> "Only root may use this option." })
2626 if $param->{force
} && $authuser ne 'root@pam';
2629 my $conf = PVE
::QemuServer
::load_config
($vmid);
2631 # try to detect errors early
2633 PVE
::QemuServer
::check_lock
($conf);
2635 if (PVE
::QemuServer
::check_running
($vmid)) {
2636 die "cant migrate running VM without --online\n"
2637 if !$param->{online
};
2640 my $storecfg = PVE
::Storage
::config
();
2641 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2643 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2648 my $service = "pvevm:$vmid";
2650 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2652 print "Executing HA migrate for VM $vmid to node $target\n";
2654 PVE
::Tools
::run_command
($cmd);
2659 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2666 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2669 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2674 __PACKAGE__-
>register_method({
2676 path
=> '{vmid}/monitor',
2680 description
=> "Execute Qemu monitor commands.",
2682 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2685 additionalProperties
=> 0,
2687 node
=> get_standard_option
('pve-node'),
2688 vmid
=> get_standard_option
('pve-vmid'),
2691 description
=> "The monitor command.",
2695 returns
=> { type
=> 'string'},
2699 my $vmid = $param->{vmid
};
2701 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2705 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2707 $res = "ERROR: $@" if $@;
2712 __PACKAGE__-
>register_method({
2713 name
=> 'resize_vm',
2714 path
=> '{vmid}/resize',
2718 description
=> "Extend volume size.",
2720 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2723 additionalProperties
=> 0,
2725 node
=> get_standard_option
('pve-node'),
2726 vmid
=> get_standard_option
('pve-vmid'),
2727 skiplock
=> get_standard_option
('skiplock'),
2730 description
=> "The disk you want to resize.",
2731 enum
=> [PVE
::QemuServer
::disknames
()],
2735 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2736 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.",
2740 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2746 returns
=> { type
=> 'null'},
2750 my $rpcenv = PVE
::RPCEnvironment
::get
();
2752 my $authuser = $rpcenv->get_user();
2754 my $node = extract_param
($param, 'node');
2756 my $vmid = extract_param
($param, 'vmid');
2758 my $digest = extract_param
($param, 'digest');
2760 my $disk = extract_param
($param, 'disk');
2762 my $sizestr = extract_param
($param, 'size');
2764 my $skiplock = extract_param
($param, 'skiplock');
2765 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2766 if $skiplock && $authuser ne 'root@pam';
2768 my $storecfg = PVE
::Storage
::config
();
2770 my $updatefn = sub {
2772 my $conf = PVE
::QemuServer
::load_config
($vmid);
2774 die "checksum missmatch (file change by other user?)\n"
2775 if $digest && $digest ne $conf->{digest
};
2776 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2778 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2780 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2782 my $volid = $drive->{file
};
2784 die "disk '$disk' has no associated volume\n" if !$volid;
2786 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2788 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2790 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2792 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2794 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2795 my ($ext, $newsize, $unit) = ($1, $2, $4);
2798 $newsize = $newsize * 1024;
2799 } elsif ($unit eq 'M') {
2800 $newsize = $newsize * 1024 * 1024;
2801 } elsif ($unit eq 'G') {
2802 $newsize = $newsize * 1024 * 1024 * 1024;
2803 } elsif ($unit eq 'T') {
2804 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2807 $newsize += $size if $ext;
2808 $newsize = int($newsize);
2810 die "unable to skrink disk size\n" if $newsize < $size;
2812 return if $size == $newsize;
2814 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2816 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2818 $drive->{size
} = $newsize;
2819 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2821 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2824 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2828 __PACKAGE__-
>register_method({
2829 name
=> 'snapshot_list',
2830 path
=> '{vmid}/snapshot',
2832 description
=> "List all snapshots.",
2834 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2837 protected
=> 1, # qemu pid files are only readable by root
2839 additionalProperties
=> 0,
2841 vmid
=> get_standard_option
('pve-vmid'),
2842 node
=> get_standard_option
('pve-node'),
2851 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2856 my $vmid = $param->{vmid
};
2858 my $conf = PVE
::QemuServer
::load_config
($vmid);
2859 my $snaphash = $conf->{snapshots
} || {};
2863 foreach my $name (keys %$snaphash) {
2864 my $d = $snaphash->{$name};
2867 snaptime
=> $d->{snaptime
} || 0,
2868 vmstate
=> $d->{vmstate
} ?
1 : 0,
2869 description
=> $d->{description
} || '',
2871 $item->{parent
} = $d->{parent
} if $d->{parent
};
2872 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2876 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2877 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2878 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2880 push @$res, $current;
2885 __PACKAGE__-
>register_method({
2887 path
=> '{vmid}/snapshot',
2891 description
=> "Snapshot a VM.",
2893 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2896 additionalProperties
=> 0,
2898 node
=> get_standard_option
('pve-node'),
2899 vmid
=> get_standard_option
('pve-vmid'),
2900 snapname
=> get_standard_option
('pve-snapshot-name'),
2904 description
=> "Save the vmstate",
2909 description
=> "A textual description or comment.",
2915 description
=> "the task ID.",
2920 my $rpcenv = PVE
::RPCEnvironment
::get
();
2922 my $authuser = $rpcenv->get_user();
2924 my $node = extract_param
($param, 'node');
2926 my $vmid = extract_param
($param, 'vmid');
2928 my $snapname = extract_param
($param, 'snapname');
2930 die "unable to use snapshot name 'current' (reserved name)\n"
2931 if $snapname eq 'current';
2934 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2935 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2936 $param->{description
});
2939 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2942 __PACKAGE__-
>register_method({
2943 name
=> 'snapshot_cmd_idx',
2944 path
=> '{vmid}/snapshot/{snapname}',
2951 additionalProperties
=> 0,
2953 vmid
=> get_standard_option
('pve-vmid'),
2954 node
=> get_standard_option
('pve-node'),
2955 snapname
=> get_standard_option
('pve-snapshot-name'),
2964 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2971 push @$res, { cmd
=> 'rollback' };
2972 push @$res, { cmd
=> 'config' };
2977 __PACKAGE__-
>register_method({
2978 name
=> 'update_snapshot_config',
2979 path
=> '{vmid}/snapshot/{snapname}/config',
2983 description
=> "Update snapshot metadata.",
2985 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2988 additionalProperties
=> 0,
2990 node
=> get_standard_option
('pve-node'),
2991 vmid
=> get_standard_option
('pve-vmid'),
2992 snapname
=> get_standard_option
('pve-snapshot-name'),
2996 description
=> "A textual description or comment.",
3000 returns
=> { type
=> 'null' },
3004 my $rpcenv = PVE
::RPCEnvironment
::get
();
3006 my $authuser = $rpcenv->get_user();
3008 my $vmid = extract_param
($param, 'vmid');
3010 my $snapname = extract_param
($param, 'snapname');
3012 return undef if !defined($param->{description
});
3014 my $updatefn = sub {
3016 my $conf = PVE
::QemuServer
::load_config
($vmid);
3018 PVE
::QemuServer
::check_lock
($conf);
3020 my $snap = $conf->{snapshots
}->{$snapname};
3022 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3024 $snap->{description
} = $param->{description
} if defined($param->{description
});
3026 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3029 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
3034 __PACKAGE__-
>register_method({
3035 name
=> 'get_snapshot_config',
3036 path
=> '{vmid}/snapshot/{snapname}/config',
3039 description
=> "Get snapshot configuration",
3041 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3044 additionalProperties
=> 0,
3046 node
=> get_standard_option
('pve-node'),
3047 vmid
=> get_standard_option
('pve-vmid'),
3048 snapname
=> get_standard_option
('pve-snapshot-name'),
3051 returns
=> { type
=> "object" },
3055 my $rpcenv = PVE
::RPCEnvironment
::get
();
3057 my $authuser = $rpcenv->get_user();
3059 my $vmid = extract_param
($param, 'vmid');
3061 my $snapname = extract_param
($param, 'snapname');
3063 my $conf = PVE
::QemuServer
::load_config
($vmid);
3065 my $snap = $conf->{snapshots
}->{$snapname};
3067 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3072 __PACKAGE__-
>register_method({
3074 path
=> '{vmid}/snapshot/{snapname}/rollback',
3078 description
=> "Rollback VM state to specified snapshot.",
3080 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3083 additionalProperties
=> 0,
3085 node
=> get_standard_option
('pve-node'),
3086 vmid
=> get_standard_option
('pve-vmid'),
3087 snapname
=> get_standard_option
('pve-snapshot-name'),
3092 description
=> "the task ID.",
3097 my $rpcenv = PVE
::RPCEnvironment
::get
();
3099 my $authuser = $rpcenv->get_user();
3101 my $node = extract_param
($param, 'node');
3103 my $vmid = extract_param
($param, 'vmid');
3105 my $snapname = extract_param
($param, 'snapname');
3108 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3109 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
3112 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3115 __PACKAGE__-
>register_method({
3116 name
=> 'delsnapshot',
3117 path
=> '{vmid}/snapshot/{snapname}',
3121 description
=> "Delete a VM snapshot.",
3123 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3126 additionalProperties
=> 0,
3128 node
=> get_standard_option
('pve-node'),
3129 vmid
=> get_standard_option
('pve-vmid'),
3130 snapname
=> get_standard_option
('pve-snapshot-name'),
3134 description
=> "For removal from config file, even if removing disk snapshots fails.",
3140 description
=> "the task ID.",
3145 my $rpcenv = PVE
::RPCEnvironment
::get
();
3147 my $authuser = $rpcenv->get_user();
3149 my $node = extract_param
($param, 'node');
3151 my $vmid = extract_param
($param, 'vmid');
3153 my $snapname = extract_param
($param, 'snapname');
3156 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3157 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3160 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3163 __PACKAGE__-
>register_method({
3165 path
=> '{vmid}/template',
3169 description
=> "Create a Template.",
3171 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3172 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3175 additionalProperties
=> 0,
3177 node
=> get_standard_option
('pve-node'),
3178 vmid
=> get_standard_option
('pve-vmid'),
3182 description
=> "If you want to convert only 1 disk to base image.",
3183 enum
=> [PVE
::QemuServer
::disknames
()],
3188 returns
=> { type
=> 'null'},
3192 my $rpcenv = PVE
::RPCEnvironment
::get
();
3194 my $authuser = $rpcenv->get_user();
3196 my $node = extract_param
($param, 'node');
3198 my $vmid = extract_param
($param, 'vmid');
3200 my $disk = extract_param
($param, 'disk');
3202 my $updatefn = sub {
3204 my $conf = PVE
::QemuServer
::load_config
($vmid);
3206 PVE
::QemuServer
::check_lock
($conf);
3208 die "unable to create template, because VM contains snapshots\n"
3209 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3211 die "you can't convert a template to a template\n"
3212 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3214 die "you can't convert a VM to template if VM is running\n"
3215 if PVE
::QemuServer
::check_running
($vmid);
3218 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3221 $conf->{template
} = 1;
3222 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3224 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3227 PVE
::QemuServer
::lock_config
($vmid, $updatefn);