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 # fixme: wrong place? howto handle pending changes? @delete ?
949 if ($param->{memory
} || defined($param->{balloon
})) {
950 my $maxmem = $param->{memory
} || $conf->{memory
} || $defaults->{memory
};
951 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{balloon
};
953 die "balloon value too large (must be smaller than assigned memory)\n"
954 if $balloon && $balloon > $maxmem;
957 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
961 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
963 # write updates to pending section
965 foreach my $opt (@delete) {
966 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
967 if ($opt =~ m/^unused/) {
968 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
969 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
970 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
971 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
972 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive);
973 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
975 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
976 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
977 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
978 if defined($conf->{pending
}->{$opt});
979 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt);
980 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
982 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt);
983 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
987 foreach my $opt (keys %$param) { # add/change
988 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
989 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
991 if (PVE
::QemuServer
::valid_drivename
($opt)) {
992 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
993 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
994 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
996 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
998 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
999 if defined($conf->{pending
}->{$opt});
1001 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1003 $conf->{pending
}->{$opt} = $param->{$opt};
1005 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1006 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
1009 # remove pending changes when nothing changed
1011 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
1012 foreach my $opt (keys %{$conf->{pending
}}) { # add/change
1013 if (defined($conf->{$opt}) && ($conf->{pending
}->{$opt} eq $conf->{$opt})) {
1015 delete $conf->{pending
}->{$opt};
1018 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1) if $changes;
1020 return if !scalar(keys %{$conf->{pending
}});
1022 my $running = PVE
::QemuServer
::check_running
($vmid);
1024 # apply pending changes
1026 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
1027 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1029 return; # TODO: remove old code below
1031 foreach my $opt (keys %$param) { # add/change
1033 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
1035 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
1037 if (PVE
::QemuServer
::valid_drivename
($opt)) {
1039 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
1040 $opt, $param->{$opt}, $force);
1042 } elsif ($opt =~ m/^net(\d+)$/) { #nics
1044 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
1045 $opt, $param->{$opt});
1049 if($opt eq 'tablet' && $param->{$opt} == 1){
1050 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
1051 } elsif($opt eq 'tablet' && $param->{$opt} == 0){
1052 PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
1055 if($opt eq 'cores' && $conf->{maxcpus
}){
1056 PVE
::QemuServer
::qemu_cpu_hotplug
($vmid, $conf, $param->{$opt});
1059 $conf->{$opt} = $param->{$opt};
1060 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
1064 # allow manual ballooning if shares is set to zero
1065 if ($running && defined($param->{balloon
}) &&
1066 defined($conf->{shares
}) && ($conf->{shares
} == 0)) {
1067 my $balloon = $param->{'balloon'} || $conf->{memory
} || $defaults->{memory
};
1068 PVE
::QemuServer
::vm_mon_cmd
($vmid, "balloon", value
=> $balloon*1024*1024);
1076 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1078 if ($background_delay) {
1080 # Note: It would be better to do that in the Event based HTTPServer
1081 # to avoid blocking call to sleep.
1083 my $end_time = time() + $background_delay;
1085 my $task = PVE
::Tools
::upid_decode
($upid);
1088 while (time() < $end_time) {
1089 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1091 sleep(1); # this gets interrupted when child process ends
1095 my $status = PVE
::Tools
::upid_read_status
($upid);
1096 return undef if $status eq 'OK';
1105 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1108 my $vm_config_perm_list = [
1113 'VM.Config.Network',
1115 'VM.Config.Options',
1118 __PACKAGE__-
>register_method({
1119 name
=> 'update_vm_async',
1120 path
=> '{vmid}/config',
1124 description
=> "Set virtual machine options (asynchrounous API).",
1126 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1129 additionalProperties
=> 0,
1130 properties
=> PVE
::QemuServer
::json_config_properties
(
1132 node
=> get_standard_option
('pve-node'),
1133 vmid
=> get_standard_option
('pve-vmid'),
1134 skiplock
=> get_standard_option
('skiplock'),
1136 type
=> 'string', format
=> 'pve-configid-list',
1137 description
=> "A list of settings you want to delete.",
1142 description
=> $opt_force_description,
1144 requires
=> 'delete',
1148 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1152 background_delay
=> {
1154 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1165 code
=> $update_vm_api,
1168 __PACKAGE__-
>register_method({
1169 name
=> 'update_vm',
1170 path
=> '{vmid}/config',
1174 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1176 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1179 additionalProperties
=> 0,
1180 properties
=> PVE
::QemuServer
::json_config_properties
(
1182 node
=> get_standard_option
('pve-node'),
1183 vmid
=> get_standard_option
('pve-vmid'),
1184 skiplock
=> get_standard_option
('skiplock'),
1186 type
=> 'string', format
=> 'pve-configid-list',
1187 description
=> "A list of settings you want to delete.",
1192 description
=> $opt_force_description,
1194 requires
=> 'delete',
1198 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1204 returns
=> { type
=> 'null' },
1207 &$update_vm_api($param, 1);
1213 __PACKAGE__-
>register_method({
1214 name
=> 'destroy_vm',
1219 description
=> "Destroy the vm (also delete all used/owned volumes).",
1221 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1224 additionalProperties
=> 0,
1226 node
=> get_standard_option
('pve-node'),
1227 vmid
=> get_standard_option
('pve-vmid'),
1228 skiplock
=> get_standard_option
('skiplock'),
1237 my $rpcenv = PVE
::RPCEnvironment
::get
();
1239 my $authuser = $rpcenv->get_user();
1241 my $vmid = $param->{vmid
};
1243 my $skiplock = $param->{skiplock
};
1244 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1245 if $skiplock && $authuser ne 'root@pam';
1248 my $conf = PVE
::QemuServer
::load_config
($vmid);
1250 my $storecfg = PVE
::Storage
::config
();
1252 my $delVMfromPoolFn = sub {
1253 my $usercfg = cfs_read_file
("user.cfg");
1254 if (my $pool = $usercfg->{vms
}->{$vmid}) {
1255 if (my $data = $usercfg->{pools
}->{$pool}) {
1256 delete $data->{vms
}->{$vmid};
1257 delete $usercfg->{vms
}->{$vmid};
1258 cfs_write_file
("user.cfg", $usercfg);
1266 syslog
('info', "destroy VM $vmid: $upid\n");
1268 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1270 PVE
::AccessControl
::remove_vm_from_pool
($vmid);
1273 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1276 __PACKAGE__-
>register_method({
1278 path
=> '{vmid}/unlink',
1282 description
=> "Unlink/delete disk images.",
1284 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1287 additionalProperties
=> 0,
1289 node
=> get_standard_option
('pve-node'),
1290 vmid
=> get_standard_option
('pve-vmid'),
1292 type
=> 'string', format
=> 'pve-configid-list',
1293 description
=> "A list of disk IDs you want to delete.",
1297 description
=> $opt_force_description,
1302 returns
=> { type
=> 'null'},
1306 $param->{delete} = extract_param
($param, 'idlist');
1308 __PACKAGE__-
>update_vm($param);
1315 __PACKAGE__-
>register_method({
1317 path
=> '{vmid}/vncproxy',
1321 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1323 description
=> "Creates a TCP VNC proxy connections.",
1325 additionalProperties
=> 0,
1327 node
=> get_standard_option
('pve-node'),
1328 vmid
=> get_standard_option
('pve-vmid'),
1332 description
=> "starts websockify instead of vncproxy",
1337 additionalProperties
=> 0,
1339 user
=> { type
=> 'string' },
1340 ticket
=> { type
=> 'string' },
1341 cert
=> { type
=> 'string' },
1342 port
=> { type
=> 'integer' },
1343 upid
=> { type
=> 'string' },
1349 my $rpcenv = PVE
::RPCEnvironment
::get
();
1351 my $authuser = $rpcenv->get_user();
1353 my $vmid = $param->{vmid
};
1354 my $node = $param->{node
};
1355 my $websocket = $param->{websocket
};
1357 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # check if VM exists
1359 my $authpath = "/vms/$vmid";
1361 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1363 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1366 my $port = PVE
::Tools
::next_vnc_port
();
1371 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1372 $remip = PVE
::Cluster
::remote_node_ip
($node);
1373 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1374 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1382 syslog
('info', "starting vnc proxy $upid\n");
1386 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1388 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1390 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1391 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1392 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1393 '-timeout', $timeout, '-authpath', $authpath,
1394 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1397 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1399 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1401 my $qmstr = join(' ', @$qmcmd);
1403 # also redirect stderr (else we get RFB protocol errors)
1404 $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1407 PVE
::Tools
::run_command
($cmd);
1412 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1414 PVE
::Tools
::wait_for_vnc_port
($port);
1425 __PACKAGE__-
>register_method({
1426 name
=> 'vncwebsocket',
1427 path
=> '{vmid}/vncwebsocket',
1430 description
=> "You also need to pass a valid ticket (vncticket).",
1431 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1433 description
=> "Opens a weksocket for VNC traffic.",
1435 additionalProperties
=> 0,
1437 node
=> get_standard_option
('pve-node'),
1438 vmid
=> get_standard_option
('pve-vmid'),
1440 description
=> "Ticket from previous call to vncproxy.",
1445 description
=> "Port number returned by previous vncproxy call.",
1455 port
=> { type
=> 'string' },
1461 my $rpcenv = PVE
::RPCEnvironment
::get
();
1463 my $authuser = $rpcenv->get_user();
1465 my $vmid = $param->{vmid
};
1466 my $node = $param->{node
};
1468 my $authpath = "/vms/$vmid";
1470 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1472 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # VM exists ?
1474 # Note: VNC ports are acessible from outside, so we do not gain any
1475 # security if we verify that $param->{port} belongs to VM $vmid. This
1476 # check is done by verifying the VNC ticket (inside VNC protocol).
1478 my $port = $param->{port
};
1480 return { port
=> $port };
1483 __PACKAGE__-
>register_method({
1484 name
=> 'spiceproxy',
1485 path
=> '{vmid}/spiceproxy',
1490 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1492 description
=> "Returns a SPICE configuration to connect to the VM.",
1494 additionalProperties
=> 0,
1496 node
=> get_standard_option
('pve-node'),
1497 vmid
=> get_standard_option
('pve-vmid'),
1498 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1501 returns
=> get_standard_option
('remote-viewer-config'),
1505 my $rpcenv = PVE
::RPCEnvironment
::get
();
1507 my $authuser = $rpcenv->get_user();
1509 my $vmid = $param->{vmid
};
1510 my $node = $param->{node
};
1511 my $proxy = $param->{proxy
};
1513 my $conf = PVE
::QemuServer
::load_config
($vmid, $node);
1514 my $title = "VM $vmid - $conf->{'name'}",
1516 my $port = PVE
::QemuServer
::spice_port
($vmid);
1518 my ($ticket, undef, $remote_viewer_config) =
1519 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1521 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1522 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1524 return $remote_viewer_config;
1527 __PACKAGE__-
>register_method({
1529 path
=> '{vmid}/status',
1532 description
=> "Directory index",
1537 additionalProperties
=> 0,
1539 node
=> get_standard_option
('pve-node'),
1540 vmid
=> get_standard_option
('pve-vmid'),
1548 subdir
=> { type
=> 'string' },
1551 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1557 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1560 { subdir
=> 'current' },
1561 { subdir
=> 'start' },
1562 { subdir
=> 'stop' },
1568 my $vm_is_ha_managed = sub {
1571 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1572 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1578 __PACKAGE__-
>register_method({
1579 name
=> 'vm_status',
1580 path
=> '{vmid}/status/current',
1583 protected
=> 1, # qemu pid files are only readable by root
1584 description
=> "Get virtual machine status.",
1586 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1589 additionalProperties
=> 0,
1591 node
=> get_standard_option
('pve-node'),
1592 vmid
=> get_standard_option
('pve-vmid'),
1595 returns
=> { type
=> 'object' },
1600 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1602 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1603 my $status = $vmstatus->{$param->{vmid
}};
1605 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1607 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1612 __PACKAGE__-
>register_method({
1614 path
=> '{vmid}/status/start',
1618 description
=> "Start virtual machine.",
1620 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1623 additionalProperties
=> 0,
1625 node
=> get_standard_option
('pve-node'),
1626 vmid
=> get_standard_option
('pve-vmid'),
1627 skiplock
=> get_standard_option
('skiplock'),
1628 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1629 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1630 machine
=> get_standard_option
('pve-qm-machine'),
1639 my $rpcenv = PVE
::RPCEnvironment
::get
();
1641 my $authuser = $rpcenv->get_user();
1643 my $node = extract_param
($param, 'node');
1645 my $vmid = extract_param
($param, 'vmid');
1647 my $machine = extract_param
($param, 'machine');
1649 my $stateuri = extract_param
($param, 'stateuri');
1650 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1651 if $stateuri && $authuser ne 'root@pam';
1653 my $skiplock = extract_param
($param, 'skiplock');
1654 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1655 if $skiplock && $authuser ne 'root@pam';
1657 my $migratedfrom = extract_param
($param, 'migratedfrom');
1658 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1659 if $migratedfrom && $authuser ne 'root@pam';
1661 # read spice ticket from STDIN
1663 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1664 if (defined(my $line = <>)) {
1666 $spice_ticket = $line;
1670 my $storecfg = PVE
::Storage
::config
();
1672 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1673 $rpcenv->{type
} ne 'ha') {
1678 my $service = "pvevm:$vmid";
1680 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1682 print "Executing HA start for VM $vmid\n";
1684 PVE
::Tools
::run_command
($cmd);
1689 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1696 syslog
('info', "start VM $vmid: $upid\n");
1698 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1699 $machine, $spice_ticket);
1704 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1708 __PACKAGE__-
>register_method({
1710 path
=> '{vmid}/status/stop',
1714 description
=> "Stop virtual machine.",
1716 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1719 additionalProperties
=> 0,
1721 node
=> get_standard_option
('pve-node'),
1722 vmid
=> get_standard_option
('pve-vmid'),
1723 skiplock
=> get_standard_option
('skiplock'),
1724 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1726 description
=> "Wait maximal timeout seconds.",
1732 description
=> "Do not decativate storage volumes.",
1745 my $rpcenv = PVE
::RPCEnvironment
::get
();
1747 my $authuser = $rpcenv->get_user();
1749 my $node = extract_param
($param, 'node');
1751 my $vmid = extract_param
($param, 'vmid');
1753 my $skiplock = extract_param
($param, 'skiplock');
1754 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1755 if $skiplock && $authuser ne 'root@pam';
1757 my $keepActive = extract_param
($param, 'keepActive');
1758 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1759 if $keepActive && $authuser ne 'root@pam';
1761 my $migratedfrom = extract_param
($param, 'migratedfrom');
1762 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1763 if $migratedfrom && $authuser ne 'root@pam';
1766 my $storecfg = PVE
::Storage
::config
();
1768 if (&$vm_is_ha_managed($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1773 my $service = "pvevm:$vmid";
1775 my $cmd = ['clusvcadm', '-d', $service];
1777 print "Executing HA stop for VM $vmid\n";
1779 PVE
::Tools
::run_command
($cmd);
1784 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1790 syslog
('info', "stop VM $vmid: $upid\n");
1792 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1793 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1798 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1802 __PACKAGE__-
>register_method({
1804 path
=> '{vmid}/status/reset',
1808 description
=> "Reset virtual machine.",
1810 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1813 additionalProperties
=> 0,
1815 node
=> get_standard_option
('pve-node'),
1816 vmid
=> get_standard_option
('pve-vmid'),
1817 skiplock
=> get_standard_option
('skiplock'),
1826 my $rpcenv = PVE
::RPCEnvironment
::get
();
1828 my $authuser = $rpcenv->get_user();
1830 my $node = extract_param
($param, 'node');
1832 my $vmid = extract_param
($param, 'vmid');
1834 my $skiplock = extract_param
($param, 'skiplock');
1835 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1836 if $skiplock && $authuser ne 'root@pam';
1838 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1843 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1848 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1851 __PACKAGE__-
>register_method({
1852 name
=> 'vm_shutdown',
1853 path
=> '{vmid}/status/shutdown',
1857 description
=> "Shutdown virtual machine.",
1859 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1862 additionalProperties
=> 0,
1864 node
=> get_standard_option
('pve-node'),
1865 vmid
=> get_standard_option
('pve-vmid'),
1866 skiplock
=> get_standard_option
('skiplock'),
1868 description
=> "Wait maximal timeout seconds.",
1874 description
=> "Make sure the VM stops.",
1880 description
=> "Do not decativate storage volumes.",
1893 my $rpcenv = PVE
::RPCEnvironment
::get
();
1895 my $authuser = $rpcenv->get_user();
1897 my $node = extract_param
($param, 'node');
1899 my $vmid = extract_param
($param, 'vmid');
1901 my $skiplock = extract_param
($param, 'skiplock');
1902 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1903 if $skiplock && $authuser ne 'root@pam';
1905 my $keepActive = extract_param
($param, 'keepActive');
1906 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1907 if $keepActive && $authuser ne 'root@pam';
1909 my $storecfg = PVE
::Storage
::config
();
1914 syslog
('info', "shutdown VM $vmid: $upid\n");
1916 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1917 1, $param->{forceStop
}, $keepActive);
1922 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1925 __PACKAGE__-
>register_method({
1926 name
=> 'vm_suspend',
1927 path
=> '{vmid}/status/suspend',
1931 description
=> "Suspend virtual machine.",
1933 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1936 additionalProperties
=> 0,
1938 node
=> get_standard_option
('pve-node'),
1939 vmid
=> get_standard_option
('pve-vmid'),
1940 skiplock
=> get_standard_option
('skiplock'),
1949 my $rpcenv = PVE
::RPCEnvironment
::get
();
1951 my $authuser = $rpcenv->get_user();
1953 my $node = extract_param
($param, 'node');
1955 my $vmid = extract_param
($param, 'vmid');
1957 my $skiplock = extract_param
($param, 'skiplock');
1958 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1959 if $skiplock && $authuser ne 'root@pam';
1961 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1966 syslog
('info', "suspend VM $vmid: $upid\n");
1968 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1973 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1976 __PACKAGE__-
>register_method({
1977 name
=> 'vm_resume',
1978 path
=> '{vmid}/status/resume',
1982 description
=> "Resume virtual machine.",
1984 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1987 additionalProperties
=> 0,
1989 node
=> get_standard_option
('pve-node'),
1990 vmid
=> get_standard_option
('pve-vmid'),
1991 skiplock
=> get_standard_option
('skiplock'),
2000 my $rpcenv = PVE
::RPCEnvironment
::get
();
2002 my $authuser = $rpcenv->get_user();
2004 my $node = extract_param
($param, 'node');
2006 my $vmid = extract_param
($param, 'vmid');
2008 my $skiplock = extract_param
($param, 'skiplock');
2009 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2010 if $skiplock && $authuser ne 'root@pam';
2012 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2017 syslog
('info', "resume VM $vmid: $upid\n");
2019 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
2024 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2027 __PACKAGE__-
>register_method({
2028 name
=> 'vm_sendkey',
2029 path
=> '{vmid}/sendkey',
2033 description
=> "Send key event to virtual machine.",
2035 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2038 additionalProperties
=> 0,
2040 node
=> get_standard_option
('pve-node'),
2041 vmid
=> get_standard_option
('pve-vmid'),
2042 skiplock
=> get_standard_option
('skiplock'),
2044 description
=> "The key (qemu monitor encoding).",
2049 returns
=> { type
=> 'null'},
2053 my $rpcenv = PVE
::RPCEnvironment
::get
();
2055 my $authuser = $rpcenv->get_user();
2057 my $node = extract_param
($param, 'node');
2059 my $vmid = extract_param
($param, 'vmid');
2061 my $skiplock = extract_param
($param, 'skiplock');
2062 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2063 if $skiplock && $authuser ne 'root@pam';
2065 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2070 __PACKAGE__-
>register_method({
2071 name
=> 'vm_feature',
2072 path
=> '{vmid}/feature',
2076 description
=> "Check if feature for virtual machine is available.",
2078 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2081 additionalProperties
=> 0,
2083 node
=> get_standard_option
('pve-node'),
2084 vmid
=> get_standard_option
('pve-vmid'),
2086 description
=> "Feature to check.",
2088 enum
=> [ 'snapshot', 'clone', 'copy' ],
2090 snapname
=> get_standard_option
('pve-snapshot-name', {
2098 hasFeature
=> { type
=> 'boolean' },
2101 items
=> { type
=> 'string' },
2108 my $node = extract_param
($param, 'node');
2110 my $vmid = extract_param
($param, 'vmid');
2112 my $snapname = extract_param
($param, 'snapname');
2114 my $feature = extract_param
($param, 'feature');
2116 my $running = PVE
::QemuServer
::check_running
($vmid);
2118 my $conf = PVE
::QemuServer
::load_config
($vmid);
2121 my $snap = $conf->{snapshots
}->{$snapname};
2122 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2125 my $storecfg = PVE
::Storage
::config
();
2127 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2128 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
2131 hasFeature
=> $hasFeature,
2132 nodes
=> [ keys %$nodelist ],
2136 __PACKAGE__-
>register_method({
2138 path
=> '{vmid}/clone',
2142 description
=> "Create a copy of virtual machine/template.",
2144 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2145 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2146 "'Datastore.AllocateSpace' on any used storage.",
2149 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2151 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2152 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2157 additionalProperties
=> 0,
2159 node
=> get_standard_option
('pve-node'),
2160 vmid
=> get_standard_option
('pve-vmid'),
2161 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2164 type
=> 'string', format
=> 'dns-name',
2165 description
=> "Set a name for the new VM.",
2170 description
=> "Description for the new VM.",
2174 type
=> 'string', format
=> 'pve-poolid',
2175 description
=> "Add the new VM to the specified pool.",
2177 snapname
=> get_standard_option
('pve-snapshot-name', {
2180 storage
=> get_standard_option
('pve-storage-id', {
2181 description
=> "Target storage for full clone.",
2186 description
=> "Target format for file storage.",
2190 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2195 description
=> "Create a full copy of all disk. This is always done when " .
2196 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2199 target
=> get_standard_option
('pve-node', {
2200 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2211 my $rpcenv = PVE
::RPCEnvironment
::get
();
2213 my $authuser = $rpcenv->get_user();
2215 my $node = extract_param
($param, 'node');
2217 my $vmid = extract_param
($param, 'vmid');
2219 my $newid = extract_param
($param, 'newid');
2221 my $pool = extract_param
($param, 'pool');
2223 if (defined($pool)) {
2224 $rpcenv->check_pool_exist($pool);
2227 my $snapname = extract_param
($param, 'snapname');
2229 my $storage = extract_param
($param, 'storage');
2231 my $format = extract_param
($param, 'format');
2233 my $target = extract_param
($param, 'target');
2235 my $localnode = PVE
::INotify
::nodename
();
2237 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2239 PVE
::Cluster
::check_node_exists
($target) if $target;
2241 my $storecfg = PVE
::Storage
::config
();
2244 # check if storage is enabled on local node
2245 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2247 # check if storage is available on target node
2248 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2249 # clone only works if target storage is shared
2250 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2251 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2255 PVE
::Cluster
::check_cfs_quorum
();
2257 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2259 # exclusive lock if VM is running - else shared lock is enough;
2260 my $shared_lock = $running ?
0 : 1;
2264 # do all tests after lock
2265 # we also try to do all tests before we fork the worker
2267 my $conf = PVE
::QemuServer
::load_config
($vmid);
2269 PVE
::QemuServer
::check_lock
($conf);
2271 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2273 die "unexpected state change\n" if $verify_running != $running;
2275 die "snapshot '$snapname' does not exist\n"
2276 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2278 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2280 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2282 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2284 my $conffile = PVE
::QemuServer
::config_file
($newid);
2286 die "unable to create VM $newid: config file already exists\n"
2289 my $newconf = { lock => 'clone' };
2293 foreach my $opt (keys %$oldconf) {
2294 my $value = $oldconf->{$opt};
2296 # do not copy snapshot related info
2297 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2298 $opt eq 'vmstate' || $opt eq 'snapstate';
2300 # always change MAC! address
2301 if ($opt =~ m/^net(\d+)$/) {
2302 my $net = PVE
::QemuServer
::parse_net
($value);
2303 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2304 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2305 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
2306 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2307 die "unable to parse drive options for '$opt'\n" if !$drive;
2308 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2309 $newconf->{$opt} = $value; # simply copy configuration
2311 if ($param->{full
}) {
2312 die "Full clone feature is not available"
2313 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2316 # not full means clone instead of copy
2317 die "Linked clone feature is not available"
2318 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2320 $drives->{$opt} = $drive;
2321 push @$vollist, $drive->{file
};
2324 # copy everything else
2325 $newconf->{$opt} = $value;
2329 # auto generate a new uuid
2330 my ($uuid, $uuid_str);
2331 UUID
::generate
($uuid);
2332 UUID
::unparse
($uuid, $uuid_str);
2333 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2334 $smbios1->{uuid
} = $uuid_str;
2335 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2337 delete $newconf->{template
};
2339 if ($param->{name
}) {
2340 $newconf->{name
} = $param->{name
};
2342 if ($oldconf->{name
}) {
2343 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2345 $newconf->{name
} = "Copy-of-VM-$vmid";
2349 if ($param->{description
}) {
2350 $newconf->{description
} = $param->{description
};
2353 # create empty/temp config - this fails if VM already exists on other node
2354 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2359 my $newvollist = [];
2362 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2364 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2366 foreach my $opt (keys %$drives) {
2367 my $drive = $drives->{$opt};
2369 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2370 $newid, $storage, $format, $drive->{full
}, $newvollist);
2372 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2374 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2377 delete $newconf->{lock};
2378 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2381 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2382 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist);
2384 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2385 die "Failed to move config to node '$target' - rename failed: $!\n"
2386 if !rename($conffile, $newconffile);
2389 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2394 sleep 1; # some storage like rbd need to wait before release volume - really?
2396 foreach my $volid (@$newvollist) {
2397 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2400 die "clone failed: $err";
2406 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2409 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2410 # Aquire exclusive lock lock for $newid
2411 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2416 __PACKAGE__-
>register_method({
2417 name
=> 'move_vm_disk',
2418 path
=> '{vmid}/move_disk',
2422 description
=> "Move volume to different storage.",
2424 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2425 "and 'Datastore.AllocateSpace' permissions on the storage.",
2428 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2429 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2433 additionalProperties
=> 0,
2435 node
=> get_standard_option
('pve-node'),
2436 vmid
=> get_standard_option
('pve-vmid'),
2439 description
=> "The disk you want to move.",
2440 enum
=> [ PVE
::QemuServer
::disknames
() ],
2442 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2445 description
=> "Target Format.",
2446 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2451 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2457 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2465 description
=> "the task ID.",
2470 my $rpcenv = PVE
::RPCEnvironment
::get
();
2472 my $authuser = $rpcenv->get_user();
2474 my $node = extract_param
($param, 'node');
2476 my $vmid = extract_param
($param, 'vmid');
2478 my $digest = extract_param
($param, 'digest');
2480 my $disk = extract_param
($param, 'disk');
2482 my $storeid = extract_param
($param, 'storage');
2484 my $format = extract_param
($param, 'format');
2486 my $storecfg = PVE
::Storage
::config
();
2488 my $updatefn = sub {
2490 my $conf = PVE
::QemuServer
::load_config
($vmid);
2492 die "checksum missmatch (file change by other user?)\n"
2493 if $digest && $digest ne $conf->{digest
};
2495 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2497 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2499 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2501 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2504 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2505 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2509 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2510 (!$format || !$oldfmt || $oldfmt eq $format);
2512 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2514 my $running = PVE
::QemuServer
::check_running
($vmid);
2516 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2520 my $newvollist = [];
2523 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2525 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2526 $vmid, $storeid, $format, 1, $newvollist);
2528 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2530 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2532 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2535 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2536 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2543 foreach my $volid (@$newvollist) {
2544 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2547 die "storage migration failed: $err";
2550 if ($param->{delete}) {
2551 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, 1);
2552 my $path = PVE
::Storage
::path
($storecfg, $old_volid);
2553 if ($used_paths->{$path}){
2554 warn "volume $old_volid have snapshots. Can't delete it\n";
2555 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid);
2556 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2558 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2564 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2567 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2570 __PACKAGE__-
>register_method({
2571 name
=> 'migrate_vm',
2572 path
=> '{vmid}/migrate',
2576 description
=> "Migrate virtual machine. Creates a new migration task.",
2578 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2581 additionalProperties
=> 0,
2583 node
=> get_standard_option
('pve-node'),
2584 vmid
=> get_standard_option
('pve-vmid'),
2585 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2588 description
=> "Use online/live migration.",
2593 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2600 description
=> "the task ID.",
2605 my $rpcenv = PVE
::RPCEnvironment
::get
();
2607 my $authuser = $rpcenv->get_user();
2609 my $target = extract_param
($param, 'target');
2611 my $localnode = PVE
::INotify
::nodename
();
2612 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2614 PVE
::Cluster
::check_cfs_quorum
();
2616 PVE
::Cluster
::check_node_exists
($target);
2618 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2620 my $vmid = extract_param
($param, 'vmid');
2622 raise_param_exc
({ force
=> "Only root may use this option." })
2623 if $param->{force
} && $authuser ne 'root@pam';
2626 my $conf = PVE
::QemuServer
::load_config
($vmid);
2628 # try to detect errors early
2630 PVE
::QemuServer
::check_lock
($conf);
2632 if (PVE
::QemuServer
::check_running
($vmid)) {
2633 die "cant migrate running VM without --online\n"
2634 if !$param->{online
};
2637 my $storecfg = PVE
::Storage
::config
();
2638 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2640 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2645 my $service = "pvevm:$vmid";
2647 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2649 print "Executing HA migrate for VM $vmid to node $target\n";
2651 PVE
::Tools
::run_command
($cmd);
2656 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2663 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2666 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2671 __PACKAGE__-
>register_method({
2673 path
=> '{vmid}/monitor',
2677 description
=> "Execute Qemu monitor commands.",
2679 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2682 additionalProperties
=> 0,
2684 node
=> get_standard_option
('pve-node'),
2685 vmid
=> get_standard_option
('pve-vmid'),
2688 description
=> "The monitor command.",
2692 returns
=> { type
=> 'string'},
2696 my $vmid = $param->{vmid
};
2698 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2702 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2704 $res = "ERROR: $@" if $@;
2709 __PACKAGE__-
>register_method({
2710 name
=> 'resize_vm',
2711 path
=> '{vmid}/resize',
2715 description
=> "Extend volume size.",
2717 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2720 additionalProperties
=> 0,
2722 node
=> get_standard_option
('pve-node'),
2723 vmid
=> get_standard_option
('pve-vmid'),
2724 skiplock
=> get_standard_option
('skiplock'),
2727 description
=> "The disk you want to resize.",
2728 enum
=> [PVE
::QemuServer
::disknames
()],
2732 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2733 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.",
2737 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2743 returns
=> { type
=> 'null'},
2747 my $rpcenv = PVE
::RPCEnvironment
::get
();
2749 my $authuser = $rpcenv->get_user();
2751 my $node = extract_param
($param, 'node');
2753 my $vmid = extract_param
($param, 'vmid');
2755 my $digest = extract_param
($param, 'digest');
2757 my $disk = extract_param
($param, 'disk');
2759 my $sizestr = extract_param
($param, 'size');
2761 my $skiplock = extract_param
($param, 'skiplock');
2762 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2763 if $skiplock && $authuser ne 'root@pam';
2765 my $storecfg = PVE
::Storage
::config
();
2767 my $updatefn = sub {
2769 my $conf = PVE
::QemuServer
::load_config
($vmid);
2771 die "checksum missmatch (file change by other user?)\n"
2772 if $digest && $digest ne $conf->{digest
};
2773 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2775 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2777 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2779 my $volid = $drive->{file
};
2781 die "disk '$disk' has no associated volume\n" if !$volid;
2783 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2785 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2787 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2789 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2791 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2792 my ($ext, $newsize, $unit) = ($1, $2, $4);
2795 $newsize = $newsize * 1024;
2796 } elsif ($unit eq 'M') {
2797 $newsize = $newsize * 1024 * 1024;
2798 } elsif ($unit eq 'G') {
2799 $newsize = $newsize * 1024 * 1024 * 1024;
2800 } elsif ($unit eq 'T') {
2801 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2804 $newsize += $size if $ext;
2805 $newsize = int($newsize);
2807 die "unable to skrink disk size\n" if $newsize < $size;
2809 return if $size == $newsize;
2811 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2813 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2815 $drive->{size
} = $newsize;
2816 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2818 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2821 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2825 __PACKAGE__-
>register_method({
2826 name
=> 'snapshot_list',
2827 path
=> '{vmid}/snapshot',
2829 description
=> "List all snapshots.",
2831 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2834 protected
=> 1, # qemu pid files are only readable by root
2836 additionalProperties
=> 0,
2838 vmid
=> get_standard_option
('pve-vmid'),
2839 node
=> get_standard_option
('pve-node'),
2848 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2853 my $vmid = $param->{vmid
};
2855 my $conf = PVE
::QemuServer
::load_config
($vmid);
2856 my $snaphash = $conf->{snapshots
} || {};
2860 foreach my $name (keys %$snaphash) {
2861 my $d = $snaphash->{$name};
2864 snaptime
=> $d->{snaptime
} || 0,
2865 vmstate
=> $d->{vmstate
} ?
1 : 0,
2866 description
=> $d->{description
} || '',
2868 $item->{parent
} = $d->{parent
} if $d->{parent
};
2869 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2873 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2874 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2875 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2877 push @$res, $current;
2882 __PACKAGE__-
>register_method({
2884 path
=> '{vmid}/snapshot',
2888 description
=> "Snapshot a VM.",
2890 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2893 additionalProperties
=> 0,
2895 node
=> get_standard_option
('pve-node'),
2896 vmid
=> get_standard_option
('pve-vmid'),
2897 snapname
=> get_standard_option
('pve-snapshot-name'),
2901 description
=> "Save the vmstate",
2906 description
=> "A textual description or comment.",
2912 description
=> "the task ID.",
2917 my $rpcenv = PVE
::RPCEnvironment
::get
();
2919 my $authuser = $rpcenv->get_user();
2921 my $node = extract_param
($param, 'node');
2923 my $vmid = extract_param
($param, 'vmid');
2925 my $snapname = extract_param
($param, 'snapname');
2927 die "unable to use snapshot name 'current' (reserved name)\n"
2928 if $snapname eq 'current';
2931 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2932 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2933 $param->{description
});
2936 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2939 __PACKAGE__-
>register_method({
2940 name
=> 'snapshot_cmd_idx',
2941 path
=> '{vmid}/snapshot/{snapname}',
2948 additionalProperties
=> 0,
2950 vmid
=> get_standard_option
('pve-vmid'),
2951 node
=> get_standard_option
('pve-node'),
2952 snapname
=> get_standard_option
('pve-snapshot-name'),
2961 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2968 push @$res, { cmd
=> 'rollback' };
2969 push @$res, { cmd
=> 'config' };
2974 __PACKAGE__-
>register_method({
2975 name
=> 'update_snapshot_config',
2976 path
=> '{vmid}/snapshot/{snapname}/config',
2980 description
=> "Update snapshot metadata.",
2982 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2985 additionalProperties
=> 0,
2987 node
=> get_standard_option
('pve-node'),
2988 vmid
=> get_standard_option
('pve-vmid'),
2989 snapname
=> get_standard_option
('pve-snapshot-name'),
2993 description
=> "A textual description or comment.",
2997 returns
=> { type
=> 'null' },
3001 my $rpcenv = PVE
::RPCEnvironment
::get
();
3003 my $authuser = $rpcenv->get_user();
3005 my $vmid = extract_param
($param, 'vmid');
3007 my $snapname = extract_param
($param, 'snapname');
3009 return undef if !defined($param->{description
});
3011 my $updatefn = sub {
3013 my $conf = PVE
::QemuServer
::load_config
($vmid);
3015 PVE
::QemuServer
::check_lock
($conf);
3017 my $snap = $conf->{snapshots
}->{$snapname};
3019 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3021 $snap->{description
} = $param->{description
} if defined($param->{description
});
3023 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3026 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
3031 __PACKAGE__-
>register_method({
3032 name
=> 'get_snapshot_config',
3033 path
=> '{vmid}/snapshot/{snapname}/config',
3036 description
=> "Get snapshot configuration",
3038 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3041 additionalProperties
=> 0,
3043 node
=> get_standard_option
('pve-node'),
3044 vmid
=> get_standard_option
('pve-vmid'),
3045 snapname
=> get_standard_option
('pve-snapshot-name'),
3048 returns
=> { type
=> "object" },
3052 my $rpcenv = PVE
::RPCEnvironment
::get
();
3054 my $authuser = $rpcenv->get_user();
3056 my $vmid = extract_param
($param, 'vmid');
3058 my $snapname = extract_param
($param, 'snapname');
3060 my $conf = PVE
::QemuServer
::load_config
($vmid);
3062 my $snap = $conf->{snapshots
}->{$snapname};
3064 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3069 __PACKAGE__-
>register_method({
3071 path
=> '{vmid}/snapshot/{snapname}/rollback',
3075 description
=> "Rollback VM state to specified snapshot.",
3077 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3080 additionalProperties
=> 0,
3082 node
=> get_standard_option
('pve-node'),
3083 vmid
=> get_standard_option
('pve-vmid'),
3084 snapname
=> get_standard_option
('pve-snapshot-name'),
3089 description
=> "the task ID.",
3094 my $rpcenv = PVE
::RPCEnvironment
::get
();
3096 my $authuser = $rpcenv->get_user();
3098 my $node = extract_param
($param, 'node');
3100 my $vmid = extract_param
($param, 'vmid');
3102 my $snapname = extract_param
($param, 'snapname');
3105 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3106 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
3109 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3112 __PACKAGE__-
>register_method({
3113 name
=> 'delsnapshot',
3114 path
=> '{vmid}/snapshot/{snapname}',
3118 description
=> "Delete a VM snapshot.",
3120 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3123 additionalProperties
=> 0,
3125 node
=> get_standard_option
('pve-node'),
3126 vmid
=> get_standard_option
('pve-vmid'),
3127 snapname
=> get_standard_option
('pve-snapshot-name'),
3131 description
=> "For removal from config file, even if removing disk snapshots fails.",
3137 description
=> "the task ID.",
3142 my $rpcenv = PVE
::RPCEnvironment
::get
();
3144 my $authuser = $rpcenv->get_user();
3146 my $node = extract_param
($param, 'node');
3148 my $vmid = extract_param
($param, 'vmid');
3150 my $snapname = extract_param
($param, 'snapname');
3153 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3154 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3157 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3160 __PACKAGE__-
>register_method({
3162 path
=> '{vmid}/template',
3166 description
=> "Create a Template.",
3168 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3169 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3172 additionalProperties
=> 0,
3174 node
=> get_standard_option
('pve-node'),
3175 vmid
=> get_standard_option
('pve-vmid'),
3179 description
=> "If you want to convert only 1 disk to base image.",
3180 enum
=> [PVE
::QemuServer
::disknames
()],
3185 returns
=> { type
=> 'null'},
3189 my $rpcenv = PVE
::RPCEnvironment
::get
();
3191 my $authuser = $rpcenv->get_user();
3193 my $node = extract_param
($param, 'node');
3195 my $vmid = extract_param
($param, 'vmid');
3197 my $disk = extract_param
($param, 'disk');
3199 my $updatefn = sub {
3201 my $conf = PVE
::QemuServer
::load_config
($vmid);
3203 PVE
::QemuServer
::check_lock
($conf);
3205 die "unable to create template, because VM contains snapshots\n"
3206 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3208 die "you can't convert a template to a template\n"
3209 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3211 die "you can't convert a VM to template if VM is running\n"
3212 if PVE
::QemuServer
::check_running
($vmid);
3215 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3218 $conf->{template
} = 1;
3219 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3221 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3224 PVE
::QemuServer
::lock_config
($vmid, $updatefn);