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
} = int($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);
954 my $pending_delete_option = sub {
955 my ($conf, $key) = @_;
957 delete $conf->{pending
}->{$key};
958 my $pending_delete_hash = { $key => 1 };
959 foreach my $opt (PVE
::Tools
::split_list
($conf->{pending
}->{delete})) {
960 $pending_delete_hash->{$opt} = 1;
962 $conf->{pending
}->{delete} = join(',', keys %$pending_delete_hash);
965 my $pending_undelete_option = sub {
966 my ($conf, $key) = @_;
968 my $pending_delete_hash = {};
969 foreach my $opt (PVE
::Tools
::split_list
($conf->{pending
}->{delete})) {
970 $pending_delete_hash->{$opt} = 1;
972 delete $pending_delete_hash->{$key};
974 my @keylist = keys %$pending_delete_hash;
975 if (scalar(@keylist)) {
976 $conf->{pending
}->{delete} = join(',', @keylist);
978 delete $conf->{pending
}->{delete};
982 my $register_unused_drive = sub {
983 my ($conf, $drive) = @_;
984 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
985 my $volid = $drive->{file
};
986 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
987 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
994 my $conf = PVE
::QemuServer
::load_config
($vmid);
996 die "checksum missmatch (file change by other user?)\n"
997 if $digest && $digest ne $conf->{digest
};
999 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
1001 # fixme: wrong place? howto handle pending changes? @delete ?
1002 if ($param->{memory
} || defined($param->{balloon
})) {
1003 my $maxmem = $param->{memory
} || $conf->{memory
} || $defaults->{memory
};
1004 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{balloon
};
1006 die "balloon value too large (must be smaller than assigned memory)\n"
1007 if $balloon && $balloon > $maxmem;
1010 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1014 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1016 # write updates to pending section
1018 foreach my $opt (@delete) {
1019 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
1020 if ($opt =~ m/^unused/) {
1021 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1022 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1023 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
1024 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
1025 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive);
1026 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
1028 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
1029 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1030 &$register_unused_drive($conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1031 if defined($conf->{pending
}->{$opt});
1032 &$pending_delete_option($conf, $opt);
1033 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
1035 &$pending_delete_option($conf, $opt);
1036 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
1040 foreach my $opt (keys %$param) { # add/change
1041 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
1042 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1044 if (PVE
::QemuServer
::valid_drivename
($opt)) {
1045 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1046 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1047 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1049 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1051 &$register_unused_drive($conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1052 if defined($conf->{pending
}->{$opt});
1054 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1056 $conf->{pending
}->{$opt} = $param->{$opt};
1058 &$pending_undelete_option($conf, $opt);
1059 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
1062 # remove pending changes when nothing changed
1064 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
1065 foreach my $opt (keys %{$conf->{pending
}}) { # add/change
1066 if (defined($conf->{$opt}) && ($conf->{pending
}->{$opt} eq $conf->{$opt})) {
1068 delete $conf->{pending
}->{$opt};
1071 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1) if $changes;
1073 return if !scalar(keys %{$conf->{pending
}});
1075 return; # TODO: apply changes
1077 my $running = PVE
::QemuServer
::check_running
($vmid);
1079 foreach my $opt (keys %$param) { # add/change
1081 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
1083 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
1085 if (PVE
::QemuServer
::valid_drivename
($opt)) {
1087 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
1088 $opt, $param->{$opt}, $force);
1090 } elsif ($opt =~ m/^net(\d+)$/) { #nics
1092 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
1093 $opt, $param->{$opt});
1097 if($opt eq 'tablet' && $param->{$opt} == 1){
1098 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
1099 } elsif($opt eq 'tablet' && $param->{$opt} == 0){
1100 PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
1103 if($opt eq 'cores' && $conf->{maxcpus
}){
1104 PVE
::QemuServer
::qemu_cpu_hotplug
($vmid, $conf, $param->{$opt});
1107 $conf->{$opt} = $param->{$opt};
1108 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
1112 # allow manual ballooning if shares is set to zero
1113 if ($running && defined($param->{balloon
}) &&
1114 defined($conf->{shares
}) && ($conf->{shares
} == 0)) {
1115 my $balloon = $param->{'balloon'} || $conf->{memory
} || $defaults->{memory
};
1116 PVE
::QemuServer
::vm_mon_cmd
($vmid, "balloon", value
=> $balloon*1024*1024);
1124 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1126 if ($background_delay) {
1128 # Note: It would be better to do that in the Event based HTTPServer
1129 # to avoid blocking call to sleep.
1131 my $end_time = time() + $background_delay;
1133 my $task = PVE
::Tools
::upid_decode
($upid);
1136 while (time() < $end_time) {
1137 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1139 sleep(1); # this gets interrupted when child process ends
1143 my $status = PVE
::Tools
::upid_read_status
($upid);
1144 return undef if $status eq 'OK';
1153 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1156 my $vm_config_perm_list = [
1161 'VM.Config.Network',
1163 'VM.Config.Options',
1166 __PACKAGE__-
>register_method({
1167 name
=> 'update_vm_async',
1168 path
=> '{vmid}/config',
1172 description
=> "Set virtual machine options (asynchrounous API).",
1174 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1177 additionalProperties
=> 0,
1178 properties
=> PVE
::QemuServer
::json_config_properties
(
1180 node
=> get_standard_option
('pve-node'),
1181 vmid
=> get_standard_option
('pve-vmid'),
1182 skiplock
=> get_standard_option
('skiplock'),
1184 type
=> 'string', format
=> 'pve-configid-list',
1185 description
=> "A list of settings you want to delete.",
1190 description
=> $opt_force_description,
1192 requires
=> 'delete',
1196 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1200 background_delay
=> {
1202 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1213 code
=> $update_vm_api,
1216 __PACKAGE__-
>register_method({
1217 name
=> 'update_vm',
1218 path
=> '{vmid}/config',
1222 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1224 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1227 additionalProperties
=> 0,
1228 properties
=> PVE
::QemuServer
::json_config_properties
(
1230 node
=> get_standard_option
('pve-node'),
1231 vmid
=> get_standard_option
('pve-vmid'),
1232 skiplock
=> get_standard_option
('skiplock'),
1234 type
=> 'string', format
=> 'pve-configid-list',
1235 description
=> "A list of settings you want to delete.",
1240 description
=> $opt_force_description,
1242 requires
=> 'delete',
1246 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1252 returns
=> { type
=> 'null' },
1255 &$update_vm_api($param, 1);
1261 __PACKAGE__-
>register_method({
1262 name
=> 'destroy_vm',
1267 description
=> "Destroy the vm (also delete all used/owned volumes).",
1269 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1272 additionalProperties
=> 0,
1274 node
=> get_standard_option
('pve-node'),
1275 vmid
=> get_standard_option
('pve-vmid'),
1276 skiplock
=> get_standard_option
('skiplock'),
1285 my $rpcenv = PVE
::RPCEnvironment
::get
();
1287 my $authuser = $rpcenv->get_user();
1289 my $vmid = $param->{vmid
};
1291 my $skiplock = $param->{skiplock
};
1292 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1293 if $skiplock && $authuser ne 'root@pam';
1296 my $conf = PVE
::QemuServer
::load_config
($vmid);
1298 my $storecfg = PVE
::Storage
::config
();
1300 my $delVMfromPoolFn = sub {
1301 my $usercfg = cfs_read_file
("user.cfg");
1302 if (my $pool = $usercfg->{vms
}->{$vmid}) {
1303 if (my $data = $usercfg->{pools
}->{$pool}) {
1304 delete $data->{vms
}->{$vmid};
1305 delete $usercfg->{vms
}->{$vmid};
1306 cfs_write_file
("user.cfg", $usercfg);
1314 syslog
('info', "destroy VM $vmid: $upid\n");
1316 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1318 PVE
::AccessControl
::remove_vm_from_pool
($vmid);
1321 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1324 __PACKAGE__-
>register_method({
1326 path
=> '{vmid}/unlink',
1330 description
=> "Unlink/delete disk images.",
1332 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1335 additionalProperties
=> 0,
1337 node
=> get_standard_option
('pve-node'),
1338 vmid
=> get_standard_option
('pve-vmid'),
1340 type
=> 'string', format
=> 'pve-configid-list',
1341 description
=> "A list of disk IDs you want to delete.",
1345 description
=> $opt_force_description,
1350 returns
=> { type
=> 'null'},
1354 $param->{delete} = extract_param
($param, 'idlist');
1356 __PACKAGE__-
>update_vm($param);
1363 __PACKAGE__-
>register_method({
1365 path
=> '{vmid}/vncproxy',
1369 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1371 description
=> "Creates a TCP VNC proxy connections.",
1373 additionalProperties
=> 0,
1375 node
=> get_standard_option
('pve-node'),
1376 vmid
=> get_standard_option
('pve-vmid'),
1380 description
=> "starts websockify instead of vncproxy",
1385 additionalProperties
=> 0,
1387 user
=> { type
=> 'string' },
1388 ticket
=> { type
=> 'string' },
1389 cert
=> { type
=> 'string' },
1390 port
=> { type
=> 'integer' },
1391 upid
=> { type
=> 'string' },
1397 my $rpcenv = PVE
::RPCEnvironment
::get
();
1399 my $authuser = $rpcenv->get_user();
1401 my $vmid = $param->{vmid
};
1402 my $node = $param->{node
};
1403 my $websocket = $param->{websocket
};
1405 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # check if VM exists
1407 my $authpath = "/vms/$vmid";
1409 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1411 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1414 my $port = PVE
::Tools
::next_vnc_port
();
1419 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1420 $remip = PVE
::Cluster
::remote_node_ip
($node);
1421 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1422 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1430 syslog
('info', "starting vnc proxy $upid\n");
1434 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1436 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1438 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1439 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1440 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1441 '-timeout', $timeout, '-authpath', $authpath,
1442 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1445 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1447 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1449 my $qmstr = join(' ', @$qmcmd);
1451 # also redirect stderr (else we get RFB protocol errors)
1452 $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1455 PVE
::Tools
::run_command
($cmd);
1460 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1462 PVE
::Tools
::wait_for_vnc_port
($port);
1473 __PACKAGE__-
>register_method({
1474 name
=> 'vncwebsocket',
1475 path
=> '{vmid}/vncwebsocket',
1478 description
=> "You also need to pass a valid ticket (vncticket).",
1479 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1481 description
=> "Opens a weksocket for VNC traffic.",
1483 additionalProperties
=> 0,
1485 node
=> get_standard_option
('pve-node'),
1486 vmid
=> get_standard_option
('pve-vmid'),
1488 description
=> "Ticket from previous call to vncproxy.",
1493 description
=> "Port number returned by previous vncproxy call.",
1503 port
=> { type
=> 'string' },
1509 my $rpcenv = PVE
::RPCEnvironment
::get
();
1511 my $authuser = $rpcenv->get_user();
1513 my $vmid = $param->{vmid
};
1514 my $node = $param->{node
};
1516 my $authpath = "/vms/$vmid";
1518 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1520 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # VM exists ?
1522 # Note: VNC ports are acessible from outside, so we do not gain any
1523 # security if we verify that $param->{port} belongs to VM $vmid. This
1524 # check is done by verifying the VNC ticket (inside VNC protocol).
1526 my $port = $param->{port
};
1528 return { port
=> $port };
1531 __PACKAGE__-
>register_method({
1532 name
=> 'spiceproxy',
1533 path
=> '{vmid}/spiceproxy',
1538 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1540 description
=> "Returns a SPICE configuration to connect to the VM.",
1542 additionalProperties
=> 0,
1544 node
=> get_standard_option
('pve-node'),
1545 vmid
=> get_standard_option
('pve-vmid'),
1546 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1549 returns
=> get_standard_option
('remote-viewer-config'),
1553 my $rpcenv = PVE
::RPCEnvironment
::get
();
1555 my $authuser = $rpcenv->get_user();
1557 my $vmid = $param->{vmid
};
1558 my $node = $param->{node
};
1559 my $proxy = $param->{proxy
};
1561 my $conf = PVE
::QemuServer
::load_config
($vmid, $node);
1562 my $title = "VM $vmid - $conf->{'name'}",
1564 my $port = PVE
::QemuServer
::spice_port
($vmid);
1566 my ($ticket, undef, $remote_viewer_config) =
1567 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1569 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1570 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1572 return $remote_viewer_config;
1575 __PACKAGE__-
>register_method({
1577 path
=> '{vmid}/status',
1580 description
=> "Directory index",
1585 additionalProperties
=> 0,
1587 node
=> get_standard_option
('pve-node'),
1588 vmid
=> get_standard_option
('pve-vmid'),
1596 subdir
=> { type
=> 'string' },
1599 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1605 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1608 { subdir
=> 'current' },
1609 { subdir
=> 'start' },
1610 { subdir
=> 'stop' },
1616 my $vm_is_ha_managed = sub {
1619 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1620 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1626 __PACKAGE__-
>register_method({
1627 name
=> 'vm_status',
1628 path
=> '{vmid}/status/current',
1631 protected
=> 1, # qemu pid files are only readable by root
1632 description
=> "Get virtual machine status.",
1634 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1637 additionalProperties
=> 0,
1639 node
=> get_standard_option
('pve-node'),
1640 vmid
=> get_standard_option
('pve-vmid'),
1643 returns
=> { type
=> 'object' },
1648 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1650 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1651 my $status = $vmstatus->{$param->{vmid
}};
1653 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1655 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1660 __PACKAGE__-
>register_method({
1662 path
=> '{vmid}/status/start',
1666 description
=> "Start virtual machine.",
1668 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1671 additionalProperties
=> 0,
1673 node
=> get_standard_option
('pve-node'),
1674 vmid
=> get_standard_option
('pve-vmid'),
1675 skiplock
=> get_standard_option
('skiplock'),
1676 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1677 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1678 machine
=> get_standard_option
('pve-qm-machine'),
1687 my $rpcenv = PVE
::RPCEnvironment
::get
();
1689 my $authuser = $rpcenv->get_user();
1691 my $node = extract_param
($param, 'node');
1693 my $vmid = extract_param
($param, 'vmid');
1695 my $machine = extract_param
($param, 'machine');
1697 my $stateuri = extract_param
($param, 'stateuri');
1698 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1699 if $stateuri && $authuser ne 'root@pam';
1701 my $skiplock = extract_param
($param, 'skiplock');
1702 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1703 if $skiplock && $authuser ne 'root@pam';
1705 my $migratedfrom = extract_param
($param, 'migratedfrom');
1706 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1707 if $migratedfrom && $authuser ne 'root@pam';
1709 # read spice ticket from STDIN
1711 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1712 if (defined(my $line = <>)) {
1714 $spice_ticket = $line;
1718 my $storecfg = PVE
::Storage
::config
();
1720 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1721 $rpcenv->{type
} ne 'ha') {
1726 my $service = "pvevm:$vmid";
1728 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1730 print "Executing HA start for VM $vmid\n";
1732 PVE
::Tools
::run_command
($cmd);
1737 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1744 syslog
('info', "start VM $vmid: $upid\n");
1746 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1747 $machine, $spice_ticket);
1752 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1756 __PACKAGE__-
>register_method({
1758 path
=> '{vmid}/status/stop',
1762 description
=> "Stop virtual machine.",
1764 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1767 additionalProperties
=> 0,
1769 node
=> get_standard_option
('pve-node'),
1770 vmid
=> get_standard_option
('pve-vmid'),
1771 skiplock
=> get_standard_option
('skiplock'),
1772 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1774 description
=> "Wait maximal timeout seconds.",
1780 description
=> "Do not decativate storage volumes.",
1793 my $rpcenv = PVE
::RPCEnvironment
::get
();
1795 my $authuser = $rpcenv->get_user();
1797 my $node = extract_param
($param, 'node');
1799 my $vmid = extract_param
($param, 'vmid');
1801 my $skiplock = extract_param
($param, 'skiplock');
1802 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1803 if $skiplock && $authuser ne 'root@pam';
1805 my $keepActive = extract_param
($param, 'keepActive');
1806 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1807 if $keepActive && $authuser ne 'root@pam';
1809 my $migratedfrom = extract_param
($param, 'migratedfrom');
1810 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1811 if $migratedfrom && $authuser ne 'root@pam';
1814 my $storecfg = PVE
::Storage
::config
();
1816 if (&$vm_is_ha_managed($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1821 my $service = "pvevm:$vmid";
1823 my $cmd = ['clusvcadm', '-d', $service];
1825 print "Executing HA stop for VM $vmid\n";
1827 PVE
::Tools
::run_command
($cmd);
1832 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1838 syslog
('info', "stop VM $vmid: $upid\n");
1840 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1841 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1846 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1850 __PACKAGE__-
>register_method({
1852 path
=> '{vmid}/status/reset',
1856 description
=> "Reset virtual machine.",
1858 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1861 additionalProperties
=> 0,
1863 node
=> get_standard_option
('pve-node'),
1864 vmid
=> get_standard_option
('pve-vmid'),
1865 skiplock
=> get_standard_option
('skiplock'),
1874 my $rpcenv = PVE
::RPCEnvironment
::get
();
1876 my $authuser = $rpcenv->get_user();
1878 my $node = extract_param
($param, 'node');
1880 my $vmid = extract_param
($param, 'vmid');
1882 my $skiplock = extract_param
($param, 'skiplock');
1883 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1884 if $skiplock && $authuser ne 'root@pam';
1886 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1891 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1896 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1899 __PACKAGE__-
>register_method({
1900 name
=> 'vm_shutdown',
1901 path
=> '{vmid}/status/shutdown',
1905 description
=> "Shutdown virtual machine.",
1907 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1910 additionalProperties
=> 0,
1912 node
=> get_standard_option
('pve-node'),
1913 vmid
=> get_standard_option
('pve-vmid'),
1914 skiplock
=> get_standard_option
('skiplock'),
1916 description
=> "Wait maximal timeout seconds.",
1922 description
=> "Make sure the VM stops.",
1928 description
=> "Do not decativate storage volumes.",
1941 my $rpcenv = PVE
::RPCEnvironment
::get
();
1943 my $authuser = $rpcenv->get_user();
1945 my $node = extract_param
($param, 'node');
1947 my $vmid = extract_param
($param, 'vmid');
1949 my $skiplock = extract_param
($param, 'skiplock');
1950 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1951 if $skiplock && $authuser ne 'root@pam';
1953 my $keepActive = extract_param
($param, 'keepActive');
1954 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1955 if $keepActive && $authuser ne 'root@pam';
1957 my $storecfg = PVE
::Storage
::config
();
1962 syslog
('info', "shutdown VM $vmid: $upid\n");
1964 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1965 1, $param->{forceStop
}, $keepActive);
1970 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1973 __PACKAGE__-
>register_method({
1974 name
=> 'vm_suspend',
1975 path
=> '{vmid}/status/suspend',
1979 description
=> "Suspend virtual machine.",
1981 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1984 additionalProperties
=> 0,
1986 node
=> get_standard_option
('pve-node'),
1987 vmid
=> get_standard_option
('pve-vmid'),
1988 skiplock
=> get_standard_option
('skiplock'),
1997 my $rpcenv = PVE
::RPCEnvironment
::get
();
1999 my $authuser = $rpcenv->get_user();
2001 my $node = extract_param
($param, 'node');
2003 my $vmid = extract_param
($param, 'vmid');
2005 my $skiplock = extract_param
($param, 'skiplock');
2006 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2007 if $skiplock && $authuser ne 'root@pam';
2009 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2014 syslog
('info', "suspend VM $vmid: $upid\n");
2016 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2021 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2024 __PACKAGE__-
>register_method({
2025 name
=> 'vm_resume',
2026 path
=> '{vmid}/status/resume',
2030 description
=> "Resume virtual machine.",
2032 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2035 additionalProperties
=> 0,
2037 node
=> get_standard_option
('pve-node'),
2038 vmid
=> get_standard_option
('pve-vmid'),
2039 skiplock
=> get_standard_option
('skiplock'),
2048 my $rpcenv = PVE
::RPCEnvironment
::get
();
2050 my $authuser = $rpcenv->get_user();
2052 my $node = extract_param
($param, 'node');
2054 my $vmid = extract_param
($param, 'vmid');
2056 my $skiplock = extract_param
($param, 'skiplock');
2057 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2058 if $skiplock && $authuser ne 'root@pam';
2060 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2065 syslog
('info', "resume VM $vmid: $upid\n");
2067 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
2072 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2075 __PACKAGE__-
>register_method({
2076 name
=> 'vm_sendkey',
2077 path
=> '{vmid}/sendkey',
2081 description
=> "Send key event to virtual machine.",
2083 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2086 additionalProperties
=> 0,
2088 node
=> get_standard_option
('pve-node'),
2089 vmid
=> get_standard_option
('pve-vmid'),
2090 skiplock
=> get_standard_option
('skiplock'),
2092 description
=> "The key (qemu monitor encoding).",
2097 returns
=> { type
=> 'null'},
2101 my $rpcenv = PVE
::RPCEnvironment
::get
();
2103 my $authuser = $rpcenv->get_user();
2105 my $node = extract_param
($param, 'node');
2107 my $vmid = extract_param
($param, 'vmid');
2109 my $skiplock = extract_param
($param, 'skiplock');
2110 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2111 if $skiplock && $authuser ne 'root@pam';
2113 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2118 __PACKAGE__-
>register_method({
2119 name
=> 'vm_feature',
2120 path
=> '{vmid}/feature',
2124 description
=> "Check if feature for virtual machine is available.",
2126 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2129 additionalProperties
=> 0,
2131 node
=> get_standard_option
('pve-node'),
2132 vmid
=> get_standard_option
('pve-vmid'),
2134 description
=> "Feature to check.",
2136 enum
=> [ 'snapshot', 'clone', 'copy' ],
2138 snapname
=> get_standard_option
('pve-snapshot-name', {
2146 hasFeature
=> { type
=> 'boolean' },
2149 items
=> { type
=> 'string' },
2156 my $node = extract_param
($param, 'node');
2158 my $vmid = extract_param
($param, 'vmid');
2160 my $snapname = extract_param
($param, 'snapname');
2162 my $feature = extract_param
($param, 'feature');
2164 my $running = PVE
::QemuServer
::check_running
($vmid);
2166 my $conf = PVE
::QemuServer
::load_config
($vmid);
2169 my $snap = $conf->{snapshots
}->{$snapname};
2170 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2173 my $storecfg = PVE
::Storage
::config
();
2175 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2176 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
2179 hasFeature
=> $hasFeature,
2180 nodes
=> [ keys %$nodelist ],
2184 __PACKAGE__-
>register_method({
2186 path
=> '{vmid}/clone',
2190 description
=> "Create a copy of virtual machine/template.",
2192 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2193 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2194 "'Datastore.AllocateSpace' on any used storage.",
2197 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2199 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2200 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2205 additionalProperties
=> 0,
2207 node
=> get_standard_option
('pve-node'),
2208 vmid
=> get_standard_option
('pve-vmid'),
2209 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2212 type
=> 'string', format
=> 'dns-name',
2213 description
=> "Set a name for the new VM.",
2218 description
=> "Description for the new VM.",
2222 type
=> 'string', format
=> 'pve-poolid',
2223 description
=> "Add the new VM to the specified pool.",
2225 snapname
=> get_standard_option
('pve-snapshot-name', {
2228 storage
=> get_standard_option
('pve-storage-id', {
2229 description
=> "Target storage for full clone.",
2234 description
=> "Target format for file storage.",
2238 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2243 description
=> "Create a full copy of all disk. This is always done when " .
2244 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2247 target
=> get_standard_option
('pve-node', {
2248 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2259 my $rpcenv = PVE
::RPCEnvironment
::get
();
2261 my $authuser = $rpcenv->get_user();
2263 my $node = extract_param
($param, 'node');
2265 my $vmid = extract_param
($param, 'vmid');
2267 my $newid = extract_param
($param, 'newid');
2269 my $pool = extract_param
($param, 'pool');
2271 if (defined($pool)) {
2272 $rpcenv->check_pool_exist($pool);
2275 my $snapname = extract_param
($param, 'snapname');
2277 my $storage = extract_param
($param, 'storage');
2279 my $format = extract_param
($param, 'format');
2281 my $target = extract_param
($param, 'target');
2283 my $localnode = PVE
::INotify
::nodename
();
2285 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2287 PVE
::Cluster
::check_node_exists
($target) if $target;
2289 my $storecfg = PVE
::Storage
::config
();
2292 # check if storage is enabled on local node
2293 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2295 # check if storage is available on target node
2296 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2297 # clone only works if target storage is shared
2298 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2299 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2303 PVE
::Cluster
::check_cfs_quorum
();
2305 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2307 # exclusive lock if VM is running - else shared lock is enough;
2308 my $shared_lock = $running ?
0 : 1;
2312 # do all tests after lock
2313 # we also try to do all tests before we fork the worker
2315 my $conf = PVE
::QemuServer
::load_config
($vmid);
2317 PVE
::QemuServer
::check_lock
($conf);
2319 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2321 die "unexpected state change\n" if $verify_running != $running;
2323 die "snapshot '$snapname' does not exist\n"
2324 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2326 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2328 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2330 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2332 my $conffile = PVE
::QemuServer
::config_file
($newid);
2334 die "unable to create VM $newid: config file already exists\n"
2337 my $newconf = { lock => 'clone' };
2341 foreach my $opt (keys %$oldconf) {
2342 my $value = $oldconf->{$opt};
2344 # do not copy snapshot related info
2345 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2346 $opt eq 'vmstate' || $opt eq 'snapstate';
2348 # always change MAC! address
2349 if ($opt =~ m/^net(\d+)$/) {
2350 my $net = PVE
::QemuServer
::parse_net
($value);
2351 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2352 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2353 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
2354 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2355 die "unable to parse drive options for '$opt'\n" if !$drive;
2356 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2357 $newconf->{$opt} = $value; # simply copy configuration
2359 if ($param->{full
}) {
2360 die "Full clone feature is not available"
2361 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2364 # not full means clone instead of copy
2365 die "Linked clone feature is not available"
2366 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2368 $drives->{$opt} = $drive;
2369 push @$vollist, $drive->{file
};
2372 # copy everything else
2373 $newconf->{$opt} = $value;
2377 # auto generate a new uuid
2378 my ($uuid, $uuid_str);
2379 UUID
::generate
($uuid);
2380 UUID
::unparse
($uuid, $uuid_str);
2381 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2382 $smbios1->{uuid
} = $uuid_str;
2383 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2385 delete $newconf->{template
};
2387 if ($param->{name
}) {
2388 $newconf->{name
} = $param->{name
};
2390 if ($oldconf->{name
}) {
2391 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2393 $newconf->{name
} = "Copy-of-VM-$vmid";
2397 if ($param->{description
}) {
2398 $newconf->{description
} = $param->{description
};
2401 # create empty/temp config - this fails if VM already exists on other node
2402 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2407 my $newvollist = [];
2410 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2412 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2414 foreach my $opt (keys %$drives) {
2415 my $drive = $drives->{$opt};
2417 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2418 $newid, $storage, $format, $drive->{full
}, $newvollist);
2420 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2422 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2425 delete $newconf->{lock};
2426 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2429 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2430 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist);
2432 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2433 die "Failed to move config to node '$target' - rename failed: $!\n"
2434 if !rename($conffile, $newconffile);
2437 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2442 sleep 1; # some storage like rbd need to wait before release volume - really?
2444 foreach my $volid (@$newvollist) {
2445 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2448 die "clone failed: $err";
2454 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2457 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2458 # Aquire exclusive lock lock for $newid
2459 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2464 __PACKAGE__-
>register_method({
2465 name
=> 'move_vm_disk',
2466 path
=> '{vmid}/move_disk',
2470 description
=> "Move volume to different storage.",
2472 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2473 "and 'Datastore.AllocateSpace' permissions on the storage.",
2476 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2477 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2481 additionalProperties
=> 0,
2483 node
=> get_standard_option
('pve-node'),
2484 vmid
=> get_standard_option
('pve-vmid'),
2487 description
=> "The disk you want to move.",
2488 enum
=> [ PVE
::QemuServer
::disknames
() ],
2490 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2493 description
=> "Target Format.",
2494 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2499 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2505 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2513 description
=> "the task ID.",
2518 my $rpcenv = PVE
::RPCEnvironment
::get
();
2520 my $authuser = $rpcenv->get_user();
2522 my $node = extract_param
($param, 'node');
2524 my $vmid = extract_param
($param, 'vmid');
2526 my $digest = extract_param
($param, 'digest');
2528 my $disk = extract_param
($param, 'disk');
2530 my $storeid = extract_param
($param, 'storage');
2532 my $format = extract_param
($param, 'format');
2534 my $storecfg = PVE
::Storage
::config
();
2536 my $updatefn = sub {
2538 my $conf = PVE
::QemuServer
::load_config
($vmid);
2540 die "checksum missmatch (file change by other user?)\n"
2541 if $digest && $digest ne $conf->{digest
};
2543 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2545 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2547 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2549 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2552 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2553 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2557 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2558 (!$format || !$oldfmt || $oldfmt eq $format);
2560 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2562 my $running = PVE
::QemuServer
::check_running
($vmid);
2564 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2568 my $newvollist = [];
2571 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2573 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2574 $vmid, $storeid, $format, 1, $newvollist);
2576 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2578 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2580 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2583 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2584 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2591 foreach my $volid (@$newvollist) {
2592 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2595 die "storage migration failed: $err";
2598 if ($param->{delete}) {
2599 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, 1);
2600 my $path = PVE
::Storage
::path
($storecfg, $old_volid);
2601 if ($used_paths->{$path}){
2602 warn "volume $old_volid have snapshots. Can't delete it\n";
2603 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid);
2604 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2606 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2612 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2615 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2618 __PACKAGE__-
>register_method({
2619 name
=> 'migrate_vm',
2620 path
=> '{vmid}/migrate',
2624 description
=> "Migrate virtual machine. Creates a new migration task.",
2626 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2629 additionalProperties
=> 0,
2631 node
=> get_standard_option
('pve-node'),
2632 vmid
=> get_standard_option
('pve-vmid'),
2633 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2636 description
=> "Use online/live migration.",
2641 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2648 description
=> "the task ID.",
2653 my $rpcenv = PVE
::RPCEnvironment
::get
();
2655 my $authuser = $rpcenv->get_user();
2657 my $target = extract_param
($param, 'target');
2659 my $localnode = PVE
::INotify
::nodename
();
2660 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2662 PVE
::Cluster
::check_cfs_quorum
();
2664 PVE
::Cluster
::check_node_exists
($target);
2666 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2668 my $vmid = extract_param
($param, 'vmid');
2670 raise_param_exc
({ force
=> "Only root may use this option." })
2671 if $param->{force
} && $authuser ne 'root@pam';
2674 my $conf = PVE
::QemuServer
::load_config
($vmid);
2676 # try to detect errors early
2678 PVE
::QemuServer
::check_lock
($conf);
2680 if (PVE
::QemuServer
::check_running
($vmid)) {
2681 die "cant migrate running VM without --online\n"
2682 if !$param->{online
};
2685 my $storecfg = PVE
::Storage
::config
();
2686 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2688 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2693 my $service = "pvevm:$vmid";
2695 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2697 print "Executing HA migrate for VM $vmid to node $target\n";
2699 PVE
::Tools
::run_command
($cmd);
2704 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2711 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2714 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2719 __PACKAGE__-
>register_method({
2721 path
=> '{vmid}/monitor',
2725 description
=> "Execute Qemu monitor commands.",
2727 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2730 additionalProperties
=> 0,
2732 node
=> get_standard_option
('pve-node'),
2733 vmid
=> get_standard_option
('pve-vmid'),
2736 description
=> "The monitor command.",
2740 returns
=> { type
=> 'string'},
2744 my $vmid = $param->{vmid
};
2746 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2750 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2752 $res = "ERROR: $@" if $@;
2757 __PACKAGE__-
>register_method({
2758 name
=> 'resize_vm',
2759 path
=> '{vmid}/resize',
2763 description
=> "Extend volume size.",
2765 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2768 additionalProperties
=> 0,
2770 node
=> get_standard_option
('pve-node'),
2771 vmid
=> get_standard_option
('pve-vmid'),
2772 skiplock
=> get_standard_option
('skiplock'),
2775 description
=> "The disk you want to resize.",
2776 enum
=> [PVE
::QemuServer
::disknames
()],
2780 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2781 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.",
2785 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2791 returns
=> { type
=> 'null'},
2795 my $rpcenv = PVE
::RPCEnvironment
::get
();
2797 my $authuser = $rpcenv->get_user();
2799 my $node = extract_param
($param, 'node');
2801 my $vmid = extract_param
($param, 'vmid');
2803 my $digest = extract_param
($param, 'digest');
2805 my $disk = extract_param
($param, 'disk');
2807 my $sizestr = extract_param
($param, 'size');
2809 my $skiplock = extract_param
($param, 'skiplock');
2810 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2811 if $skiplock && $authuser ne 'root@pam';
2813 my $storecfg = PVE
::Storage
::config
();
2815 my $updatefn = sub {
2817 my $conf = PVE
::QemuServer
::load_config
($vmid);
2819 die "checksum missmatch (file change by other user?)\n"
2820 if $digest && $digest ne $conf->{digest
};
2821 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2823 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2825 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2827 my $volid = $drive->{file
};
2829 die "disk '$disk' has no associated volume\n" if !$volid;
2831 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2833 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2835 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2837 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2839 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2840 my ($ext, $newsize, $unit) = ($1, $2, $4);
2843 $newsize = $newsize * 1024;
2844 } elsif ($unit eq 'M') {
2845 $newsize = $newsize * 1024 * 1024;
2846 } elsif ($unit eq 'G') {
2847 $newsize = $newsize * 1024 * 1024 * 1024;
2848 } elsif ($unit eq 'T') {
2849 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2852 $newsize += $size if $ext;
2853 $newsize = int($newsize);
2855 die "unable to skrink disk size\n" if $newsize < $size;
2857 return if $size == $newsize;
2859 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2861 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2863 $drive->{size
} = $newsize;
2864 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2866 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2869 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2873 __PACKAGE__-
>register_method({
2874 name
=> 'snapshot_list',
2875 path
=> '{vmid}/snapshot',
2877 description
=> "List all snapshots.",
2879 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2882 protected
=> 1, # qemu pid files are only readable by root
2884 additionalProperties
=> 0,
2886 vmid
=> get_standard_option
('pve-vmid'),
2887 node
=> get_standard_option
('pve-node'),
2896 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2901 my $vmid = $param->{vmid
};
2903 my $conf = PVE
::QemuServer
::load_config
($vmid);
2904 my $snaphash = $conf->{snapshots
} || {};
2908 foreach my $name (keys %$snaphash) {
2909 my $d = $snaphash->{$name};
2912 snaptime
=> $d->{snaptime
} || 0,
2913 vmstate
=> $d->{vmstate
} ?
1 : 0,
2914 description
=> $d->{description
} || '',
2916 $item->{parent
} = $d->{parent
} if $d->{parent
};
2917 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2921 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2922 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2923 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2925 push @$res, $current;
2930 __PACKAGE__-
>register_method({
2932 path
=> '{vmid}/snapshot',
2936 description
=> "Snapshot a VM.",
2938 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2941 additionalProperties
=> 0,
2943 node
=> get_standard_option
('pve-node'),
2944 vmid
=> get_standard_option
('pve-vmid'),
2945 snapname
=> get_standard_option
('pve-snapshot-name'),
2949 description
=> "Save the vmstate",
2954 description
=> "A textual description or comment.",
2960 description
=> "the task ID.",
2965 my $rpcenv = PVE
::RPCEnvironment
::get
();
2967 my $authuser = $rpcenv->get_user();
2969 my $node = extract_param
($param, 'node');
2971 my $vmid = extract_param
($param, 'vmid');
2973 my $snapname = extract_param
($param, 'snapname');
2975 die "unable to use snapshot name 'current' (reserved name)\n"
2976 if $snapname eq 'current';
2979 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2980 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2981 $param->{description
});
2984 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2987 __PACKAGE__-
>register_method({
2988 name
=> 'snapshot_cmd_idx',
2989 path
=> '{vmid}/snapshot/{snapname}',
2996 additionalProperties
=> 0,
2998 vmid
=> get_standard_option
('pve-vmid'),
2999 node
=> get_standard_option
('pve-node'),
3000 snapname
=> get_standard_option
('pve-snapshot-name'),
3009 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3016 push @$res, { cmd
=> 'rollback' };
3017 push @$res, { cmd
=> 'config' };
3022 __PACKAGE__-
>register_method({
3023 name
=> 'update_snapshot_config',
3024 path
=> '{vmid}/snapshot/{snapname}/config',
3028 description
=> "Update snapshot metadata.",
3030 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3033 additionalProperties
=> 0,
3035 node
=> get_standard_option
('pve-node'),
3036 vmid
=> get_standard_option
('pve-vmid'),
3037 snapname
=> get_standard_option
('pve-snapshot-name'),
3041 description
=> "A textual description or comment.",
3045 returns
=> { type
=> 'null' },
3049 my $rpcenv = PVE
::RPCEnvironment
::get
();
3051 my $authuser = $rpcenv->get_user();
3053 my $vmid = extract_param
($param, 'vmid');
3055 my $snapname = extract_param
($param, 'snapname');
3057 return undef if !defined($param->{description
});
3059 my $updatefn = sub {
3061 my $conf = PVE
::QemuServer
::load_config
($vmid);
3063 PVE
::QemuServer
::check_lock
($conf);
3065 my $snap = $conf->{snapshots
}->{$snapname};
3067 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3069 $snap->{description
} = $param->{description
} if defined($param->{description
});
3071 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3074 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
3079 __PACKAGE__-
>register_method({
3080 name
=> 'get_snapshot_config',
3081 path
=> '{vmid}/snapshot/{snapname}/config',
3084 description
=> "Get snapshot configuration",
3086 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3089 additionalProperties
=> 0,
3091 node
=> get_standard_option
('pve-node'),
3092 vmid
=> get_standard_option
('pve-vmid'),
3093 snapname
=> get_standard_option
('pve-snapshot-name'),
3096 returns
=> { type
=> "object" },
3100 my $rpcenv = PVE
::RPCEnvironment
::get
();
3102 my $authuser = $rpcenv->get_user();
3104 my $vmid = extract_param
($param, 'vmid');
3106 my $snapname = extract_param
($param, 'snapname');
3108 my $conf = PVE
::QemuServer
::load_config
($vmid);
3110 my $snap = $conf->{snapshots
}->{$snapname};
3112 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3117 __PACKAGE__-
>register_method({
3119 path
=> '{vmid}/snapshot/{snapname}/rollback',
3123 description
=> "Rollback VM state to specified snapshot.",
3125 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3128 additionalProperties
=> 0,
3130 node
=> get_standard_option
('pve-node'),
3131 vmid
=> get_standard_option
('pve-vmid'),
3132 snapname
=> get_standard_option
('pve-snapshot-name'),
3137 description
=> "the task ID.",
3142 my $rpcenv = PVE
::RPCEnvironment
::get
();
3144 my $authuser = $rpcenv->get_user();
3146 my $node = extract_param
($param, 'node');
3148 my $vmid = extract_param
($param, 'vmid');
3150 my $snapname = extract_param
($param, 'snapname');
3153 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3154 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
3157 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3160 __PACKAGE__-
>register_method({
3161 name
=> 'delsnapshot',
3162 path
=> '{vmid}/snapshot/{snapname}',
3166 description
=> "Delete a VM snapshot.",
3168 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3171 additionalProperties
=> 0,
3173 node
=> get_standard_option
('pve-node'),
3174 vmid
=> get_standard_option
('pve-vmid'),
3175 snapname
=> get_standard_option
('pve-snapshot-name'),
3179 description
=> "For removal from config file, even if removing disk snapshots fails.",
3185 description
=> "the task ID.",
3190 my $rpcenv = PVE
::RPCEnvironment
::get
();
3192 my $authuser = $rpcenv->get_user();
3194 my $node = extract_param
($param, 'node');
3196 my $vmid = extract_param
($param, 'vmid');
3198 my $snapname = extract_param
($param, 'snapname');
3201 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3202 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3205 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3208 __PACKAGE__-
>register_method({
3210 path
=> '{vmid}/template',
3214 description
=> "Create a Template.",
3216 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3217 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3220 additionalProperties
=> 0,
3222 node
=> get_standard_option
('pve-node'),
3223 vmid
=> get_standard_option
('pve-vmid'),
3227 description
=> "If you want to convert only 1 disk to base image.",
3228 enum
=> [PVE
::QemuServer
::disknames
()],
3233 returns
=> { type
=> 'null'},
3237 my $rpcenv = PVE
::RPCEnvironment
::get
();
3239 my $authuser = $rpcenv->get_user();
3241 my $node = extract_param
($param, 'node');
3243 my $vmid = extract_param
($param, 'vmid');
3245 my $disk = extract_param
($param, 'disk');
3247 my $updatefn = sub {
3249 my $conf = PVE
::QemuServer
::load_config
($vmid);
3251 PVE
::QemuServer
::check_lock
($conf);
3253 die "unable to create template, because VM contains snapshots\n"
3254 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3256 die "you can't convert a template to a template\n"
3257 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3259 die "you can't convert a VM to template if VM is running\n"
3260 if PVE
::QemuServer
::check_running
($vmid);
3263 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3266 $conf->{template
} = 1;
3267 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3269 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3272 PVE
::QemuServer
::lock_config
($vmid, $updatefn);