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
};
41 my $check_storage_access = sub {
42 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
44 PVE
::QemuServer
::foreach_drive
($settings, sub {
45 my ($ds, $drive) = @_;
47 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
49 my $volid = $drive->{file
};
51 if (!$volid || $volid eq 'none') {
53 } elsif ($isCDROM && ($volid eq 'cdrom')) {
54 $rpcenv->check($authuser, "/", ['Sys.Console']);
55 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
56 my ($storeid, $size) = ($2 || $default_storage, $3);
57 die "no storage ID specified (and no default storage)\n" if !$storeid;
58 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
60 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
65 my $check_storage_access_clone = sub {
66 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
70 PVE
::QemuServer
::foreach_drive
($conf, sub {
71 my ($ds, $drive) = @_;
73 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
75 my $volid = $drive->{file
};
77 return if !$volid || $volid eq 'none';
80 if ($volid eq 'cdrom') {
81 $rpcenv->check($authuser, "/", ['Sys.Console']);
83 # we simply allow access
84 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
85 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
86 $sharedvm = 0 if !$scfg->{shared
};
90 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
91 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
92 $sharedvm = 0 if !$scfg->{shared
};
94 $sid = $storage if $storage;
95 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
102 # Note: $pool is only needed when creating a VM, because pool permissions
103 # are automatically inherited if VM already exists inside a pool.
104 my $create_disks = sub {
105 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
110 PVE
::QemuServer
::foreach_drive
($settings, sub {
111 my ($ds, $disk) = @_;
113 my $volid = $disk->{file
};
115 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
116 delete $disk->{size
};
117 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
118 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
119 my ($storeid, $size) = ($2 || $default_storage, $3);
120 die "no storage ID specified (and no default storage)\n" if !$storeid;
121 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
122 my $fmt = $disk->{format
} || $defformat;
123 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
124 $fmt, undef, $size*1024*1024);
125 $disk->{file
} = $volid;
126 $disk->{size
} = $size*1024*1024*1024;
127 push @$vollist, $volid;
128 delete $disk->{format
}; # no longer needed
129 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
132 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
134 my $volid_is_new = 1;
137 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
138 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
143 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
145 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
147 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
149 die "volume $volid does not exists\n" if !$size;
151 $disk->{size
} = $size;
154 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
158 # free allocated images on error
160 syslog
('err', "VM $vmid creating disks failed");
161 foreach my $volid (@$vollist) {
162 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
168 # modify vm config if everything went well
169 foreach my $ds (keys %$res) {
170 $conf->{$ds} = $res->{$ds};
176 my $check_vm_modify_config_perm = sub {
177 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
179 return 1 if $authuser eq 'root@pam';
181 foreach my $opt (@$key_list) {
182 # disk checks need to be done somewhere else
183 next if PVE
::QemuServer
::valid_drivename
($opt);
185 if ($opt eq 'sockets' || $opt eq 'cores' ||
186 $opt eq 'cpu' || $opt eq 'smp' ||
187 $opt eq 'cpulimit' || $opt eq 'cpuunits') {
188 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
189 } elsif ($opt eq 'boot' || $opt eq 'bootdisk') {
190 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
191 } elsif ($opt eq 'memory' || $opt eq 'balloon' || $opt eq 'shares') {
192 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
193 } elsif ($opt eq 'args' || $opt eq 'lock') {
194 die "only root can set '$opt' config\n";
195 } elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' || $opt eq 'machine' ||
196 $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet' || $opt eq 'smbios1') {
197 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
198 } elsif ($opt =~ m/^net\d+$/) {
199 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
201 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
208 __PACKAGE__-
>register_method({
212 description
=> "Virtual machine index (per node).",
214 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
218 protected
=> 1, # qemu pid files are only readable by root
220 additionalProperties
=> 0,
222 node
=> get_standard_option
('pve-node'),
231 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
236 my $rpcenv = PVE
::RPCEnvironment
::get
();
237 my $authuser = $rpcenv->get_user();
239 my $vmstatus = PVE
::QemuServer
::vmstatus
();
242 foreach my $vmid (keys %$vmstatus) {
243 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
245 my $data = $vmstatus->{$vmid};
246 $data->{vmid
} = $vmid;
255 __PACKAGE__-
>register_method({
259 description
=> "Create or restore a virtual machine.",
261 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
262 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
263 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
264 user
=> 'all', # check inside
269 additionalProperties
=> 0,
270 properties
=> PVE
::QemuServer
::json_config_properties
(
272 node
=> get_standard_option
('pve-node'),
273 vmid
=> get_standard_option
('pve-vmid'),
275 description
=> "The backup file.",
280 storage
=> get_standard_option
('pve-storage-id', {
281 description
=> "Default storage.",
287 description
=> "Allow to overwrite existing VM.",
288 requires
=> 'archive',
293 description
=> "Assign a unique random ethernet address.",
294 requires
=> 'archive',
298 type
=> 'string', format
=> 'pve-poolid',
299 description
=> "Add the VM to the specified pool.",
309 my $rpcenv = PVE
::RPCEnvironment
::get
();
311 my $authuser = $rpcenv->get_user();
313 my $node = extract_param
($param, 'node');
315 my $vmid = extract_param
($param, 'vmid');
317 my $archive = extract_param
($param, 'archive');
319 my $storage = extract_param
($param, 'storage');
321 my $force = extract_param
($param, 'force');
323 my $unique = extract_param
($param, 'unique');
325 my $pool = extract_param
($param, 'pool');
327 my $filename = PVE
::QemuServer
::config_file
($vmid);
329 my $storecfg = PVE
::Storage
::config
();
331 PVE
::Cluster
::check_cfs_quorum
();
333 if (defined($pool)) {
334 $rpcenv->check_pool_exist($pool);
337 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
338 if defined($storage);
340 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
342 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
344 } elsif ($archive && $force && (-f
$filename) &&
345 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
346 # OK: user has VM.Backup permissions, and want to restore an existing VM
352 &$resolve_cdrom_alias($param);
354 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
356 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
358 foreach my $opt (keys %$param) {
359 if (PVE
::QemuServer
::valid_drivename
($opt)) {
360 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
361 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
363 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
364 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
368 PVE
::QemuServer
::add_random_macs
($param);
370 my $keystr = join(' ', keys %$param);
371 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
373 if ($archive eq '-') {
374 die "pipe requires cli environment\n"
375 if $rpcenv->{type
} ne 'cli';
377 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
378 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
382 my $restorefn = sub {
384 # fixme: this test does not work if VM exists on other node!
386 die "unable to restore vm $vmid: config file already exists\n"
389 die "unable to restore vm $vmid: vm is running\n"
390 if PVE
::QemuServer
::check_running
($vmid);
394 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
397 unique
=> $unique });
399 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
402 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
408 die "unable to create vm $vmid: config file already exists\n"
419 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
421 # try to be smart about bootdisk
422 my @disks = PVE
::QemuServer
::disknames
();
424 foreach my $ds (reverse @disks) {
425 next if !$conf->{$ds};
426 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
427 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
431 if (!$conf->{bootdisk
} && $firstdisk) {
432 $conf->{bootdisk
} = $firstdisk;
435 # auto generate uuid if user did not specify smbios1 option
436 if (!$conf->{smbios1
}) {
437 my ($uuid, $uuid_str);
438 UUID
::generate
($uuid);
439 UUID
::unparse
($uuid, $uuid_str);
440 $conf->{smbios1
} = "uuid=$uuid_str";
443 PVE
::QemuServer
::update_config_nolock
($vmid, $conf);
449 foreach my $volid (@$vollist) {
450 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
453 die "create failed - $err";
456 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
459 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
462 return PVE
::QemuServer
::lock_config_full
($vmid, 1, $archive ?
$restorefn : $createfn);
465 __PACKAGE__-
>register_method({
470 description
=> "Directory index",
475 additionalProperties
=> 0,
477 node
=> get_standard_option
('pve-node'),
478 vmid
=> get_standard_option
('pve-vmid'),
486 subdir
=> { type
=> 'string' },
489 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
495 { subdir
=> 'config' },
496 { subdir
=> 'status' },
497 { subdir
=> 'unlink' },
498 { subdir
=> 'vncproxy' },
499 { subdir
=> 'migrate' },
500 { subdir
=> 'resize' },
501 { subdir
=> 'move' },
503 { subdir
=> 'rrddata' },
504 { subdir
=> 'monitor' },
505 { subdir
=> 'snapshot' },
506 { subdir
=> 'spiceproxy' },
507 { subdir
=> 'sendkey' },
508 { subdir
=> 'firewall' },
514 __PACKAGE__-
>register_method ({
515 subclass
=> "PVE::API2::Firewall::VM",
516 path
=> '{vmid}/firewall',
519 __PACKAGE__-
>register_method({
521 path
=> '{vmid}/rrd',
523 protected
=> 1, # fixme: can we avoid that?
525 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
527 description
=> "Read VM RRD statistics (returns PNG)",
529 additionalProperties
=> 0,
531 node
=> get_standard_option
('pve-node'),
532 vmid
=> get_standard_option
('pve-vmid'),
534 description
=> "Specify the time frame you are interested in.",
536 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
539 description
=> "The list of datasources you want to display.",
540 type
=> 'string', format
=> 'pve-configid-list',
543 description
=> "The RRD consolidation function",
545 enum
=> [ 'AVERAGE', 'MAX' ],
553 filename
=> { type
=> 'string' },
559 return PVE
::Cluster
::create_rrd_graph
(
560 "pve2-vm/$param->{vmid}", $param->{timeframe
},
561 $param->{ds
}, $param->{cf
});
565 __PACKAGE__-
>register_method({
567 path
=> '{vmid}/rrddata',
569 protected
=> 1, # fixme: can we avoid that?
571 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
573 description
=> "Read VM RRD statistics",
575 additionalProperties
=> 0,
577 node
=> get_standard_option
('pve-node'),
578 vmid
=> get_standard_option
('pve-vmid'),
580 description
=> "Specify the time frame you are interested in.",
582 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
585 description
=> "The RRD consolidation function",
587 enum
=> [ 'AVERAGE', 'MAX' ],
602 return PVE
::Cluster
::create_rrd_data
(
603 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
607 __PACKAGE__-
>register_method({
609 path
=> '{vmid}/config',
612 description
=> "Get virtual machine configuration.",
614 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
617 additionalProperties
=> 0,
619 node
=> get_standard_option
('pve-node'),
620 vmid
=> get_standard_option
('pve-vmid'),
628 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
635 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
637 delete $conf->{snapshots
};
642 my $vm_is_volid_owner = sub {
643 my ($storecfg, $vmid, $volid) =@_;
645 if ($volid !~ m
|^/|) {
647 eval { ($path, $owner) = PVE
::Storage
::path
($storecfg, $volid); };
648 if ($owner && ($owner == $vmid)) {
656 my $test_deallocate_drive = sub {
657 my ($storecfg, $vmid, $key, $drive, $force) = @_;
659 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
660 my $volid = $drive->{file
};
661 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
662 if ($force || $key =~ m/^unused/) {
663 my $sid = PVE
::Storage
::parse_volume_id
($volid);
672 my $delete_drive = sub {
673 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
675 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
676 my $volid = $drive->{file
};
678 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
679 if ($force || $key =~ m/^unused/) {
681 # check if the disk is really unused
682 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, $key);
683 my $path = PVE
::Storage
::path
($storecfg, $volid);
685 die "unable to delete '$volid' - volume is still in use (snapshot?)\n"
686 if $used_paths->{$path};
688 PVE
::Storage
::vdisk_free
($storecfg, $volid);
692 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
697 delete $conf->{$key};
700 my $vmconfig_delete_option = sub {
701 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
703 return if !defined($conf->{$opt});
705 my $isDisk = PVE
::QemuServer
::valid_drivename
($opt)|| ($opt =~ m/^unused/);
708 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
710 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
711 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
712 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
716 my $unplugwarning = "";
717 if ($conf->{ostype
} && $conf->{ostype
} eq 'l26') {
718 $unplugwarning = "<br>verify that you have acpiphp && pci_hotplug modules loaded in your guest VM";
719 } elsif ($conf->{ostype
} && $conf->{ostype
} eq 'l24') {
720 $unplugwarning = "<br>kernel 2.4 don't support hotplug, please disable hotplug in options";
721 } elsif (!$conf->{ostype
} || ($conf->{ostype
} && $conf->{ostype
} eq 'other')) {
722 $unplugwarning = "<br>verify that your guest support acpi hotplug";
725 if ($opt eq 'tablet') {
726 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
728 die "error hot-unplug $opt $unplugwarning" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
732 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
733 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
735 delete $conf->{$opt};
738 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
741 my $safe_num_ne = sub {
744 return 0 if !defined($a) && !defined($b);
745 return 1 if !defined($a);
746 return 1 if !defined($b);
751 my $vmconfig_update_disk = sub {
752 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
754 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
756 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { #cdrom
757 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
759 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
764 if (my $old_drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt})) {
766 my $media = $drive->{media
} || 'disk';
767 my $oldmedia = $old_drive->{media
} || 'disk';
768 die "unable to change media type\n" if $media ne $oldmedia;
770 if (!PVE
::QemuServer
::drive_is_cdrom
($old_drive) &&
771 ($drive->{file
} ne $old_drive->{file
})) { # delete old disks
773 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
774 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
777 if(&$safe_num_ne($drive->{mbps
}, $old_drive->{mbps
}) ||
778 &$safe_num_ne($drive->{mbps_rd
}, $old_drive->{mbps_rd
}) ||
779 &$safe_num_ne($drive->{mbps_wr
}, $old_drive->{mbps_wr
}) ||
780 &$safe_num_ne($drive->{iops
}, $old_drive->{iops
}) ||
781 &$safe_num_ne($drive->{iops_rd
}, $old_drive->{iops_rd
}) ||
782 &$safe_num_ne($drive->{iops_wr
}, $old_drive->{iops_wr
}) ||
783 &$safe_num_ne($drive->{mbps_max
}, $old_drive->{mbps_max
}) ||
784 &$safe_num_ne($drive->{mbps_rd_max
}, $old_drive->{mbps_rd_max
}) ||
785 &$safe_num_ne($drive->{mbps_wr_max
}, $old_drive->{mbps_wr_max
}) ||
786 &$safe_num_ne($drive->{iops_max
}, $old_drive->{iops_max
}) ||
787 &$safe_num_ne($drive->{iops_rd_max
}, $old_drive->{iops_rd_max
}) ||
788 &$safe_num_ne($drive->{iops_wr_max
}, $old_drive->{iops_wr_max
})) {
789 PVE
::QemuServer
::qemu_block_set_io_throttle
($vmid,"drive-$opt",
790 ($drive->{mbps
} || 0)*1024*1024,
791 ($drive->{mbps_rd
} || 0)*1024*1024,
792 ($drive->{mbps_wr
} || 0)*1024*1024,
794 $drive->{iops_rd
} || 0,
795 $drive->{iops_wr
} || 0,
796 ($drive->{mbps_max
} || 0)*1024*1024,
797 ($drive->{mbps_rd_max
} || 0)*1024*1024,
798 ($drive->{mbps_wr_max
} || 0)*1024*1024,
799 $drive->{iops_max
} || 0,
800 $drive->{iops_rd_max
} || 0,
801 $drive->{iops_wr_max
} || 0)
802 if !PVE
::QemuServer
::drive_is_cdrom
($drive);
807 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
808 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
810 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
811 $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
813 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # cdrom
815 if (PVE
::QemuServer
::check_running
($vmid)) {
816 if ($drive->{file
} eq 'none') {
817 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt");
819 my $path = PVE
::QemuServer
::get_iso_path
($storecfg, $vmid, $drive->{file
});
820 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt"); #force eject if locked
821 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change",device
=> "drive-$opt",target
=> "$path") if $path;
825 } else { # hotplug new disks
827 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $drive);
831 my $vmconfig_update_net = sub {
832 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
834 if ($conf->{$opt} && PVE
::QemuServer
::check_running
($vmid)) {
835 my $oldnet = PVE
::QemuServer
::parse_net
($conf->{$opt});
836 my $newnet = PVE
::QemuServer
::parse_net
($value);
838 if($oldnet->{model
} ne $newnet->{model
}){
839 #if model change, we try to hot-unplug
840 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
843 if($newnet->{bridge
} && $oldnet->{bridge
}){
844 my $iface = "tap".$vmid."i".$1 if $opt =~ m/net(\d+)/;
846 if($newnet->{rate
} ne $oldnet->{rate
}){
847 PVE
::Network
::tap_rate_limit
($iface, $newnet->{rate
});
850 if(($newnet->{bridge
} ne $oldnet->{bridge
}) || ($newnet->{tag
} ne $oldnet->{tag
}) || ($newnet->{firewall
} ne $oldnet->{firewall
})){
851 PVE
::Network
::tap_unplug
($iface);
852 PVE
::Network
::tap_plug
($iface, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
856 #if bridge/nat mode change, we try to hot-unplug
857 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
862 $conf->{$opt} = $value;
863 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
864 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
866 my $net = PVE
::QemuServer
::parse_net
($conf->{$opt});
868 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $net);
871 # POST/PUT {vmid}/config implementation
873 # The original API used PUT (idempotent) an we assumed that all operations
874 # are fast. But it turned out that almost any configuration change can
875 # involve hot-plug actions, or disk alloc/free. Such actions can take long
876 # time to complete and have side effects (not idempotent).
878 # The new implementation uses POST and forks a worker process. We added
879 # a new option 'background_delay'. If specified we wait up to
880 # 'background_delay' second for the worker task to complete. It returns null
881 # if the task is finished within that time, else we return the UPID.
883 my $update_vm_api = sub {
884 my ($param, $sync) = @_;
886 my $rpcenv = PVE
::RPCEnvironment
::get
();
888 my $authuser = $rpcenv->get_user();
890 my $node = extract_param
($param, 'node');
892 my $vmid = extract_param
($param, 'vmid');
894 my $digest = extract_param
($param, 'digest');
896 my $background_delay = extract_param
($param, 'background_delay');
898 my @paramarr = (); # used for log message
899 foreach my $key (keys %$param) {
900 push @paramarr, "-$key", $param->{$key};
903 my $skiplock = extract_param
($param, 'skiplock');
904 raise_param_exc
({ skiplock
=> "Only root may use this option." })
905 if $skiplock && $authuser ne 'root@pam';
907 my $delete_str = extract_param
($param, 'delete');
909 my $force = extract_param
($param, 'force');
911 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
913 my $storecfg = PVE
::Storage
::config
();
915 my $defaults = PVE
::QemuServer
::load_defaults
();
917 &$resolve_cdrom_alias($param);
919 # now try to verify all parameters
922 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
923 $opt = 'ide2' if $opt eq 'cdrom';
924 raise_param_exc
({ delete => "you can't use '-$opt' and " .
925 "-delete $opt' at the same time" })
926 if defined($param->{$opt});
928 if (!PVE
::QemuServer
::option_exists
($opt)) {
929 raise_param_exc
({ delete => "unknown option '$opt'" });
935 foreach my $opt (keys %$param) {
936 if (PVE
::QemuServer
::valid_drivename
($opt)) {
938 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
939 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
940 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
941 } elsif ($opt =~ m/^net(\d+)$/) {
943 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
944 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
948 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
950 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
952 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
956 my $conf = PVE
::QemuServer
::load_config
($vmid);
958 die "checksum missmatch (file change by other user?)\n"
959 if $digest && $digest ne $conf->{digest
};
961 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
963 if ($param->{memory
} || defined($param->{balloon
})) {
964 my $maxmem = $param->{memory
} || $conf->{memory
} || $defaults->{memory
};
965 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{balloon
};
967 die "balloon value too large (must be smaller than assigned memory)\n"
968 if $balloon && $balloon > $maxmem;
971 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
975 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
977 foreach my $opt (@delete) { # delete
978 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
979 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
982 my $running = PVE
::QemuServer
::check_running
($vmid);
984 foreach my $opt (keys %$param) { # add/change
986 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
988 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
990 if (PVE
::QemuServer
::valid_drivename
($opt)) {
992 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
993 $opt, $param->{$opt}, $force);
995 } elsif ($opt =~ m/^net(\d+)$/) { #nics
997 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
998 $opt, $param->{$opt});
1002 if($opt eq 'tablet' && $param->{$opt} == 1){
1003 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
1004 } elsif($opt eq 'tablet' && $param->{$opt} == 0){
1005 PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
1008 if($opt eq 'cores' && $conf->{maxcpus
}){
1009 PVE
::QemuServer
::qemu_cpu_hotplug
($vmid, $conf, $param->{$opt});
1012 $conf->{$opt} = $param->{$opt};
1013 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
1017 # allow manual ballooning if shares is set to zero
1018 if ($running && defined($param->{balloon
}) &&
1019 defined($conf->{shares
}) && ($conf->{shares
} == 0)) {
1020 my $balloon = $param->{'balloon'} || $conf->{memory
} || $defaults->{memory
};
1021 PVE
::QemuServer
::vm_mon_cmd
($vmid, "balloon", value
=> $balloon*1024*1024);
1029 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1031 if ($background_delay) {
1033 # Note: It would be better to do that in the Event based HTTPServer
1034 # to avoid blocking call to sleep.
1036 my $end_time = time() + $background_delay;
1038 my $task = PVE
::Tools
::upid_decode
($upid);
1041 while (time() < $end_time) {
1042 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1044 sleep(1); # this gets interrupted when child process ends
1048 my $status = PVE
::Tools
::upid_read_status
($upid);
1049 return undef if $status eq 'OK';
1058 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1061 my $vm_config_perm_list = [
1066 'VM.Config.Network',
1068 'VM.Config.Options',
1071 __PACKAGE__-
>register_method({
1072 name
=> 'update_vm_async',
1073 path
=> '{vmid}/config',
1077 description
=> "Set virtual machine options (asynchrounous API).",
1079 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1082 additionalProperties
=> 0,
1083 properties
=> PVE
::QemuServer
::json_config_properties
(
1085 node
=> get_standard_option
('pve-node'),
1086 vmid
=> get_standard_option
('pve-vmid'),
1087 skiplock
=> get_standard_option
('skiplock'),
1089 type
=> 'string', format
=> 'pve-configid-list',
1090 description
=> "A list of settings you want to delete.",
1095 description
=> $opt_force_description,
1097 requires
=> 'delete',
1101 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1105 background_delay
=> {
1107 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1118 code
=> $update_vm_api,
1121 __PACKAGE__-
>register_method({
1122 name
=> 'update_vm',
1123 path
=> '{vmid}/config',
1127 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1129 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1132 additionalProperties
=> 0,
1133 properties
=> PVE
::QemuServer
::json_config_properties
(
1135 node
=> get_standard_option
('pve-node'),
1136 vmid
=> get_standard_option
('pve-vmid'),
1137 skiplock
=> get_standard_option
('skiplock'),
1139 type
=> 'string', format
=> 'pve-configid-list',
1140 description
=> "A list of settings you want to delete.",
1145 description
=> $opt_force_description,
1147 requires
=> 'delete',
1151 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1157 returns
=> { type
=> 'null' },
1160 &$update_vm_api($param, 1);
1166 __PACKAGE__-
>register_method({
1167 name
=> 'destroy_vm',
1172 description
=> "Destroy the vm (also delete all used/owned volumes).",
1174 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1177 additionalProperties
=> 0,
1179 node
=> get_standard_option
('pve-node'),
1180 vmid
=> get_standard_option
('pve-vmid'),
1181 skiplock
=> get_standard_option
('skiplock'),
1190 my $rpcenv = PVE
::RPCEnvironment
::get
();
1192 my $authuser = $rpcenv->get_user();
1194 my $vmid = $param->{vmid
};
1196 my $skiplock = $param->{skiplock
};
1197 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1198 if $skiplock && $authuser ne 'root@pam';
1201 my $conf = PVE
::QemuServer
::load_config
($vmid);
1203 my $storecfg = PVE
::Storage
::config
();
1205 my $delVMfromPoolFn = sub {
1206 my $usercfg = cfs_read_file
("user.cfg");
1207 if (my $pool = $usercfg->{vms
}->{$vmid}) {
1208 if (my $data = $usercfg->{pools
}->{$pool}) {
1209 delete $data->{vms
}->{$vmid};
1210 delete $usercfg->{vms
}->{$vmid};
1211 cfs_write_file
("user.cfg", $usercfg);
1219 syslog
('info', "destroy VM $vmid: $upid\n");
1221 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1223 PVE
::AccessControl
::remove_vm_from_pool
($vmid);
1226 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1229 __PACKAGE__-
>register_method({
1231 path
=> '{vmid}/unlink',
1235 description
=> "Unlink/delete disk images.",
1237 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1240 additionalProperties
=> 0,
1242 node
=> get_standard_option
('pve-node'),
1243 vmid
=> get_standard_option
('pve-vmid'),
1245 type
=> 'string', format
=> 'pve-configid-list',
1246 description
=> "A list of disk IDs you want to delete.",
1250 description
=> $opt_force_description,
1255 returns
=> { type
=> 'null'},
1259 $param->{delete} = extract_param
($param, 'idlist');
1261 __PACKAGE__-
>update_vm($param);
1268 __PACKAGE__-
>register_method({
1270 path
=> '{vmid}/vncproxy',
1274 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1276 description
=> "Creates a TCP VNC proxy connections.",
1278 additionalProperties
=> 0,
1280 node
=> get_standard_option
('pve-node'),
1281 vmid
=> get_standard_option
('pve-vmid'),
1285 description
=> "starts websockify instead of vncproxy",
1290 additionalProperties
=> 0,
1292 user
=> { type
=> 'string' },
1293 ticket
=> { type
=> 'string' },
1294 cert
=> { type
=> 'string' },
1295 port
=> { type
=> 'integer' },
1296 upid
=> { type
=> 'string' },
1302 my $rpcenv = PVE
::RPCEnvironment
::get
();
1304 my $authuser = $rpcenv->get_user();
1306 my $vmid = $param->{vmid
};
1307 my $node = $param->{node
};
1308 my $websocket = $param->{websocket
};
1310 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # check if VM exists
1312 my $authpath = "/vms/$vmid";
1314 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1316 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1319 my $port = PVE
::Tools
::next_vnc_port
();
1324 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1325 $remip = PVE
::Cluster
::remote_node_ip
($node);
1326 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1327 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1335 syslog
('info', "starting vnc proxy $upid\n");
1339 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1341 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1343 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1344 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1345 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1346 '-timeout', $timeout, '-authpath', $authpath,
1347 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1350 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1352 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1354 my $qmstr = join(' ', @$qmcmd);
1356 # also redirect stderr (else we get RFB protocol errors)
1357 $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1360 PVE
::Tools
::run_command
($cmd);
1365 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1367 PVE
::Tools
::wait_for_vnc_port
($port);
1378 __PACKAGE__-
>register_method({
1379 name
=> 'vncwebsocket',
1380 path
=> '{vmid}/vncwebsocket',
1383 description
=> "You also need to pass a valid ticket (vncticket).",
1384 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1386 description
=> "Opens a weksocket for VNC traffic.",
1388 additionalProperties
=> 0,
1390 node
=> get_standard_option
('pve-node'),
1391 vmid
=> get_standard_option
('pve-vmid'),
1393 description
=> "Ticket from previous call to vncproxy.",
1398 description
=> "Port number returned by previous vncproxy call.",
1408 port
=> { type
=> 'string' },
1414 my $rpcenv = PVE
::RPCEnvironment
::get
();
1416 my $authuser = $rpcenv->get_user();
1418 my $vmid = $param->{vmid
};
1419 my $node = $param->{node
};
1421 my $authpath = "/vms/$vmid";
1423 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1425 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # VM exists ?
1427 # Note: VNC ports are acessible from outside, so we do not gain any
1428 # security if we verify that $param->{port} belongs to VM $vmid. This
1429 # check is done by verifying the VNC ticket (inside VNC protocol).
1431 my $port = $param->{port
};
1433 return { port
=> $port };
1436 __PACKAGE__-
>register_method({
1437 name
=> 'spiceproxy',
1438 path
=> '{vmid}/spiceproxy',
1443 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1445 description
=> "Returns a SPICE configuration to connect to the VM.",
1447 additionalProperties
=> 0,
1449 node
=> get_standard_option
('pve-node'),
1450 vmid
=> get_standard_option
('pve-vmid'),
1451 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1454 returns
=> get_standard_option
('remote-viewer-config'),
1458 my $rpcenv = PVE
::RPCEnvironment
::get
();
1460 my $authuser = $rpcenv->get_user();
1462 my $vmid = $param->{vmid
};
1463 my $node = $param->{node
};
1464 my $proxy = $param->{proxy
};
1466 my $conf = PVE
::QemuServer
::load_config
($vmid, $node);
1467 my $title = "VM $vmid - $conf->{'name'}",
1469 my $port = PVE
::QemuServer
::spice_port
($vmid);
1471 my ($ticket, undef, $remote_viewer_config) =
1472 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1474 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1475 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1477 return $remote_viewer_config;
1480 __PACKAGE__-
>register_method({
1482 path
=> '{vmid}/status',
1485 description
=> "Directory index",
1490 additionalProperties
=> 0,
1492 node
=> get_standard_option
('pve-node'),
1493 vmid
=> get_standard_option
('pve-vmid'),
1501 subdir
=> { type
=> 'string' },
1504 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1510 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1513 { subdir
=> 'current' },
1514 { subdir
=> 'start' },
1515 { subdir
=> 'stop' },
1521 my $vm_is_ha_managed = sub {
1524 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1525 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1531 __PACKAGE__-
>register_method({
1532 name
=> 'vm_status',
1533 path
=> '{vmid}/status/current',
1536 protected
=> 1, # qemu pid files are only readable by root
1537 description
=> "Get virtual machine status.",
1539 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1542 additionalProperties
=> 0,
1544 node
=> get_standard_option
('pve-node'),
1545 vmid
=> get_standard_option
('pve-vmid'),
1548 returns
=> { type
=> 'object' },
1553 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1555 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1556 my $status = $vmstatus->{$param->{vmid
}};
1558 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1560 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1565 __PACKAGE__-
>register_method({
1567 path
=> '{vmid}/status/start',
1571 description
=> "Start virtual machine.",
1573 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1576 additionalProperties
=> 0,
1578 node
=> get_standard_option
('pve-node'),
1579 vmid
=> get_standard_option
('pve-vmid'),
1580 skiplock
=> get_standard_option
('skiplock'),
1581 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1582 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1583 machine
=> get_standard_option
('pve-qm-machine'),
1592 my $rpcenv = PVE
::RPCEnvironment
::get
();
1594 my $authuser = $rpcenv->get_user();
1596 my $node = extract_param
($param, 'node');
1598 my $vmid = extract_param
($param, 'vmid');
1600 my $machine = extract_param
($param, 'machine');
1602 my $stateuri = extract_param
($param, 'stateuri');
1603 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1604 if $stateuri && $authuser ne 'root@pam';
1606 my $skiplock = extract_param
($param, 'skiplock');
1607 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1608 if $skiplock && $authuser ne 'root@pam';
1610 my $migratedfrom = extract_param
($param, 'migratedfrom');
1611 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1612 if $migratedfrom && $authuser ne 'root@pam';
1614 # read spice ticket from STDIN
1616 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1617 if (defined(my $line = <>)) {
1619 $spice_ticket = $line;
1623 my $storecfg = PVE
::Storage
::config
();
1625 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1626 $rpcenv->{type
} ne 'ha') {
1631 my $service = "pvevm:$vmid";
1633 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1635 print "Executing HA start for VM $vmid\n";
1637 PVE
::Tools
::run_command
($cmd);
1642 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1649 syslog
('info', "start VM $vmid: $upid\n");
1651 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1652 $machine, $spice_ticket);
1657 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1661 __PACKAGE__-
>register_method({
1663 path
=> '{vmid}/status/stop',
1667 description
=> "Stop virtual machine.",
1669 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1672 additionalProperties
=> 0,
1674 node
=> get_standard_option
('pve-node'),
1675 vmid
=> get_standard_option
('pve-vmid'),
1676 skiplock
=> get_standard_option
('skiplock'),
1677 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1679 description
=> "Wait maximal timeout seconds.",
1685 description
=> "Do not decativate storage volumes.",
1698 my $rpcenv = PVE
::RPCEnvironment
::get
();
1700 my $authuser = $rpcenv->get_user();
1702 my $node = extract_param
($param, 'node');
1704 my $vmid = extract_param
($param, 'vmid');
1706 my $skiplock = extract_param
($param, 'skiplock');
1707 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1708 if $skiplock && $authuser ne 'root@pam';
1710 my $keepActive = extract_param
($param, 'keepActive');
1711 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1712 if $keepActive && $authuser ne 'root@pam';
1714 my $migratedfrom = extract_param
($param, 'migratedfrom');
1715 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1716 if $migratedfrom && $authuser ne 'root@pam';
1719 my $storecfg = PVE
::Storage
::config
();
1721 if (&$vm_is_ha_managed($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1726 my $service = "pvevm:$vmid";
1728 my $cmd = ['clusvcadm', '-d', $service];
1730 print "Executing HA stop for VM $vmid\n";
1732 PVE
::Tools
::run_command
($cmd);
1737 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1743 syslog
('info', "stop VM $vmid: $upid\n");
1745 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1746 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1751 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1755 __PACKAGE__-
>register_method({
1757 path
=> '{vmid}/status/reset',
1761 description
=> "Reset virtual machine.",
1763 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1766 additionalProperties
=> 0,
1768 node
=> get_standard_option
('pve-node'),
1769 vmid
=> get_standard_option
('pve-vmid'),
1770 skiplock
=> get_standard_option
('skiplock'),
1779 my $rpcenv = PVE
::RPCEnvironment
::get
();
1781 my $authuser = $rpcenv->get_user();
1783 my $node = extract_param
($param, 'node');
1785 my $vmid = extract_param
($param, 'vmid');
1787 my $skiplock = extract_param
($param, 'skiplock');
1788 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1789 if $skiplock && $authuser ne 'root@pam';
1791 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1796 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1801 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1804 __PACKAGE__-
>register_method({
1805 name
=> 'vm_shutdown',
1806 path
=> '{vmid}/status/shutdown',
1810 description
=> "Shutdown virtual machine.",
1812 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1815 additionalProperties
=> 0,
1817 node
=> get_standard_option
('pve-node'),
1818 vmid
=> get_standard_option
('pve-vmid'),
1819 skiplock
=> get_standard_option
('skiplock'),
1821 description
=> "Wait maximal timeout seconds.",
1827 description
=> "Make sure the VM stops.",
1833 description
=> "Do not decativate storage volumes.",
1846 my $rpcenv = PVE
::RPCEnvironment
::get
();
1848 my $authuser = $rpcenv->get_user();
1850 my $node = extract_param
($param, 'node');
1852 my $vmid = extract_param
($param, 'vmid');
1854 my $skiplock = extract_param
($param, 'skiplock');
1855 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1856 if $skiplock && $authuser ne 'root@pam';
1858 my $keepActive = extract_param
($param, 'keepActive');
1859 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1860 if $keepActive && $authuser ne 'root@pam';
1862 my $storecfg = PVE
::Storage
::config
();
1867 syslog
('info', "shutdown VM $vmid: $upid\n");
1869 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1870 1, $param->{forceStop
}, $keepActive);
1875 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1878 __PACKAGE__-
>register_method({
1879 name
=> 'vm_suspend',
1880 path
=> '{vmid}/status/suspend',
1884 description
=> "Suspend virtual machine.",
1886 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1889 additionalProperties
=> 0,
1891 node
=> get_standard_option
('pve-node'),
1892 vmid
=> get_standard_option
('pve-vmid'),
1893 skiplock
=> get_standard_option
('skiplock'),
1902 my $rpcenv = PVE
::RPCEnvironment
::get
();
1904 my $authuser = $rpcenv->get_user();
1906 my $node = extract_param
($param, 'node');
1908 my $vmid = extract_param
($param, 'vmid');
1910 my $skiplock = extract_param
($param, 'skiplock');
1911 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1912 if $skiplock && $authuser ne 'root@pam';
1914 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1919 syslog
('info', "suspend VM $vmid: $upid\n");
1921 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1926 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1929 __PACKAGE__-
>register_method({
1930 name
=> 'vm_resume',
1931 path
=> '{vmid}/status/resume',
1935 description
=> "Resume virtual machine.",
1937 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1940 additionalProperties
=> 0,
1942 node
=> get_standard_option
('pve-node'),
1943 vmid
=> get_standard_option
('pve-vmid'),
1944 skiplock
=> get_standard_option
('skiplock'),
1953 my $rpcenv = PVE
::RPCEnvironment
::get
();
1955 my $authuser = $rpcenv->get_user();
1957 my $node = extract_param
($param, 'node');
1959 my $vmid = extract_param
($param, 'vmid');
1961 my $skiplock = extract_param
($param, 'skiplock');
1962 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1963 if $skiplock && $authuser ne 'root@pam';
1965 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1970 syslog
('info', "resume VM $vmid: $upid\n");
1972 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1977 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1980 __PACKAGE__-
>register_method({
1981 name
=> 'vm_sendkey',
1982 path
=> '{vmid}/sendkey',
1986 description
=> "Send key event to virtual machine.",
1988 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1991 additionalProperties
=> 0,
1993 node
=> get_standard_option
('pve-node'),
1994 vmid
=> get_standard_option
('pve-vmid'),
1995 skiplock
=> get_standard_option
('skiplock'),
1997 description
=> "The key (qemu monitor encoding).",
2002 returns
=> { type
=> 'null'},
2006 my $rpcenv = PVE
::RPCEnvironment
::get
();
2008 my $authuser = $rpcenv->get_user();
2010 my $node = extract_param
($param, 'node');
2012 my $vmid = extract_param
($param, 'vmid');
2014 my $skiplock = extract_param
($param, 'skiplock');
2015 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2016 if $skiplock && $authuser ne 'root@pam';
2018 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2023 __PACKAGE__-
>register_method({
2024 name
=> 'vm_feature',
2025 path
=> '{vmid}/feature',
2029 description
=> "Check if feature for virtual machine is available.",
2031 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2034 additionalProperties
=> 0,
2036 node
=> get_standard_option
('pve-node'),
2037 vmid
=> get_standard_option
('pve-vmid'),
2039 description
=> "Feature to check.",
2041 enum
=> [ 'snapshot', 'clone', 'copy' ],
2043 snapname
=> get_standard_option
('pve-snapshot-name', {
2051 hasFeature
=> { type
=> 'boolean' },
2054 items
=> { type
=> 'string' },
2061 my $node = extract_param
($param, 'node');
2063 my $vmid = extract_param
($param, 'vmid');
2065 my $snapname = extract_param
($param, 'snapname');
2067 my $feature = extract_param
($param, 'feature');
2069 my $running = PVE
::QemuServer
::check_running
($vmid);
2071 my $conf = PVE
::QemuServer
::load_config
($vmid);
2074 my $snap = $conf->{snapshots
}->{$snapname};
2075 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2078 my $storecfg = PVE
::Storage
::config
();
2080 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2081 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
2084 hasFeature
=> $hasFeature,
2085 nodes
=> [ keys %$nodelist ],
2089 __PACKAGE__-
>register_method({
2091 path
=> '{vmid}/clone',
2095 description
=> "Create a copy of virtual machine/template.",
2097 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2098 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2099 "'Datastore.AllocateSpace' on any used storage.",
2102 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2104 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2105 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2110 additionalProperties
=> 0,
2112 node
=> get_standard_option
('pve-node'),
2113 vmid
=> get_standard_option
('pve-vmid'),
2114 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2117 type
=> 'string', format
=> 'dns-name',
2118 description
=> "Set a name for the new VM.",
2123 description
=> "Description for the new VM.",
2127 type
=> 'string', format
=> 'pve-poolid',
2128 description
=> "Add the new VM to the specified pool.",
2130 snapname
=> get_standard_option
('pve-snapshot-name', {
2133 storage
=> get_standard_option
('pve-storage-id', {
2134 description
=> "Target storage for full clone.",
2139 description
=> "Target format for file storage.",
2143 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2148 description
=> "Create a full copy of all disk. This is always done when " .
2149 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2152 target
=> get_standard_option
('pve-node', {
2153 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2164 my $rpcenv = PVE
::RPCEnvironment
::get
();
2166 my $authuser = $rpcenv->get_user();
2168 my $node = extract_param
($param, 'node');
2170 my $vmid = extract_param
($param, 'vmid');
2172 my $newid = extract_param
($param, 'newid');
2174 my $pool = extract_param
($param, 'pool');
2176 if (defined($pool)) {
2177 $rpcenv->check_pool_exist($pool);
2180 my $snapname = extract_param
($param, 'snapname');
2182 my $storage = extract_param
($param, 'storage');
2184 my $format = extract_param
($param, 'format');
2186 my $target = extract_param
($param, 'target');
2188 my $localnode = PVE
::INotify
::nodename
();
2190 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2192 PVE
::Cluster
::check_node_exists
($target) if $target;
2194 my $storecfg = PVE
::Storage
::config
();
2197 # check if storage is enabled on local node
2198 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2200 # check if storage is available on target node
2201 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2202 # clone only works if target storage is shared
2203 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2204 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2208 PVE
::Cluster
::check_cfs_quorum
();
2210 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2212 # exclusive lock if VM is running - else shared lock is enough;
2213 my $shared_lock = $running ?
0 : 1;
2217 # do all tests after lock
2218 # we also try to do all tests before we fork the worker
2220 my $conf = PVE
::QemuServer
::load_config
($vmid);
2222 PVE
::QemuServer
::check_lock
($conf);
2224 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2226 die "unexpected state change\n" if $verify_running != $running;
2228 die "snapshot '$snapname' does not exist\n"
2229 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2231 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2233 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2235 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2237 my $conffile = PVE
::QemuServer
::config_file
($newid);
2239 die "unable to create VM $newid: config file already exists\n"
2242 my $newconf = { lock => 'clone' };
2246 foreach my $opt (keys %$oldconf) {
2247 my $value = $oldconf->{$opt};
2249 # do not copy snapshot related info
2250 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2251 $opt eq 'vmstate' || $opt eq 'snapstate';
2253 # always change MAC! address
2254 if ($opt =~ m/^net(\d+)$/) {
2255 my $net = PVE
::QemuServer
::parse_net
($value);
2256 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2257 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2258 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
2259 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2260 die "unable to parse drive options for '$opt'\n" if !$drive;
2261 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2262 $newconf->{$opt} = $value; # simply copy configuration
2264 if ($param->{full
}) {
2265 die "Full clone feature is not available"
2266 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2269 # not full means clone instead of copy
2270 die "Linked clone feature is not available"
2271 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2273 $drives->{$opt} = $drive;
2274 push @$vollist, $drive->{file
};
2277 # copy everything else
2278 $newconf->{$opt} = $value;
2282 # auto generate a new uuid
2283 my ($uuid, $uuid_str);
2284 UUID
::generate
($uuid);
2285 UUID
::unparse
($uuid, $uuid_str);
2286 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2287 $smbios1->{uuid
} = $uuid_str;
2288 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2290 delete $newconf->{template
};
2292 if ($param->{name
}) {
2293 $newconf->{name
} = $param->{name
};
2295 if ($oldconf->{name
}) {
2296 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2298 $newconf->{name
} = "Copy-of-VM-$vmid";
2302 if ($param->{description
}) {
2303 $newconf->{description
} = $param->{description
};
2306 # create empty/temp config - this fails if VM already exists on other node
2307 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2312 my $newvollist = [];
2315 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2317 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2319 foreach my $opt (keys %$drives) {
2320 my $drive = $drives->{$opt};
2322 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2323 $newid, $storage, $format, $drive->{full
}, $newvollist);
2325 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2327 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2330 delete $newconf->{lock};
2331 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2334 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2335 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist);
2337 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2338 die "Failed to move config to node '$target' - rename failed: $!\n"
2339 if !rename($conffile, $newconffile);
2342 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2347 sleep 1; # some storage like rbd need to wait before release volume - really?
2349 foreach my $volid (@$newvollist) {
2350 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2353 die "clone failed: $err";
2359 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2362 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2363 # Aquire exclusive lock lock for $newid
2364 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2369 __PACKAGE__-
>register_method({
2370 name
=> 'move_vm_disk',
2371 path
=> '{vmid}/move_disk',
2375 description
=> "Move volume to different storage.",
2377 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2378 "and 'Datastore.AllocateSpace' permissions on the storage.",
2381 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2382 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2386 additionalProperties
=> 0,
2388 node
=> get_standard_option
('pve-node'),
2389 vmid
=> get_standard_option
('pve-vmid'),
2392 description
=> "The disk you want to move.",
2393 enum
=> [ PVE
::QemuServer
::disknames
() ],
2395 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2398 description
=> "Target Format.",
2399 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2404 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2410 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2418 description
=> "the task ID.",
2423 my $rpcenv = PVE
::RPCEnvironment
::get
();
2425 my $authuser = $rpcenv->get_user();
2427 my $node = extract_param
($param, 'node');
2429 my $vmid = extract_param
($param, 'vmid');
2431 my $digest = extract_param
($param, 'digest');
2433 my $disk = extract_param
($param, 'disk');
2435 my $storeid = extract_param
($param, 'storage');
2437 my $format = extract_param
($param, 'format');
2439 my $storecfg = PVE
::Storage
::config
();
2441 my $updatefn = sub {
2443 my $conf = PVE
::QemuServer
::load_config
($vmid);
2445 die "checksum missmatch (file change by other user?)\n"
2446 if $digest && $digest ne $conf->{digest
};
2448 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2450 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2452 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2454 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2457 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2458 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2462 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2463 (!$format || !$oldfmt || $oldfmt eq $format);
2465 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2467 my $running = PVE
::QemuServer
::check_running
($vmid);
2469 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2473 my $newvollist = [];
2476 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2478 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2479 $vmid, $storeid, $format, 1, $newvollist);
2481 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2483 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2485 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2488 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2489 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2496 foreach my $volid (@$newvollist) {
2497 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2500 die "storage migration failed: $err";
2503 if ($param->{delete}) {
2504 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, 1);
2505 my $path = PVE
::Storage
::path
($storecfg, $old_volid);
2506 if ($used_paths->{$path}){
2507 warn "volume $old_volid have snapshots. Can't delete it\n";
2508 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid);
2509 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2511 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2517 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2520 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2523 __PACKAGE__-
>register_method({
2524 name
=> 'migrate_vm',
2525 path
=> '{vmid}/migrate',
2529 description
=> "Migrate virtual machine. Creates a new migration task.",
2531 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2534 additionalProperties
=> 0,
2536 node
=> get_standard_option
('pve-node'),
2537 vmid
=> get_standard_option
('pve-vmid'),
2538 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2541 description
=> "Use online/live migration.",
2546 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2553 description
=> "the task ID.",
2558 my $rpcenv = PVE
::RPCEnvironment
::get
();
2560 my $authuser = $rpcenv->get_user();
2562 my $target = extract_param
($param, 'target');
2564 my $localnode = PVE
::INotify
::nodename
();
2565 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2567 PVE
::Cluster
::check_cfs_quorum
();
2569 PVE
::Cluster
::check_node_exists
($target);
2571 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2573 my $vmid = extract_param
($param, 'vmid');
2575 raise_param_exc
({ force
=> "Only root may use this option." })
2576 if $param->{force
} && $authuser ne 'root@pam';
2579 my $conf = PVE
::QemuServer
::load_config
($vmid);
2581 # try to detect errors early
2583 PVE
::QemuServer
::check_lock
($conf);
2585 if (PVE
::QemuServer
::check_running
($vmid)) {
2586 die "cant migrate running VM without --online\n"
2587 if !$param->{online
};
2590 my $storecfg = PVE
::Storage
::config
();
2591 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2593 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2598 my $service = "pvevm:$vmid";
2600 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2602 print "Executing HA migrate for VM $vmid to node $target\n";
2604 PVE
::Tools
::run_command
($cmd);
2609 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2616 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2619 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2624 __PACKAGE__-
>register_method({
2626 path
=> '{vmid}/monitor',
2630 description
=> "Execute Qemu monitor commands.",
2632 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2635 additionalProperties
=> 0,
2637 node
=> get_standard_option
('pve-node'),
2638 vmid
=> get_standard_option
('pve-vmid'),
2641 description
=> "The monitor command.",
2645 returns
=> { type
=> 'string'},
2649 my $vmid = $param->{vmid
};
2651 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2655 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2657 $res = "ERROR: $@" if $@;
2662 __PACKAGE__-
>register_method({
2663 name
=> 'resize_vm',
2664 path
=> '{vmid}/resize',
2668 description
=> "Extend volume size.",
2670 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2673 additionalProperties
=> 0,
2675 node
=> get_standard_option
('pve-node'),
2676 vmid
=> get_standard_option
('pve-vmid'),
2677 skiplock
=> get_standard_option
('skiplock'),
2680 description
=> "The disk you want to resize.",
2681 enum
=> [PVE
::QemuServer
::disknames
()],
2685 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2686 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.",
2690 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2696 returns
=> { type
=> 'null'},
2700 my $rpcenv = PVE
::RPCEnvironment
::get
();
2702 my $authuser = $rpcenv->get_user();
2704 my $node = extract_param
($param, 'node');
2706 my $vmid = extract_param
($param, 'vmid');
2708 my $digest = extract_param
($param, 'digest');
2710 my $disk = extract_param
($param, 'disk');
2712 my $sizestr = extract_param
($param, 'size');
2714 my $skiplock = extract_param
($param, 'skiplock');
2715 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2716 if $skiplock && $authuser ne 'root@pam';
2718 my $storecfg = PVE
::Storage
::config
();
2720 my $updatefn = sub {
2722 my $conf = PVE
::QemuServer
::load_config
($vmid);
2724 die "checksum missmatch (file change by other user?)\n"
2725 if $digest && $digest ne $conf->{digest
};
2726 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2728 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2730 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2732 my $volid = $drive->{file
};
2734 die "disk '$disk' has no associated volume\n" if !$volid;
2736 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2738 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2740 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2742 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2744 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2745 my ($ext, $newsize, $unit) = ($1, $2, $4);
2748 $newsize = $newsize * 1024;
2749 } elsif ($unit eq 'M') {
2750 $newsize = $newsize * 1024 * 1024;
2751 } elsif ($unit eq 'G') {
2752 $newsize = $newsize * 1024 * 1024 * 1024;
2753 } elsif ($unit eq 'T') {
2754 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2757 $newsize += $size if $ext;
2758 $newsize = int($newsize);
2760 die "unable to skrink disk size\n" if $newsize < $size;
2762 return if $size == $newsize;
2764 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2766 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2768 $drive->{size
} = $newsize;
2769 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2771 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2774 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2778 __PACKAGE__-
>register_method({
2779 name
=> 'snapshot_list',
2780 path
=> '{vmid}/snapshot',
2782 description
=> "List all snapshots.",
2784 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2787 protected
=> 1, # qemu pid files are only readable by root
2789 additionalProperties
=> 0,
2791 vmid
=> get_standard_option
('pve-vmid'),
2792 node
=> get_standard_option
('pve-node'),
2801 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2806 my $vmid = $param->{vmid
};
2808 my $conf = PVE
::QemuServer
::load_config
($vmid);
2809 my $snaphash = $conf->{snapshots
} || {};
2813 foreach my $name (keys %$snaphash) {
2814 my $d = $snaphash->{$name};
2817 snaptime
=> $d->{snaptime
} || 0,
2818 vmstate
=> $d->{vmstate
} ?
1 : 0,
2819 description
=> $d->{description
} || '',
2821 $item->{parent
} = $d->{parent
} if $d->{parent
};
2822 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2826 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2827 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2828 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2830 push @$res, $current;
2835 __PACKAGE__-
>register_method({
2837 path
=> '{vmid}/snapshot',
2841 description
=> "Snapshot a VM.",
2843 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2846 additionalProperties
=> 0,
2848 node
=> get_standard_option
('pve-node'),
2849 vmid
=> get_standard_option
('pve-vmid'),
2850 snapname
=> get_standard_option
('pve-snapshot-name'),
2854 description
=> "Save the vmstate",
2859 description
=> "Freeze the filesystem",
2864 description
=> "A textual description or comment.",
2870 description
=> "the task ID.",
2875 my $rpcenv = PVE
::RPCEnvironment
::get
();
2877 my $authuser = $rpcenv->get_user();
2879 my $node = extract_param
($param, 'node');
2881 my $vmid = extract_param
($param, 'vmid');
2883 my $snapname = extract_param
($param, 'snapname');
2885 die "unable to use snapshot name 'current' (reserved name)\n"
2886 if $snapname eq 'current';
2889 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2890 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2891 $param->{freezefs
}, $param->{description
});
2894 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2897 __PACKAGE__-
>register_method({
2898 name
=> 'snapshot_cmd_idx',
2899 path
=> '{vmid}/snapshot/{snapname}',
2906 additionalProperties
=> 0,
2908 vmid
=> get_standard_option
('pve-vmid'),
2909 node
=> get_standard_option
('pve-node'),
2910 snapname
=> get_standard_option
('pve-snapshot-name'),
2919 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2926 push @$res, { cmd
=> 'rollback' };
2927 push @$res, { cmd
=> 'config' };
2932 __PACKAGE__-
>register_method({
2933 name
=> 'update_snapshot_config',
2934 path
=> '{vmid}/snapshot/{snapname}/config',
2938 description
=> "Update snapshot metadata.",
2940 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2943 additionalProperties
=> 0,
2945 node
=> get_standard_option
('pve-node'),
2946 vmid
=> get_standard_option
('pve-vmid'),
2947 snapname
=> get_standard_option
('pve-snapshot-name'),
2951 description
=> "A textual description or comment.",
2955 returns
=> { type
=> 'null' },
2959 my $rpcenv = PVE
::RPCEnvironment
::get
();
2961 my $authuser = $rpcenv->get_user();
2963 my $vmid = extract_param
($param, 'vmid');
2965 my $snapname = extract_param
($param, 'snapname');
2967 return undef if !defined($param->{description
});
2969 my $updatefn = sub {
2971 my $conf = PVE
::QemuServer
::load_config
($vmid);
2973 PVE
::QemuServer
::check_lock
($conf);
2975 my $snap = $conf->{snapshots
}->{$snapname};
2977 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2979 $snap->{description
} = $param->{description
} if defined($param->{description
});
2981 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2984 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2989 __PACKAGE__-
>register_method({
2990 name
=> 'get_snapshot_config',
2991 path
=> '{vmid}/snapshot/{snapname}/config',
2994 description
=> "Get snapshot configuration",
2996 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2999 additionalProperties
=> 0,
3001 node
=> get_standard_option
('pve-node'),
3002 vmid
=> get_standard_option
('pve-vmid'),
3003 snapname
=> get_standard_option
('pve-snapshot-name'),
3006 returns
=> { type
=> "object" },
3010 my $rpcenv = PVE
::RPCEnvironment
::get
();
3012 my $authuser = $rpcenv->get_user();
3014 my $vmid = extract_param
($param, 'vmid');
3016 my $snapname = extract_param
($param, 'snapname');
3018 my $conf = PVE
::QemuServer
::load_config
($vmid);
3020 my $snap = $conf->{snapshots
}->{$snapname};
3022 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3027 __PACKAGE__-
>register_method({
3029 path
=> '{vmid}/snapshot/{snapname}/rollback',
3033 description
=> "Rollback VM state to specified snapshot.",
3035 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3038 additionalProperties
=> 0,
3040 node
=> get_standard_option
('pve-node'),
3041 vmid
=> get_standard_option
('pve-vmid'),
3042 snapname
=> get_standard_option
('pve-snapshot-name'),
3047 description
=> "the task ID.",
3052 my $rpcenv = PVE
::RPCEnvironment
::get
();
3054 my $authuser = $rpcenv->get_user();
3056 my $node = extract_param
($param, 'node');
3058 my $vmid = extract_param
($param, 'vmid');
3060 my $snapname = extract_param
($param, 'snapname');
3063 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3064 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
3067 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3070 __PACKAGE__-
>register_method({
3071 name
=> 'delsnapshot',
3072 path
=> '{vmid}/snapshot/{snapname}',
3076 description
=> "Delete a VM snapshot.",
3078 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3081 additionalProperties
=> 0,
3083 node
=> get_standard_option
('pve-node'),
3084 vmid
=> get_standard_option
('pve-vmid'),
3085 snapname
=> get_standard_option
('pve-snapshot-name'),
3089 description
=> "For removal from config file, even if removing disk snapshots fails.",
3095 description
=> "the task ID.",
3100 my $rpcenv = PVE
::RPCEnvironment
::get
();
3102 my $authuser = $rpcenv->get_user();
3104 my $node = extract_param
($param, 'node');
3106 my $vmid = extract_param
($param, 'vmid');
3108 my $snapname = extract_param
($param, 'snapname');
3111 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3112 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3115 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3118 __PACKAGE__-
>register_method({
3120 path
=> '{vmid}/template',
3124 description
=> "Create a Template.",
3126 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3127 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3130 additionalProperties
=> 0,
3132 node
=> get_standard_option
('pve-node'),
3133 vmid
=> get_standard_option
('pve-vmid'),
3137 description
=> "If you want to convert only 1 disk to base image.",
3138 enum
=> [PVE
::QemuServer
::disknames
()],
3143 returns
=> { type
=> 'null'},
3147 my $rpcenv = PVE
::RPCEnvironment
::get
();
3149 my $authuser = $rpcenv->get_user();
3151 my $node = extract_param
($param, 'node');
3153 my $vmid = extract_param
($param, 'vmid');
3155 my $disk = extract_param
($param, 'disk');
3157 my $updatefn = sub {
3159 my $conf = PVE
::QemuServer
::load_config
($vmid);
3161 PVE
::QemuServer
::check_lock
($conf);
3163 die "unable to create template, because VM contains snapshots\n"
3164 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3166 die "you can't convert a template to a template\n"
3167 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3169 die "you can't convert a VM to template if VM is running\n"
3170 if PVE
::QemuServer
::check_running
($vmid);
3173 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3176 $conf->{template
} = 1;
3177 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3179 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3182 PVE
::QemuServer
::lock_config
($vmid, $updatefn);