1 package PVE
::API2
::Qemu
;
8 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
10 use PVE
::Tools
qw(extract_param);
11 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
13 use PVE
::JSONSchema
qw(get_standard_option);
17 use PVE
::RPCEnvironment
;
18 use PVE
::AccessControl
;
21 use PVE
::API2
::Firewall
::VM
;
23 use Data
::Dumper
; # fixme: remove
25 use base
qw(PVE::RESTHandler);
27 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.";
29 my $resolve_cdrom_alias = sub {
32 if (my $value = $param->{cdrom
}) {
33 $value .= ",media=cdrom" if $value !~ m/media=/;
34 $param->{ide2
} = $value;
35 delete $param->{cdrom
};
40 my $check_storage_access = sub {
41 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
43 PVE
::QemuServer
::foreach_drive
($settings, sub {
44 my ($ds, $drive) = @_;
46 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
48 my $volid = $drive->{file
};
50 if (!$volid || $volid eq 'none') {
52 } elsif ($isCDROM && ($volid eq 'cdrom')) {
53 $rpcenv->check($authuser, "/", ['Sys.Console']);
54 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
55 my ($storeid, $size) = ($2 || $default_storage, $3);
56 die "no storage ID specified (and no default storage)\n" if !$storeid;
57 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
59 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
64 my $check_storage_access_clone = sub {
65 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
69 PVE
::QemuServer
::foreach_drive
($conf, sub {
70 my ($ds, $drive) = @_;
72 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
74 my $volid = $drive->{file
};
76 return if !$volid || $volid eq 'none';
79 if ($volid eq 'cdrom') {
80 $rpcenv->check($authuser, "/", ['Sys.Console']);
82 # we simply allow access
83 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
84 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
85 $sharedvm = 0 if !$scfg->{shared
};
89 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
90 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
91 $sharedvm = 0 if !$scfg->{shared
};
93 $sid = $storage if $storage;
94 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
101 # Note: $pool is only needed when creating a VM, because pool permissions
102 # are automatically inherited if VM already exists inside a pool.
103 my $create_disks = sub {
104 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
109 PVE
::QemuServer
::foreach_drive
($settings, sub {
110 my ($ds, $disk) = @_;
112 my $volid = $disk->{file
};
114 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
115 delete $disk->{size
};
116 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
117 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
118 my ($storeid, $size) = ($2 || $default_storage, $3);
119 die "no storage ID specified (and no default storage)\n" if !$storeid;
120 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
121 my $fmt = $disk->{format
} || $defformat;
122 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
123 $fmt, undef, $size*1024*1024);
124 $disk->{file
} = $volid;
125 $disk->{size
} = $size*1024*1024*1024;
126 push @$vollist, $volid;
127 delete $disk->{format
}; # no longer needed
128 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
131 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
133 my $volid_is_new = 1;
136 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
137 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
142 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
144 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
146 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
148 die "volume $volid does not exists\n" if !$size;
150 $disk->{size
} = $size;
153 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
157 # free allocated images on error
159 syslog
('err', "VM $vmid creating disks failed");
160 foreach my $volid (@$vollist) {
161 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
167 # modify vm config if everything went well
168 foreach my $ds (keys %$res) {
169 $conf->{$ds} = $res->{$ds};
175 my $check_vm_modify_config_perm = sub {
176 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
178 return 1 if $authuser eq 'root@pam';
180 foreach my $opt (@$key_list) {
181 # disk checks need to be done somewhere else
182 next if PVE
::QemuServer
::valid_drivename
($opt);
184 if ($opt eq 'sockets' || $opt eq 'cores' ||
185 $opt eq 'cpu' || $opt eq 'smp' ||
186 $opt eq 'cpulimit' || $opt eq 'cpuunits') {
187 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
188 } elsif ($opt eq 'boot' || $opt eq 'bootdisk') {
189 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
190 } elsif ($opt eq 'memory' || $opt eq 'balloon' || $opt eq 'shares') {
191 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
192 } elsif ($opt eq 'args' || $opt eq 'lock') {
193 die "only root can set '$opt' config\n";
194 } elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' || $opt eq 'machine' ||
195 $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet') {
196 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
197 } elsif ($opt =~ m/^net\d+$/) {
198 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
200 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
207 __PACKAGE__-
>register_method({
211 description
=> "Virtual machine index (per node).",
213 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
217 protected
=> 1, # qemu pid files are only readable by root
219 additionalProperties
=> 0,
221 node
=> get_standard_option
('pve-node'),
230 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
235 my $rpcenv = PVE
::RPCEnvironment
::get
();
236 my $authuser = $rpcenv->get_user();
238 my $vmstatus = PVE
::QemuServer
::vmstatus
();
241 foreach my $vmid (keys %$vmstatus) {
242 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
244 my $data = $vmstatus->{$vmid};
245 $data->{vmid
} = $vmid;
254 __PACKAGE__-
>register_method({
258 description
=> "Create or restore a virtual machine.",
260 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
261 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
262 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
263 user
=> 'all', # check inside
268 additionalProperties
=> 0,
269 properties
=> PVE
::QemuServer
::json_config_properties
(
271 node
=> get_standard_option
('pve-node'),
272 vmid
=> get_standard_option
('pve-vmid'),
274 description
=> "The backup file.",
279 storage
=> get_standard_option
('pve-storage-id', {
280 description
=> "Default storage.",
286 description
=> "Allow to overwrite existing VM.",
287 requires
=> 'archive',
292 description
=> "Assign a unique random ethernet address.",
293 requires
=> 'archive',
297 type
=> 'string', format
=> 'pve-poolid',
298 description
=> "Add the VM to the specified pool.",
308 my $rpcenv = PVE
::RPCEnvironment
::get
();
310 my $authuser = $rpcenv->get_user();
312 my $node = extract_param
($param, 'node');
314 my $vmid = extract_param
($param, 'vmid');
316 my $archive = extract_param
($param, 'archive');
318 my $storage = extract_param
($param, 'storage');
320 my $force = extract_param
($param, 'force');
322 my $unique = extract_param
($param, 'unique');
324 my $pool = extract_param
($param, 'pool');
326 my $filename = PVE
::QemuServer
::config_file
($vmid);
328 my $storecfg = PVE
::Storage
::config
();
330 PVE
::Cluster
::check_cfs_quorum
();
332 if (defined($pool)) {
333 $rpcenv->check_pool_exist($pool);
336 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
337 if defined($storage);
339 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
341 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
343 } elsif ($archive && $force && (-f
$filename) &&
344 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
345 # OK: user has VM.Backup permissions, and want to restore an existing VM
351 &$resolve_cdrom_alias($param);
353 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
355 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
357 foreach my $opt (keys %$param) {
358 if (PVE
::QemuServer
::valid_drivename
($opt)) {
359 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
360 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
362 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
363 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
367 PVE
::QemuServer
::add_random_macs
($param);
369 my $keystr = join(' ', keys %$param);
370 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
372 if ($archive eq '-') {
373 die "pipe requires cli environment\n"
374 if $rpcenv->{type
} ne 'cli';
376 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
377 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
381 my $restorefn = sub {
383 # fixme: this test does not work if VM exists on other node!
385 die "unable to restore vm $vmid: config file already exists\n"
388 die "unable to restore vm $vmid: vm is running\n"
389 if PVE
::QemuServer
::check_running
($vmid);
393 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
396 unique
=> $unique });
398 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
401 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
407 die "unable to create vm $vmid: config file already exists\n"
418 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
420 # try to be smart about bootdisk
421 my @disks = PVE
::QemuServer
::disknames
();
423 foreach my $ds (reverse @disks) {
424 next if !$conf->{$ds};
425 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
426 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
430 if (!$conf->{bootdisk
} && $firstdisk) {
431 $conf->{bootdisk
} = $firstdisk;
434 PVE
::QemuServer
::update_config_nolock
($vmid, $conf);
440 foreach my $volid (@$vollist) {
441 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
444 die "create failed - $err";
447 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
450 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
453 return PVE
::QemuServer
::lock_config_full
($vmid, 1, $archive ?
$restorefn : $createfn);
456 __PACKAGE__-
>register_method({
461 description
=> "Directory index",
466 additionalProperties
=> 0,
468 node
=> get_standard_option
('pve-node'),
469 vmid
=> get_standard_option
('pve-vmid'),
477 subdir
=> { type
=> 'string' },
480 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
486 { subdir
=> 'config' },
487 { subdir
=> 'status' },
488 { subdir
=> 'unlink' },
489 { subdir
=> 'vncproxy' },
490 { subdir
=> 'migrate' },
491 { subdir
=> 'resize' },
492 { subdir
=> 'move' },
494 { subdir
=> 'rrddata' },
495 { subdir
=> 'monitor' },
496 { subdir
=> 'snapshot' },
497 { subdir
=> 'spiceproxy' },
498 { subdir
=> 'sendkey' },
499 { subdir
=> 'firewall' },
505 __PACKAGE__-
>register_method ({
506 subclass
=> "PVE::API2::Firewall::VM",
507 path
=> '{vmid}/firewall',
510 __PACKAGE__-
>register_method({
512 path
=> '{vmid}/rrd',
514 protected
=> 1, # fixme: can we avoid that?
516 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
518 description
=> "Read VM RRD statistics (returns PNG)",
520 additionalProperties
=> 0,
522 node
=> get_standard_option
('pve-node'),
523 vmid
=> get_standard_option
('pve-vmid'),
525 description
=> "Specify the time frame you are interested in.",
527 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
530 description
=> "The list of datasources you want to display.",
531 type
=> 'string', format
=> 'pve-configid-list',
534 description
=> "The RRD consolidation function",
536 enum
=> [ 'AVERAGE', 'MAX' ],
544 filename
=> { type
=> 'string' },
550 return PVE
::Cluster
::create_rrd_graph
(
551 "pve2-vm/$param->{vmid}", $param->{timeframe
},
552 $param->{ds
}, $param->{cf
});
556 __PACKAGE__-
>register_method({
558 path
=> '{vmid}/rrddata',
560 protected
=> 1, # fixme: can we avoid that?
562 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
564 description
=> "Read VM RRD statistics",
566 additionalProperties
=> 0,
568 node
=> get_standard_option
('pve-node'),
569 vmid
=> get_standard_option
('pve-vmid'),
571 description
=> "Specify the time frame you are interested in.",
573 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
576 description
=> "The RRD consolidation function",
578 enum
=> [ 'AVERAGE', 'MAX' ],
593 return PVE
::Cluster
::create_rrd_data
(
594 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
598 __PACKAGE__-
>register_method({
600 path
=> '{vmid}/config',
603 description
=> "Get virtual machine configuration.",
605 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
608 additionalProperties
=> 0,
610 node
=> get_standard_option
('pve-node'),
611 vmid
=> get_standard_option
('pve-vmid'),
619 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
626 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
628 delete $conf->{snapshots
};
633 my $vm_is_volid_owner = sub {
634 my ($storecfg, $vmid, $volid) =@_;
636 if ($volid !~ m
|^/|) {
638 eval { ($path, $owner) = PVE
::Storage
::path
($storecfg, $volid); };
639 if ($owner && ($owner == $vmid)) {
647 my $test_deallocate_drive = sub {
648 my ($storecfg, $vmid, $key, $drive, $force) = @_;
650 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
651 my $volid = $drive->{file
};
652 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
653 if ($force || $key =~ m/^unused/) {
654 my $sid = PVE
::Storage
::parse_volume_id
($volid);
663 my $delete_drive = sub {
664 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
666 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
667 my $volid = $drive->{file
};
669 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
670 if ($force || $key =~ m/^unused/) {
672 # check if the disk is really unused
673 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, $key);
674 my $path = PVE
::Storage
::path
($storecfg, $volid);
676 die "unable to delete '$volid' - volume is still in use (snapshot?)\n"
677 if $used_paths->{$path};
679 PVE
::Storage
::vdisk_free
($storecfg, $volid);
683 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
688 delete $conf->{$key};
691 my $vmconfig_delete_option = sub {
692 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
694 return if !defined($conf->{$opt});
696 my $isDisk = PVE
::QemuServer
::valid_drivename
($opt)|| ($opt =~ m/^unused/);
699 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
701 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
702 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
703 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
707 my $unplugwarning = "";
708 if ($conf->{ostype
} && $conf->{ostype
} eq 'l26') {
709 $unplugwarning = "<br>verify that you have acpiphp && pci_hotplug modules loaded in your guest VM";
710 } elsif ($conf->{ostype
} && $conf->{ostype
} eq 'l24') {
711 $unplugwarning = "<br>kernel 2.4 don't support hotplug, please disable hotplug in options";
712 } elsif (!$conf->{ostype
} || ($conf->{ostype
} && $conf->{ostype
} eq 'other')) {
713 $unplugwarning = "<br>verify that your guest support acpi hotplug";
716 if ($opt eq 'tablet') {
717 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
719 die "error hot-unplug $opt $unplugwarning" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
723 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
724 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
726 delete $conf->{$opt};
729 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
732 my $safe_num_ne = sub {
735 return 0 if !defined($a) && !defined($b);
736 return 1 if !defined($a);
737 return 1 if !defined($b);
742 my $vmconfig_update_disk = sub {
743 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
745 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
747 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { #cdrom
748 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
750 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
755 if (my $old_drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt})) {
757 my $media = $drive->{media
} || 'disk';
758 my $oldmedia = $old_drive->{media
} || 'disk';
759 die "unable to change media type\n" if $media ne $oldmedia;
761 if (!PVE
::QemuServer
::drive_is_cdrom
($old_drive) &&
762 ($drive->{file
} ne $old_drive->{file
})) { # delete old disks
764 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
765 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
768 if(&$safe_num_ne($drive->{mbps
}, $old_drive->{mbps
}) ||
769 &$safe_num_ne($drive->{mbps_rd
}, $old_drive->{mbps_rd
}) ||
770 &$safe_num_ne($drive->{mbps_wr
}, $old_drive->{mbps_wr
}) ||
771 &$safe_num_ne($drive->{iops
}, $old_drive->{iops
}) ||
772 &$safe_num_ne($drive->{iops_rd
}, $old_drive->{iops_rd
}) ||
773 &$safe_num_ne($drive->{iops_wr
}, $old_drive->{iops_wr
}) ||
774 &$safe_num_ne($drive->{mbps_max
}, $old_drive->{mbps_max
}) ||
775 &$safe_num_ne($drive->{mbps_rd_max
}, $old_drive->{mbps_rd_max
}) ||
776 &$safe_num_ne($drive->{mbps_wr_max
}, $old_drive->{mbps_wr_max
}) ||
777 &$safe_num_ne($drive->{iops_max
}, $old_drive->{iops_max
}) ||
778 &$safe_num_ne($drive->{iops_rd_max
}, $old_drive->{iops_rd_max
}) ||
779 &$safe_num_ne($drive->{iops_wr_max
}, $old_drive->{iops_wr_max
})) {
780 PVE
::QemuServer
::qemu_block_set_io_throttle
($vmid,"drive-$opt",
781 ($drive->{mbps
} || 0)*1024*1024,
782 ($drive->{mbps_rd
} || 0)*1024*1024,
783 ($drive->{mbps_wr
} || 0)*1024*1024,
785 $drive->{iops_rd
} || 0,
786 $drive->{iops_wr
} || 0,
787 ($drive->{mbps_max
} || 0)*1024*1024,
788 ($drive->{mbps_rd_max
} || 0)*1024*1024,
789 ($drive->{mbps_wr_max
} || 0)*1024*1024,
790 $drive->{iops_max
} || 0,
791 $drive->{iops_rd_max
} || 0,
792 $drive->{iops_wr_max
} || 0)
793 if !PVE
::QemuServer
::drive_is_cdrom
($drive);
798 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
799 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
801 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
802 $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
804 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # cdrom
806 if (PVE
::QemuServer
::check_running
($vmid)) {
807 if ($drive->{file
} eq 'none') {
808 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt");
810 my $path = PVE
::QemuServer
::get_iso_path
($storecfg, $vmid, $drive->{file
});
811 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt"); #force eject if locked
812 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change",device
=> "drive-$opt",target
=> "$path") if $path;
816 } else { # hotplug new disks
818 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $drive);
822 my $vmconfig_update_net = sub {
823 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
825 if ($conf->{$opt} && PVE
::QemuServer
::check_running
($vmid)) {
826 my $oldnet = PVE
::QemuServer
::parse_net
($conf->{$opt});
827 my $newnet = PVE
::QemuServer
::parse_net
($value);
829 if($oldnet->{model
} ne $newnet->{model
}){
830 #if model change, we try to hot-unplug
831 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
834 if($newnet->{bridge
} && $oldnet->{bridge
}){
835 my $iface = "tap".$vmid."i".$1 if $opt =~ m/net(\d+)/;
837 if($newnet->{rate
} ne $oldnet->{rate
}){
838 PVE
::Network
::tap_rate_limit
($iface, $newnet->{rate
});
841 if(($newnet->{bridge
} ne $oldnet->{bridge
}) || ($newnet->{tag
} ne $oldnet->{tag
}) || ($newnet->{firewall
} ne $oldnet->{firewall
})){
842 PVE
::Network
::tap_unplug
($iface);
843 PVE
::Network
::tap_plug
($iface, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
847 #if bridge/nat mode change, we try to hot-unplug
848 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
853 $conf->{$opt} = $value;
854 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
855 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
857 my $net = PVE
::QemuServer
::parse_net
($conf->{$opt});
859 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $net);
862 # POST/PUT {vmid}/config implementation
864 # The original API used PUT (idempotent) an we assumed that all operations
865 # are fast. But it turned out that almost any configuration change can
866 # involve hot-plug actions, or disk alloc/free. Such actions can take long
867 # time to complete and have side effects (not idempotent).
869 # The new implementation uses POST and forks a worker process. We added
870 # a new option 'background_delay'. If specified we wait up to
871 # 'background_delay' second for the worker task to complete. It returns null
872 # if the task is finished within that time, else we return the UPID.
874 my $update_vm_api = sub {
875 my ($param, $sync) = @_;
877 my $rpcenv = PVE
::RPCEnvironment
::get
();
879 my $authuser = $rpcenv->get_user();
881 my $node = extract_param
($param, 'node');
883 my $vmid = extract_param
($param, 'vmid');
885 my $digest = extract_param
($param, 'digest');
887 my $background_delay = extract_param
($param, 'background_delay');
889 my @paramarr = (); # used for log message
890 foreach my $key (keys %$param) {
891 push @paramarr, "-$key", $param->{$key};
894 my $skiplock = extract_param
($param, 'skiplock');
895 raise_param_exc
({ skiplock
=> "Only root may use this option." })
896 if $skiplock && $authuser ne 'root@pam';
898 my $delete_str = extract_param
($param, 'delete');
900 my $force = extract_param
($param, 'force');
902 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
904 my $storecfg = PVE
::Storage
::config
();
906 my $defaults = PVE
::QemuServer
::load_defaults
();
908 &$resolve_cdrom_alias($param);
910 # now try to verify all parameters
913 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
914 $opt = 'ide2' if $opt eq 'cdrom';
915 raise_param_exc
({ delete => "you can't use '-$opt' and " .
916 "-delete $opt' at the same time" })
917 if defined($param->{$opt});
919 if (!PVE
::QemuServer
::option_exists
($opt)) {
920 raise_param_exc
({ delete => "unknown option '$opt'" });
926 foreach my $opt (keys %$param) {
927 if (PVE
::QemuServer
::valid_drivename
($opt)) {
929 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
930 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
931 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
932 } elsif ($opt =~ m/^net(\d+)$/) {
934 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
935 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
939 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
941 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
943 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
947 my $conf = PVE
::QemuServer
::load_config
($vmid);
949 die "checksum missmatch (file change by other user?)\n"
950 if $digest && $digest ne $conf->{digest
};
952 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
954 if ($param->{memory
} || defined($param->{balloon
})) {
955 my $maxmem = $param->{memory
} || $conf->{memory
} || $defaults->{memory
};
956 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{balloon
};
958 die "balloon value too large (must be smaller than assigned memory)\n"
959 if $balloon && $balloon > $maxmem;
962 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
966 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
968 foreach my $opt (@delete) { # delete
969 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
970 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
973 my $running = PVE
::QemuServer
::check_running
($vmid);
975 foreach my $opt (keys %$param) { # add/change
977 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
979 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
981 if (PVE
::QemuServer
::valid_drivename
($opt)) {
983 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
984 $opt, $param->{$opt}, $force);
986 } elsif ($opt =~ m/^net(\d+)$/) { #nics
988 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
989 $opt, $param->{$opt});
993 if($opt eq 'tablet' && $param->{$opt} == 1){
994 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
995 } elsif($opt eq 'tablet' && $param->{$opt} == 0){
996 PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
999 if($opt eq 'cores' && $conf->{maxcpus
}){
1000 PVE
::QemuServer
::qemu_cpu_hotplug
($vmid, $conf, $param->{$opt});
1003 $conf->{$opt} = $param->{$opt};
1004 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
1008 # allow manual ballooning if shares is set to zero
1009 if ($running && defined($param->{balloon
}) &&
1010 defined($conf->{shares
}) && ($conf->{shares
} == 0)) {
1011 my $balloon = $param->{'balloon'} || $conf->{memory
} || $defaults->{memory
};
1012 PVE
::QemuServer
::vm_mon_cmd
($vmid, "balloon", value
=> $balloon*1024*1024);
1020 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1022 if ($background_delay) {
1024 # Note: It would be better to do that in the Event based HTTPServer
1025 # to avoid blocking call to sleep.
1027 my $end_time = time() + $background_delay;
1029 my $task = PVE
::Tools
::upid_decode
($upid);
1032 while (time() < $end_time) {
1033 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1035 sleep(1); # this gets interrupted when child process ends
1039 my $status = PVE
::Tools
::upid_read_status
($upid);
1040 return undef if $status eq 'OK';
1049 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1052 my $vm_config_perm_list = [
1057 'VM.Config.Network',
1059 'VM.Config.Options',
1062 __PACKAGE__-
>register_method({
1063 name
=> 'update_vm_async',
1064 path
=> '{vmid}/config',
1068 description
=> "Set virtual machine options (asynchrounous API).",
1070 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1073 additionalProperties
=> 0,
1074 properties
=> PVE
::QemuServer
::json_config_properties
(
1076 node
=> get_standard_option
('pve-node'),
1077 vmid
=> get_standard_option
('pve-vmid'),
1078 skiplock
=> get_standard_option
('skiplock'),
1080 type
=> 'string', format
=> 'pve-configid-list',
1081 description
=> "A list of settings you want to delete.",
1086 description
=> $opt_force_description,
1088 requires
=> 'delete',
1092 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1096 background_delay
=> {
1098 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1109 code
=> $update_vm_api,
1112 __PACKAGE__-
>register_method({
1113 name
=> 'update_vm',
1114 path
=> '{vmid}/config',
1118 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1120 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1123 additionalProperties
=> 0,
1124 properties
=> PVE
::QemuServer
::json_config_properties
(
1126 node
=> get_standard_option
('pve-node'),
1127 vmid
=> get_standard_option
('pve-vmid'),
1128 skiplock
=> get_standard_option
('skiplock'),
1130 type
=> 'string', format
=> 'pve-configid-list',
1131 description
=> "A list of settings you want to delete.",
1136 description
=> $opt_force_description,
1138 requires
=> 'delete',
1142 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1148 returns
=> { type
=> 'null' },
1151 &$update_vm_api($param, 1);
1157 __PACKAGE__-
>register_method({
1158 name
=> 'destroy_vm',
1163 description
=> "Destroy the vm (also delete all used/owned volumes).",
1165 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1168 additionalProperties
=> 0,
1170 node
=> get_standard_option
('pve-node'),
1171 vmid
=> get_standard_option
('pve-vmid'),
1172 skiplock
=> get_standard_option
('skiplock'),
1181 my $rpcenv = PVE
::RPCEnvironment
::get
();
1183 my $authuser = $rpcenv->get_user();
1185 my $vmid = $param->{vmid
};
1187 my $skiplock = $param->{skiplock
};
1188 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1189 if $skiplock && $authuser ne 'root@pam';
1192 my $conf = PVE
::QemuServer
::load_config
($vmid);
1194 my $storecfg = PVE
::Storage
::config
();
1196 my $delVMfromPoolFn = sub {
1197 my $usercfg = cfs_read_file
("user.cfg");
1198 if (my $pool = $usercfg->{vms
}->{$vmid}) {
1199 if (my $data = $usercfg->{pools
}->{$pool}) {
1200 delete $data->{vms
}->{$vmid};
1201 delete $usercfg->{vms
}->{$vmid};
1202 cfs_write_file
("user.cfg", $usercfg);
1210 syslog
('info', "destroy VM $vmid: $upid\n");
1212 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1214 PVE
::AccessControl
::remove_vm_from_pool
($vmid);
1217 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1220 __PACKAGE__-
>register_method({
1222 path
=> '{vmid}/unlink',
1226 description
=> "Unlink/delete disk images.",
1228 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1231 additionalProperties
=> 0,
1233 node
=> get_standard_option
('pve-node'),
1234 vmid
=> get_standard_option
('pve-vmid'),
1236 type
=> 'string', format
=> 'pve-configid-list',
1237 description
=> "A list of disk IDs you want to delete.",
1241 description
=> $opt_force_description,
1246 returns
=> { type
=> 'null'},
1250 $param->{delete} = extract_param
($param, 'idlist');
1252 __PACKAGE__-
>update_vm($param);
1259 __PACKAGE__-
>register_method({
1261 path
=> '{vmid}/vncproxy',
1265 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1267 description
=> "Creates a TCP VNC proxy connections.",
1269 additionalProperties
=> 0,
1271 node
=> get_standard_option
('pve-node'),
1272 vmid
=> get_standard_option
('pve-vmid'),
1276 description
=> "disables x509 auth",
1281 description
=> "starts websockify instead of vncproxy",
1286 additionalProperties
=> 0,
1288 user
=> { type
=> 'string' },
1289 ticket
=> { type
=> 'string' },
1290 cert
=> { type
=> 'string' },
1291 port
=> { type
=> 'integer' },
1292 upid
=> { type
=> 'string' },
1298 my $rpcenv = PVE
::RPCEnvironment
::get
();
1300 my $authuser = $rpcenv->get_user();
1302 my $vmid = $param->{vmid
};
1303 my $node = $param->{node
};
1304 my $unsecure = $param->{unsecure
} // 0;
1305 my $websocket = $param->{websocket
} // 0;
1307 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # check if VM exists
1309 my $authpath = "/vms/$vmid";
1311 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1313 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1316 my $port = PVE
::Tools
::next_vnc_port
();
1321 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1322 $remip = PVE
::Cluster
::remote_node_ip
($node);
1323 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1324 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1332 syslog
('info', "starting vnc proxy $upid\n");
1336 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1338 die "Unsecure mode is not supported in vga serial mode!" if $unsecure;
1340 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1341 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1342 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1343 '-timeout', $timeout, '-authpath', $authpath,
1344 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1347 my $vnc_socket = PVE
::QemuServer
::vnc_socket
($vmid);
1349 if (defined $remip) {
1353 use PVE
::QemuServer
;
1355 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change", device
=> "vnc", target
=> "unix:$vnc_socket,password");
1357 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> "vnc", password
=> "$ticket");
1359 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> "vnc", time => "+30");
1363 use PVE
::QemuServer
;
1365 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change", device
=> "vnc", target
=> "unix:$vnc_socket,x509,password");
1369 PVE
::Tools
::run_command
([@$remcmd, 'perl', '-'], input
=> $perlcode, outfunc
=> sub {print shift;}, errfunc
=> sub {print STDERR
shift;});
1373 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change", device
=> 'vnc', target
=> "unix:$vnc_socket,password");
1374 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'vnc', password
=> $ticket);
1375 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'vnc', time => "+30");
1377 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change", device
=> 'vnc', target
=> "unix:$vnc_socket,x509,password");
1381 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1383 my $qmstr = join(' ', @$qmcmd);
1385 # also redirect stderr (else we get RFB protocol errors)
1386 $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1389 $cmd = ["/usr/share/novnc/utils/wsproxy.py", '--run-once', "--timeout=$timeout", "--idle-timeout=$timeout", '--ssl-only', '--cert', '/etc/pve/local/pve-ssl.pem', '--key', '/etc/pve/local/pve-ssl.key', $port, '--', @$cmd];
1393 PVE
::Tools
::run_command
($cmd);
1398 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1400 PVE
::Tools
::wait_for_vnc_port
($port);
1411 __PACKAGE__-
>register_method({
1412 name
=> 'spiceproxy',
1413 path
=> '{vmid}/spiceproxy',
1418 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1420 description
=> "Returns a SPICE configuration to connect to the VM.",
1422 additionalProperties
=> 0,
1424 node
=> get_standard_option
('pve-node'),
1425 vmid
=> get_standard_option
('pve-vmid'),
1426 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1429 returns
=> get_standard_option
('remote-viewer-config'),
1433 my $rpcenv = PVE
::RPCEnvironment
::get
();
1435 my $authuser = $rpcenv->get_user();
1437 my $vmid = $param->{vmid
};
1438 my $node = $param->{node
};
1439 my $proxy = $param->{proxy
};
1441 my $conf = PVE
::QemuServer
::load_config
($vmid, $node);
1442 my $title = "VM $vmid - $conf->{'name'}",
1444 my $port = PVE
::QemuServer
::spice_port
($vmid);
1446 my ($ticket, undef, $remote_viewer_config) =
1447 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1449 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1450 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1452 return $remote_viewer_config;
1455 __PACKAGE__-
>register_method({
1457 path
=> '{vmid}/status',
1460 description
=> "Directory index",
1465 additionalProperties
=> 0,
1467 node
=> get_standard_option
('pve-node'),
1468 vmid
=> get_standard_option
('pve-vmid'),
1476 subdir
=> { type
=> 'string' },
1479 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1485 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1488 { subdir
=> 'current' },
1489 { subdir
=> 'start' },
1490 { subdir
=> 'stop' },
1496 my $vm_is_ha_managed = sub {
1499 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1500 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1506 __PACKAGE__-
>register_method({
1507 name
=> 'vm_status',
1508 path
=> '{vmid}/status/current',
1511 protected
=> 1, # qemu pid files are only readable by root
1512 description
=> "Get virtual machine status.",
1514 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1517 additionalProperties
=> 0,
1519 node
=> get_standard_option
('pve-node'),
1520 vmid
=> get_standard_option
('pve-vmid'),
1523 returns
=> { type
=> 'object' },
1528 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1530 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1531 my $status = $vmstatus->{$param->{vmid
}};
1533 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1535 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1540 __PACKAGE__-
>register_method({
1542 path
=> '{vmid}/status/start',
1546 description
=> "Start virtual machine.",
1548 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1551 additionalProperties
=> 0,
1553 node
=> get_standard_option
('pve-node'),
1554 vmid
=> get_standard_option
('pve-vmid'),
1555 skiplock
=> get_standard_option
('skiplock'),
1556 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1557 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1558 machine
=> get_standard_option
('pve-qm-machine'),
1567 my $rpcenv = PVE
::RPCEnvironment
::get
();
1569 my $authuser = $rpcenv->get_user();
1571 my $node = extract_param
($param, 'node');
1573 my $vmid = extract_param
($param, 'vmid');
1575 my $machine = extract_param
($param, 'machine');
1577 my $stateuri = extract_param
($param, 'stateuri');
1578 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1579 if $stateuri && $authuser ne 'root@pam';
1581 my $skiplock = extract_param
($param, 'skiplock');
1582 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1583 if $skiplock && $authuser ne 'root@pam';
1585 my $migratedfrom = extract_param
($param, 'migratedfrom');
1586 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1587 if $migratedfrom && $authuser ne 'root@pam';
1589 # read spice ticket from STDIN
1591 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1592 if (defined(my $line = <>)) {
1594 $spice_ticket = $line;
1598 my $storecfg = PVE
::Storage
::config
();
1600 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1601 $rpcenv->{type
} ne 'ha') {
1606 my $service = "pvevm:$vmid";
1608 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1610 print "Executing HA start for VM $vmid\n";
1612 PVE
::Tools
::run_command
($cmd);
1617 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1624 syslog
('info', "start VM $vmid: $upid\n");
1626 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1627 $machine, $spice_ticket);
1632 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1636 __PACKAGE__-
>register_method({
1638 path
=> '{vmid}/status/stop',
1642 description
=> "Stop virtual machine.",
1644 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1647 additionalProperties
=> 0,
1649 node
=> get_standard_option
('pve-node'),
1650 vmid
=> get_standard_option
('pve-vmid'),
1651 skiplock
=> get_standard_option
('skiplock'),
1652 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1654 description
=> "Wait maximal timeout seconds.",
1660 description
=> "Do not decativate storage volumes.",
1673 my $rpcenv = PVE
::RPCEnvironment
::get
();
1675 my $authuser = $rpcenv->get_user();
1677 my $node = extract_param
($param, 'node');
1679 my $vmid = extract_param
($param, 'vmid');
1681 my $skiplock = extract_param
($param, 'skiplock');
1682 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1683 if $skiplock && $authuser ne 'root@pam';
1685 my $keepActive = extract_param
($param, 'keepActive');
1686 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1687 if $keepActive && $authuser ne 'root@pam';
1689 my $migratedfrom = extract_param
($param, 'migratedfrom');
1690 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1691 if $migratedfrom && $authuser ne 'root@pam';
1694 my $storecfg = PVE
::Storage
::config
();
1696 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1701 my $service = "pvevm:$vmid";
1703 my $cmd = ['clusvcadm', '-d', $service];
1705 print "Executing HA stop for VM $vmid\n";
1707 PVE
::Tools
::run_command
($cmd);
1712 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1718 syslog
('info', "stop VM $vmid: $upid\n");
1720 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1721 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1726 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1730 __PACKAGE__-
>register_method({
1732 path
=> '{vmid}/status/reset',
1736 description
=> "Reset virtual machine.",
1738 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1741 additionalProperties
=> 0,
1743 node
=> get_standard_option
('pve-node'),
1744 vmid
=> get_standard_option
('pve-vmid'),
1745 skiplock
=> get_standard_option
('skiplock'),
1754 my $rpcenv = PVE
::RPCEnvironment
::get
();
1756 my $authuser = $rpcenv->get_user();
1758 my $node = extract_param
($param, 'node');
1760 my $vmid = extract_param
($param, 'vmid');
1762 my $skiplock = extract_param
($param, 'skiplock');
1763 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1764 if $skiplock && $authuser ne 'root@pam';
1766 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1771 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1776 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1779 __PACKAGE__-
>register_method({
1780 name
=> 'vm_shutdown',
1781 path
=> '{vmid}/status/shutdown',
1785 description
=> "Shutdown virtual machine.",
1787 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1790 additionalProperties
=> 0,
1792 node
=> get_standard_option
('pve-node'),
1793 vmid
=> get_standard_option
('pve-vmid'),
1794 skiplock
=> get_standard_option
('skiplock'),
1796 description
=> "Wait maximal timeout seconds.",
1802 description
=> "Make sure the VM stops.",
1808 description
=> "Do not decativate storage volumes.",
1821 my $rpcenv = PVE
::RPCEnvironment
::get
();
1823 my $authuser = $rpcenv->get_user();
1825 my $node = extract_param
($param, 'node');
1827 my $vmid = extract_param
($param, 'vmid');
1829 my $skiplock = extract_param
($param, 'skiplock');
1830 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1831 if $skiplock && $authuser ne 'root@pam';
1833 my $keepActive = extract_param
($param, 'keepActive');
1834 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1835 if $keepActive && $authuser ne 'root@pam';
1837 my $storecfg = PVE
::Storage
::config
();
1842 syslog
('info', "shutdown VM $vmid: $upid\n");
1844 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1845 1, $param->{forceStop
}, $keepActive);
1850 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1853 __PACKAGE__-
>register_method({
1854 name
=> 'vm_suspend',
1855 path
=> '{vmid}/status/suspend',
1859 description
=> "Suspend virtual machine.",
1861 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1864 additionalProperties
=> 0,
1866 node
=> get_standard_option
('pve-node'),
1867 vmid
=> get_standard_option
('pve-vmid'),
1868 skiplock
=> get_standard_option
('skiplock'),
1877 my $rpcenv = PVE
::RPCEnvironment
::get
();
1879 my $authuser = $rpcenv->get_user();
1881 my $node = extract_param
($param, 'node');
1883 my $vmid = extract_param
($param, 'vmid');
1885 my $skiplock = extract_param
($param, 'skiplock');
1886 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1887 if $skiplock && $authuser ne 'root@pam';
1889 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1894 syslog
('info', "suspend VM $vmid: $upid\n");
1896 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1901 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1904 __PACKAGE__-
>register_method({
1905 name
=> 'vm_resume',
1906 path
=> '{vmid}/status/resume',
1910 description
=> "Resume virtual machine.",
1912 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1915 additionalProperties
=> 0,
1917 node
=> get_standard_option
('pve-node'),
1918 vmid
=> get_standard_option
('pve-vmid'),
1919 skiplock
=> get_standard_option
('skiplock'),
1928 my $rpcenv = PVE
::RPCEnvironment
::get
();
1930 my $authuser = $rpcenv->get_user();
1932 my $node = extract_param
($param, 'node');
1934 my $vmid = extract_param
($param, 'vmid');
1936 my $skiplock = extract_param
($param, 'skiplock');
1937 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1938 if $skiplock && $authuser ne 'root@pam';
1940 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1945 syslog
('info', "resume VM $vmid: $upid\n");
1947 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1952 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1955 __PACKAGE__-
>register_method({
1956 name
=> 'vm_sendkey',
1957 path
=> '{vmid}/sendkey',
1961 description
=> "Send key event to virtual machine.",
1963 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1966 additionalProperties
=> 0,
1968 node
=> get_standard_option
('pve-node'),
1969 vmid
=> get_standard_option
('pve-vmid'),
1970 skiplock
=> get_standard_option
('skiplock'),
1972 description
=> "The key (qemu monitor encoding).",
1977 returns
=> { type
=> 'null'},
1981 my $rpcenv = PVE
::RPCEnvironment
::get
();
1983 my $authuser = $rpcenv->get_user();
1985 my $node = extract_param
($param, 'node');
1987 my $vmid = extract_param
($param, 'vmid');
1989 my $skiplock = extract_param
($param, 'skiplock');
1990 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1991 if $skiplock && $authuser ne 'root@pam';
1993 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1998 __PACKAGE__-
>register_method({
1999 name
=> 'vm_feature',
2000 path
=> '{vmid}/feature',
2004 description
=> "Check if feature for virtual machine is available.",
2006 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2009 additionalProperties
=> 0,
2011 node
=> get_standard_option
('pve-node'),
2012 vmid
=> get_standard_option
('pve-vmid'),
2014 description
=> "Feature to check.",
2016 enum
=> [ 'snapshot', 'clone', 'copy' ],
2018 snapname
=> get_standard_option
('pve-snapshot-name', {
2026 hasFeature
=> { type
=> 'boolean' },
2029 items
=> { type
=> 'string' },
2036 my $node = extract_param
($param, 'node');
2038 my $vmid = extract_param
($param, 'vmid');
2040 my $snapname = extract_param
($param, 'snapname');
2042 my $feature = extract_param
($param, 'feature');
2044 my $running = PVE
::QemuServer
::check_running
($vmid);
2046 my $conf = PVE
::QemuServer
::load_config
($vmid);
2049 my $snap = $conf->{snapshots
}->{$snapname};
2050 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2053 my $storecfg = PVE
::Storage
::config
();
2055 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2056 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
2059 hasFeature
=> $hasFeature,
2060 nodes
=> [ keys %$nodelist ],
2064 __PACKAGE__-
>register_method({
2066 path
=> '{vmid}/clone',
2070 description
=> "Create a copy of virtual machine/template.",
2072 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2073 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2074 "'Datastore.AllocateSpace' on any used storage.",
2077 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2079 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2080 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2085 additionalProperties
=> 0,
2087 node
=> get_standard_option
('pve-node'),
2088 vmid
=> get_standard_option
('pve-vmid'),
2089 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2092 type
=> 'string', format
=> 'dns-name',
2093 description
=> "Set a name for the new VM.",
2098 description
=> "Description for the new VM.",
2102 type
=> 'string', format
=> 'pve-poolid',
2103 description
=> "Add the new VM to the specified pool.",
2105 snapname
=> get_standard_option
('pve-snapshot-name', {
2109 storage
=> get_standard_option
('pve-storage-id', {
2110 description
=> "Target storage for full clone.",
2115 description
=> "Target format for file storage.",
2119 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2124 description
=> "Create a full copy of all disk. This is always done when " .
2125 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2128 target
=> get_standard_option
('pve-node', {
2129 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2140 my $rpcenv = PVE
::RPCEnvironment
::get
();
2142 my $authuser = $rpcenv->get_user();
2144 my $node = extract_param
($param, 'node');
2146 my $vmid = extract_param
($param, 'vmid');
2148 my $newid = extract_param
($param, 'newid');
2150 my $pool = extract_param
($param, 'pool');
2152 if (defined($pool)) {
2153 $rpcenv->check_pool_exist($pool);
2156 my $snapname = extract_param
($param, 'snapname');
2158 my $storage = extract_param
($param, 'storage');
2160 my $format = extract_param
($param, 'format');
2162 my $target = extract_param
($param, 'target');
2164 my $localnode = PVE
::INotify
::nodename
();
2166 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2168 PVE
::Cluster
::check_node_exists
($target) if $target;
2170 my $storecfg = PVE
::Storage
::config
();
2173 # check if storage is enabled on local node
2174 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2176 # check if storage is available on target node
2177 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2178 # clone only works if target storage is shared
2179 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2180 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2184 PVE
::Cluster
::check_cfs_quorum
();
2186 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2188 # exclusive lock if VM is running - else shared lock is enough;
2189 my $shared_lock = $running ?
0 : 1;
2193 # do all tests after lock
2194 # we also try to do all tests before we fork the worker
2196 my $conf = PVE
::QemuServer
::load_config
($vmid);
2198 PVE
::QemuServer
::check_lock
($conf);
2200 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2202 die "unexpected state change\n" if $verify_running != $running;
2204 die "snapshot '$snapname' does not exist\n"
2205 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2207 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2209 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2211 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2213 my $conffile = PVE
::QemuServer
::config_file
($newid);
2215 die "unable to create VM $newid: config file already exists\n"
2218 my $newconf = { lock => 'clone' };
2222 foreach my $opt (keys %$oldconf) {
2223 my $value = $oldconf->{$opt};
2225 # do not copy snapshot related info
2226 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2227 $opt eq 'vmstate' || $opt eq 'snapstate';
2229 # always change MAC! address
2230 if ($opt =~ m/^net(\d+)$/) {
2231 my $net = PVE
::QemuServer
::parse_net
($value);
2232 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2233 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2234 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
2235 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2236 die "unable to parse drive options for '$opt'\n" if !$drive;
2237 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2238 $newconf->{$opt} = $value; # simply copy configuration
2240 if ($param->{full
} || !PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
2241 die "Full clone feature is not available"
2242 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2245 $drives->{$opt} = $drive;
2246 push @$vollist, $drive->{file
};
2249 # copy everything else
2250 $newconf->{$opt} = $value;
2254 delete $newconf->{template
};
2256 if ($param->{name
}) {
2257 $newconf->{name
} = $param->{name
};
2259 if ($oldconf->{name
}) {
2260 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2262 $newconf->{name
} = "Copy-of-VM-$vmid";
2266 if ($param->{description
}) {
2267 $newconf->{description
} = $param->{description
};
2270 # create empty/temp config - this fails if VM already exists on other node
2271 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2276 my $newvollist = [];
2279 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2281 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2283 foreach my $opt (keys %$drives) {
2284 my $drive = $drives->{$opt};
2286 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2287 $newid, $storage, $format, $drive->{full
}, $newvollist);
2289 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2291 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2294 delete $newconf->{lock};
2295 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2298 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2299 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist);
2301 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2302 die "Failed to move config to node '$target' - rename failed: $!\n"
2303 if !rename($conffile, $newconffile);
2306 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2311 sleep 1; # some storage like rbd need to wait before release volume - really?
2313 foreach my $volid (@$newvollist) {
2314 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2317 die "clone failed: $err";
2323 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2326 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2327 # Aquire exclusive lock lock for $newid
2328 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2333 __PACKAGE__-
>register_method({
2334 name
=> 'move_vm_disk',
2335 path
=> '{vmid}/move_disk',
2339 description
=> "Move volume to different storage.",
2341 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2342 "and 'Datastore.AllocateSpace' permissions on the storage.",
2345 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2346 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2350 additionalProperties
=> 0,
2352 node
=> get_standard_option
('pve-node'),
2353 vmid
=> get_standard_option
('pve-vmid'),
2356 description
=> "The disk you want to move.",
2357 enum
=> [ PVE
::QemuServer
::disknames
() ],
2359 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2362 description
=> "Target Format.",
2363 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2368 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2374 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2382 description
=> "the task ID.",
2387 my $rpcenv = PVE
::RPCEnvironment
::get
();
2389 my $authuser = $rpcenv->get_user();
2391 my $node = extract_param
($param, 'node');
2393 my $vmid = extract_param
($param, 'vmid');
2395 my $digest = extract_param
($param, 'digest');
2397 my $disk = extract_param
($param, 'disk');
2399 my $storeid = extract_param
($param, 'storage');
2401 my $format = extract_param
($param, 'format');
2403 my $storecfg = PVE
::Storage
::config
();
2405 my $updatefn = sub {
2407 my $conf = PVE
::QemuServer
::load_config
($vmid);
2409 die "checksum missmatch (file change by other user?)\n"
2410 if $digest && $digest ne $conf->{digest
};
2412 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2414 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2416 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2418 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2421 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2422 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2426 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2427 (!$format || !$oldfmt || $oldfmt eq $format);
2429 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2431 my $running = PVE
::QemuServer
::check_running
($vmid);
2433 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2437 my $newvollist = [];
2440 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2442 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2443 $vmid, $storeid, $format, 1, $newvollist);
2445 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2447 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2449 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2452 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2453 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2460 foreach my $volid (@$newvollist) {
2461 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2464 die "storage migration failed: $err";
2467 if ($param->{delete}) {
2468 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, 1);
2469 my $path = PVE
::Storage
::path
($storecfg, $old_volid);
2470 if ($used_paths->{$path}){
2471 warn "volume $old_volid have snapshots. Can't delete it\n";
2472 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid);
2473 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2475 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2481 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2484 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2487 __PACKAGE__-
>register_method({
2488 name
=> 'migrate_vm',
2489 path
=> '{vmid}/migrate',
2493 description
=> "Migrate virtual machine. Creates a new migration task.",
2495 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2498 additionalProperties
=> 0,
2500 node
=> get_standard_option
('pve-node'),
2501 vmid
=> get_standard_option
('pve-vmid'),
2502 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2505 description
=> "Use online/live migration.",
2510 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2517 description
=> "the task ID.",
2522 my $rpcenv = PVE
::RPCEnvironment
::get
();
2524 my $authuser = $rpcenv->get_user();
2526 my $target = extract_param
($param, 'target');
2528 my $localnode = PVE
::INotify
::nodename
();
2529 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2531 PVE
::Cluster
::check_cfs_quorum
();
2533 PVE
::Cluster
::check_node_exists
($target);
2535 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2537 my $vmid = extract_param
($param, 'vmid');
2539 raise_param_exc
({ force
=> "Only root may use this option." })
2540 if $param->{force
} && $authuser ne 'root@pam';
2543 my $conf = PVE
::QemuServer
::load_config
($vmid);
2545 # try to detect errors early
2547 PVE
::QemuServer
::check_lock
($conf);
2549 if (PVE
::QemuServer
::check_running
($vmid)) {
2550 die "cant migrate running VM without --online\n"
2551 if !$param->{online
};
2554 my $storecfg = PVE
::Storage
::config
();
2555 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2557 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2562 my $service = "pvevm:$vmid";
2564 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2566 print "Executing HA migrate for VM $vmid to node $target\n";
2568 PVE
::Tools
::run_command
($cmd);
2573 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2580 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2583 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2588 __PACKAGE__-
>register_method({
2590 path
=> '{vmid}/monitor',
2594 description
=> "Execute Qemu monitor commands.",
2596 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2599 additionalProperties
=> 0,
2601 node
=> get_standard_option
('pve-node'),
2602 vmid
=> get_standard_option
('pve-vmid'),
2605 description
=> "The monitor command.",
2609 returns
=> { type
=> 'string'},
2613 my $vmid = $param->{vmid
};
2615 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2619 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2621 $res = "ERROR: $@" if $@;
2626 __PACKAGE__-
>register_method({
2627 name
=> 'resize_vm',
2628 path
=> '{vmid}/resize',
2632 description
=> "Extend volume size.",
2634 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2637 additionalProperties
=> 0,
2639 node
=> get_standard_option
('pve-node'),
2640 vmid
=> get_standard_option
('pve-vmid'),
2641 skiplock
=> get_standard_option
('skiplock'),
2644 description
=> "The disk you want to resize.",
2645 enum
=> [PVE
::QemuServer
::disknames
()],
2649 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2650 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.",
2654 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2660 returns
=> { type
=> 'null'},
2664 my $rpcenv = PVE
::RPCEnvironment
::get
();
2666 my $authuser = $rpcenv->get_user();
2668 my $node = extract_param
($param, 'node');
2670 my $vmid = extract_param
($param, 'vmid');
2672 my $digest = extract_param
($param, 'digest');
2674 my $disk = extract_param
($param, 'disk');
2676 my $sizestr = extract_param
($param, 'size');
2678 my $skiplock = extract_param
($param, 'skiplock');
2679 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2680 if $skiplock && $authuser ne 'root@pam';
2682 my $storecfg = PVE
::Storage
::config
();
2684 my $updatefn = sub {
2686 my $conf = PVE
::QemuServer
::load_config
($vmid);
2688 die "checksum missmatch (file change by other user?)\n"
2689 if $digest && $digest ne $conf->{digest
};
2690 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2692 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2694 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2696 my $volid = $drive->{file
};
2698 die "disk '$disk' has no associated volume\n" if !$volid;
2700 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2702 die "you can't online resize a virtio windows bootdisk\n"
2703 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2705 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2707 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2709 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2711 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2712 my ($ext, $newsize, $unit) = ($1, $2, $4);
2715 $newsize = $newsize * 1024;
2716 } elsif ($unit eq 'M') {
2717 $newsize = $newsize * 1024 * 1024;
2718 } elsif ($unit eq 'G') {
2719 $newsize = $newsize * 1024 * 1024 * 1024;
2720 } elsif ($unit eq 'T') {
2721 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2724 $newsize += $size if $ext;
2725 $newsize = int($newsize);
2727 die "unable to skrink disk size\n" if $newsize < $size;
2729 return if $size == $newsize;
2731 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2733 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2735 $drive->{size
} = $newsize;
2736 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2738 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2741 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2745 __PACKAGE__-
>register_method({
2746 name
=> 'snapshot_list',
2747 path
=> '{vmid}/snapshot',
2749 description
=> "List all snapshots.",
2751 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2754 protected
=> 1, # qemu pid files are only readable by root
2756 additionalProperties
=> 0,
2758 vmid
=> get_standard_option
('pve-vmid'),
2759 node
=> get_standard_option
('pve-node'),
2768 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2773 my $vmid = $param->{vmid
};
2775 my $conf = PVE
::QemuServer
::load_config
($vmid);
2776 my $snaphash = $conf->{snapshots
} || {};
2780 foreach my $name (keys %$snaphash) {
2781 my $d = $snaphash->{$name};
2784 snaptime
=> $d->{snaptime
} || 0,
2785 vmstate
=> $d->{vmstate
} ?
1 : 0,
2786 description
=> $d->{description
} || '',
2788 $item->{parent
} = $d->{parent
} if $d->{parent
};
2789 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2793 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2794 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2795 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2797 push @$res, $current;
2802 __PACKAGE__-
>register_method({
2804 path
=> '{vmid}/snapshot',
2808 description
=> "Snapshot a VM.",
2810 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2813 additionalProperties
=> 0,
2815 node
=> get_standard_option
('pve-node'),
2816 vmid
=> get_standard_option
('pve-vmid'),
2817 snapname
=> get_standard_option
('pve-snapshot-name'),
2821 description
=> "Save the vmstate",
2826 description
=> "Freeze the filesystem",
2831 description
=> "A textual description or comment.",
2837 description
=> "the task ID.",
2842 my $rpcenv = PVE
::RPCEnvironment
::get
();
2844 my $authuser = $rpcenv->get_user();
2846 my $node = extract_param
($param, 'node');
2848 my $vmid = extract_param
($param, 'vmid');
2850 my $snapname = extract_param
($param, 'snapname');
2852 die "unable to use snapshot name 'current' (reserved name)\n"
2853 if $snapname eq 'current';
2856 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2857 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2858 $param->{freezefs
}, $param->{description
});
2861 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2864 __PACKAGE__-
>register_method({
2865 name
=> 'snapshot_cmd_idx',
2866 path
=> '{vmid}/snapshot/{snapname}',
2873 additionalProperties
=> 0,
2875 vmid
=> get_standard_option
('pve-vmid'),
2876 node
=> get_standard_option
('pve-node'),
2877 snapname
=> get_standard_option
('pve-snapshot-name'),
2886 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2893 push @$res, { cmd
=> 'rollback' };
2894 push @$res, { cmd
=> 'config' };
2899 __PACKAGE__-
>register_method({
2900 name
=> 'update_snapshot_config',
2901 path
=> '{vmid}/snapshot/{snapname}/config',
2905 description
=> "Update snapshot metadata.",
2907 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2910 additionalProperties
=> 0,
2912 node
=> get_standard_option
('pve-node'),
2913 vmid
=> get_standard_option
('pve-vmid'),
2914 snapname
=> get_standard_option
('pve-snapshot-name'),
2918 description
=> "A textual description or comment.",
2922 returns
=> { type
=> 'null' },
2926 my $rpcenv = PVE
::RPCEnvironment
::get
();
2928 my $authuser = $rpcenv->get_user();
2930 my $vmid = extract_param
($param, 'vmid');
2932 my $snapname = extract_param
($param, 'snapname');
2934 return undef if !defined($param->{description
});
2936 my $updatefn = sub {
2938 my $conf = PVE
::QemuServer
::load_config
($vmid);
2940 PVE
::QemuServer
::check_lock
($conf);
2942 my $snap = $conf->{snapshots
}->{$snapname};
2944 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2946 $snap->{description
} = $param->{description
} if defined($param->{description
});
2948 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2951 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2956 __PACKAGE__-
>register_method({
2957 name
=> 'get_snapshot_config',
2958 path
=> '{vmid}/snapshot/{snapname}/config',
2961 description
=> "Get snapshot configuration",
2963 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2966 additionalProperties
=> 0,
2968 node
=> get_standard_option
('pve-node'),
2969 vmid
=> get_standard_option
('pve-vmid'),
2970 snapname
=> get_standard_option
('pve-snapshot-name'),
2973 returns
=> { type
=> "object" },
2977 my $rpcenv = PVE
::RPCEnvironment
::get
();
2979 my $authuser = $rpcenv->get_user();
2981 my $vmid = extract_param
($param, 'vmid');
2983 my $snapname = extract_param
($param, 'snapname');
2985 my $conf = PVE
::QemuServer
::load_config
($vmid);
2987 my $snap = $conf->{snapshots
}->{$snapname};
2989 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2994 __PACKAGE__-
>register_method({
2996 path
=> '{vmid}/snapshot/{snapname}/rollback',
3000 description
=> "Rollback VM state to specified snapshot.",
3002 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3005 additionalProperties
=> 0,
3007 node
=> get_standard_option
('pve-node'),
3008 vmid
=> get_standard_option
('pve-vmid'),
3009 snapname
=> get_standard_option
('pve-snapshot-name'),
3014 description
=> "the task ID.",
3019 my $rpcenv = PVE
::RPCEnvironment
::get
();
3021 my $authuser = $rpcenv->get_user();
3023 my $node = extract_param
($param, 'node');
3025 my $vmid = extract_param
($param, 'vmid');
3027 my $snapname = extract_param
($param, 'snapname');
3030 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3031 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
3034 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3037 __PACKAGE__-
>register_method({
3038 name
=> 'delsnapshot',
3039 path
=> '{vmid}/snapshot/{snapname}',
3043 description
=> "Delete a VM snapshot.",
3045 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3048 additionalProperties
=> 0,
3050 node
=> get_standard_option
('pve-node'),
3051 vmid
=> get_standard_option
('pve-vmid'),
3052 snapname
=> get_standard_option
('pve-snapshot-name'),
3056 description
=> "For removal from config file, even if removing disk snapshots fails.",
3062 description
=> "the task ID.",
3067 my $rpcenv = PVE
::RPCEnvironment
::get
();
3069 my $authuser = $rpcenv->get_user();
3071 my $node = extract_param
($param, 'node');
3073 my $vmid = extract_param
($param, 'vmid');
3075 my $snapname = extract_param
($param, 'snapname');
3078 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3079 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3082 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3085 __PACKAGE__-
>register_method({
3087 path
=> '{vmid}/template',
3091 description
=> "Create a Template.",
3093 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3094 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3097 additionalProperties
=> 0,
3099 node
=> get_standard_option
('pve-node'),
3100 vmid
=> get_standard_option
('pve-vmid'),
3104 description
=> "If you want to convert only 1 disk to base image.",
3105 enum
=> [PVE
::QemuServer
::disknames
()],
3110 returns
=> { type
=> 'null'},
3114 my $rpcenv = PVE
::RPCEnvironment
::get
();
3116 my $authuser = $rpcenv->get_user();
3118 my $node = extract_param
($param, 'node');
3120 my $vmid = extract_param
($param, 'vmid');
3122 my $disk = extract_param
($param, 'disk');
3124 my $updatefn = sub {
3126 my $conf = PVE
::QemuServer
::load_config
($vmid);
3128 PVE
::QemuServer
::check_lock
($conf);
3130 die "unable to create template, because VM contains snapshots\n"
3131 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3133 die "you can't convert a template to a template\n"
3134 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3136 die "you can't convert a VM to template if VM is running\n"
3137 if PVE
::QemuServer
::check_running
($vmid);
3140 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3143 $conf->{template
} = 1;
3144 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3146 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3149 PVE
::QemuServer
::lock_config
($vmid, $updatefn);