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 delete $newconf->{template
};
2284 if ($param->{name
}) {
2285 $newconf->{name
} = $param->{name
};
2287 if ($oldconf->{name
}) {
2288 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2290 $newconf->{name
} = "Copy-of-VM-$vmid";
2294 if ($param->{description
}) {
2295 $newconf->{description
} = $param->{description
};
2298 # create empty/temp config - this fails if VM already exists on other node
2299 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2304 my $newvollist = [];
2307 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2309 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2311 foreach my $opt (keys %$drives) {
2312 my $drive = $drives->{$opt};
2314 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2315 $newid, $storage, $format, $drive->{full
}, $newvollist);
2317 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2319 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2322 delete $newconf->{lock};
2323 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2326 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2327 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist);
2329 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2330 die "Failed to move config to node '$target' - rename failed: $!\n"
2331 if !rename($conffile, $newconffile);
2334 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2339 sleep 1; # some storage like rbd need to wait before release volume - really?
2341 foreach my $volid (@$newvollist) {
2342 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2345 die "clone failed: $err";
2351 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2354 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2355 # Aquire exclusive lock lock for $newid
2356 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2361 __PACKAGE__-
>register_method({
2362 name
=> 'move_vm_disk',
2363 path
=> '{vmid}/move_disk',
2367 description
=> "Move volume to different storage.",
2369 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2370 "and 'Datastore.AllocateSpace' permissions on the storage.",
2373 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2374 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2378 additionalProperties
=> 0,
2380 node
=> get_standard_option
('pve-node'),
2381 vmid
=> get_standard_option
('pve-vmid'),
2384 description
=> "The disk you want to move.",
2385 enum
=> [ PVE
::QemuServer
::disknames
() ],
2387 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2390 description
=> "Target Format.",
2391 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2396 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2402 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2410 description
=> "the task ID.",
2415 my $rpcenv = PVE
::RPCEnvironment
::get
();
2417 my $authuser = $rpcenv->get_user();
2419 my $node = extract_param
($param, 'node');
2421 my $vmid = extract_param
($param, 'vmid');
2423 my $digest = extract_param
($param, 'digest');
2425 my $disk = extract_param
($param, 'disk');
2427 my $storeid = extract_param
($param, 'storage');
2429 my $format = extract_param
($param, 'format');
2431 my $storecfg = PVE
::Storage
::config
();
2433 my $updatefn = sub {
2435 my $conf = PVE
::QemuServer
::load_config
($vmid);
2437 die "checksum missmatch (file change by other user?)\n"
2438 if $digest && $digest ne $conf->{digest
};
2440 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2442 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2444 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2446 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2449 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2450 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2454 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2455 (!$format || !$oldfmt || $oldfmt eq $format);
2457 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2459 my $running = PVE
::QemuServer
::check_running
($vmid);
2461 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2465 my $newvollist = [];
2468 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2470 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2471 $vmid, $storeid, $format, 1, $newvollist);
2473 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2475 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2477 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2480 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2481 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2488 foreach my $volid (@$newvollist) {
2489 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2492 die "storage migration failed: $err";
2495 if ($param->{delete}) {
2496 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, 1);
2497 my $path = PVE
::Storage
::path
($storecfg, $old_volid);
2498 if ($used_paths->{$path}){
2499 warn "volume $old_volid have snapshots. Can't delete it\n";
2500 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid);
2501 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2503 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2509 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2512 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2515 __PACKAGE__-
>register_method({
2516 name
=> 'migrate_vm',
2517 path
=> '{vmid}/migrate',
2521 description
=> "Migrate virtual machine. Creates a new migration task.",
2523 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2526 additionalProperties
=> 0,
2528 node
=> get_standard_option
('pve-node'),
2529 vmid
=> get_standard_option
('pve-vmid'),
2530 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2533 description
=> "Use online/live migration.",
2538 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2545 description
=> "the task ID.",
2550 my $rpcenv = PVE
::RPCEnvironment
::get
();
2552 my $authuser = $rpcenv->get_user();
2554 my $target = extract_param
($param, 'target');
2556 my $localnode = PVE
::INotify
::nodename
();
2557 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2559 PVE
::Cluster
::check_cfs_quorum
();
2561 PVE
::Cluster
::check_node_exists
($target);
2563 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2565 my $vmid = extract_param
($param, 'vmid');
2567 raise_param_exc
({ force
=> "Only root may use this option." })
2568 if $param->{force
} && $authuser ne 'root@pam';
2571 my $conf = PVE
::QemuServer
::load_config
($vmid);
2573 # try to detect errors early
2575 PVE
::QemuServer
::check_lock
($conf);
2577 if (PVE
::QemuServer
::check_running
($vmid)) {
2578 die "cant migrate running VM without --online\n"
2579 if !$param->{online
};
2582 my $storecfg = PVE
::Storage
::config
();
2583 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2585 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2590 my $service = "pvevm:$vmid";
2592 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2594 print "Executing HA migrate for VM $vmid to node $target\n";
2596 PVE
::Tools
::run_command
($cmd);
2601 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2608 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2611 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2616 __PACKAGE__-
>register_method({
2618 path
=> '{vmid}/monitor',
2622 description
=> "Execute Qemu monitor commands.",
2624 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2627 additionalProperties
=> 0,
2629 node
=> get_standard_option
('pve-node'),
2630 vmid
=> get_standard_option
('pve-vmid'),
2633 description
=> "The monitor command.",
2637 returns
=> { type
=> 'string'},
2641 my $vmid = $param->{vmid
};
2643 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2647 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2649 $res = "ERROR: $@" if $@;
2654 __PACKAGE__-
>register_method({
2655 name
=> 'resize_vm',
2656 path
=> '{vmid}/resize',
2660 description
=> "Extend volume size.",
2662 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2665 additionalProperties
=> 0,
2667 node
=> get_standard_option
('pve-node'),
2668 vmid
=> get_standard_option
('pve-vmid'),
2669 skiplock
=> get_standard_option
('skiplock'),
2672 description
=> "The disk you want to resize.",
2673 enum
=> [PVE
::QemuServer
::disknames
()],
2677 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2678 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.",
2682 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2688 returns
=> { type
=> 'null'},
2692 my $rpcenv = PVE
::RPCEnvironment
::get
();
2694 my $authuser = $rpcenv->get_user();
2696 my $node = extract_param
($param, 'node');
2698 my $vmid = extract_param
($param, 'vmid');
2700 my $digest = extract_param
($param, 'digest');
2702 my $disk = extract_param
($param, 'disk');
2704 my $sizestr = extract_param
($param, 'size');
2706 my $skiplock = extract_param
($param, 'skiplock');
2707 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2708 if $skiplock && $authuser ne 'root@pam';
2710 my $storecfg = PVE
::Storage
::config
();
2712 my $updatefn = sub {
2714 my $conf = PVE
::QemuServer
::load_config
($vmid);
2716 die "checksum missmatch (file change by other user?)\n"
2717 if $digest && $digest ne $conf->{digest
};
2718 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2720 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2722 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2724 my $volid = $drive->{file
};
2726 die "disk '$disk' has no associated volume\n" if !$volid;
2728 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
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);