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 foreach my $opt (@delete) {
965 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
966 if ($opt =~ m/^unused/) {
967 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
968 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
969 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
970 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
971 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive);
972 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
974 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
975 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
976 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
977 if defined($conf->{pending
}->{$opt});
978 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt);
979 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
981 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt);
982 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
986 foreach my $opt (keys %$param) { # add/change
987 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
988 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
990 if (PVE
::QemuServer
::valid_drivename
($opt)) {
991 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
992 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
993 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
995 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
997 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
998 if defined($conf->{pending
}->{$opt});
1000 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1002 $conf->{pending
}->{$opt} = $param->{$opt};
1004 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1005 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
1008 # remove pending changes when nothing changed
1010 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
1011 foreach my $opt (keys %{$conf->{pending
}}) { # add/change
1012 if (defined($conf->{$opt}) && ($conf->{pending
}->{$opt} eq $conf->{$opt})) {
1014 delete $conf->{pending
}->{$opt};
1017 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1) if $changes;
1019 return if !scalar(keys %{$conf->{pending
}});
1021 my $running = PVE
::QemuServer
::check_running
($vmid);
1023 # apply pending changes
1025 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
1026 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1028 return; # TODO: remove old code below
1030 foreach my $opt (keys %$param) { # add/change
1032 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
1034 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
1036 if (PVE
::QemuServer
::valid_drivename
($opt)) {
1038 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
1039 $opt, $param->{$opt}, $force);
1041 } elsif ($opt =~ m/^net(\d+)$/) { #nics
1043 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
1044 $opt, $param->{$opt});
1048 if($opt eq 'tablet' && $param->{$opt} == 1){
1049 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
1050 } elsif($opt eq 'tablet' && $param->{$opt} == 0){
1051 PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
1054 if($opt eq 'cores' && $conf->{maxcpus
}){
1055 PVE
::QemuServer
::qemu_cpu_hotplug
($vmid, $conf, $param->{$opt});
1058 $conf->{$opt} = $param->{$opt};
1059 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
1063 # allow manual ballooning if shares is set to zero
1064 if ($running && defined($param->{balloon
}) &&
1065 defined($conf->{shares
}) && ($conf->{shares
} == 0)) {
1066 my $balloon = $param->{'balloon'} || $conf->{memory
} || $defaults->{memory
};
1067 PVE
::QemuServer
::vm_mon_cmd
($vmid, "balloon", value
=> $balloon*1024*1024);
1075 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1077 if ($background_delay) {
1079 # Note: It would be better to do that in the Event based HTTPServer
1080 # to avoid blocking call to sleep.
1082 my $end_time = time() + $background_delay;
1084 my $task = PVE
::Tools
::upid_decode
($upid);
1087 while (time() < $end_time) {
1088 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1090 sleep(1); # this gets interrupted when child process ends
1094 my $status = PVE
::Tools
::upid_read_status
($upid);
1095 return undef if $status eq 'OK';
1104 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1107 my $vm_config_perm_list = [
1112 'VM.Config.Network',
1114 'VM.Config.Options',
1117 __PACKAGE__-
>register_method({
1118 name
=> 'update_vm_async',
1119 path
=> '{vmid}/config',
1123 description
=> "Set virtual machine options (asynchrounous API).",
1125 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1128 additionalProperties
=> 0,
1129 properties
=> PVE
::QemuServer
::json_config_properties
(
1131 node
=> get_standard_option
('pve-node'),
1132 vmid
=> get_standard_option
('pve-vmid'),
1133 skiplock
=> get_standard_option
('skiplock'),
1135 type
=> 'string', format
=> 'pve-configid-list',
1136 description
=> "A list of settings you want to delete.",
1141 description
=> $opt_force_description,
1143 requires
=> 'delete',
1147 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1151 background_delay
=> {
1153 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1164 code
=> $update_vm_api,
1167 __PACKAGE__-
>register_method({
1168 name
=> 'update_vm',
1169 path
=> '{vmid}/config',
1173 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1175 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1178 additionalProperties
=> 0,
1179 properties
=> PVE
::QemuServer
::json_config_properties
(
1181 node
=> get_standard_option
('pve-node'),
1182 vmid
=> get_standard_option
('pve-vmid'),
1183 skiplock
=> get_standard_option
('skiplock'),
1185 type
=> 'string', format
=> 'pve-configid-list',
1186 description
=> "A list of settings you want to delete.",
1191 description
=> $opt_force_description,
1193 requires
=> 'delete',
1197 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1203 returns
=> { type
=> 'null' },
1206 &$update_vm_api($param, 1);
1212 __PACKAGE__-
>register_method({
1213 name
=> 'destroy_vm',
1218 description
=> "Destroy the vm (also delete all used/owned volumes).",
1220 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1223 additionalProperties
=> 0,
1225 node
=> get_standard_option
('pve-node'),
1226 vmid
=> get_standard_option
('pve-vmid'),
1227 skiplock
=> get_standard_option
('skiplock'),
1236 my $rpcenv = PVE
::RPCEnvironment
::get
();
1238 my $authuser = $rpcenv->get_user();
1240 my $vmid = $param->{vmid
};
1242 my $skiplock = $param->{skiplock
};
1243 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1244 if $skiplock && $authuser ne 'root@pam';
1247 my $conf = PVE
::QemuServer
::load_config
($vmid);
1249 my $storecfg = PVE
::Storage
::config
();
1251 my $delVMfromPoolFn = sub {
1252 my $usercfg = cfs_read_file
("user.cfg");
1253 if (my $pool = $usercfg->{vms
}->{$vmid}) {
1254 if (my $data = $usercfg->{pools
}->{$pool}) {
1255 delete $data->{vms
}->{$vmid};
1256 delete $usercfg->{vms
}->{$vmid};
1257 cfs_write_file
("user.cfg", $usercfg);
1265 syslog
('info', "destroy VM $vmid: $upid\n");
1267 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1269 PVE
::AccessControl
::remove_vm_from_pool
($vmid);
1272 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1275 __PACKAGE__-
>register_method({
1277 path
=> '{vmid}/unlink',
1281 description
=> "Unlink/delete disk images.",
1283 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1286 additionalProperties
=> 0,
1288 node
=> get_standard_option
('pve-node'),
1289 vmid
=> get_standard_option
('pve-vmid'),
1291 type
=> 'string', format
=> 'pve-configid-list',
1292 description
=> "A list of disk IDs you want to delete.",
1296 description
=> $opt_force_description,
1301 returns
=> { type
=> 'null'},
1305 $param->{delete} = extract_param
($param, 'idlist');
1307 __PACKAGE__-
>update_vm($param);
1314 __PACKAGE__-
>register_method({
1316 path
=> '{vmid}/vncproxy',
1320 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1322 description
=> "Creates a TCP VNC proxy connections.",
1324 additionalProperties
=> 0,
1326 node
=> get_standard_option
('pve-node'),
1327 vmid
=> get_standard_option
('pve-vmid'),
1331 description
=> "starts websockify instead of vncproxy",
1336 additionalProperties
=> 0,
1338 user
=> { type
=> 'string' },
1339 ticket
=> { type
=> 'string' },
1340 cert
=> { type
=> 'string' },
1341 port
=> { type
=> 'integer' },
1342 upid
=> { type
=> 'string' },
1348 my $rpcenv = PVE
::RPCEnvironment
::get
();
1350 my $authuser = $rpcenv->get_user();
1352 my $vmid = $param->{vmid
};
1353 my $node = $param->{node
};
1354 my $websocket = $param->{websocket
};
1356 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # check if VM exists
1358 my $authpath = "/vms/$vmid";
1360 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1362 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1365 my $port = PVE
::Tools
::next_vnc_port
();
1370 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1371 $remip = PVE
::Cluster
::remote_node_ip
($node);
1372 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1373 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1381 syslog
('info', "starting vnc proxy $upid\n");
1385 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1387 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1389 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1390 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1391 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1392 '-timeout', $timeout, '-authpath', $authpath,
1393 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1396 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1398 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1400 my $qmstr = join(' ', @$qmcmd);
1402 # also redirect stderr (else we get RFB protocol errors)
1403 $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1406 PVE
::Tools
::run_command
($cmd);
1411 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1413 PVE
::Tools
::wait_for_vnc_port
($port);
1424 __PACKAGE__-
>register_method({
1425 name
=> 'vncwebsocket',
1426 path
=> '{vmid}/vncwebsocket',
1429 description
=> "You also need to pass a valid ticket (vncticket).",
1430 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1432 description
=> "Opens a weksocket for VNC traffic.",
1434 additionalProperties
=> 0,
1436 node
=> get_standard_option
('pve-node'),
1437 vmid
=> get_standard_option
('pve-vmid'),
1439 description
=> "Ticket from previous call to vncproxy.",
1444 description
=> "Port number returned by previous vncproxy call.",
1454 port
=> { type
=> 'string' },
1460 my $rpcenv = PVE
::RPCEnvironment
::get
();
1462 my $authuser = $rpcenv->get_user();
1464 my $vmid = $param->{vmid
};
1465 my $node = $param->{node
};
1467 my $authpath = "/vms/$vmid";
1469 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1471 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # VM exists ?
1473 # Note: VNC ports are acessible from outside, so we do not gain any
1474 # security if we verify that $param->{port} belongs to VM $vmid. This
1475 # check is done by verifying the VNC ticket (inside VNC protocol).
1477 my $port = $param->{port
};
1479 return { port
=> $port };
1482 __PACKAGE__-
>register_method({
1483 name
=> 'spiceproxy',
1484 path
=> '{vmid}/spiceproxy',
1489 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1491 description
=> "Returns a SPICE configuration to connect to the VM.",
1493 additionalProperties
=> 0,
1495 node
=> get_standard_option
('pve-node'),
1496 vmid
=> get_standard_option
('pve-vmid'),
1497 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1500 returns
=> get_standard_option
('remote-viewer-config'),
1504 my $rpcenv = PVE
::RPCEnvironment
::get
();
1506 my $authuser = $rpcenv->get_user();
1508 my $vmid = $param->{vmid
};
1509 my $node = $param->{node
};
1510 my $proxy = $param->{proxy
};
1512 my $conf = PVE
::QemuServer
::load_config
($vmid, $node);
1513 my $title = "VM $vmid - $conf->{'name'}",
1515 my $port = PVE
::QemuServer
::spice_port
($vmid);
1517 my ($ticket, undef, $remote_viewer_config) =
1518 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1520 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1521 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1523 return $remote_viewer_config;
1526 __PACKAGE__-
>register_method({
1528 path
=> '{vmid}/status',
1531 description
=> "Directory index",
1536 additionalProperties
=> 0,
1538 node
=> get_standard_option
('pve-node'),
1539 vmid
=> get_standard_option
('pve-vmid'),
1547 subdir
=> { type
=> 'string' },
1550 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1556 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1559 { subdir
=> 'current' },
1560 { subdir
=> 'start' },
1561 { subdir
=> 'stop' },
1567 my $vm_is_ha_managed = sub {
1570 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1571 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1577 __PACKAGE__-
>register_method({
1578 name
=> 'vm_status',
1579 path
=> '{vmid}/status/current',
1582 protected
=> 1, # qemu pid files are only readable by root
1583 description
=> "Get virtual machine status.",
1585 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1588 additionalProperties
=> 0,
1590 node
=> get_standard_option
('pve-node'),
1591 vmid
=> get_standard_option
('pve-vmid'),
1594 returns
=> { type
=> 'object' },
1599 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1601 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1602 my $status = $vmstatus->{$param->{vmid
}};
1604 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1606 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1611 __PACKAGE__-
>register_method({
1613 path
=> '{vmid}/status/start',
1617 description
=> "Start virtual machine.",
1619 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1622 additionalProperties
=> 0,
1624 node
=> get_standard_option
('pve-node'),
1625 vmid
=> get_standard_option
('pve-vmid'),
1626 skiplock
=> get_standard_option
('skiplock'),
1627 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1628 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1629 machine
=> get_standard_option
('pve-qm-machine'),
1638 my $rpcenv = PVE
::RPCEnvironment
::get
();
1640 my $authuser = $rpcenv->get_user();
1642 my $node = extract_param
($param, 'node');
1644 my $vmid = extract_param
($param, 'vmid');
1646 my $machine = extract_param
($param, 'machine');
1648 my $stateuri = extract_param
($param, 'stateuri');
1649 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1650 if $stateuri && $authuser ne 'root@pam';
1652 my $skiplock = extract_param
($param, 'skiplock');
1653 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1654 if $skiplock && $authuser ne 'root@pam';
1656 my $migratedfrom = extract_param
($param, 'migratedfrom');
1657 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1658 if $migratedfrom && $authuser ne 'root@pam';
1660 # read spice ticket from STDIN
1662 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1663 if (defined(my $line = <>)) {
1665 $spice_ticket = $line;
1669 my $storecfg = PVE
::Storage
::config
();
1671 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1672 $rpcenv->{type
} ne 'ha') {
1677 my $service = "pvevm:$vmid";
1679 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1681 print "Executing HA start for VM $vmid\n";
1683 PVE
::Tools
::run_command
($cmd);
1688 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1695 syslog
('info', "start VM $vmid: $upid\n");
1697 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1698 $machine, $spice_ticket);
1703 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1707 __PACKAGE__-
>register_method({
1709 path
=> '{vmid}/status/stop',
1713 description
=> "Stop virtual machine.",
1715 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1718 additionalProperties
=> 0,
1720 node
=> get_standard_option
('pve-node'),
1721 vmid
=> get_standard_option
('pve-vmid'),
1722 skiplock
=> get_standard_option
('skiplock'),
1723 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1725 description
=> "Wait maximal timeout seconds.",
1731 description
=> "Do not decativate storage volumes.",
1744 my $rpcenv = PVE
::RPCEnvironment
::get
();
1746 my $authuser = $rpcenv->get_user();
1748 my $node = extract_param
($param, 'node');
1750 my $vmid = extract_param
($param, 'vmid');
1752 my $skiplock = extract_param
($param, 'skiplock');
1753 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1754 if $skiplock && $authuser ne 'root@pam';
1756 my $keepActive = extract_param
($param, 'keepActive');
1757 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1758 if $keepActive && $authuser ne 'root@pam';
1760 my $migratedfrom = extract_param
($param, 'migratedfrom');
1761 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1762 if $migratedfrom && $authuser ne 'root@pam';
1765 my $storecfg = PVE
::Storage
::config
();
1767 if (&$vm_is_ha_managed($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1772 my $service = "pvevm:$vmid";
1774 my $cmd = ['clusvcadm', '-d', $service];
1776 print "Executing HA stop for VM $vmid\n";
1778 PVE
::Tools
::run_command
($cmd);
1783 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1789 syslog
('info', "stop VM $vmid: $upid\n");
1791 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1792 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1797 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1801 __PACKAGE__-
>register_method({
1803 path
=> '{vmid}/status/reset',
1807 description
=> "Reset virtual machine.",
1809 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1812 additionalProperties
=> 0,
1814 node
=> get_standard_option
('pve-node'),
1815 vmid
=> get_standard_option
('pve-vmid'),
1816 skiplock
=> get_standard_option
('skiplock'),
1825 my $rpcenv = PVE
::RPCEnvironment
::get
();
1827 my $authuser = $rpcenv->get_user();
1829 my $node = extract_param
($param, 'node');
1831 my $vmid = extract_param
($param, 'vmid');
1833 my $skiplock = extract_param
($param, 'skiplock');
1834 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1835 if $skiplock && $authuser ne 'root@pam';
1837 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1842 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1847 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1850 __PACKAGE__-
>register_method({
1851 name
=> 'vm_shutdown',
1852 path
=> '{vmid}/status/shutdown',
1856 description
=> "Shutdown virtual machine.",
1858 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1861 additionalProperties
=> 0,
1863 node
=> get_standard_option
('pve-node'),
1864 vmid
=> get_standard_option
('pve-vmid'),
1865 skiplock
=> get_standard_option
('skiplock'),
1867 description
=> "Wait maximal timeout seconds.",
1873 description
=> "Make sure the VM stops.",
1879 description
=> "Do not decativate storage volumes.",
1892 my $rpcenv = PVE
::RPCEnvironment
::get
();
1894 my $authuser = $rpcenv->get_user();
1896 my $node = extract_param
($param, 'node');
1898 my $vmid = extract_param
($param, 'vmid');
1900 my $skiplock = extract_param
($param, 'skiplock');
1901 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1902 if $skiplock && $authuser ne 'root@pam';
1904 my $keepActive = extract_param
($param, 'keepActive');
1905 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1906 if $keepActive && $authuser ne 'root@pam';
1908 my $storecfg = PVE
::Storage
::config
();
1913 syslog
('info', "shutdown VM $vmid: $upid\n");
1915 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1916 1, $param->{forceStop
}, $keepActive);
1921 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1924 __PACKAGE__-
>register_method({
1925 name
=> 'vm_suspend',
1926 path
=> '{vmid}/status/suspend',
1930 description
=> "Suspend virtual machine.",
1932 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1935 additionalProperties
=> 0,
1937 node
=> get_standard_option
('pve-node'),
1938 vmid
=> get_standard_option
('pve-vmid'),
1939 skiplock
=> get_standard_option
('skiplock'),
1948 my $rpcenv = PVE
::RPCEnvironment
::get
();
1950 my $authuser = $rpcenv->get_user();
1952 my $node = extract_param
($param, 'node');
1954 my $vmid = extract_param
($param, 'vmid');
1956 my $skiplock = extract_param
($param, 'skiplock');
1957 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1958 if $skiplock && $authuser ne 'root@pam';
1960 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1965 syslog
('info', "suspend VM $vmid: $upid\n");
1967 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1972 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1975 __PACKAGE__-
>register_method({
1976 name
=> 'vm_resume',
1977 path
=> '{vmid}/status/resume',
1981 description
=> "Resume virtual machine.",
1983 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1986 additionalProperties
=> 0,
1988 node
=> get_standard_option
('pve-node'),
1989 vmid
=> get_standard_option
('pve-vmid'),
1990 skiplock
=> get_standard_option
('skiplock'),
1999 my $rpcenv = PVE
::RPCEnvironment
::get
();
2001 my $authuser = $rpcenv->get_user();
2003 my $node = extract_param
($param, 'node');
2005 my $vmid = extract_param
($param, 'vmid');
2007 my $skiplock = extract_param
($param, 'skiplock');
2008 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2009 if $skiplock && $authuser ne 'root@pam';
2011 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2016 syslog
('info', "resume VM $vmid: $upid\n");
2018 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
2023 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2026 __PACKAGE__-
>register_method({
2027 name
=> 'vm_sendkey',
2028 path
=> '{vmid}/sendkey',
2032 description
=> "Send key event to virtual machine.",
2034 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2037 additionalProperties
=> 0,
2039 node
=> get_standard_option
('pve-node'),
2040 vmid
=> get_standard_option
('pve-vmid'),
2041 skiplock
=> get_standard_option
('skiplock'),
2043 description
=> "The key (qemu monitor encoding).",
2048 returns
=> { type
=> 'null'},
2052 my $rpcenv = PVE
::RPCEnvironment
::get
();
2054 my $authuser = $rpcenv->get_user();
2056 my $node = extract_param
($param, 'node');
2058 my $vmid = extract_param
($param, 'vmid');
2060 my $skiplock = extract_param
($param, 'skiplock');
2061 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2062 if $skiplock && $authuser ne 'root@pam';
2064 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2069 __PACKAGE__-
>register_method({
2070 name
=> 'vm_feature',
2071 path
=> '{vmid}/feature',
2075 description
=> "Check if feature for virtual machine is available.",
2077 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2080 additionalProperties
=> 0,
2082 node
=> get_standard_option
('pve-node'),
2083 vmid
=> get_standard_option
('pve-vmid'),
2085 description
=> "Feature to check.",
2087 enum
=> [ 'snapshot', 'clone', 'copy' ],
2089 snapname
=> get_standard_option
('pve-snapshot-name', {
2097 hasFeature
=> { type
=> 'boolean' },
2100 items
=> { type
=> 'string' },
2107 my $node = extract_param
($param, 'node');
2109 my $vmid = extract_param
($param, 'vmid');
2111 my $snapname = extract_param
($param, 'snapname');
2113 my $feature = extract_param
($param, 'feature');
2115 my $running = PVE
::QemuServer
::check_running
($vmid);
2117 my $conf = PVE
::QemuServer
::load_config
($vmid);
2120 my $snap = $conf->{snapshots
}->{$snapname};
2121 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2124 my $storecfg = PVE
::Storage
::config
();
2126 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2127 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
2130 hasFeature
=> $hasFeature,
2131 nodes
=> [ keys %$nodelist ],
2135 __PACKAGE__-
>register_method({
2137 path
=> '{vmid}/clone',
2141 description
=> "Create a copy of virtual machine/template.",
2143 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2144 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2145 "'Datastore.AllocateSpace' on any used storage.",
2148 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2150 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2151 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2156 additionalProperties
=> 0,
2158 node
=> get_standard_option
('pve-node'),
2159 vmid
=> get_standard_option
('pve-vmid'),
2160 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2163 type
=> 'string', format
=> 'dns-name',
2164 description
=> "Set a name for the new VM.",
2169 description
=> "Description for the new VM.",
2173 type
=> 'string', format
=> 'pve-poolid',
2174 description
=> "Add the new VM to the specified pool.",
2176 snapname
=> get_standard_option
('pve-snapshot-name', {
2179 storage
=> get_standard_option
('pve-storage-id', {
2180 description
=> "Target storage for full clone.",
2185 description
=> "Target format for file storage.",
2189 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2194 description
=> "Create a full copy of all disk. This is always done when " .
2195 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2198 target
=> get_standard_option
('pve-node', {
2199 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2210 my $rpcenv = PVE
::RPCEnvironment
::get
();
2212 my $authuser = $rpcenv->get_user();
2214 my $node = extract_param
($param, 'node');
2216 my $vmid = extract_param
($param, 'vmid');
2218 my $newid = extract_param
($param, 'newid');
2220 my $pool = extract_param
($param, 'pool');
2222 if (defined($pool)) {
2223 $rpcenv->check_pool_exist($pool);
2226 my $snapname = extract_param
($param, 'snapname');
2228 my $storage = extract_param
($param, 'storage');
2230 my $format = extract_param
($param, 'format');
2232 my $target = extract_param
($param, 'target');
2234 my $localnode = PVE
::INotify
::nodename
();
2236 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2238 PVE
::Cluster
::check_node_exists
($target) if $target;
2240 my $storecfg = PVE
::Storage
::config
();
2243 # check if storage is enabled on local node
2244 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2246 # check if storage is available on target node
2247 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2248 # clone only works if target storage is shared
2249 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2250 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2254 PVE
::Cluster
::check_cfs_quorum
();
2256 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2258 # exclusive lock if VM is running - else shared lock is enough;
2259 my $shared_lock = $running ?
0 : 1;
2263 # do all tests after lock
2264 # we also try to do all tests before we fork the worker
2266 my $conf = PVE
::QemuServer
::load_config
($vmid);
2268 PVE
::QemuServer
::check_lock
($conf);
2270 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2272 die "unexpected state change\n" if $verify_running != $running;
2274 die "snapshot '$snapname' does not exist\n"
2275 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2277 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2279 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2281 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2283 my $conffile = PVE
::QemuServer
::config_file
($newid);
2285 die "unable to create VM $newid: config file already exists\n"
2288 my $newconf = { lock => 'clone' };
2292 foreach my $opt (keys %$oldconf) {
2293 my $value = $oldconf->{$opt};
2295 # do not copy snapshot related info
2296 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2297 $opt eq 'vmstate' || $opt eq 'snapstate';
2299 # always change MAC! address
2300 if ($opt =~ m/^net(\d+)$/) {
2301 my $net = PVE
::QemuServer
::parse_net
($value);
2302 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2303 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2304 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
2305 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2306 die "unable to parse drive options for '$opt'\n" if !$drive;
2307 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2308 $newconf->{$opt} = $value; # simply copy configuration
2310 if ($param->{full
}) {
2311 die "Full clone feature is not available"
2312 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2315 # not full means clone instead of copy
2316 die "Linked clone feature is not available"
2317 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2319 $drives->{$opt} = $drive;
2320 push @$vollist, $drive->{file
};
2323 # copy everything else
2324 $newconf->{$opt} = $value;
2328 # auto generate a new uuid
2329 my ($uuid, $uuid_str);
2330 UUID
::generate
($uuid);
2331 UUID
::unparse
($uuid, $uuid_str);
2332 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2333 $smbios1->{uuid
} = $uuid_str;
2334 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2336 delete $newconf->{template
};
2338 if ($param->{name
}) {
2339 $newconf->{name
} = $param->{name
};
2341 if ($oldconf->{name
}) {
2342 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2344 $newconf->{name
} = "Copy-of-VM-$vmid";
2348 if ($param->{description
}) {
2349 $newconf->{description
} = $param->{description
};
2352 # create empty/temp config - this fails if VM already exists on other node
2353 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2358 my $newvollist = [];
2361 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2363 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2365 foreach my $opt (keys %$drives) {
2366 my $drive = $drives->{$opt};
2368 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2369 $newid, $storage, $format, $drive->{full
}, $newvollist);
2371 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2373 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2376 delete $newconf->{lock};
2377 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2380 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2381 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist);
2383 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2384 die "Failed to move config to node '$target' - rename failed: $!\n"
2385 if !rename($conffile, $newconffile);
2388 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2393 sleep 1; # some storage like rbd need to wait before release volume - really?
2395 foreach my $volid (@$newvollist) {
2396 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2399 die "clone failed: $err";
2405 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2408 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2409 # Aquire exclusive lock lock for $newid
2410 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2415 __PACKAGE__-
>register_method({
2416 name
=> 'move_vm_disk',
2417 path
=> '{vmid}/move_disk',
2421 description
=> "Move volume to different storage.",
2423 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2424 "and 'Datastore.AllocateSpace' permissions on the storage.",
2427 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2428 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2432 additionalProperties
=> 0,
2434 node
=> get_standard_option
('pve-node'),
2435 vmid
=> get_standard_option
('pve-vmid'),
2438 description
=> "The disk you want to move.",
2439 enum
=> [ PVE
::QemuServer
::disknames
() ],
2441 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2444 description
=> "Target Format.",
2445 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2450 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2456 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2464 description
=> "the task ID.",
2469 my $rpcenv = PVE
::RPCEnvironment
::get
();
2471 my $authuser = $rpcenv->get_user();
2473 my $node = extract_param
($param, 'node');
2475 my $vmid = extract_param
($param, 'vmid');
2477 my $digest = extract_param
($param, 'digest');
2479 my $disk = extract_param
($param, 'disk');
2481 my $storeid = extract_param
($param, 'storage');
2483 my $format = extract_param
($param, 'format');
2485 my $storecfg = PVE
::Storage
::config
();
2487 my $updatefn = sub {
2489 my $conf = PVE
::QemuServer
::load_config
($vmid);
2491 die "checksum missmatch (file change by other user?)\n"
2492 if $digest && $digest ne $conf->{digest
};
2494 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2496 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2498 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2500 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2503 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2504 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2508 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2509 (!$format || !$oldfmt || $oldfmt eq $format);
2511 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2513 my $running = PVE
::QemuServer
::check_running
($vmid);
2515 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2519 my $newvollist = [];
2522 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2524 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2525 $vmid, $storeid, $format, 1, $newvollist);
2527 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2529 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2531 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2534 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2535 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2542 foreach my $volid (@$newvollist) {
2543 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2546 die "storage migration failed: $err";
2549 if ($param->{delete}) {
2550 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, 1);
2551 my $path = PVE
::Storage
::path
($storecfg, $old_volid);
2552 if ($used_paths->{$path}){
2553 warn "volume $old_volid have snapshots. Can't delete it\n";
2554 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid);
2555 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2557 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2563 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2566 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2569 __PACKAGE__-
>register_method({
2570 name
=> 'migrate_vm',
2571 path
=> '{vmid}/migrate',
2575 description
=> "Migrate virtual machine. Creates a new migration task.",
2577 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2580 additionalProperties
=> 0,
2582 node
=> get_standard_option
('pve-node'),
2583 vmid
=> get_standard_option
('pve-vmid'),
2584 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2587 description
=> "Use online/live migration.",
2592 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2599 description
=> "the task ID.",
2604 my $rpcenv = PVE
::RPCEnvironment
::get
();
2606 my $authuser = $rpcenv->get_user();
2608 my $target = extract_param
($param, 'target');
2610 my $localnode = PVE
::INotify
::nodename
();
2611 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2613 PVE
::Cluster
::check_cfs_quorum
();
2615 PVE
::Cluster
::check_node_exists
($target);
2617 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2619 my $vmid = extract_param
($param, 'vmid');
2621 raise_param_exc
({ force
=> "Only root may use this option." })
2622 if $param->{force
} && $authuser ne 'root@pam';
2625 my $conf = PVE
::QemuServer
::load_config
($vmid);
2627 # try to detect errors early
2629 PVE
::QemuServer
::check_lock
($conf);
2631 if (PVE
::QemuServer
::check_running
($vmid)) {
2632 die "cant migrate running VM without --online\n"
2633 if !$param->{online
};
2636 my $storecfg = PVE
::Storage
::config
();
2637 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2639 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2644 my $service = "pvevm:$vmid";
2646 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2648 print "Executing HA migrate for VM $vmid to node $target\n";
2650 PVE
::Tools
::run_command
($cmd);
2655 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2662 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2665 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2670 __PACKAGE__-
>register_method({
2672 path
=> '{vmid}/monitor',
2676 description
=> "Execute Qemu monitor commands.",
2678 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2681 additionalProperties
=> 0,
2683 node
=> get_standard_option
('pve-node'),
2684 vmid
=> get_standard_option
('pve-vmid'),
2687 description
=> "The monitor command.",
2691 returns
=> { type
=> 'string'},
2695 my $vmid = $param->{vmid
};
2697 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2701 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2703 $res = "ERROR: $@" if $@;
2708 __PACKAGE__-
>register_method({
2709 name
=> 'resize_vm',
2710 path
=> '{vmid}/resize',
2714 description
=> "Extend volume size.",
2716 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2719 additionalProperties
=> 0,
2721 node
=> get_standard_option
('pve-node'),
2722 vmid
=> get_standard_option
('pve-vmid'),
2723 skiplock
=> get_standard_option
('skiplock'),
2726 description
=> "The disk you want to resize.",
2727 enum
=> [PVE
::QemuServer
::disknames
()],
2731 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2732 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.",
2736 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2742 returns
=> { type
=> 'null'},
2746 my $rpcenv = PVE
::RPCEnvironment
::get
();
2748 my $authuser = $rpcenv->get_user();
2750 my $node = extract_param
($param, 'node');
2752 my $vmid = extract_param
($param, 'vmid');
2754 my $digest = extract_param
($param, 'digest');
2756 my $disk = extract_param
($param, 'disk');
2758 my $sizestr = extract_param
($param, 'size');
2760 my $skiplock = extract_param
($param, 'skiplock');
2761 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2762 if $skiplock && $authuser ne 'root@pam';
2764 my $storecfg = PVE
::Storage
::config
();
2766 my $updatefn = sub {
2768 my $conf = PVE
::QemuServer
::load_config
($vmid);
2770 die "checksum missmatch (file change by other user?)\n"
2771 if $digest && $digest ne $conf->{digest
};
2772 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2774 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2776 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2778 my $volid = $drive->{file
};
2780 die "disk '$disk' has no associated volume\n" if !$volid;
2782 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2784 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2786 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2788 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2790 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2791 my ($ext, $newsize, $unit) = ($1, $2, $4);
2794 $newsize = $newsize * 1024;
2795 } elsif ($unit eq 'M') {
2796 $newsize = $newsize * 1024 * 1024;
2797 } elsif ($unit eq 'G') {
2798 $newsize = $newsize * 1024 * 1024 * 1024;
2799 } elsif ($unit eq 'T') {
2800 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2803 $newsize += $size if $ext;
2804 $newsize = int($newsize);
2806 die "unable to skrink disk size\n" if $newsize < $size;
2808 return if $size == $newsize;
2810 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2812 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2814 $drive->{size
} = $newsize;
2815 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2817 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2820 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2824 __PACKAGE__-
>register_method({
2825 name
=> 'snapshot_list',
2826 path
=> '{vmid}/snapshot',
2828 description
=> "List all snapshots.",
2830 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2833 protected
=> 1, # qemu pid files are only readable by root
2835 additionalProperties
=> 0,
2837 vmid
=> get_standard_option
('pve-vmid'),
2838 node
=> get_standard_option
('pve-node'),
2847 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2852 my $vmid = $param->{vmid
};
2854 my $conf = PVE
::QemuServer
::load_config
($vmid);
2855 my $snaphash = $conf->{snapshots
} || {};
2859 foreach my $name (keys %$snaphash) {
2860 my $d = $snaphash->{$name};
2863 snaptime
=> $d->{snaptime
} || 0,
2864 vmstate
=> $d->{vmstate
} ?
1 : 0,
2865 description
=> $d->{description
} || '',
2867 $item->{parent
} = $d->{parent
} if $d->{parent
};
2868 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2872 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2873 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2874 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2876 push @$res, $current;
2881 __PACKAGE__-
>register_method({
2883 path
=> '{vmid}/snapshot',
2887 description
=> "Snapshot a VM.",
2889 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2892 additionalProperties
=> 0,
2894 node
=> get_standard_option
('pve-node'),
2895 vmid
=> get_standard_option
('pve-vmid'),
2896 snapname
=> get_standard_option
('pve-snapshot-name'),
2900 description
=> "Save the vmstate",
2905 description
=> "A textual description or comment.",
2911 description
=> "the task ID.",
2916 my $rpcenv = PVE
::RPCEnvironment
::get
();
2918 my $authuser = $rpcenv->get_user();
2920 my $node = extract_param
($param, 'node');
2922 my $vmid = extract_param
($param, 'vmid');
2924 my $snapname = extract_param
($param, 'snapname');
2926 die "unable to use snapshot name 'current' (reserved name)\n"
2927 if $snapname eq 'current';
2930 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2931 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2932 $param->{description
});
2935 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2938 __PACKAGE__-
>register_method({
2939 name
=> 'snapshot_cmd_idx',
2940 path
=> '{vmid}/snapshot/{snapname}',
2947 additionalProperties
=> 0,
2949 vmid
=> get_standard_option
('pve-vmid'),
2950 node
=> get_standard_option
('pve-node'),
2951 snapname
=> get_standard_option
('pve-snapshot-name'),
2960 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2967 push @$res, { cmd
=> 'rollback' };
2968 push @$res, { cmd
=> 'config' };
2973 __PACKAGE__-
>register_method({
2974 name
=> 'update_snapshot_config',
2975 path
=> '{vmid}/snapshot/{snapname}/config',
2979 description
=> "Update snapshot metadata.",
2981 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2984 additionalProperties
=> 0,
2986 node
=> get_standard_option
('pve-node'),
2987 vmid
=> get_standard_option
('pve-vmid'),
2988 snapname
=> get_standard_option
('pve-snapshot-name'),
2992 description
=> "A textual description or comment.",
2996 returns
=> { type
=> 'null' },
3000 my $rpcenv = PVE
::RPCEnvironment
::get
();
3002 my $authuser = $rpcenv->get_user();
3004 my $vmid = extract_param
($param, 'vmid');
3006 my $snapname = extract_param
($param, 'snapname');
3008 return undef if !defined($param->{description
});
3010 my $updatefn = sub {
3012 my $conf = PVE
::QemuServer
::load_config
($vmid);
3014 PVE
::QemuServer
::check_lock
($conf);
3016 my $snap = $conf->{snapshots
}->{$snapname};
3018 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3020 $snap->{description
} = $param->{description
} if defined($param->{description
});
3022 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3025 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
3030 __PACKAGE__-
>register_method({
3031 name
=> 'get_snapshot_config',
3032 path
=> '{vmid}/snapshot/{snapname}/config',
3035 description
=> "Get snapshot configuration",
3037 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3040 additionalProperties
=> 0,
3042 node
=> get_standard_option
('pve-node'),
3043 vmid
=> get_standard_option
('pve-vmid'),
3044 snapname
=> get_standard_option
('pve-snapshot-name'),
3047 returns
=> { type
=> "object" },
3051 my $rpcenv = PVE
::RPCEnvironment
::get
();
3053 my $authuser = $rpcenv->get_user();
3055 my $vmid = extract_param
($param, 'vmid');
3057 my $snapname = extract_param
($param, 'snapname');
3059 my $conf = PVE
::QemuServer
::load_config
($vmid);
3061 my $snap = $conf->{snapshots
}->{$snapname};
3063 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3068 __PACKAGE__-
>register_method({
3070 path
=> '{vmid}/snapshot/{snapname}/rollback',
3074 description
=> "Rollback VM state to specified snapshot.",
3076 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3079 additionalProperties
=> 0,
3081 node
=> get_standard_option
('pve-node'),
3082 vmid
=> get_standard_option
('pve-vmid'),
3083 snapname
=> get_standard_option
('pve-snapshot-name'),
3088 description
=> "the task ID.",
3093 my $rpcenv = PVE
::RPCEnvironment
::get
();
3095 my $authuser = $rpcenv->get_user();
3097 my $node = extract_param
($param, 'node');
3099 my $vmid = extract_param
($param, 'vmid');
3101 my $snapname = extract_param
($param, 'snapname');
3104 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3105 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
3108 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3111 __PACKAGE__-
>register_method({
3112 name
=> 'delsnapshot',
3113 path
=> '{vmid}/snapshot/{snapname}',
3117 description
=> "Delete a VM snapshot.",
3119 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3122 additionalProperties
=> 0,
3124 node
=> get_standard_option
('pve-node'),
3125 vmid
=> get_standard_option
('pve-vmid'),
3126 snapname
=> get_standard_option
('pve-snapshot-name'),
3130 description
=> "For removal from config file, even if removing disk snapshots fails.",
3136 description
=> "the task ID.",
3141 my $rpcenv = PVE
::RPCEnvironment
::get
();
3143 my $authuser = $rpcenv->get_user();
3145 my $node = extract_param
($param, 'node');
3147 my $vmid = extract_param
($param, 'vmid');
3149 my $snapname = extract_param
($param, 'snapname');
3152 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3153 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3156 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3159 __PACKAGE__-
>register_method({
3161 path
=> '{vmid}/template',
3165 description
=> "Create a Template.",
3167 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3168 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3171 additionalProperties
=> 0,
3173 node
=> get_standard_option
('pve-node'),
3174 vmid
=> get_standard_option
('pve-vmid'),
3178 description
=> "If you want to convert only 1 disk to base image.",
3179 enum
=> [PVE
::QemuServer
::disknames
()],
3184 returns
=> { type
=> 'null'},
3188 my $rpcenv = PVE
::RPCEnvironment
::get
();
3190 my $authuser = $rpcenv->get_user();
3192 my $node = extract_param
($param, 'node');
3194 my $vmid = extract_param
($param, 'vmid');
3196 my $disk = extract_param
($param, 'disk');
3198 my $updatefn = sub {
3200 my $conf = PVE
::QemuServer
::load_config
($vmid);
3202 PVE
::QemuServer
::check_lock
($conf);
3204 die "unable to create template, because VM contains snapshots\n"
3205 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3207 die "you can't convert a template to a template\n"
3208 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3210 die "you can't convert a VM to template if VM is running\n"
3211 if PVE
::QemuServer
::check_running
($vmid);
3214 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3217 $conf->{template
} = 1;
3218 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3220 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3223 PVE
::QemuServer
::lock_config
($vmid, $updatefn);