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
1009 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
1010 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1011 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1) if $changes;
1013 return if !scalar(keys %{$conf->{pending
}});
1015 my $running = PVE
::QemuServer
::check_running
($vmid);
1017 # apply pending changes
1019 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
1020 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1022 return; # TODO: remove old code below
1024 foreach my $opt (keys %$param) { # add/change
1026 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
1028 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
1030 if (PVE
::QemuServer
::valid_drivename
($opt)) {
1032 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
1033 $opt, $param->{$opt}, $force);
1035 } elsif ($opt =~ m/^net(\d+)$/) { #nics
1037 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
1038 $opt, $param->{$opt});
1042 if($opt eq 'tablet' && $param->{$opt} == 1){
1043 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
1044 } elsif($opt eq 'tablet' && $param->{$opt} == 0){
1045 PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
1048 if($opt eq 'cores' && $conf->{maxcpus
}){
1049 PVE
::QemuServer
::qemu_cpu_hotplug
($vmid, $conf, $param->{$opt});
1052 $conf->{$opt} = $param->{$opt};
1053 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
1057 # allow manual ballooning if shares is set to zero
1058 if ($running && defined($param->{balloon
}) &&
1059 defined($conf->{shares
}) && ($conf->{shares
} == 0)) {
1060 my $balloon = $param->{'balloon'} || $conf->{memory
} || $defaults->{memory
};
1061 PVE
::QemuServer
::vm_mon_cmd
($vmid, "balloon", value
=> $balloon*1024*1024);
1069 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1071 if ($background_delay) {
1073 # Note: It would be better to do that in the Event based HTTPServer
1074 # to avoid blocking call to sleep.
1076 my $end_time = time() + $background_delay;
1078 my $task = PVE
::Tools
::upid_decode
($upid);
1081 while (time() < $end_time) {
1082 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1084 sleep(1); # this gets interrupted when child process ends
1088 my $status = PVE
::Tools
::upid_read_status
($upid);
1089 return undef if $status eq 'OK';
1098 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1101 my $vm_config_perm_list = [
1106 'VM.Config.Network',
1108 'VM.Config.Options',
1111 __PACKAGE__-
>register_method({
1112 name
=> 'update_vm_async',
1113 path
=> '{vmid}/config',
1117 description
=> "Set virtual machine options (asynchrounous API).",
1119 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1122 additionalProperties
=> 0,
1123 properties
=> PVE
::QemuServer
::json_config_properties
(
1125 node
=> get_standard_option
('pve-node'),
1126 vmid
=> get_standard_option
('pve-vmid'),
1127 skiplock
=> get_standard_option
('skiplock'),
1129 type
=> 'string', format
=> 'pve-configid-list',
1130 description
=> "A list of settings you want to delete.",
1135 description
=> $opt_force_description,
1137 requires
=> 'delete',
1141 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1145 background_delay
=> {
1147 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1158 code
=> $update_vm_api,
1161 __PACKAGE__-
>register_method({
1162 name
=> 'update_vm',
1163 path
=> '{vmid}/config',
1167 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1169 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1172 additionalProperties
=> 0,
1173 properties
=> PVE
::QemuServer
::json_config_properties
(
1175 node
=> get_standard_option
('pve-node'),
1176 vmid
=> get_standard_option
('pve-vmid'),
1177 skiplock
=> get_standard_option
('skiplock'),
1179 type
=> 'string', format
=> 'pve-configid-list',
1180 description
=> "A list of settings you want to delete.",
1185 description
=> $opt_force_description,
1187 requires
=> 'delete',
1191 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1197 returns
=> { type
=> 'null' },
1200 &$update_vm_api($param, 1);
1206 __PACKAGE__-
>register_method({
1207 name
=> 'destroy_vm',
1212 description
=> "Destroy the vm (also delete all used/owned volumes).",
1214 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1217 additionalProperties
=> 0,
1219 node
=> get_standard_option
('pve-node'),
1220 vmid
=> get_standard_option
('pve-vmid'),
1221 skiplock
=> get_standard_option
('skiplock'),
1230 my $rpcenv = PVE
::RPCEnvironment
::get
();
1232 my $authuser = $rpcenv->get_user();
1234 my $vmid = $param->{vmid
};
1236 my $skiplock = $param->{skiplock
};
1237 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1238 if $skiplock && $authuser ne 'root@pam';
1241 my $conf = PVE
::QemuServer
::load_config
($vmid);
1243 my $storecfg = PVE
::Storage
::config
();
1245 my $delVMfromPoolFn = sub {
1246 my $usercfg = cfs_read_file
("user.cfg");
1247 if (my $pool = $usercfg->{vms
}->{$vmid}) {
1248 if (my $data = $usercfg->{pools
}->{$pool}) {
1249 delete $data->{vms
}->{$vmid};
1250 delete $usercfg->{vms
}->{$vmid};
1251 cfs_write_file
("user.cfg", $usercfg);
1259 syslog
('info', "destroy VM $vmid: $upid\n");
1261 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1263 PVE
::AccessControl
::remove_vm_from_pool
($vmid);
1266 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1269 __PACKAGE__-
>register_method({
1271 path
=> '{vmid}/unlink',
1275 description
=> "Unlink/delete disk images.",
1277 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1280 additionalProperties
=> 0,
1282 node
=> get_standard_option
('pve-node'),
1283 vmid
=> get_standard_option
('pve-vmid'),
1285 type
=> 'string', format
=> 'pve-configid-list',
1286 description
=> "A list of disk IDs you want to delete.",
1290 description
=> $opt_force_description,
1295 returns
=> { type
=> 'null'},
1299 $param->{delete} = extract_param
($param, 'idlist');
1301 __PACKAGE__-
>update_vm($param);
1308 __PACKAGE__-
>register_method({
1310 path
=> '{vmid}/vncproxy',
1314 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1316 description
=> "Creates a TCP VNC proxy connections.",
1318 additionalProperties
=> 0,
1320 node
=> get_standard_option
('pve-node'),
1321 vmid
=> get_standard_option
('pve-vmid'),
1325 description
=> "starts websockify instead of vncproxy",
1330 additionalProperties
=> 0,
1332 user
=> { type
=> 'string' },
1333 ticket
=> { type
=> 'string' },
1334 cert
=> { type
=> 'string' },
1335 port
=> { type
=> 'integer' },
1336 upid
=> { type
=> 'string' },
1342 my $rpcenv = PVE
::RPCEnvironment
::get
();
1344 my $authuser = $rpcenv->get_user();
1346 my $vmid = $param->{vmid
};
1347 my $node = $param->{node
};
1348 my $websocket = $param->{websocket
};
1350 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # check if VM exists
1352 my $authpath = "/vms/$vmid";
1354 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1356 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1359 my $port = PVE
::Tools
::next_vnc_port
();
1364 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1365 $remip = PVE
::Cluster
::remote_node_ip
($node);
1366 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1367 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1375 syslog
('info', "starting vnc proxy $upid\n");
1379 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1381 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1383 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1384 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1385 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1386 '-timeout', $timeout, '-authpath', $authpath,
1387 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1390 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1392 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1394 my $qmstr = join(' ', @$qmcmd);
1396 # also redirect stderr (else we get RFB protocol errors)
1397 $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1400 PVE
::Tools
::run_command
($cmd);
1405 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1407 PVE
::Tools
::wait_for_vnc_port
($port);
1418 __PACKAGE__-
>register_method({
1419 name
=> 'vncwebsocket',
1420 path
=> '{vmid}/vncwebsocket',
1423 description
=> "You also need to pass a valid ticket (vncticket).",
1424 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1426 description
=> "Opens a weksocket for VNC traffic.",
1428 additionalProperties
=> 0,
1430 node
=> get_standard_option
('pve-node'),
1431 vmid
=> get_standard_option
('pve-vmid'),
1433 description
=> "Ticket from previous call to vncproxy.",
1438 description
=> "Port number returned by previous vncproxy call.",
1448 port
=> { type
=> 'string' },
1454 my $rpcenv = PVE
::RPCEnvironment
::get
();
1456 my $authuser = $rpcenv->get_user();
1458 my $vmid = $param->{vmid
};
1459 my $node = $param->{node
};
1461 my $authpath = "/vms/$vmid";
1463 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1465 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # VM exists ?
1467 # Note: VNC ports are acessible from outside, so we do not gain any
1468 # security if we verify that $param->{port} belongs to VM $vmid. This
1469 # check is done by verifying the VNC ticket (inside VNC protocol).
1471 my $port = $param->{port
};
1473 return { port
=> $port };
1476 __PACKAGE__-
>register_method({
1477 name
=> 'spiceproxy',
1478 path
=> '{vmid}/spiceproxy',
1483 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1485 description
=> "Returns a SPICE configuration to connect to the VM.",
1487 additionalProperties
=> 0,
1489 node
=> get_standard_option
('pve-node'),
1490 vmid
=> get_standard_option
('pve-vmid'),
1491 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1494 returns
=> get_standard_option
('remote-viewer-config'),
1498 my $rpcenv = PVE
::RPCEnvironment
::get
();
1500 my $authuser = $rpcenv->get_user();
1502 my $vmid = $param->{vmid
};
1503 my $node = $param->{node
};
1504 my $proxy = $param->{proxy
};
1506 my $conf = PVE
::QemuServer
::load_config
($vmid, $node);
1507 my $title = "VM $vmid - $conf->{'name'}",
1509 my $port = PVE
::QemuServer
::spice_port
($vmid);
1511 my ($ticket, undef, $remote_viewer_config) =
1512 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1514 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1515 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1517 return $remote_viewer_config;
1520 __PACKAGE__-
>register_method({
1522 path
=> '{vmid}/status',
1525 description
=> "Directory index",
1530 additionalProperties
=> 0,
1532 node
=> get_standard_option
('pve-node'),
1533 vmid
=> get_standard_option
('pve-vmid'),
1541 subdir
=> { type
=> 'string' },
1544 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1550 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1553 { subdir
=> 'current' },
1554 { subdir
=> 'start' },
1555 { subdir
=> 'stop' },
1561 my $vm_is_ha_managed = sub {
1564 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1565 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1571 __PACKAGE__-
>register_method({
1572 name
=> 'vm_status',
1573 path
=> '{vmid}/status/current',
1576 protected
=> 1, # qemu pid files are only readable by root
1577 description
=> "Get virtual machine status.",
1579 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1582 additionalProperties
=> 0,
1584 node
=> get_standard_option
('pve-node'),
1585 vmid
=> get_standard_option
('pve-vmid'),
1588 returns
=> { type
=> 'object' },
1593 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1595 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1596 my $status = $vmstatus->{$param->{vmid
}};
1598 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1600 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1605 __PACKAGE__-
>register_method({
1607 path
=> '{vmid}/status/start',
1611 description
=> "Start virtual machine.",
1613 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1616 additionalProperties
=> 0,
1618 node
=> get_standard_option
('pve-node'),
1619 vmid
=> get_standard_option
('pve-vmid'),
1620 skiplock
=> get_standard_option
('skiplock'),
1621 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1622 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1623 machine
=> get_standard_option
('pve-qm-machine'),
1632 my $rpcenv = PVE
::RPCEnvironment
::get
();
1634 my $authuser = $rpcenv->get_user();
1636 my $node = extract_param
($param, 'node');
1638 my $vmid = extract_param
($param, 'vmid');
1640 my $machine = extract_param
($param, 'machine');
1642 my $stateuri = extract_param
($param, 'stateuri');
1643 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1644 if $stateuri && $authuser ne 'root@pam';
1646 my $skiplock = extract_param
($param, 'skiplock');
1647 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1648 if $skiplock && $authuser ne 'root@pam';
1650 my $migratedfrom = extract_param
($param, 'migratedfrom');
1651 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1652 if $migratedfrom && $authuser ne 'root@pam';
1654 # read spice ticket from STDIN
1656 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1657 if (defined(my $line = <>)) {
1659 $spice_ticket = $line;
1663 my $storecfg = PVE
::Storage
::config
();
1665 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1666 $rpcenv->{type
} ne 'ha') {
1671 my $service = "pvevm:$vmid";
1673 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1675 print "Executing HA start for VM $vmid\n";
1677 PVE
::Tools
::run_command
($cmd);
1682 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1689 syslog
('info', "start VM $vmid: $upid\n");
1691 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1692 $machine, $spice_ticket);
1697 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1701 __PACKAGE__-
>register_method({
1703 path
=> '{vmid}/status/stop',
1707 description
=> "Stop virtual machine.",
1709 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1712 additionalProperties
=> 0,
1714 node
=> get_standard_option
('pve-node'),
1715 vmid
=> get_standard_option
('pve-vmid'),
1716 skiplock
=> get_standard_option
('skiplock'),
1717 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1719 description
=> "Wait maximal timeout seconds.",
1725 description
=> "Do not decativate storage volumes.",
1738 my $rpcenv = PVE
::RPCEnvironment
::get
();
1740 my $authuser = $rpcenv->get_user();
1742 my $node = extract_param
($param, 'node');
1744 my $vmid = extract_param
($param, 'vmid');
1746 my $skiplock = extract_param
($param, 'skiplock');
1747 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1748 if $skiplock && $authuser ne 'root@pam';
1750 my $keepActive = extract_param
($param, 'keepActive');
1751 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1752 if $keepActive && $authuser ne 'root@pam';
1754 my $migratedfrom = extract_param
($param, 'migratedfrom');
1755 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1756 if $migratedfrom && $authuser ne 'root@pam';
1759 my $storecfg = PVE
::Storage
::config
();
1761 if (&$vm_is_ha_managed($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1766 my $service = "pvevm:$vmid";
1768 my $cmd = ['clusvcadm', '-d', $service];
1770 print "Executing HA stop for VM $vmid\n";
1772 PVE
::Tools
::run_command
($cmd);
1777 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1783 syslog
('info', "stop VM $vmid: $upid\n");
1785 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1786 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1791 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1795 __PACKAGE__-
>register_method({
1797 path
=> '{vmid}/status/reset',
1801 description
=> "Reset virtual machine.",
1803 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1806 additionalProperties
=> 0,
1808 node
=> get_standard_option
('pve-node'),
1809 vmid
=> get_standard_option
('pve-vmid'),
1810 skiplock
=> get_standard_option
('skiplock'),
1819 my $rpcenv = PVE
::RPCEnvironment
::get
();
1821 my $authuser = $rpcenv->get_user();
1823 my $node = extract_param
($param, 'node');
1825 my $vmid = extract_param
($param, 'vmid');
1827 my $skiplock = extract_param
($param, 'skiplock');
1828 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1829 if $skiplock && $authuser ne 'root@pam';
1831 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1836 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1841 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1844 __PACKAGE__-
>register_method({
1845 name
=> 'vm_shutdown',
1846 path
=> '{vmid}/status/shutdown',
1850 description
=> "Shutdown virtual machine.",
1852 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1855 additionalProperties
=> 0,
1857 node
=> get_standard_option
('pve-node'),
1858 vmid
=> get_standard_option
('pve-vmid'),
1859 skiplock
=> get_standard_option
('skiplock'),
1861 description
=> "Wait maximal timeout seconds.",
1867 description
=> "Make sure the VM stops.",
1873 description
=> "Do not decativate storage volumes.",
1886 my $rpcenv = PVE
::RPCEnvironment
::get
();
1888 my $authuser = $rpcenv->get_user();
1890 my $node = extract_param
($param, 'node');
1892 my $vmid = extract_param
($param, 'vmid');
1894 my $skiplock = extract_param
($param, 'skiplock');
1895 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1896 if $skiplock && $authuser ne 'root@pam';
1898 my $keepActive = extract_param
($param, 'keepActive');
1899 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1900 if $keepActive && $authuser ne 'root@pam';
1902 my $storecfg = PVE
::Storage
::config
();
1907 syslog
('info', "shutdown VM $vmid: $upid\n");
1909 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1910 1, $param->{forceStop
}, $keepActive);
1915 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1918 __PACKAGE__-
>register_method({
1919 name
=> 'vm_suspend',
1920 path
=> '{vmid}/status/suspend',
1924 description
=> "Suspend virtual machine.",
1926 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1929 additionalProperties
=> 0,
1931 node
=> get_standard_option
('pve-node'),
1932 vmid
=> get_standard_option
('pve-vmid'),
1933 skiplock
=> get_standard_option
('skiplock'),
1942 my $rpcenv = PVE
::RPCEnvironment
::get
();
1944 my $authuser = $rpcenv->get_user();
1946 my $node = extract_param
($param, 'node');
1948 my $vmid = extract_param
($param, 'vmid');
1950 my $skiplock = extract_param
($param, 'skiplock');
1951 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1952 if $skiplock && $authuser ne 'root@pam';
1954 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1959 syslog
('info', "suspend VM $vmid: $upid\n");
1961 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1966 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1969 __PACKAGE__-
>register_method({
1970 name
=> 'vm_resume',
1971 path
=> '{vmid}/status/resume',
1975 description
=> "Resume virtual machine.",
1977 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1980 additionalProperties
=> 0,
1982 node
=> get_standard_option
('pve-node'),
1983 vmid
=> get_standard_option
('pve-vmid'),
1984 skiplock
=> get_standard_option
('skiplock'),
1993 my $rpcenv = PVE
::RPCEnvironment
::get
();
1995 my $authuser = $rpcenv->get_user();
1997 my $node = extract_param
($param, 'node');
1999 my $vmid = extract_param
($param, 'vmid');
2001 my $skiplock = extract_param
($param, 'skiplock');
2002 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2003 if $skiplock && $authuser ne 'root@pam';
2005 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2010 syslog
('info', "resume VM $vmid: $upid\n");
2012 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
2017 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2020 __PACKAGE__-
>register_method({
2021 name
=> 'vm_sendkey',
2022 path
=> '{vmid}/sendkey',
2026 description
=> "Send key event to virtual machine.",
2028 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2031 additionalProperties
=> 0,
2033 node
=> get_standard_option
('pve-node'),
2034 vmid
=> get_standard_option
('pve-vmid'),
2035 skiplock
=> get_standard_option
('skiplock'),
2037 description
=> "The key (qemu monitor encoding).",
2042 returns
=> { type
=> 'null'},
2046 my $rpcenv = PVE
::RPCEnvironment
::get
();
2048 my $authuser = $rpcenv->get_user();
2050 my $node = extract_param
($param, 'node');
2052 my $vmid = extract_param
($param, 'vmid');
2054 my $skiplock = extract_param
($param, 'skiplock');
2055 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2056 if $skiplock && $authuser ne 'root@pam';
2058 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2063 __PACKAGE__-
>register_method({
2064 name
=> 'vm_feature',
2065 path
=> '{vmid}/feature',
2069 description
=> "Check if feature for virtual machine is available.",
2071 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2074 additionalProperties
=> 0,
2076 node
=> get_standard_option
('pve-node'),
2077 vmid
=> get_standard_option
('pve-vmid'),
2079 description
=> "Feature to check.",
2081 enum
=> [ 'snapshot', 'clone', 'copy' ],
2083 snapname
=> get_standard_option
('pve-snapshot-name', {
2091 hasFeature
=> { type
=> 'boolean' },
2094 items
=> { type
=> 'string' },
2101 my $node = extract_param
($param, 'node');
2103 my $vmid = extract_param
($param, 'vmid');
2105 my $snapname = extract_param
($param, 'snapname');
2107 my $feature = extract_param
($param, 'feature');
2109 my $running = PVE
::QemuServer
::check_running
($vmid);
2111 my $conf = PVE
::QemuServer
::load_config
($vmid);
2114 my $snap = $conf->{snapshots
}->{$snapname};
2115 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2118 my $storecfg = PVE
::Storage
::config
();
2120 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2121 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
2124 hasFeature
=> $hasFeature,
2125 nodes
=> [ keys %$nodelist ],
2129 __PACKAGE__-
>register_method({
2131 path
=> '{vmid}/clone',
2135 description
=> "Create a copy of virtual machine/template.",
2137 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2138 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2139 "'Datastore.AllocateSpace' on any used storage.",
2142 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2144 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2145 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2150 additionalProperties
=> 0,
2152 node
=> get_standard_option
('pve-node'),
2153 vmid
=> get_standard_option
('pve-vmid'),
2154 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2157 type
=> 'string', format
=> 'dns-name',
2158 description
=> "Set a name for the new VM.",
2163 description
=> "Description for the new VM.",
2167 type
=> 'string', format
=> 'pve-poolid',
2168 description
=> "Add the new VM to the specified pool.",
2170 snapname
=> get_standard_option
('pve-snapshot-name', {
2173 storage
=> get_standard_option
('pve-storage-id', {
2174 description
=> "Target storage for full clone.",
2179 description
=> "Target format for file storage.",
2183 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2188 description
=> "Create a full copy of all disk. This is always done when " .
2189 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2192 target
=> get_standard_option
('pve-node', {
2193 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2204 my $rpcenv = PVE
::RPCEnvironment
::get
();
2206 my $authuser = $rpcenv->get_user();
2208 my $node = extract_param
($param, 'node');
2210 my $vmid = extract_param
($param, 'vmid');
2212 my $newid = extract_param
($param, 'newid');
2214 my $pool = extract_param
($param, 'pool');
2216 if (defined($pool)) {
2217 $rpcenv->check_pool_exist($pool);
2220 my $snapname = extract_param
($param, 'snapname');
2222 my $storage = extract_param
($param, 'storage');
2224 my $format = extract_param
($param, 'format');
2226 my $target = extract_param
($param, 'target');
2228 my $localnode = PVE
::INotify
::nodename
();
2230 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2232 PVE
::Cluster
::check_node_exists
($target) if $target;
2234 my $storecfg = PVE
::Storage
::config
();
2237 # check if storage is enabled on local node
2238 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2240 # check if storage is available on target node
2241 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2242 # clone only works if target storage is shared
2243 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2244 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2248 PVE
::Cluster
::check_cfs_quorum
();
2250 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2252 # exclusive lock if VM is running - else shared lock is enough;
2253 my $shared_lock = $running ?
0 : 1;
2257 # do all tests after lock
2258 # we also try to do all tests before we fork the worker
2260 my $conf = PVE
::QemuServer
::load_config
($vmid);
2262 PVE
::QemuServer
::check_lock
($conf);
2264 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2266 die "unexpected state change\n" if $verify_running != $running;
2268 die "snapshot '$snapname' does not exist\n"
2269 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2271 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2273 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2275 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2277 my $conffile = PVE
::QemuServer
::config_file
($newid);
2279 die "unable to create VM $newid: config file already exists\n"
2282 my $newconf = { lock => 'clone' };
2286 foreach my $opt (keys %$oldconf) {
2287 my $value = $oldconf->{$opt};
2289 # do not copy snapshot related info
2290 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2291 $opt eq 'vmstate' || $opt eq 'snapstate';
2293 # always change MAC! address
2294 if ($opt =~ m/^net(\d+)$/) {
2295 my $net = PVE
::QemuServer
::parse_net
($value);
2296 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2297 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2298 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
2299 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2300 die "unable to parse drive options for '$opt'\n" if !$drive;
2301 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2302 $newconf->{$opt} = $value; # simply copy configuration
2304 if ($param->{full
}) {
2305 die "Full clone feature is not available"
2306 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2309 # not full means clone instead of copy
2310 die "Linked clone feature is not available"
2311 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2313 $drives->{$opt} = $drive;
2314 push @$vollist, $drive->{file
};
2317 # copy everything else
2318 $newconf->{$opt} = $value;
2322 # auto generate a new uuid
2323 my ($uuid, $uuid_str);
2324 UUID
::generate
($uuid);
2325 UUID
::unparse
($uuid, $uuid_str);
2326 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2327 $smbios1->{uuid
} = $uuid_str;
2328 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2330 delete $newconf->{template
};
2332 if ($param->{name
}) {
2333 $newconf->{name
} = $param->{name
};
2335 if ($oldconf->{name
}) {
2336 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2338 $newconf->{name
} = "Copy-of-VM-$vmid";
2342 if ($param->{description
}) {
2343 $newconf->{description
} = $param->{description
};
2346 # create empty/temp config - this fails if VM already exists on other node
2347 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2352 my $newvollist = [];
2355 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2357 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2359 foreach my $opt (keys %$drives) {
2360 my $drive = $drives->{$opt};
2362 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2363 $newid, $storage, $format, $drive->{full
}, $newvollist);
2365 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2367 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2370 delete $newconf->{lock};
2371 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2374 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2375 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist);
2377 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2378 die "Failed to move config to node '$target' - rename failed: $!\n"
2379 if !rename($conffile, $newconffile);
2382 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2387 sleep 1; # some storage like rbd need to wait before release volume - really?
2389 foreach my $volid (@$newvollist) {
2390 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2393 die "clone failed: $err";
2399 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2402 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2403 # Aquire exclusive lock lock for $newid
2404 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2409 __PACKAGE__-
>register_method({
2410 name
=> 'move_vm_disk',
2411 path
=> '{vmid}/move_disk',
2415 description
=> "Move volume to different storage.",
2417 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2418 "and 'Datastore.AllocateSpace' permissions on the storage.",
2421 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2422 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2426 additionalProperties
=> 0,
2428 node
=> get_standard_option
('pve-node'),
2429 vmid
=> get_standard_option
('pve-vmid'),
2432 description
=> "The disk you want to move.",
2433 enum
=> [ PVE
::QemuServer
::disknames
() ],
2435 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2438 description
=> "Target Format.",
2439 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2444 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2450 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2458 description
=> "the task ID.",
2463 my $rpcenv = PVE
::RPCEnvironment
::get
();
2465 my $authuser = $rpcenv->get_user();
2467 my $node = extract_param
($param, 'node');
2469 my $vmid = extract_param
($param, 'vmid');
2471 my $digest = extract_param
($param, 'digest');
2473 my $disk = extract_param
($param, 'disk');
2475 my $storeid = extract_param
($param, 'storage');
2477 my $format = extract_param
($param, 'format');
2479 my $storecfg = PVE
::Storage
::config
();
2481 my $updatefn = sub {
2483 my $conf = PVE
::QemuServer
::load_config
($vmid);
2485 die "checksum missmatch (file change by other user?)\n"
2486 if $digest && $digest ne $conf->{digest
};
2488 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2490 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2492 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2494 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2497 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2498 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2502 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2503 (!$format || !$oldfmt || $oldfmt eq $format);
2505 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2507 my $running = PVE
::QemuServer
::check_running
($vmid);
2509 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2513 my $newvollist = [];
2516 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2518 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2519 $vmid, $storeid, $format, 1, $newvollist);
2521 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2523 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2525 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2528 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2529 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2536 foreach my $volid (@$newvollist) {
2537 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2540 die "storage migration failed: $err";
2543 if ($param->{delete}) {
2544 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, 1);
2545 my $path = PVE
::Storage
::path
($storecfg, $old_volid);
2546 if ($used_paths->{$path}){
2547 warn "volume $old_volid have snapshots. Can't delete it\n";
2548 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid);
2549 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2551 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2557 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2560 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2563 __PACKAGE__-
>register_method({
2564 name
=> 'migrate_vm',
2565 path
=> '{vmid}/migrate',
2569 description
=> "Migrate virtual machine. Creates a new migration task.",
2571 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2574 additionalProperties
=> 0,
2576 node
=> get_standard_option
('pve-node'),
2577 vmid
=> get_standard_option
('pve-vmid'),
2578 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2581 description
=> "Use online/live migration.",
2586 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2593 description
=> "the task ID.",
2598 my $rpcenv = PVE
::RPCEnvironment
::get
();
2600 my $authuser = $rpcenv->get_user();
2602 my $target = extract_param
($param, 'target');
2604 my $localnode = PVE
::INotify
::nodename
();
2605 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2607 PVE
::Cluster
::check_cfs_quorum
();
2609 PVE
::Cluster
::check_node_exists
($target);
2611 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2613 my $vmid = extract_param
($param, 'vmid');
2615 raise_param_exc
({ force
=> "Only root may use this option." })
2616 if $param->{force
} && $authuser ne 'root@pam';
2619 my $conf = PVE
::QemuServer
::load_config
($vmid);
2621 # try to detect errors early
2623 PVE
::QemuServer
::check_lock
($conf);
2625 if (PVE
::QemuServer
::check_running
($vmid)) {
2626 die "cant migrate running VM without --online\n"
2627 if !$param->{online
};
2630 my $storecfg = PVE
::Storage
::config
();
2631 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2633 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2638 my $service = "pvevm:$vmid";
2640 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2642 print "Executing HA migrate for VM $vmid to node $target\n";
2644 PVE
::Tools
::run_command
($cmd);
2649 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2656 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2659 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2664 __PACKAGE__-
>register_method({
2666 path
=> '{vmid}/monitor',
2670 description
=> "Execute Qemu monitor commands.",
2672 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2675 additionalProperties
=> 0,
2677 node
=> get_standard_option
('pve-node'),
2678 vmid
=> get_standard_option
('pve-vmid'),
2681 description
=> "The monitor command.",
2685 returns
=> { type
=> 'string'},
2689 my $vmid = $param->{vmid
};
2691 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2695 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2697 $res = "ERROR: $@" if $@;
2702 __PACKAGE__-
>register_method({
2703 name
=> 'resize_vm',
2704 path
=> '{vmid}/resize',
2708 description
=> "Extend volume size.",
2710 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2713 additionalProperties
=> 0,
2715 node
=> get_standard_option
('pve-node'),
2716 vmid
=> get_standard_option
('pve-vmid'),
2717 skiplock
=> get_standard_option
('skiplock'),
2720 description
=> "The disk you want to resize.",
2721 enum
=> [PVE
::QemuServer
::disknames
()],
2725 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2726 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.",
2730 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2736 returns
=> { type
=> 'null'},
2740 my $rpcenv = PVE
::RPCEnvironment
::get
();
2742 my $authuser = $rpcenv->get_user();
2744 my $node = extract_param
($param, 'node');
2746 my $vmid = extract_param
($param, 'vmid');
2748 my $digest = extract_param
($param, 'digest');
2750 my $disk = extract_param
($param, 'disk');
2752 my $sizestr = extract_param
($param, 'size');
2754 my $skiplock = extract_param
($param, 'skiplock');
2755 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2756 if $skiplock && $authuser ne 'root@pam';
2758 my $storecfg = PVE
::Storage
::config
();
2760 my $updatefn = sub {
2762 my $conf = PVE
::QemuServer
::load_config
($vmid);
2764 die "checksum missmatch (file change by other user?)\n"
2765 if $digest && $digest ne $conf->{digest
};
2766 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2768 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2770 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2772 my $volid = $drive->{file
};
2774 die "disk '$disk' has no associated volume\n" if !$volid;
2776 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2778 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2780 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2782 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2784 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2785 my ($ext, $newsize, $unit) = ($1, $2, $4);
2788 $newsize = $newsize * 1024;
2789 } elsif ($unit eq 'M') {
2790 $newsize = $newsize * 1024 * 1024;
2791 } elsif ($unit eq 'G') {
2792 $newsize = $newsize * 1024 * 1024 * 1024;
2793 } elsif ($unit eq 'T') {
2794 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2797 $newsize += $size if $ext;
2798 $newsize = int($newsize);
2800 die "unable to skrink disk size\n" if $newsize < $size;
2802 return if $size == $newsize;
2804 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2806 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2808 $drive->{size
} = $newsize;
2809 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2811 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2814 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2818 __PACKAGE__-
>register_method({
2819 name
=> 'snapshot_list',
2820 path
=> '{vmid}/snapshot',
2822 description
=> "List all snapshots.",
2824 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2827 protected
=> 1, # qemu pid files are only readable by root
2829 additionalProperties
=> 0,
2831 vmid
=> get_standard_option
('pve-vmid'),
2832 node
=> get_standard_option
('pve-node'),
2841 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2846 my $vmid = $param->{vmid
};
2848 my $conf = PVE
::QemuServer
::load_config
($vmid);
2849 my $snaphash = $conf->{snapshots
} || {};
2853 foreach my $name (keys %$snaphash) {
2854 my $d = $snaphash->{$name};
2857 snaptime
=> $d->{snaptime
} || 0,
2858 vmstate
=> $d->{vmstate
} ?
1 : 0,
2859 description
=> $d->{description
} || '',
2861 $item->{parent
} = $d->{parent
} if $d->{parent
};
2862 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2866 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2867 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2868 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2870 push @$res, $current;
2875 __PACKAGE__-
>register_method({
2877 path
=> '{vmid}/snapshot',
2881 description
=> "Snapshot a VM.",
2883 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2886 additionalProperties
=> 0,
2888 node
=> get_standard_option
('pve-node'),
2889 vmid
=> get_standard_option
('pve-vmid'),
2890 snapname
=> get_standard_option
('pve-snapshot-name'),
2894 description
=> "Save the vmstate",
2899 description
=> "A textual description or comment.",
2905 description
=> "the task ID.",
2910 my $rpcenv = PVE
::RPCEnvironment
::get
();
2912 my $authuser = $rpcenv->get_user();
2914 my $node = extract_param
($param, 'node');
2916 my $vmid = extract_param
($param, 'vmid');
2918 my $snapname = extract_param
($param, 'snapname');
2920 die "unable to use snapshot name 'current' (reserved name)\n"
2921 if $snapname eq 'current';
2924 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2925 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2926 $param->{description
});
2929 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2932 __PACKAGE__-
>register_method({
2933 name
=> 'snapshot_cmd_idx',
2934 path
=> '{vmid}/snapshot/{snapname}',
2941 additionalProperties
=> 0,
2943 vmid
=> get_standard_option
('pve-vmid'),
2944 node
=> get_standard_option
('pve-node'),
2945 snapname
=> get_standard_option
('pve-snapshot-name'),
2954 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2961 push @$res, { cmd
=> 'rollback' };
2962 push @$res, { cmd
=> 'config' };
2967 __PACKAGE__-
>register_method({
2968 name
=> 'update_snapshot_config',
2969 path
=> '{vmid}/snapshot/{snapname}/config',
2973 description
=> "Update snapshot metadata.",
2975 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2978 additionalProperties
=> 0,
2980 node
=> get_standard_option
('pve-node'),
2981 vmid
=> get_standard_option
('pve-vmid'),
2982 snapname
=> get_standard_option
('pve-snapshot-name'),
2986 description
=> "A textual description or comment.",
2990 returns
=> { type
=> 'null' },
2994 my $rpcenv = PVE
::RPCEnvironment
::get
();
2996 my $authuser = $rpcenv->get_user();
2998 my $vmid = extract_param
($param, 'vmid');
3000 my $snapname = extract_param
($param, 'snapname');
3002 return undef if !defined($param->{description
});
3004 my $updatefn = sub {
3006 my $conf = PVE
::QemuServer
::load_config
($vmid);
3008 PVE
::QemuServer
::check_lock
($conf);
3010 my $snap = $conf->{snapshots
}->{$snapname};
3012 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3014 $snap->{description
} = $param->{description
} if defined($param->{description
});
3016 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3019 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
3024 __PACKAGE__-
>register_method({
3025 name
=> 'get_snapshot_config',
3026 path
=> '{vmid}/snapshot/{snapname}/config',
3029 description
=> "Get snapshot configuration",
3031 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3034 additionalProperties
=> 0,
3036 node
=> get_standard_option
('pve-node'),
3037 vmid
=> get_standard_option
('pve-vmid'),
3038 snapname
=> get_standard_option
('pve-snapshot-name'),
3041 returns
=> { type
=> "object" },
3045 my $rpcenv = PVE
::RPCEnvironment
::get
();
3047 my $authuser = $rpcenv->get_user();
3049 my $vmid = extract_param
($param, 'vmid');
3051 my $snapname = extract_param
($param, 'snapname');
3053 my $conf = PVE
::QemuServer
::load_config
($vmid);
3055 my $snap = $conf->{snapshots
}->{$snapname};
3057 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3062 __PACKAGE__-
>register_method({
3064 path
=> '{vmid}/snapshot/{snapname}/rollback',
3068 description
=> "Rollback VM state to specified snapshot.",
3070 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3073 additionalProperties
=> 0,
3075 node
=> get_standard_option
('pve-node'),
3076 vmid
=> get_standard_option
('pve-vmid'),
3077 snapname
=> get_standard_option
('pve-snapshot-name'),
3082 description
=> "the task ID.",
3087 my $rpcenv = PVE
::RPCEnvironment
::get
();
3089 my $authuser = $rpcenv->get_user();
3091 my $node = extract_param
($param, 'node');
3093 my $vmid = extract_param
($param, 'vmid');
3095 my $snapname = extract_param
($param, 'snapname');
3098 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3099 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
3102 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3105 __PACKAGE__-
>register_method({
3106 name
=> 'delsnapshot',
3107 path
=> '{vmid}/snapshot/{snapname}',
3111 description
=> "Delete a VM snapshot.",
3113 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3116 additionalProperties
=> 0,
3118 node
=> get_standard_option
('pve-node'),
3119 vmid
=> get_standard_option
('pve-vmid'),
3120 snapname
=> get_standard_option
('pve-snapshot-name'),
3124 description
=> "For removal from config file, even if removing disk snapshots fails.",
3130 description
=> "the task ID.",
3135 my $rpcenv = PVE
::RPCEnvironment
::get
();
3137 my $authuser = $rpcenv->get_user();
3139 my $node = extract_param
($param, 'node');
3141 my $vmid = extract_param
($param, 'vmid');
3143 my $snapname = extract_param
($param, 'snapname');
3146 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3147 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3150 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3153 __PACKAGE__-
>register_method({
3155 path
=> '{vmid}/template',
3159 description
=> "Create a Template.",
3161 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3162 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3165 additionalProperties
=> 0,
3167 node
=> get_standard_option
('pve-node'),
3168 vmid
=> get_standard_option
('pve-vmid'),
3172 description
=> "If you want to convert only 1 disk to base image.",
3173 enum
=> [PVE
::QemuServer
::disknames
()],
3178 returns
=> { type
=> 'null'},
3182 my $rpcenv = PVE
::RPCEnvironment
::get
();
3184 my $authuser = $rpcenv->get_user();
3186 my $node = extract_param
($param, 'node');
3188 my $vmid = extract_param
($param, 'vmid');
3190 my $disk = extract_param
($param, 'disk');
3192 my $updatefn = sub {
3194 my $conf = PVE
::QemuServer
::load_config
($vmid);
3196 PVE
::QemuServer
::check_lock
($conf);
3198 die "unable to create template, because VM contains snapshots\n"
3199 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3201 die "you can't convert a template to a template\n"
3202 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3204 die "you can't convert a VM to template if VM is running\n"
3205 if PVE
::QemuServer
::check_running
($vmid);
3208 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3211 $conf->{template
} = 1;
3212 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3214 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3217 PVE
::QemuServer
::lock_config
($vmid, $updatefn);