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') {
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', {
2134 storage
=> get_standard_option
('pve-storage-id', {
2135 description
=> "Target storage for full clone.",
2140 description
=> "Target format for file storage.",
2144 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2149 description
=> "Create a full copy of all disk. This is always done when " .
2150 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2153 target
=> get_standard_option
('pve-node', {
2154 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2165 my $rpcenv = PVE
::RPCEnvironment
::get
();
2167 my $authuser = $rpcenv->get_user();
2169 my $node = extract_param
($param, 'node');
2171 my $vmid = extract_param
($param, 'vmid');
2173 my $newid = extract_param
($param, 'newid');
2175 my $pool = extract_param
($param, 'pool');
2177 if (defined($pool)) {
2178 $rpcenv->check_pool_exist($pool);
2181 my $snapname = extract_param
($param, 'snapname');
2183 my $storage = extract_param
($param, 'storage');
2185 my $format = extract_param
($param, 'format');
2187 my $target = extract_param
($param, 'target');
2189 my $localnode = PVE
::INotify
::nodename
();
2191 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2193 PVE
::Cluster
::check_node_exists
($target) if $target;
2195 my $storecfg = PVE
::Storage
::config
();
2198 # check if storage is enabled on local node
2199 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2201 # check if storage is available on target node
2202 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2203 # clone only works if target storage is shared
2204 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2205 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2209 PVE
::Cluster
::check_cfs_quorum
();
2211 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2213 # exclusive lock if VM is running - else shared lock is enough;
2214 my $shared_lock = $running ?
0 : 1;
2218 # do all tests after lock
2219 # we also try to do all tests before we fork the worker
2221 my $conf = PVE
::QemuServer
::load_config
($vmid);
2223 PVE
::QemuServer
::check_lock
($conf);
2225 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2227 die "unexpected state change\n" if $verify_running != $running;
2229 die "snapshot '$snapname' does not exist\n"
2230 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2232 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2234 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2236 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2238 my $conffile = PVE
::QemuServer
::config_file
($newid);
2240 die "unable to create VM $newid: config file already exists\n"
2243 my $newconf = { lock => 'clone' };
2247 foreach my $opt (keys %$oldconf) {
2248 my $value = $oldconf->{$opt};
2250 # do not copy snapshot related info
2251 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2252 $opt eq 'vmstate' || $opt eq 'snapstate';
2254 # always change MAC! address
2255 if ($opt =~ m/^net(\d+)$/) {
2256 my $net = PVE
::QemuServer
::parse_net
($value);
2257 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2258 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2259 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
2260 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2261 die "unable to parse drive options for '$opt'\n" if !$drive;
2262 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2263 $newconf->{$opt} = $value; # simply copy configuration
2265 if ($param->{full
} || !PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
2266 die "Full clone feature is not available"
2267 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2270 $drives->{$opt} = $drive;
2271 push @$vollist, $drive->{file
};
2274 # copy everything else
2275 $newconf->{$opt} = $value;
2279 delete $newconf->{template
};
2281 if ($param->{name
}) {
2282 $newconf->{name
} = $param->{name
};
2284 if ($oldconf->{name
}) {
2285 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2287 $newconf->{name
} = "Copy-of-VM-$vmid";
2291 if ($param->{description
}) {
2292 $newconf->{description
} = $param->{description
};
2295 # create empty/temp config - this fails if VM already exists on other node
2296 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2301 my $newvollist = [];
2304 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2306 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2308 foreach my $opt (keys %$drives) {
2309 my $drive = $drives->{$opt};
2311 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2312 $newid, $storage, $format, $drive->{full
}, $newvollist);
2314 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2316 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2319 delete $newconf->{lock};
2320 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2323 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2324 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist);
2326 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2327 die "Failed to move config to node '$target' - rename failed: $!\n"
2328 if !rename($conffile, $newconffile);
2331 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2336 sleep 1; # some storage like rbd need to wait before release volume - really?
2338 foreach my $volid (@$newvollist) {
2339 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2342 die "clone failed: $err";
2348 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2351 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2352 # Aquire exclusive lock lock for $newid
2353 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2358 __PACKAGE__-
>register_method({
2359 name
=> 'move_vm_disk',
2360 path
=> '{vmid}/move_disk',
2364 description
=> "Move volume to different storage.",
2366 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2367 "and 'Datastore.AllocateSpace' permissions on the storage.",
2370 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2371 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2375 additionalProperties
=> 0,
2377 node
=> get_standard_option
('pve-node'),
2378 vmid
=> get_standard_option
('pve-vmid'),
2381 description
=> "The disk you want to move.",
2382 enum
=> [ PVE
::QemuServer
::disknames
() ],
2384 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2387 description
=> "Target Format.",
2388 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2393 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2399 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2407 description
=> "the task ID.",
2412 my $rpcenv = PVE
::RPCEnvironment
::get
();
2414 my $authuser = $rpcenv->get_user();
2416 my $node = extract_param
($param, 'node');
2418 my $vmid = extract_param
($param, 'vmid');
2420 my $digest = extract_param
($param, 'digest');
2422 my $disk = extract_param
($param, 'disk');
2424 my $storeid = extract_param
($param, 'storage');
2426 my $format = extract_param
($param, 'format');
2428 my $storecfg = PVE
::Storage
::config
();
2430 my $updatefn = sub {
2432 my $conf = PVE
::QemuServer
::load_config
($vmid);
2434 die "checksum missmatch (file change by other user?)\n"
2435 if $digest && $digest ne $conf->{digest
};
2437 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2439 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2441 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2443 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2446 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2447 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2451 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2452 (!$format || !$oldfmt || $oldfmt eq $format);
2454 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2456 my $running = PVE
::QemuServer
::check_running
($vmid);
2458 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2462 my $newvollist = [];
2465 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2467 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2468 $vmid, $storeid, $format, 1, $newvollist);
2470 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2472 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2474 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2477 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2478 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2485 foreach my $volid (@$newvollist) {
2486 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2489 die "storage migration failed: $err";
2492 if ($param->{delete}) {
2493 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, 1);
2494 my $path = PVE
::Storage
::path
($storecfg, $old_volid);
2495 if ($used_paths->{$path}){
2496 warn "volume $old_volid have snapshots. Can't delete it\n";
2497 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid);
2498 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2500 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2506 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2509 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2512 __PACKAGE__-
>register_method({
2513 name
=> 'migrate_vm',
2514 path
=> '{vmid}/migrate',
2518 description
=> "Migrate virtual machine. Creates a new migration task.",
2520 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2523 additionalProperties
=> 0,
2525 node
=> get_standard_option
('pve-node'),
2526 vmid
=> get_standard_option
('pve-vmid'),
2527 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2530 description
=> "Use online/live migration.",
2535 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2542 description
=> "the task ID.",
2547 my $rpcenv = PVE
::RPCEnvironment
::get
();
2549 my $authuser = $rpcenv->get_user();
2551 my $target = extract_param
($param, 'target');
2553 my $localnode = PVE
::INotify
::nodename
();
2554 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2556 PVE
::Cluster
::check_cfs_quorum
();
2558 PVE
::Cluster
::check_node_exists
($target);
2560 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2562 my $vmid = extract_param
($param, 'vmid');
2564 raise_param_exc
({ force
=> "Only root may use this option." })
2565 if $param->{force
} && $authuser ne 'root@pam';
2568 my $conf = PVE
::QemuServer
::load_config
($vmid);
2570 # try to detect errors early
2572 PVE
::QemuServer
::check_lock
($conf);
2574 if (PVE
::QemuServer
::check_running
($vmid)) {
2575 die "cant migrate running VM without --online\n"
2576 if !$param->{online
};
2579 my $storecfg = PVE
::Storage
::config
();
2580 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2582 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2587 my $service = "pvevm:$vmid";
2589 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2591 print "Executing HA migrate for VM $vmid to node $target\n";
2593 PVE
::Tools
::run_command
($cmd);
2598 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2605 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2608 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2613 __PACKAGE__-
>register_method({
2615 path
=> '{vmid}/monitor',
2619 description
=> "Execute Qemu monitor commands.",
2621 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2624 additionalProperties
=> 0,
2626 node
=> get_standard_option
('pve-node'),
2627 vmid
=> get_standard_option
('pve-vmid'),
2630 description
=> "The monitor command.",
2634 returns
=> { type
=> 'string'},
2638 my $vmid = $param->{vmid
};
2640 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2644 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2646 $res = "ERROR: $@" if $@;
2651 __PACKAGE__-
>register_method({
2652 name
=> 'resize_vm',
2653 path
=> '{vmid}/resize',
2657 description
=> "Extend volume size.",
2659 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2662 additionalProperties
=> 0,
2664 node
=> get_standard_option
('pve-node'),
2665 vmid
=> get_standard_option
('pve-vmid'),
2666 skiplock
=> get_standard_option
('skiplock'),
2669 description
=> "The disk you want to resize.",
2670 enum
=> [PVE
::QemuServer
::disknames
()],
2674 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2675 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.",
2679 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2685 returns
=> { type
=> 'null'},
2689 my $rpcenv = PVE
::RPCEnvironment
::get
();
2691 my $authuser = $rpcenv->get_user();
2693 my $node = extract_param
($param, 'node');
2695 my $vmid = extract_param
($param, 'vmid');
2697 my $digest = extract_param
($param, 'digest');
2699 my $disk = extract_param
($param, 'disk');
2701 my $sizestr = extract_param
($param, 'size');
2703 my $skiplock = extract_param
($param, 'skiplock');
2704 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2705 if $skiplock && $authuser ne 'root@pam';
2707 my $storecfg = PVE
::Storage
::config
();
2709 my $updatefn = sub {
2711 my $conf = PVE
::QemuServer
::load_config
($vmid);
2713 die "checksum missmatch (file change by other user?)\n"
2714 if $digest && $digest ne $conf->{digest
};
2715 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2717 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2719 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2721 my $volid = $drive->{file
};
2723 die "disk '$disk' has no associated volume\n" if !$volid;
2725 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2727 die "you can't online resize a virtio windows bootdisk\n"
2728 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2730 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2732 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2734 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2736 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2737 my ($ext, $newsize, $unit) = ($1, $2, $4);
2740 $newsize = $newsize * 1024;
2741 } elsif ($unit eq 'M') {
2742 $newsize = $newsize * 1024 * 1024;
2743 } elsif ($unit eq 'G') {
2744 $newsize = $newsize * 1024 * 1024 * 1024;
2745 } elsif ($unit eq 'T') {
2746 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2749 $newsize += $size if $ext;
2750 $newsize = int($newsize);
2752 die "unable to skrink disk size\n" if $newsize < $size;
2754 return if $size == $newsize;
2756 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2758 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2760 $drive->{size
} = $newsize;
2761 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2763 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2766 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2770 __PACKAGE__-
>register_method({
2771 name
=> 'snapshot_list',
2772 path
=> '{vmid}/snapshot',
2774 description
=> "List all snapshots.",
2776 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2779 protected
=> 1, # qemu pid files are only readable by root
2781 additionalProperties
=> 0,
2783 vmid
=> get_standard_option
('pve-vmid'),
2784 node
=> get_standard_option
('pve-node'),
2793 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2798 my $vmid = $param->{vmid
};
2800 my $conf = PVE
::QemuServer
::load_config
($vmid);
2801 my $snaphash = $conf->{snapshots
} || {};
2805 foreach my $name (keys %$snaphash) {
2806 my $d = $snaphash->{$name};
2809 snaptime
=> $d->{snaptime
} || 0,
2810 vmstate
=> $d->{vmstate
} ?
1 : 0,
2811 description
=> $d->{description
} || '',
2813 $item->{parent
} = $d->{parent
} if $d->{parent
};
2814 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2818 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2819 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2820 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2822 push @$res, $current;
2827 __PACKAGE__-
>register_method({
2829 path
=> '{vmid}/snapshot',
2833 description
=> "Snapshot a VM.",
2835 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2838 additionalProperties
=> 0,
2840 node
=> get_standard_option
('pve-node'),
2841 vmid
=> get_standard_option
('pve-vmid'),
2842 snapname
=> get_standard_option
('pve-snapshot-name'),
2846 description
=> "Save the vmstate",
2851 description
=> "Freeze the filesystem",
2856 description
=> "A textual description or comment.",
2862 description
=> "the task ID.",
2867 my $rpcenv = PVE
::RPCEnvironment
::get
();
2869 my $authuser = $rpcenv->get_user();
2871 my $node = extract_param
($param, 'node');
2873 my $vmid = extract_param
($param, 'vmid');
2875 my $snapname = extract_param
($param, 'snapname');
2877 die "unable to use snapshot name 'current' (reserved name)\n"
2878 if $snapname eq 'current';
2881 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2882 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2883 $param->{freezefs
}, $param->{description
});
2886 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2889 __PACKAGE__-
>register_method({
2890 name
=> 'snapshot_cmd_idx',
2891 path
=> '{vmid}/snapshot/{snapname}',
2898 additionalProperties
=> 0,
2900 vmid
=> get_standard_option
('pve-vmid'),
2901 node
=> get_standard_option
('pve-node'),
2902 snapname
=> get_standard_option
('pve-snapshot-name'),
2911 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2918 push @$res, { cmd
=> 'rollback' };
2919 push @$res, { cmd
=> 'config' };
2924 __PACKAGE__-
>register_method({
2925 name
=> 'update_snapshot_config',
2926 path
=> '{vmid}/snapshot/{snapname}/config',
2930 description
=> "Update snapshot metadata.",
2932 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2935 additionalProperties
=> 0,
2937 node
=> get_standard_option
('pve-node'),
2938 vmid
=> get_standard_option
('pve-vmid'),
2939 snapname
=> get_standard_option
('pve-snapshot-name'),
2943 description
=> "A textual description or comment.",
2947 returns
=> { type
=> 'null' },
2951 my $rpcenv = PVE
::RPCEnvironment
::get
();
2953 my $authuser = $rpcenv->get_user();
2955 my $vmid = extract_param
($param, 'vmid');
2957 my $snapname = extract_param
($param, 'snapname');
2959 return undef if !defined($param->{description
});
2961 my $updatefn = sub {
2963 my $conf = PVE
::QemuServer
::load_config
($vmid);
2965 PVE
::QemuServer
::check_lock
($conf);
2967 my $snap = $conf->{snapshots
}->{$snapname};
2969 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2971 $snap->{description
} = $param->{description
} if defined($param->{description
});
2973 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2976 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2981 __PACKAGE__-
>register_method({
2982 name
=> 'get_snapshot_config',
2983 path
=> '{vmid}/snapshot/{snapname}/config',
2986 description
=> "Get snapshot configuration",
2988 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2991 additionalProperties
=> 0,
2993 node
=> get_standard_option
('pve-node'),
2994 vmid
=> get_standard_option
('pve-vmid'),
2995 snapname
=> get_standard_option
('pve-snapshot-name'),
2998 returns
=> { type
=> "object" },
3002 my $rpcenv = PVE
::RPCEnvironment
::get
();
3004 my $authuser = $rpcenv->get_user();
3006 my $vmid = extract_param
($param, 'vmid');
3008 my $snapname = extract_param
($param, 'snapname');
3010 my $conf = PVE
::QemuServer
::load_config
($vmid);
3012 my $snap = $conf->{snapshots
}->{$snapname};
3014 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3019 __PACKAGE__-
>register_method({
3021 path
=> '{vmid}/snapshot/{snapname}/rollback',
3025 description
=> "Rollback VM state to specified snapshot.",
3027 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3030 additionalProperties
=> 0,
3032 node
=> get_standard_option
('pve-node'),
3033 vmid
=> get_standard_option
('pve-vmid'),
3034 snapname
=> get_standard_option
('pve-snapshot-name'),
3039 description
=> "the task ID.",
3044 my $rpcenv = PVE
::RPCEnvironment
::get
();
3046 my $authuser = $rpcenv->get_user();
3048 my $node = extract_param
($param, 'node');
3050 my $vmid = extract_param
($param, 'vmid');
3052 my $snapname = extract_param
($param, 'snapname');
3055 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3056 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
3059 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3062 __PACKAGE__-
>register_method({
3063 name
=> 'delsnapshot',
3064 path
=> '{vmid}/snapshot/{snapname}',
3068 description
=> "Delete a VM 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'),
3081 description
=> "For removal from config file, even if removing disk snapshots fails.",
3087 description
=> "the task ID.",
3092 my $rpcenv = PVE
::RPCEnvironment
::get
();
3094 my $authuser = $rpcenv->get_user();
3096 my $node = extract_param
($param, 'node');
3098 my $vmid = extract_param
($param, 'vmid');
3100 my $snapname = extract_param
($param, 'snapname');
3103 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3104 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3107 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3110 __PACKAGE__-
>register_method({
3112 path
=> '{vmid}/template',
3116 description
=> "Create a Template.",
3118 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3119 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3122 additionalProperties
=> 0,
3124 node
=> get_standard_option
('pve-node'),
3125 vmid
=> get_standard_option
('pve-vmid'),
3129 description
=> "If you want to convert only 1 disk to base image.",
3130 enum
=> [PVE
::QemuServer
::disknames
()],
3135 returns
=> { type
=> 'null'},
3139 my $rpcenv = PVE
::RPCEnvironment
::get
();
3141 my $authuser = $rpcenv->get_user();
3143 my $node = extract_param
($param, 'node');
3145 my $vmid = extract_param
($param, 'vmid');
3147 my $disk = extract_param
($param, 'disk');
3149 my $updatefn = sub {
3151 my $conf = PVE
::QemuServer
::load_config
($vmid);
3153 PVE
::QemuServer
::check_lock
($conf);
3155 die "unable to create template, because VM contains snapshots\n"
3156 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3158 die "you can't convert a template to a template\n"
3159 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3161 die "you can't convert a VM to template if VM is running\n"
3162 if PVE
::QemuServer
::check_running
($vmid);
3165 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3168 $conf->{template
} = 1;
3169 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3171 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3174 PVE
::QemuServer
::lock_config
($vmid, $updatefn);