1 package PVE
::API2
::Qemu
;
9 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
11 use PVE
::Tools
qw(extract_param);
12 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
14 use PVE
::JSONSchema
qw(get_standard_option);
18 use PVE
::RPCEnvironment
;
19 use PVE
::AccessControl
;
22 use PVE
::API2
::Firewall
::VM
;
24 use Data
::Dumper
; # fixme: remove
26 use base
qw(PVE::RESTHandler);
28 my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal.";
30 my $resolve_cdrom_alias = sub {
33 if (my $value = $param->{cdrom
}) {
34 $value .= ",media=cdrom" if $value !~ m/media=/;
35 $param->{ide2
} = $value;
36 delete $param->{cdrom
};
41 my $check_storage_access = sub {
42 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
44 PVE
::QemuServer
::foreach_drive
($settings, sub {
45 my ($ds, $drive) = @_;
47 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
49 my $volid = $drive->{file
};
51 if (!$volid || $volid eq 'none') {
53 } elsif ($isCDROM && ($volid eq 'cdrom')) {
54 $rpcenv->check($authuser, "/", ['Sys.Console']);
55 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
56 my ($storeid, $size) = ($2 || $default_storage, $3);
57 die "no storage ID specified (and no default storage)\n" if !$storeid;
58 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
60 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
65 my $check_storage_access_clone = sub {
66 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
70 PVE
::QemuServer
::foreach_drive
($conf, sub {
71 my ($ds, $drive) = @_;
73 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
75 my $volid = $drive->{file
};
77 return if !$volid || $volid eq 'none';
80 if ($volid eq 'cdrom') {
81 $rpcenv->check($authuser, "/", ['Sys.Console']);
83 # we simply allow access
84 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
85 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
86 $sharedvm = 0 if !$scfg->{shared
};
90 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
91 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
92 $sharedvm = 0 if !$scfg->{shared
};
94 $sid = $storage if $storage;
95 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
102 # Note: $pool is only needed when creating a VM, because pool permissions
103 # are automatically inherited if VM already exists inside a pool.
104 my $create_disks = sub {
105 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
110 PVE
::QemuServer
::foreach_drive
($settings, sub {
111 my ($ds, $disk) = @_;
113 my $volid = $disk->{file
};
115 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
116 delete $disk->{size
};
117 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
118 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
119 my ($storeid, $size) = ($2 || $default_storage, $3);
120 die "no storage ID specified (and no default storage)\n" if !$storeid;
121 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
122 my $fmt = $disk->{format
} || $defformat;
123 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
124 $fmt, undef, $size*1024*1024);
125 $disk->{file
} = $volid;
126 $disk->{size
} = $size*1024*1024*1024;
127 push @$vollist, $volid;
128 delete $disk->{format
}; # no longer needed
129 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
132 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
134 my $volid_is_new = 1;
137 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
138 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
143 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
145 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
147 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
149 die "volume $volid does not exists\n" if !$size;
151 $disk->{size
} = $size;
154 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
158 # free allocated images on error
160 syslog
('err', "VM $vmid creating disks failed");
161 foreach my $volid (@$vollist) {
162 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
168 # modify vm config if everything went well
169 foreach my $ds (keys %$res) {
170 $conf->{$ds} = $res->{$ds};
176 my $check_vm_modify_config_perm = sub {
177 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
179 return 1 if $authuser eq 'root@pam';
181 foreach my $opt (@$key_list) {
182 # disk checks need to be done somewhere else
183 next if PVE
::QemuServer
::valid_drivename
($opt);
185 if ($opt eq 'sockets' || $opt eq 'cores' ||
186 $opt eq 'cpu' || $opt eq 'smp' ||
187 $opt eq 'cpulimit' || $opt eq 'cpuunits') {
188 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
189 } elsif ($opt eq 'boot' || $opt eq 'bootdisk') {
190 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
191 } elsif ($opt eq 'memory' || $opt eq 'balloon' || $opt eq 'shares') {
192 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
193 } elsif ($opt eq 'args' || $opt eq 'lock') {
194 die "only root can set '$opt' config\n";
195 } elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' || $opt eq 'machine' ||
196 $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet' || $opt eq 'smbios1') {
197 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
198 } elsif ($opt =~ m/^net\d+$/) {
199 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
201 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
208 __PACKAGE__-
>register_method({
212 description
=> "Virtual machine index (per node).",
214 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
218 protected
=> 1, # qemu pid files are only readable by root
220 additionalProperties
=> 0,
222 node
=> get_standard_option
('pve-node'),
231 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
236 my $rpcenv = PVE
::RPCEnvironment
::get
();
237 my $authuser = $rpcenv->get_user();
239 my $vmstatus = PVE
::QemuServer
::vmstatus
();
242 foreach my $vmid (keys %$vmstatus) {
243 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
245 my $data = $vmstatus->{$vmid};
246 $data->{vmid
} = $vmid;
255 __PACKAGE__-
>register_method({
259 description
=> "Create or restore a virtual machine.",
261 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
262 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
263 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
264 user
=> 'all', # check inside
269 additionalProperties
=> 0,
270 properties
=> PVE
::QemuServer
::json_config_properties
(
272 node
=> get_standard_option
('pve-node'),
273 vmid
=> get_standard_option
('pve-vmid'),
275 description
=> "The backup file.",
280 storage
=> get_standard_option
('pve-storage-id', {
281 description
=> "Default storage.",
287 description
=> "Allow to overwrite existing VM.",
288 requires
=> 'archive',
293 description
=> "Assign a unique random ethernet address.",
294 requires
=> 'archive',
298 type
=> 'string', format
=> 'pve-poolid',
299 description
=> "Add the VM to the specified pool.",
309 my $rpcenv = PVE
::RPCEnvironment
::get
();
311 my $authuser = $rpcenv->get_user();
313 my $node = extract_param
($param, 'node');
315 my $vmid = extract_param
($param, 'vmid');
317 my $archive = extract_param
($param, 'archive');
319 my $storage = extract_param
($param, 'storage');
321 my $force = extract_param
($param, 'force');
323 my $unique = extract_param
($param, 'unique');
325 my $pool = extract_param
($param, 'pool');
327 my $filename = PVE
::QemuServer
::config_file
($vmid);
329 my $storecfg = PVE
::Storage
::config
();
331 PVE
::Cluster
::check_cfs_quorum
();
333 if (defined($pool)) {
334 $rpcenv->check_pool_exist($pool);
337 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
338 if defined($storage);
340 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
342 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
344 } elsif ($archive && $force && (-f
$filename) &&
345 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
346 # OK: user has VM.Backup permissions, and want to restore an existing VM
352 &$resolve_cdrom_alias($param);
354 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
356 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
358 foreach my $opt (keys %$param) {
359 if (PVE
::QemuServer
::valid_drivename
($opt)) {
360 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
361 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
363 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
364 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
368 PVE
::QemuServer
::add_random_macs
($param);
370 my $keystr = join(' ', keys %$param);
371 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
373 if ($archive eq '-') {
374 die "pipe requires cli environment\n"
375 if $rpcenv->{type
} ne 'cli';
377 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
378 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
382 my $restorefn = sub {
384 # fixme: this test does not work if VM exists on other node!
386 die "unable to restore vm $vmid: config file already exists\n"
389 die "unable to restore vm $vmid: vm is running\n"
390 if PVE
::QemuServer
::check_running
($vmid);
394 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
397 unique
=> $unique });
399 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
402 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
408 die "unable to create vm $vmid: config file already exists\n"
419 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
421 # try to be smart about bootdisk
422 my @disks = PVE
::QemuServer
::disknames
();
424 foreach my $ds (reverse @disks) {
425 next if !$conf->{$ds};
426 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
427 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
431 if (!$conf->{bootdisk
} && $firstdisk) {
432 $conf->{bootdisk
} = $firstdisk;
435 # auto generate uuid if user did not specify smbios1 option
436 if (!$conf->{smbios1
}) {
437 my ($uuid, $uuid_str);
438 UUID
::generate
($uuid);
439 UUID
::unparse
($uuid, $uuid_str);
440 $conf->{smbios1
} = "uuid=$uuid_str";
443 PVE
::QemuServer
::update_config_nolock
($vmid, $conf);
449 foreach my $volid (@$vollist) {
450 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
453 die "create failed - $err";
456 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
459 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
462 return PVE
::QemuServer
::lock_config_full
($vmid, 1, $archive ?
$restorefn : $createfn);
465 __PACKAGE__-
>register_method({
470 description
=> "Directory index",
475 additionalProperties
=> 0,
477 node
=> get_standard_option
('pve-node'),
478 vmid
=> get_standard_option
('pve-vmid'),
486 subdir
=> { type
=> 'string' },
489 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
495 { subdir
=> 'config' },
496 { subdir
=> 'status' },
497 { subdir
=> 'unlink' },
498 { subdir
=> 'vncproxy' },
499 { subdir
=> 'migrate' },
500 { subdir
=> 'resize' },
501 { subdir
=> 'move' },
503 { subdir
=> 'rrddata' },
504 { subdir
=> 'monitor' },
505 { subdir
=> 'snapshot' },
506 { subdir
=> 'spiceproxy' },
507 { subdir
=> 'sendkey' },
508 { subdir
=> 'firewall' },
514 __PACKAGE__-
>register_method ({
515 subclass
=> "PVE::API2::Firewall::VM",
516 path
=> '{vmid}/firewall',
519 __PACKAGE__-
>register_method({
521 path
=> '{vmid}/rrd',
523 protected
=> 1, # fixme: can we avoid that?
525 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
527 description
=> "Read VM RRD statistics (returns PNG)",
529 additionalProperties
=> 0,
531 node
=> get_standard_option
('pve-node'),
532 vmid
=> get_standard_option
('pve-vmid'),
534 description
=> "Specify the time frame you are interested in.",
536 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
539 description
=> "The list of datasources you want to display.",
540 type
=> 'string', format
=> 'pve-configid-list',
543 description
=> "The RRD consolidation function",
545 enum
=> [ 'AVERAGE', 'MAX' ],
553 filename
=> { type
=> 'string' },
559 return PVE
::Cluster
::create_rrd_graph
(
560 "pve2-vm/$param->{vmid}", $param->{timeframe
},
561 $param->{ds
}, $param->{cf
});
565 __PACKAGE__-
>register_method({
567 path
=> '{vmid}/rrddata',
569 protected
=> 1, # fixme: can we avoid that?
571 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
573 description
=> "Read VM RRD statistics",
575 additionalProperties
=> 0,
577 node
=> get_standard_option
('pve-node'),
578 vmid
=> get_standard_option
('pve-vmid'),
580 description
=> "Specify the time frame you are interested in.",
582 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
585 description
=> "The RRD consolidation function",
587 enum
=> [ 'AVERAGE', 'MAX' ],
602 return PVE
::Cluster
::create_rrd_data
(
603 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
607 __PACKAGE__-
>register_method({
609 path
=> '{vmid}/config',
612 description
=> "Get virtual machine configuration.",
614 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
617 additionalProperties
=> 0,
619 node
=> get_standard_option
('pve-node'),
620 vmid
=> get_standard_option
('pve-vmid'),
628 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
635 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
637 delete $conf->{snapshots
};
642 my $vm_is_volid_owner = sub {
643 my ($storecfg, $vmid, $volid) =@_;
645 if ($volid !~ m
|^/|) {
647 eval { ($path, $owner) = PVE
::Storage
::path
($storecfg, $volid); };
648 if ($owner && ($owner == $vmid)) {
656 my $test_deallocate_drive = sub {
657 my ($storecfg, $vmid, $key, $drive, $force) = @_;
659 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
660 my $volid = $drive->{file
};
661 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
662 if ($force || $key =~ m/^unused/) {
663 my $sid = PVE
::Storage
::parse_volume_id
($volid);
672 my $delete_drive = sub {
673 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
675 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
676 my $volid = $drive->{file
};
678 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
679 if ($force || $key =~ m/^unused/) {
681 # check if the disk is really unused
682 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, $key);
683 my $path = PVE
::Storage
::path
($storecfg, $volid);
685 die "unable to delete '$volid' - volume is still in use (snapshot?)\n"
686 if $used_paths->{$path};
688 PVE
::Storage
::vdisk_free
($storecfg, $volid);
692 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
697 delete $conf->{$key};
700 my $vmconfig_delete_option = sub {
701 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
703 return if !defined($conf->{$opt});
705 my $isDisk = PVE
::QemuServer
::valid_drivename
($opt)|| ($opt =~ m/^unused/);
708 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
710 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
711 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
712 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
716 my $unplugwarning = "";
717 if ($conf->{ostype
} && $conf->{ostype
} eq 'l26') {
718 $unplugwarning = "<br>verify that you have acpiphp && pci_hotplug modules loaded in your guest VM";
719 } elsif ($conf->{ostype
} && $conf->{ostype
} eq 'l24') {
720 $unplugwarning = "<br>kernel 2.4 don't support hotplug, please disable hotplug in options";
721 } elsif (!$conf->{ostype
} || ($conf->{ostype
} && $conf->{ostype
} eq 'other')) {
722 $unplugwarning = "<br>verify that your guest support acpi hotplug";
725 if ($opt eq 'tablet') {
726 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
728 die "error hot-unplug $opt $unplugwarning" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
732 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
733 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
735 delete $conf->{$opt};
738 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
741 my $safe_num_ne = sub {
744 return 0 if !defined($a) && !defined($b);
745 return 1 if !defined($a);
746 return 1 if !defined($b);
751 my $vmconfig_update_disk = sub {
752 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
754 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
756 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { #cdrom
757 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
759 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
764 if (my $old_drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt})) {
766 my $media = $drive->{media
} || 'disk';
767 my $oldmedia = $old_drive->{media
} || 'disk';
768 die "unable to change media type\n" if $media ne $oldmedia;
770 if (!PVE
::QemuServer
::drive_is_cdrom
($old_drive) &&
771 ($drive->{file
} ne $old_drive->{file
})) { # delete old disks
773 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
774 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
777 if(&$safe_num_ne($drive->{mbps
}, $old_drive->{mbps
}) ||
778 &$safe_num_ne($drive->{mbps_rd
}, $old_drive->{mbps_rd
}) ||
779 &$safe_num_ne($drive->{mbps_wr
}, $old_drive->{mbps_wr
}) ||
780 &$safe_num_ne($drive->{iops
}, $old_drive->{iops
}) ||
781 &$safe_num_ne($drive->{iops_rd
}, $old_drive->{iops_rd
}) ||
782 &$safe_num_ne($drive->{iops_wr
}, $old_drive->{iops_wr
}) ||
783 &$safe_num_ne($drive->{mbps_max
}, $old_drive->{mbps_max
}) ||
784 &$safe_num_ne($drive->{mbps_rd_max
}, $old_drive->{mbps_rd_max
}) ||
785 &$safe_num_ne($drive->{mbps_wr_max
}, $old_drive->{mbps_wr_max
}) ||
786 &$safe_num_ne($drive->{iops_max
}, $old_drive->{iops_max
}) ||
787 &$safe_num_ne($drive->{iops_rd_max
}, $old_drive->{iops_rd_max
}) ||
788 &$safe_num_ne($drive->{iops_wr_max
}, $old_drive->{iops_wr_max
})) {
789 PVE
::QemuServer
::qemu_block_set_io_throttle
($vmid,"drive-$opt",
790 ($drive->{mbps
} || 0)*1024*1024,
791 ($drive->{mbps_rd
} || 0)*1024*1024,
792 ($drive->{mbps_wr
} || 0)*1024*1024,
794 $drive->{iops_rd
} || 0,
795 $drive->{iops_wr
} || 0,
796 ($drive->{mbps_max
} || 0)*1024*1024,
797 ($drive->{mbps_rd_max
} || 0)*1024*1024,
798 ($drive->{mbps_wr_max
} || 0)*1024*1024,
799 $drive->{iops_max
} || 0,
800 $drive->{iops_rd_max
} || 0,
801 $drive->{iops_wr_max
} || 0)
802 if !PVE
::QemuServer
::drive_is_cdrom
($drive);
807 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
808 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
810 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
811 $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
813 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # cdrom
815 if (PVE
::QemuServer
::check_running
($vmid)) {
816 if ($drive->{file
} eq 'none') {
817 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt");
819 my $path = PVE
::QemuServer
::get_iso_path
($storecfg, $vmid, $drive->{file
});
820 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt"); #force eject if locked
821 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change",device
=> "drive-$opt",target
=> "$path") if $path;
825 } else { # hotplug new disks
827 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $drive);
831 my $vmconfig_update_net = sub {
832 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
834 if ($conf->{$opt} && PVE
::QemuServer
::check_running
($vmid)) {
835 my $oldnet = PVE
::QemuServer
::parse_net
($conf->{$opt});
836 my $newnet = PVE
::QemuServer
::parse_net
($value);
838 if($oldnet->{model
} ne $newnet->{model
}){
839 #if model change, we try to hot-unplug
840 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
843 if($newnet->{bridge
} && $oldnet->{bridge
}){
844 my $iface = "tap".$vmid."i".$1 if $opt =~ m/net(\d+)/;
846 if($newnet->{rate
} ne $oldnet->{rate
}){
847 PVE
::Network
::tap_rate_limit
($iface, $newnet->{rate
});
850 if(($newnet->{bridge
} ne $oldnet->{bridge
}) || ($newnet->{tag
} ne $oldnet->{tag
}) || ($newnet->{firewall
} ne $oldnet->{firewall
})){
851 PVE
::Network
::tap_unplug
($iface);
852 PVE
::Network
::tap_plug
($iface, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
856 #if bridge/nat mode change, we try to hot-unplug
857 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
862 $conf->{$opt} = $value;
863 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
864 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
866 my $net = PVE
::QemuServer
::parse_net
($conf->{$opt});
868 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $net);
871 # POST/PUT {vmid}/config implementation
873 # The original API used PUT (idempotent) an we assumed that all operations
874 # are fast. But it turned out that almost any configuration change can
875 # involve hot-plug actions, or disk alloc/free. Such actions can take long
876 # time to complete and have side effects (not idempotent).
878 # The new implementation uses POST and forks a worker process. We added
879 # a new option 'background_delay'. If specified we wait up to
880 # 'background_delay' second for the worker task to complete. It returns null
881 # if the task is finished within that time, else we return the UPID.
883 my $update_vm_api = sub {
884 my ($param, $sync) = @_;
886 my $rpcenv = PVE
::RPCEnvironment
::get
();
888 my $authuser = $rpcenv->get_user();
890 my $node = extract_param
($param, 'node');
892 my $vmid = extract_param
($param, 'vmid');
894 my $digest = extract_param
($param, 'digest');
896 my $background_delay = extract_param
($param, 'background_delay');
898 my @paramarr = (); # used for log message
899 foreach my $key (keys %$param) {
900 push @paramarr, "-$key", $param->{$key};
903 my $skiplock = extract_param
($param, 'skiplock');
904 raise_param_exc
({ skiplock
=> "Only root may use this option." })
905 if $skiplock && $authuser ne 'root@pam';
907 my $delete_str = extract_param
($param, 'delete');
909 my $force = extract_param
($param, 'force');
911 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
913 my $storecfg = PVE
::Storage
::config
();
915 my $defaults = PVE
::QemuServer
::load_defaults
();
917 &$resolve_cdrom_alias($param);
919 # now try to verify all parameters
922 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
923 $opt = 'ide2' if $opt eq 'cdrom';
924 raise_param_exc
({ delete => "you can't use '-$opt' and " .
925 "-delete $opt' at the same time" })
926 if defined($param->{$opt});
928 if (!PVE
::QemuServer
::option_exists
($opt)) {
929 raise_param_exc
({ delete => "unknown option '$opt'" });
935 foreach my $opt (keys %$param) {
936 if (PVE
::QemuServer
::valid_drivename
($opt)) {
938 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
939 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
940 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
941 } elsif ($opt =~ m/^net(\d+)$/) {
943 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
944 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
948 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
950 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
952 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
956 my $conf = PVE
::QemuServer
::load_config
($vmid);
958 die "checksum missmatch (file change by other user?)\n"
959 if $digest && $digest ne $conf->{digest
};
961 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
963 if ($param->{memory
} || defined($param->{balloon
})) {
964 my $maxmem = $param->{memory
} || $conf->{memory
} || $defaults->{memory
};
965 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{balloon
};
967 die "balloon value too large (must be smaller than assigned memory)\n"
968 if $balloon && $balloon > $maxmem;
971 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
975 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
977 foreach my $opt (@delete) { # delete
978 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
979 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
982 my $running = PVE
::QemuServer
::check_running
($vmid);
984 foreach my $opt (keys %$param) { # add/change
986 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
988 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
990 if (PVE
::QemuServer
::valid_drivename
($opt)) {
992 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
993 $opt, $param->{$opt}, $force);
995 } elsif ($opt =~ m/^net(\d+)$/) { #nics
997 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
998 $opt, $param->{$opt});
1002 if($opt eq 'tablet' && $param->{$opt} == 1){
1003 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
1004 } elsif($opt eq 'tablet' && $param->{$opt} == 0){
1005 PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
1008 if($opt eq 'cores' && $conf->{maxcpus
}){
1009 PVE
::QemuServer
::qemu_cpu_hotplug
($vmid, $conf, $param->{$opt});
1012 $conf->{$opt} = $param->{$opt};
1013 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
1017 # allow manual ballooning if shares is set to zero
1018 if ($running && defined($param->{balloon
}) &&
1019 defined($conf->{shares
}) && ($conf->{shares
} == 0)) {
1020 my $balloon = $param->{'balloon'} || $conf->{memory
} || $defaults->{memory
};
1021 PVE
::QemuServer
::vm_mon_cmd
($vmid, "balloon", value
=> $balloon*1024*1024);
1029 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1031 if ($background_delay) {
1033 # Note: It would be better to do that in the Event based HTTPServer
1034 # to avoid blocking call to sleep.
1036 my $end_time = time() + $background_delay;
1038 my $task = PVE
::Tools
::upid_decode
($upid);
1041 while (time() < $end_time) {
1042 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1044 sleep(1); # this gets interrupted when child process ends
1048 my $status = PVE
::Tools
::upid_read_status
($upid);
1049 return undef if $status eq 'OK';
1058 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1061 my $vm_config_perm_list = [
1066 'VM.Config.Network',
1068 'VM.Config.Options',
1071 __PACKAGE__-
>register_method({
1072 name
=> 'update_vm_async',
1073 path
=> '{vmid}/config',
1077 description
=> "Set virtual machine options (asynchrounous API).",
1079 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1082 additionalProperties
=> 0,
1083 properties
=> PVE
::QemuServer
::json_config_properties
(
1085 node
=> get_standard_option
('pve-node'),
1086 vmid
=> get_standard_option
('pve-vmid'),
1087 skiplock
=> get_standard_option
('skiplock'),
1089 type
=> 'string', format
=> 'pve-configid-list',
1090 description
=> "A list of settings you want to delete.",
1095 description
=> $opt_force_description,
1097 requires
=> 'delete',
1101 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1105 background_delay
=> {
1107 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1118 code
=> $update_vm_api,
1121 __PACKAGE__-
>register_method({
1122 name
=> 'update_vm',
1123 path
=> '{vmid}/config',
1127 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1129 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1132 additionalProperties
=> 0,
1133 properties
=> PVE
::QemuServer
::json_config_properties
(
1135 node
=> get_standard_option
('pve-node'),
1136 vmid
=> get_standard_option
('pve-vmid'),
1137 skiplock
=> get_standard_option
('skiplock'),
1139 type
=> 'string', format
=> 'pve-configid-list',
1140 description
=> "A list of settings you want to delete.",
1145 description
=> $opt_force_description,
1147 requires
=> 'delete',
1151 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1157 returns
=> { type
=> 'null' },
1160 &$update_vm_api($param, 1);
1166 __PACKAGE__-
>register_method({
1167 name
=> 'destroy_vm',
1172 description
=> "Destroy the vm (also delete all used/owned volumes).",
1174 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1177 additionalProperties
=> 0,
1179 node
=> get_standard_option
('pve-node'),
1180 vmid
=> get_standard_option
('pve-vmid'),
1181 skiplock
=> get_standard_option
('skiplock'),
1190 my $rpcenv = PVE
::RPCEnvironment
::get
();
1192 my $authuser = $rpcenv->get_user();
1194 my $vmid = $param->{vmid
};
1196 my $skiplock = $param->{skiplock
};
1197 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1198 if $skiplock && $authuser ne 'root@pam';
1201 my $conf = PVE
::QemuServer
::load_config
($vmid);
1203 my $storecfg = PVE
::Storage
::config
();
1205 my $delVMfromPoolFn = sub {
1206 my $usercfg = cfs_read_file
("user.cfg");
1207 if (my $pool = $usercfg->{vms
}->{$vmid}) {
1208 if (my $data = $usercfg->{pools
}->{$pool}) {
1209 delete $data->{vms
}->{$vmid};
1210 delete $usercfg->{vms
}->{$vmid};
1211 cfs_write_file
("user.cfg", $usercfg);
1219 syslog
('info', "destroy VM $vmid: $upid\n");
1221 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1223 PVE
::AccessControl
::remove_vm_from_pool
($vmid);
1226 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1229 __PACKAGE__-
>register_method({
1231 path
=> '{vmid}/unlink',
1235 description
=> "Unlink/delete disk images.",
1237 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1240 additionalProperties
=> 0,
1242 node
=> get_standard_option
('pve-node'),
1243 vmid
=> get_standard_option
('pve-vmid'),
1245 type
=> 'string', format
=> 'pve-configid-list',
1246 description
=> "A list of disk IDs you want to delete.",
1250 description
=> $opt_force_description,
1255 returns
=> { type
=> 'null'},
1259 $param->{delete} = extract_param
($param, 'idlist');
1261 __PACKAGE__-
>update_vm($param);
1268 __PACKAGE__-
>register_method({
1270 path
=> '{vmid}/vncproxy',
1274 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1276 description
=> "Creates a TCP VNC proxy connections.",
1278 additionalProperties
=> 0,
1280 node
=> get_standard_option
('pve-node'),
1281 vmid
=> get_standard_option
('pve-vmid'),
1285 description
=> "starts websockify instead of vncproxy",
1290 additionalProperties
=> 0,
1292 user
=> { type
=> 'string' },
1293 ticket
=> { type
=> 'string' },
1294 cert
=> { type
=> 'string' },
1295 port
=> { type
=> 'integer' },
1296 upid
=> { type
=> 'string' },
1302 my $rpcenv = PVE
::RPCEnvironment
::get
();
1304 my $authuser = $rpcenv->get_user();
1306 my $vmid = $param->{vmid
};
1307 my $node = $param->{node
};
1308 my $websocket = $param->{websocket
};
1310 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # check if VM exists
1312 my $authpath = "/vms/$vmid";
1314 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1316 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1319 my $port = PVE
::Tools
::next_vnc_port
();
1324 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1325 $remip = PVE
::Cluster
::remote_node_ip
($node);
1326 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1327 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1335 syslog
('info', "starting vnc proxy $upid\n");
1339 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1341 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1343 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1344 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1345 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1346 '-timeout', $timeout, '-authpath', $authpath,
1347 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1350 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1352 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1354 my $qmstr = join(' ', @$qmcmd);
1356 # also redirect stderr (else we get RFB protocol errors)
1357 $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1360 PVE
::Tools
::run_command
($cmd);
1365 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1367 PVE
::Tools
::wait_for_vnc_port
($port);
1378 __PACKAGE__-
>register_method({
1379 name
=> 'vncwebsocket',
1380 path
=> '{vmid}/vncwebsocket',
1383 description
=> "You also need to pass a valid ticket (vncticket).",
1384 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1386 description
=> "Opens a weksocket for VNC traffic.",
1388 additionalProperties
=> 0,
1390 node
=> get_standard_option
('pve-node'),
1391 vmid
=> get_standard_option
('pve-vmid'),
1393 description
=> "Ticket from previous call to vncproxy.",
1398 description
=> "Port number returned by previous vncproxy call.",
1408 port
=> { type
=> 'string' },
1414 my $rpcenv = PVE
::RPCEnvironment
::get
();
1416 my $authuser = $rpcenv->get_user();
1418 my $vmid = $param->{vmid
};
1419 my $node = $param->{node
};
1421 my $authpath = "/vms/$vmid";
1423 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1425 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # VM exists ?
1427 # Note: VNC ports are acessible from outside, so we do not gain any
1428 # security if we verify that $param->{port} belongs to VM $vmid. This
1429 # check is done by verifying the VNC ticket (inside VNC protocol).
1431 my $port = $param->{port
};
1433 return { port
=> $port };
1436 __PACKAGE__-
>register_method({
1437 name
=> 'spiceproxy',
1438 path
=> '{vmid}/spiceproxy',
1443 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1445 description
=> "Returns a SPICE configuration to connect to the VM.",
1447 additionalProperties
=> 0,
1449 node
=> get_standard_option
('pve-node'),
1450 vmid
=> get_standard_option
('pve-vmid'),
1451 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1454 returns
=> get_standard_option
('remote-viewer-config'),
1458 my $rpcenv = PVE
::RPCEnvironment
::get
();
1460 my $authuser = $rpcenv->get_user();
1462 my $vmid = $param->{vmid
};
1463 my $node = $param->{node
};
1464 my $proxy = $param->{proxy
};
1466 my $conf = PVE
::QemuServer
::load_config
($vmid, $node);
1467 my $title = "VM $vmid - $conf->{'name'}",
1469 my $port = PVE
::QemuServer
::spice_port
($vmid);
1471 my ($ticket, undef, $remote_viewer_config) =
1472 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1474 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1475 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1477 return $remote_viewer_config;
1480 __PACKAGE__-
>register_method({
1482 path
=> '{vmid}/status',
1485 description
=> "Directory index",
1490 additionalProperties
=> 0,
1492 node
=> get_standard_option
('pve-node'),
1493 vmid
=> get_standard_option
('pve-vmid'),
1501 subdir
=> { type
=> 'string' },
1504 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1510 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1513 { subdir
=> 'current' },
1514 { subdir
=> 'start' },
1515 { subdir
=> 'stop' },
1521 my $vm_is_ha_managed = sub {
1524 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1525 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1531 __PACKAGE__-
>register_method({
1532 name
=> 'vm_status',
1533 path
=> '{vmid}/status/current',
1536 protected
=> 1, # qemu pid files are only readable by root
1537 description
=> "Get virtual machine status.",
1539 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1542 additionalProperties
=> 0,
1544 node
=> get_standard_option
('pve-node'),
1545 vmid
=> get_standard_option
('pve-vmid'),
1548 returns
=> { type
=> 'object' },
1553 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1555 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1556 my $status = $vmstatus->{$param->{vmid
}};
1558 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1560 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1565 __PACKAGE__-
>register_method({
1567 path
=> '{vmid}/status/start',
1571 description
=> "Start virtual machine.",
1573 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1576 additionalProperties
=> 0,
1578 node
=> get_standard_option
('pve-node'),
1579 vmid
=> get_standard_option
('pve-vmid'),
1580 skiplock
=> get_standard_option
('skiplock'),
1581 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1582 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1583 machine
=> get_standard_option
('pve-qm-machine'),
1592 my $rpcenv = PVE
::RPCEnvironment
::get
();
1594 my $authuser = $rpcenv->get_user();
1596 my $node = extract_param
($param, 'node');
1598 my $vmid = extract_param
($param, 'vmid');
1600 my $machine = extract_param
($param, 'machine');
1602 my $stateuri = extract_param
($param, 'stateuri');
1603 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1604 if $stateuri && $authuser ne 'root@pam';
1606 my $skiplock = extract_param
($param, 'skiplock');
1607 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1608 if $skiplock && $authuser ne 'root@pam';
1610 my $migratedfrom = extract_param
($param, 'migratedfrom');
1611 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1612 if $migratedfrom && $authuser ne 'root@pam';
1614 # read spice ticket from STDIN
1616 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1617 if (defined(my $line = <>)) {
1619 $spice_ticket = $line;
1623 my $storecfg = PVE
::Storage
::config
();
1625 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1626 $rpcenv->{type
} ne 'ha') {
1631 my $service = "pvevm:$vmid";
1633 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1635 print "Executing HA start for VM $vmid\n";
1637 PVE
::Tools
::run_command
($cmd);
1642 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1649 syslog
('info', "start VM $vmid: $upid\n");
1651 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1652 $machine, $spice_ticket);
1657 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1661 __PACKAGE__-
>register_method({
1663 path
=> '{vmid}/status/stop',
1667 description
=> "Stop virtual machine.",
1669 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1672 additionalProperties
=> 0,
1674 node
=> get_standard_option
('pve-node'),
1675 vmid
=> get_standard_option
('pve-vmid'),
1676 skiplock
=> get_standard_option
('skiplock'),
1677 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1679 description
=> "Wait maximal timeout seconds.",
1685 description
=> "Do not decativate storage volumes.",
1698 my $rpcenv = PVE
::RPCEnvironment
::get
();
1700 my $authuser = $rpcenv->get_user();
1702 my $node = extract_param
($param, 'node');
1704 my $vmid = extract_param
($param, 'vmid');
1706 my $skiplock = extract_param
($param, 'skiplock');
1707 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1708 if $skiplock && $authuser ne 'root@pam';
1710 my $keepActive = extract_param
($param, 'keepActive');
1711 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1712 if $keepActive && $authuser ne 'root@pam';
1714 my $migratedfrom = extract_param
($param, 'migratedfrom');
1715 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1716 if $migratedfrom && $authuser ne 'root@pam';
1719 my $storecfg = PVE
::Storage
::config
();
1721 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1726 my $service = "pvevm:$vmid";
1728 my $cmd = ['clusvcadm', '-d', $service];
1730 print "Executing HA stop for VM $vmid\n";
1732 PVE
::Tools
::run_command
($cmd);
1737 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1743 syslog
('info', "stop VM $vmid: $upid\n");
1745 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1746 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1751 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1755 __PACKAGE__-
>register_method({
1757 path
=> '{vmid}/status/reset',
1761 description
=> "Reset virtual machine.",
1763 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1766 additionalProperties
=> 0,
1768 node
=> get_standard_option
('pve-node'),
1769 vmid
=> get_standard_option
('pve-vmid'),
1770 skiplock
=> get_standard_option
('skiplock'),
1779 my $rpcenv = PVE
::RPCEnvironment
::get
();
1781 my $authuser = $rpcenv->get_user();
1783 my $node = extract_param
($param, 'node');
1785 my $vmid = extract_param
($param, 'vmid');
1787 my $skiplock = extract_param
($param, 'skiplock');
1788 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1789 if $skiplock && $authuser ne 'root@pam';
1791 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1796 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1801 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1804 __PACKAGE__-
>register_method({
1805 name
=> 'vm_shutdown',
1806 path
=> '{vmid}/status/shutdown',
1810 description
=> "Shutdown virtual machine.",
1812 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1815 additionalProperties
=> 0,
1817 node
=> get_standard_option
('pve-node'),
1818 vmid
=> get_standard_option
('pve-vmid'),
1819 skiplock
=> get_standard_option
('skiplock'),
1821 description
=> "Wait maximal timeout seconds.",
1827 description
=> "Make sure the VM stops.",
1833 description
=> "Do not decativate storage volumes.",
1846 my $rpcenv = PVE
::RPCEnvironment
::get
();
1848 my $authuser = $rpcenv->get_user();
1850 my $node = extract_param
($param, 'node');
1852 my $vmid = extract_param
($param, 'vmid');
1854 my $skiplock = extract_param
($param, 'skiplock');
1855 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1856 if $skiplock && $authuser ne 'root@pam';
1858 my $keepActive = extract_param
($param, 'keepActive');
1859 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1860 if $keepActive && $authuser ne 'root@pam';
1862 my $storecfg = PVE
::Storage
::config
();
1867 syslog
('info', "shutdown VM $vmid: $upid\n");
1869 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1870 1, $param->{forceStop
}, $keepActive);
1875 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1878 __PACKAGE__-
>register_method({
1879 name
=> 'vm_suspend',
1880 path
=> '{vmid}/status/suspend',
1884 description
=> "Suspend virtual machine.",
1886 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1889 additionalProperties
=> 0,
1891 node
=> get_standard_option
('pve-node'),
1892 vmid
=> get_standard_option
('pve-vmid'),
1893 skiplock
=> get_standard_option
('skiplock'),
1902 my $rpcenv = PVE
::RPCEnvironment
::get
();
1904 my $authuser = $rpcenv->get_user();
1906 my $node = extract_param
($param, 'node');
1908 my $vmid = extract_param
($param, 'vmid');
1910 my $skiplock = extract_param
($param, 'skiplock');
1911 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1912 if $skiplock && $authuser ne 'root@pam';
1914 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1919 syslog
('info', "suspend VM $vmid: $upid\n");
1921 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1926 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1929 __PACKAGE__-
>register_method({
1930 name
=> 'vm_resume',
1931 path
=> '{vmid}/status/resume',
1935 description
=> "Resume virtual machine.",
1937 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1940 additionalProperties
=> 0,
1942 node
=> get_standard_option
('pve-node'),
1943 vmid
=> get_standard_option
('pve-vmid'),
1944 skiplock
=> get_standard_option
('skiplock'),
1953 my $rpcenv = PVE
::RPCEnvironment
::get
();
1955 my $authuser = $rpcenv->get_user();
1957 my $node = extract_param
($param, 'node');
1959 my $vmid = extract_param
($param, 'vmid');
1961 my $skiplock = extract_param
($param, 'skiplock');
1962 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1963 if $skiplock && $authuser ne 'root@pam';
1965 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1970 syslog
('info', "resume VM $vmid: $upid\n");
1972 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1977 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1980 __PACKAGE__-
>register_method({
1981 name
=> 'vm_sendkey',
1982 path
=> '{vmid}/sendkey',
1986 description
=> "Send key event to virtual machine.",
1988 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1991 additionalProperties
=> 0,
1993 node
=> get_standard_option
('pve-node'),
1994 vmid
=> get_standard_option
('pve-vmid'),
1995 skiplock
=> get_standard_option
('skiplock'),
1997 description
=> "The key (qemu monitor encoding).",
2002 returns
=> { type
=> 'null'},
2006 my $rpcenv = PVE
::RPCEnvironment
::get
();
2008 my $authuser = $rpcenv->get_user();
2010 my $node = extract_param
($param, 'node');
2012 my $vmid = extract_param
($param, 'vmid');
2014 my $skiplock = extract_param
($param, 'skiplock');
2015 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2016 if $skiplock && $authuser ne 'root@pam';
2018 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2023 __PACKAGE__-
>register_method({
2024 name
=> 'vm_feature',
2025 path
=> '{vmid}/feature',
2029 description
=> "Check if feature for virtual machine is available.",
2031 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2034 additionalProperties
=> 0,
2036 node
=> get_standard_option
('pve-node'),
2037 vmid
=> get_standard_option
('pve-vmid'),
2039 description
=> "Feature to check.",
2041 enum
=> [ 'snapshot', 'clone', 'copy' ],
2043 snapname
=> get_standard_option
('pve-snapshot-name', {
2051 hasFeature
=> { type
=> 'boolean' },
2054 items
=> { type
=> 'string' },
2061 my $node = extract_param
($param, 'node');
2063 my $vmid = extract_param
($param, 'vmid');
2065 my $snapname = extract_param
($param, 'snapname');
2067 my $feature = extract_param
($param, 'feature');
2069 my $running = PVE
::QemuServer
::check_running
($vmid);
2071 my $conf = PVE
::QemuServer
::load_config
($vmid);
2074 my $snap = $conf->{snapshots
}->{$snapname};
2075 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2078 my $storecfg = PVE
::Storage
::config
();
2080 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2081 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
2084 hasFeature
=> $hasFeature,
2085 nodes
=> [ keys %$nodelist ],
2089 __PACKAGE__-
>register_method({
2091 path
=> '{vmid}/clone',
2095 description
=> "Create a copy of virtual machine/template.",
2097 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2098 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2099 "'Datastore.AllocateSpace' on any used storage.",
2102 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2104 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2105 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2110 additionalProperties
=> 0,
2112 node
=> get_standard_option
('pve-node'),
2113 vmid
=> get_standard_option
('pve-vmid'),
2114 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2117 type
=> 'string', format
=> 'dns-name',
2118 description
=> "Set a name for the new VM.",
2123 description
=> "Description for the new VM.",
2127 type
=> 'string', format
=> 'pve-poolid',
2128 description
=> "Add the new VM to the specified pool.",
2130 snapname
=> get_standard_option
('pve-snapshot-name', {
2134 storage
=> get_standard_option
('pve-storage-id', {
2135 description
=> "Target storage for full clone.",
2140 description
=> "Target format for file storage.",
2144 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2149 description
=> "Create a full copy of all disk. This is always done when " .
2150 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2153 target
=> get_standard_option
('pve-node', {
2154 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2165 my $rpcenv = PVE
::RPCEnvironment
::get
();
2167 my $authuser = $rpcenv->get_user();
2169 my $node = extract_param
($param, 'node');
2171 my $vmid = extract_param
($param, 'vmid');
2173 my $newid = extract_param
($param, 'newid');
2175 my $pool = extract_param
($param, 'pool');
2177 if (defined($pool)) {
2178 $rpcenv->check_pool_exist($pool);
2181 my $snapname = extract_param
($param, 'snapname');
2183 my $storage = extract_param
($param, 'storage');
2185 my $format = extract_param
($param, 'format');
2187 my $target = extract_param
($param, 'target');
2189 my $localnode = PVE
::INotify
::nodename
();
2191 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2193 PVE
::Cluster
::check_node_exists
($target) if $target;
2195 my $storecfg = PVE
::Storage
::config
();
2198 # check if storage is enabled on local node
2199 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2201 # check if storage is available on target node
2202 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2203 # clone only works if target storage is shared
2204 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2205 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2209 PVE
::Cluster
::check_cfs_quorum
();
2211 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2213 # exclusive lock if VM is running - else shared lock is enough;
2214 my $shared_lock = $running ?
0 : 1;
2218 # do all tests after lock
2219 # we also try to do all tests before we fork the worker
2221 my $conf = PVE
::QemuServer
::load_config
($vmid);
2223 PVE
::QemuServer
::check_lock
($conf);
2225 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2227 die "unexpected state change\n" if $verify_running != $running;
2229 die "snapshot '$snapname' does not exist\n"
2230 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2232 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2234 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2236 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2238 my $conffile = PVE
::QemuServer
::config_file
($newid);
2240 die "unable to create VM $newid: config file already exists\n"
2243 my $newconf = { lock => 'clone' };
2247 foreach my $opt (keys %$oldconf) {
2248 my $value = $oldconf->{$opt};
2250 # do not copy snapshot related info
2251 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2252 $opt eq 'vmstate' || $opt eq 'snapstate';
2254 # always change MAC! address
2255 if ($opt =~ m/^net(\d+)$/) {
2256 my $net = PVE
::QemuServer
::parse_net
($value);
2257 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2258 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2259 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
2260 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2261 die "unable to parse drive options for '$opt'\n" if !$drive;
2262 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2263 $newconf->{$opt} = $value; # simply copy configuration
2265 if ($param->{full
} || !PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
2266 die "Full clone feature is not available"
2267 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2270 $drives->{$opt} = $drive;
2271 push @$vollist, $drive->{file
};
2274 # copy everything else
2275 $newconf->{$opt} = $value;
2279 delete $newconf->{template
};
2281 if ($param->{name
}) {
2282 $newconf->{name
} = $param->{name
};
2284 if ($oldconf->{name
}) {
2285 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2287 $newconf->{name
} = "Copy-of-VM-$vmid";
2291 if ($param->{description
}) {
2292 $newconf->{description
} = $param->{description
};
2295 # create empty/temp config - this fails if VM already exists on other node
2296 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2301 my $newvollist = [];
2304 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2306 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2308 foreach my $opt (keys %$drives) {
2309 my $drive = $drives->{$opt};
2311 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2312 $newid, $storage, $format, $drive->{full
}, $newvollist);
2314 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2316 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2319 delete $newconf->{lock};
2320 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2323 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2324 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist);
2326 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2327 die "Failed to move config to node '$target' - rename failed: $!\n"
2328 if !rename($conffile, $newconffile);
2331 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2336 sleep 1; # some storage like rbd need to wait before release volume - really?
2338 foreach my $volid (@$newvollist) {
2339 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2342 die "clone failed: $err";
2348 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2351 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2352 # Aquire exclusive lock lock for $newid
2353 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2358 __PACKAGE__-
>register_method({
2359 name
=> 'move_vm_disk',
2360 path
=> '{vmid}/move_disk',
2364 description
=> "Move volume to different storage.",
2366 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2367 "and 'Datastore.AllocateSpace' permissions on the storage.",
2370 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2371 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2375 additionalProperties
=> 0,
2377 node
=> get_standard_option
('pve-node'),
2378 vmid
=> get_standard_option
('pve-vmid'),
2381 description
=> "The disk you want to move.",
2382 enum
=> [ PVE
::QemuServer
::disknames
() ],
2384 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2387 description
=> "Target Format.",
2388 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2393 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2399 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2407 description
=> "the task ID.",
2412 my $rpcenv = PVE
::RPCEnvironment
::get
();
2414 my $authuser = $rpcenv->get_user();
2416 my $node = extract_param
($param, 'node');
2418 my $vmid = extract_param
($param, 'vmid');
2420 my $digest = extract_param
($param, 'digest');
2422 my $disk = extract_param
($param, 'disk');
2424 my $storeid = extract_param
($param, 'storage');
2426 my $format = extract_param
($param, 'format');
2428 my $storecfg = PVE
::Storage
::config
();
2430 my $updatefn = sub {
2432 my $conf = PVE
::QemuServer
::load_config
($vmid);
2434 die "checksum missmatch (file change by other user?)\n"
2435 if $digest && $digest ne $conf->{digest
};
2437 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2439 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2441 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2443 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2446 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2447 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2451 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2452 (!$format || !$oldfmt || $oldfmt eq $format);
2454 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2456 my $running = PVE
::QemuServer
::check_running
($vmid);
2458 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2462 my $newvollist = [];
2465 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2467 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2468 $vmid, $storeid, $format, 1, $newvollist);
2470 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2472 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2474 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2477 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2478 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2485 foreach my $volid (@$newvollist) {
2486 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2489 die "storage migration failed: $err";
2492 if ($param->{delete}) {
2493 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, 1);
2494 my $path = PVE
::Storage
::path
($storecfg, $old_volid);
2495 if ($used_paths->{$path}){
2496 warn "volume $old_volid have snapshots. Can't delete it\n";
2497 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid);
2498 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2500 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2506 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2509 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2512 __PACKAGE__-
>register_method({
2513 name
=> 'migrate_vm',
2514 path
=> '{vmid}/migrate',
2518 description
=> "Migrate virtual machine. Creates a new migration task.",
2520 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2523 additionalProperties
=> 0,
2525 node
=> get_standard_option
('pve-node'),
2526 vmid
=> get_standard_option
('pve-vmid'),
2527 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2530 description
=> "Use online/live migration.",
2535 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2542 description
=> "the task ID.",
2547 my $rpcenv = PVE
::RPCEnvironment
::get
();
2549 my $authuser = $rpcenv->get_user();
2551 my $target = extract_param
($param, 'target');
2553 my $localnode = PVE
::INotify
::nodename
();
2554 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2556 PVE
::Cluster
::check_cfs_quorum
();
2558 PVE
::Cluster
::check_node_exists
($target);
2560 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2562 my $vmid = extract_param
($param, 'vmid');
2564 raise_param_exc
({ force
=> "Only root may use this option." })
2565 if $param->{force
} && $authuser ne 'root@pam';
2568 my $conf = PVE
::QemuServer
::load_config
($vmid);
2570 # try to detect errors early
2572 PVE
::QemuServer
::check_lock
($conf);
2574 if (PVE
::QemuServer
::check_running
($vmid)) {
2575 die "cant migrate running VM without --online\n"
2576 if !$param->{online
};
2579 my $storecfg = PVE
::Storage
::config
();
2580 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2582 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2587 my $service = "pvevm:$vmid";
2589 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2591 print "Executing HA migrate for VM $vmid to node $target\n";
2593 PVE
::Tools
::run_command
($cmd);
2598 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2605 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2608 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2613 __PACKAGE__-
>register_method({
2615 path
=> '{vmid}/monitor',
2619 description
=> "Execute Qemu monitor commands.",
2621 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2624 additionalProperties
=> 0,
2626 node
=> get_standard_option
('pve-node'),
2627 vmid
=> get_standard_option
('pve-vmid'),
2630 description
=> "The monitor command.",
2634 returns
=> { type
=> 'string'},
2638 my $vmid = $param->{vmid
};
2640 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2644 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2646 $res = "ERROR: $@" if $@;
2651 __PACKAGE__-
>register_method({
2652 name
=> 'resize_vm',
2653 path
=> '{vmid}/resize',
2657 description
=> "Extend volume size.",
2659 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2662 additionalProperties
=> 0,
2664 node
=> get_standard_option
('pve-node'),
2665 vmid
=> get_standard_option
('pve-vmid'),
2666 skiplock
=> get_standard_option
('skiplock'),
2669 description
=> "The disk you want to resize.",
2670 enum
=> [PVE
::QemuServer
::disknames
()],
2674 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2675 description
=> "The new size. With the '+' sign the value is added to the actual size of the volume and without it, the value is taken as an absolute one. Shrinking disk size is not supported.",
2679 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2685 returns
=> { type
=> 'null'},
2689 my $rpcenv = PVE
::RPCEnvironment
::get
();
2691 my $authuser = $rpcenv->get_user();
2693 my $node = extract_param
($param, 'node');
2695 my $vmid = extract_param
($param, 'vmid');
2697 my $digest = extract_param
($param, 'digest');
2699 my $disk = extract_param
($param, 'disk');
2701 my $sizestr = extract_param
($param, 'size');
2703 my $skiplock = extract_param
($param, 'skiplock');
2704 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2705 if $skiplock && $authuser ne 'root@pam';
2707 my $storecfg = PVE
::Storage
::config
();
2709 my $updatefn = sub {
2711 my $conf = PVE
::QemuServer
::load_config
($vmid);
2713 die "checksum missmatch (file change by other user?)\n"
2714 if $digest && $digest ne $conf->{digest
};
2715 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2717 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2719 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2721 my $volid = $drive->{file
};
2723 die "disk '$disk' has no associated volume\n" if !$volid;
2725 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2727 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2729 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2731 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2733 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2734 my ($ext, $newsize, $unit) = ($1, $2, $4);
2737 $newsize = $newsize * 1024;
2738 } elsif ($unit eq 'M') {
2739 $newsize = $newsize * 1024 * 1024;
2740 } elsif ($unit eq 'G') {
2741 $newsize = $newsize * 1024 * 1024 * 1024;
2742 } elsif ($unit eq 'T') {
2743 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2746 $newsize += $size if $ext;
2747 $newsize = int($newsize);
2749 die "unable to skrink disk size\n" if $newsize < $size;
2751 return if $size == $newsize;
2753 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2755 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2757 $drive->{size
} = $newsize;
2758 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2760 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2763 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2767 __PACKAGE__-
>register_method({
2768 name
=> 'snapshot_list',
2769 path
=> '{vmid}/snapshot',
2771 description
=> "List all snapshots.",
2773 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2776 protected
=> 1, # qemu pid files are only readable by root
2778 additionalProperties
=> 0,
2780 vmid
=> get_standard_option
('pve-vmid'),
2781 node
=> get_standard_option
('pve-node'),
2790 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2795 my $vmid = $param->{vmid
};
2797 my $conf = PVE
::QemuServer
::load_config
($vmid);
2798 my $snaphash = $conf->{snapshots
} || {};
2802 foreach my $name (keys %$snaphash) {
2803 my $d = $snaphash->{$name};
2806 snaptime
=> $d->{snaptime
} || 0,
2807 vmstate
=> $d->{vmstate
} ?
1 : 0,
2808 description
=> $d->{description
} || '',
2810 $item->{parent
} = $d->{parent
} if $d->{parent
};
2811 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2815 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2816 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2817 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2819 push @$res, $current;
2824 __PACKAGE__-
>register_method({
2826 path
=> '{vmid}/snapshot',
2830 description
=> "Snapshot a VM.",
2832 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2835 additionalProperties
=> 0,
2837 node
=> get_standard_option
('pve-node'),
2838 vmid
=> get_standard_option
('pve-vmid'),
2839 snapname
=> get_standard_option
('pve-snapshot-name'),
2843 description
=> "Save the vmstate",
2848 description
=> "Freeze the filesystem",
2853 description
=> "A textual description or comment.",
2859 description
=> "the task ID.",
2864 my $rpcenv = PVE
::RPCEnvironment
::get
();
2866 my $authuser = $rpcenv->get_user();
2868 my $node = extract_param
($param, 'node');
2870 my $vmid = extract_param
($param, 'vmid');
2872 my $snapname = extract_param
($param, 'snapname');
2874 die "unable to use snapshot name 'current' (reserved name)\n"
2875 if $snapname eq 'current';
2878 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2879 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2880 $param->{freezefs
}, $param->{description
});
2883 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2886 __PACKAGE__-
>register_method({
2887 name
=> 'snapshot_cmd_idx',
2888 path
=> '{vmid}/snapshot/{snapname}',
2895 additionalProperties
=> 0,
2897 vmid
=> get_standard_option
('pve-vmid'),
2898 node
=> get_standard_option
('pve-node'),
2899 snapname
=> get_standard_option
('pve-snapshot-name'),
2908 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2915 push @$res, { cmd
=> 'rollback' };
2916 push @$res, { cmd
=> 'config' };
2921 __PACKAGE__-
>register_method({
2922 name
=> 'update_snapshot_config',
2923 path
=> '{vmid}/snapshot/{snapname}/config',
2927 description
=> "Update snapshot metadata.",
2929 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2932 additionalProperties
=> 0,
2934 node
=> get_standard_option
('pve-node'),
2935 vmid
=> get_standard_option
('pve-vmid'),
2936 snapname
=> get_standard_option
('pve-snapshot-name'),
2940 description
=> "A textual description or comment.",
2944 returns
=> { type
=> 'null' },
2948 my $rpcenv = PVE
::RPCEnvironment
::get
();
2950 my $authuser = $rpcenv->get_user();
2952 my $vmid = extract_param
($param, 'vmid');
2954 my $snapname = extract_param
($param, 'snapname');
2956 return undef if !defined($param->{description
});
2958 my $updatefn = sub {
2960 my $conf = PVE
::QemuServer
::load_config
($vmid);
2962 PVE
::QemuServer
::check_lock
($conf);
2964 my $snap = $conf->{snapshots
}->{$snapname};
2966 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2968 $snap->{description
} = $param->{description
} if defined($param->{description
});
2970 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2973 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2978 __PACKAGE__-
>register_method({
2979 name
=> 'get_snapshot_config',
2980 path
=> '{vmid}/snapshot/{snapname}/config',
2983 description
=> "Get snapshot configuration",
2985 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2988 additionalProperties
=> 0,
2990 node
=> get_standard_option
('pve-node'),
2991 vmid
=> get_standard_option
('pve-vmid'),
2992 snapname
=> get_standard_option
('pve-snapshot-name'),
2995 returns
=> { type
=> "object" },
2999 my $rpcenv = PVE
::RPCEnvironment
::get
();
3001 my $authuser = $rpcenv->get_user();
3003 my $vmid = extract_param
($param, 'vmid');
3005 my $snapname = extract_param
($param, 'snapname');
3007 my $conf = PVE
::QemuServer
::load_config
($vmid);
3009 my $snap = $conf->{snapshots
}->{$snapname};
3011 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3016 __PACKAGE__-
>register_method({
3018 path
=> '{vmid}/snapshot/{snapname}/rollback',
3022 description
=> "Rollback VM state to specified snapshot.",
3024 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3027 additionalProperties
=> 0,
3029 node
=> get_standard_option
('pve-node'),
3030 vmid
=> get_standard_option
('pve-vmid'),
3031 snapname
=> get_standard_option
('pve-snapshot-name'),
3036 description
=> "the task ID.",
3041 my $rpcenv = PVE
::RPCEnvironment
::get
();
3043 my $authuser = $rpcenv->get_user();
3045 my $node = extract_param
($param, 'node');
3047 my $vmid = extract_param
($param, 'vmid');
3049 my $snapname = extract_param
($param, 'snapname');
3052 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3053 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
3056 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3059 __PACKAGE__-
>register_method({
3060 name
=> 'delsnapshot',
3061 path
=> '{vmid}/snapshot/{snapname}',
3065 description
=> "Delete a VM snapshot.",
3067 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3070 additionalProperties
=> 0,
3072 node
=> get_standard_option
('pve-node'),
3073 vmid
=> get_standard_option
('pve-vmid'),
3074 snapname
=> get_standard_option
('pve-snapshot-name'),
3078 description
=> "For removal from config file, even if removing disk snapshots fails.",
3084 description
=> "the task ID.",
3089 my $rpcenv = PVE
::RPCEnvironment
::get
();
3091 my $authuser = $rpcenv->get_user();
3093 my $node = extract_param
($param, 'node');
3095 my $vmid = extract_param
($param, 'vmid');
3097 my $snapname = extract_param
($param, 'snapname');
3100 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3101 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3104 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3107 __PACKAGE__-
>register_method({
3109 path
=> '{vmid}/template',
3113 description
=> "Create a Template.",
3115 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3116 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3119 additionalProperties
=> 0,
3121 node
=> get_standard_option
('pve-node'),
3122 vmid
=> get_standard_option
('pve-vmid'),
3126 description
=> "If you want to convert only 1 disk to base image.",
3127 enum
=> [PVE
::QemuServer
::disknames
()],
3132 returns
=> { type
=> 'null'},
3136 my $rpcenv = PVE
::RPCEnvironment
::get
();
3138 my $authuser = $rpcenv->get_user();
3140 my $node = extract_param
($param, 'node');
3142 my $vmid = extract_param
($param, 'vmid');
3144 my $disk = extract_param
($param, 'disk');
3146 my $updatefn = sub {
3148 my $conf = PVE
::QemuServer
::load_config
($vmid);
3150 PVE
::QemuServer
::check_lock
($conf);
3152 die "unable to create template, because VM contains snapshots\n"
3153 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3155 die "you can't convert a template to a template\n"
3156 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3158 die "you can't convert a VM to template if VM is running\n"
3159 if PVE
::QemuServer
::check_running
($vmid);
3162 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3165 $conf->{template
} = 1;
3166 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3168 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3171 PVE
::QemuServer
::lock_config
($vmid, $updatefn);