1 package PVE
::API2
::Qemu
;
12 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
14 use PVE
::Tools
qw(extract_param);
15 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
17 use PVE
::JSONSchema
qw(get_standard_option);
19 use PVE
::ReplicationConfig
;
20 use PVE
::GuestHelpers
;
24 use PVE
::RPCEnvironment
;
25 use PVE
::AccessControl
;
29 use PVE
::API2
::Firewall
::VM
;
30 use PVE
::API2
::Qemu
::Agent
;
33 if (!$ENV{PVE_GENERATING_DOCS
}) {
34 require PVE
::HA
::Env
::PVE2
;
35 import PVE
::HA
::Env
::PVE2
;
36 require PVE
::HA
::Config
;
37 import PVE
::HA
::Config
;
41 use Data
::Dumper
; # fixme: remove
43 use base
qw(PVE::RESTHandler);
45 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.";
47 my $resolve_cdrom_alias = sub {
50 if (my $value = $param->{cdrom
}) {
51 $value .= ",media=cdrom" if $value !~ m/media=/;
52 $param->{ide2
} = $value;
53 delete $param->{cdrom
};
57 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
58 my $check_storage_access = sub {
59 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
61 PVE
::QemuServer
::foreach_drive
($settings, sub {
62 my ($ds, $drive) = @_;
64 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
66 my $volid = $drive->{file
};
68 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit')) {
70 } elsif ($volid =~ m/^(([^:\s]+):)?(cloudinit)$/) {
72 } elsif ($isCDROM && ($volid eq 'cdrom')) {
73 $rpcenv->check($authuser, "/", ['Sys.Console']);
74 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
75 my ($storeid, $size) = ($2 || $default_storage, $3);
76 die "no storage ID specified (and no default storage)\n" if !$storeid;
77 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
78 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
79 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
80 if !$scfg->{content
}->{images
};
82 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
86 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
87 if defined($settings->{vmstatestorage
});
90 my $check_storage_access_clone = sub {
91 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
95 PVE
::QemuServer
::foreach_drive
($conf, sub {
96 my ($ds, $drive) = @_;
98 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
100 my $volid = $drive->{file
};
102 return if !$volid || $volid eq 'none';
105 if ($volid eq 'cdrom') {
106 $rpcenv->check($authuser, "/", ['Sys.Console']);
108 # we simply allow access
109 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
110 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
111 $sharedvm = 0 if !$scfg->{shared
};
115 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
116 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
117 $sharedvm = 0 if !$scfg->{shared
};
119 $sid = $storage if $storage;
120 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
124 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
125 if defined($conf->{vmstatestorage
});
130 # Note: $pool is only needed when creating a VM, because pool permissions
131 # are automatically inherited if VM already exists inside a pool.
132 my $create_disks = sub {
133 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
140 my ($ds, $disk) = @_;
142 my $volid = $disk->{file
};
144 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
145 delete $disk->{size
};
146 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
147 } elsif ($volid =~ m!^(?:([^/:\s]+):)?cloudinit$!) {
148 my $storeid = $1 || $default_storage;
149 die "no storage ID specified (and no default storage)\n" if !$storeid;
150 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
151 my $name = "vm-$vmid-cloudinit";
159 # FIXME: Reasonable size? qcow2 shouldn't grow if the space isn't used anyway?
160 my $cloudinit_iso_size = 5; # in MB
161 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
162 $fmt, $name, $cloudinit_iso_size*1024);
163 $disk->{file
} = $volid;
164 $disk->{media
} = 'cdrom';
165 push @$vollist, $volid;
166 delete $disk->{format
}; # no longer needed
167 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
168 } elsif ($volid =~ $NEW_DISK_RE) {
169 my ($storeid, $size) = ($2 || $default_storage, $3);
170 die "no storage ID specified (and no default storage)\n" if !$storeid;
171 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
172 my $fmt = $disk->{format
} || $defformat;
174 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
177 if ($ds eq 'efidisk0') {
178 ($volid, $size) = PVE
::QemuServer
::create_efidisk
($storecfg, $storeid, $vmid, $fmt);
180 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
182 push @$vollist, $volid;
183 $disk->{file
} = $volid;
184 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
185 delete $disk->{format
}; # no longer needed
186 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
189 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
191 my $volid_is_new = 1;
194 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
195 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
200 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
202 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
204 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
206 die "volume $volid does not exists\n" if !$size;
208 $disk->{size
} = $size;
211 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
215 eval { PVE
::QemuServer
::foreach_drive
($settings, $code); };
217 # free allocated images on error
219 syslog
('err', "VM $vmid creating disks failed");
220 foreach my $volid (@$vollist) {
221 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
227 # modify vm config if everything went well
228 foreach my $ds (keys %$res) {
229 $conf->{$ds} = $res->{$ds};
246 my $memoryoptions = {
252 my $hwtypeoptions = {
264 my $generaloptions = {
271 'migrate_downtime' => 1,
272 'migrate_speed' => 1,
284 my $vmpoweroptions = {
291 'vmstatestorage' => 1,
294 my $cloudinitoptions = {
303 my $check_vm_modify_config_perm = sub {
304 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
306 return 1 if $authuser eq 'root@pam';
308 foreach my $opt (@$key_list) {
309 # disk checks need to be done somewhere else
310 next if PVE
::QemuServer
::is_valid_drivename
($opt);
311 next if $opt eq 'cdrom';
312 next if $opt =~ m/^unused\d+$/;
314 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
315 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
316 } elsif ($memoryoptions->{$opt}) {
317 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
318 } elsif ($hwtypeoptions->{$opt}) {
319 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
320 } elsif ($generaloptions->{$opt}) {
321 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
322 # special case for startup since it changes host behaviour
323 if ($opt eq 'startup') {
324 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
326 } elsif ($vmpoweroptions->{$opt}) {
327 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
328 } elsif ($diskoptions->{$opt}) {
329 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
330 } elsif ($cloudinitoptions->{$opt} || ($opt =~ m/^(?:net|ipconfig)\d+$/)) {
331 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
333 # catches usb\d+, hostpci\d+, args, lock, etc.
334 # new options will be checked here
335 die "only root can set '$opt' config\n";
342 __PACKAGE__-
>register_method({
346 description
=> "Virtual machine index (per node).",
348 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
352 protected
=> 1, # qemu pid files are only readable by root
354 additionalProperties
=> 0,
356 node
=> get_standard_option
('pve-node'),
360 description
=> "Determine the full status of active VMs.",
368 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
370 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
375 my $rpcenv = PVE
::RPCEnvironment
::get
();
376 my $authuser = $rpcenv->get_user();
378 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
381 foreach my $vmid (keys %$vmstatus) {
382 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
384 my $data = $vmstatus->{$vmid};
393 __PACKAGE__-
>register_method({
397 description
=> "Create or restore a virtual machine.",
399 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
400 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
401 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
402 user
=> 'all', # check inside
407 additionalProperties
=> 0,
408 properties
=> PVE
::QemuServer
::json_config_properties
(
410 node
=> get_standard_option
('pve-node'),
411 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
413 description
=> "The backup file.",
417 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
419 storage
=> get_standard_option
('pve-storage-id', {
420 description
=> "Default storage.",
422 completion
=> \
&PVE
::QemuServer
::complete_storage
,
427 description
=> "Allow to overwrite existing VM.",
428 requires
=> 'archive',
433 description
=> "Assign a unique random ethernet address.",
434 requires
=> 'archive',
438 type
=> 'string', format
=> 'pve-poolid',
439 description
=> "Add the VM to the specified pool.",
442 description
=> "Override i/o bandwidth limit (in KiB/s).",
451 description
=> "Start VM after it was created successfully.",
461 my $rpcenv = PVE
::RPCEnvironment
::get
();
463 my $authuser = $rpcenv->get_user();
465 my $node = extract_param
($param, 'node');
467 my $vmid = extract_param
($param, 'vmid');
469 my $archive = extract_param
($param, 'archive');
470 my $is_restore = !!$archive;
472 my $storage = extract_param
($param, 'storage');
474 my $force = extract_param
($param, 'force');
476 my $unique = extract_param
($param, 'unique');
478 my $pool = extract_param
($param, 'pool');
480 my $bwlimit = extract_param
($param, 'bwlimit');
482 my $start_after_create = extract_param
($param, 'start');
484 my $filename = PVE
::QemuConfig-
>config_file($vmid);
486 my $storecfg = PVE
::Storage
::config
();
488 if (defined(my $ssh_keys = $param->{sshkeys
})) {
489 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
490 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
493 PVE
::Cluster
::check_cfs_quorum
();
495 if (defined($pool)) {
496 $rpcenv->check_pool_exist($pool);
499 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
500 if defined($storage);
502 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
504 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
506 } elsif ($archive && $force && (-f
$filename) &&
507 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
508 # OK: user has VM.Backup permissions, and want to restore an existing VM
514 &$resolve_cdrom_alias($param);
516 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
518 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
520 foreach my $opt (keys %$param) {
521 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
522 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
523 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
525 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
526 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
530 PVE
::QemuServer
::add_random_macs
($param);
532 my $keystr = join(' ', keys %$param);
533 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
535 if ($archive eq '-') {
536 die "pipe requires cli environment\n"
537 if $rpcenv->{type
} ne 'cli';
539 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
540 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
544 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
546 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
547 die "$emsg $@" if $@;
549 my $restorefn = sub {
550 my $conf = PVE
::QemuConfig-
>load_config($vmid);
552 PVE
::QemuConfig-
>check_protection($conf, $emsg);
554 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
555 die "$emsg vm is a template\n" if PVE
::QemuConfig-
>is_template($conf);
558 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
562 bwlimit
=> $bwlimit, });
564 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
566 if ($start_after_create) {
567 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
572 # ensure no old replication state are exists
573 PVE
::ReplicationState
::delete_guest_states
($vmid);
575 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
579 # ensure no old replication state are exists
580 PVE
::ReplicationState
::delete_guest_states
($vmid);
590 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
592 if (!$conf->{bootdisk
}) {
593 my $firstdisk = PVE
::QemuServer
::resolve_first_disk
($conf);
594 $conf->{bootdisk
} = $firstdisk if $firstdisk;
597 # auto generate uuid if user did not specify smbios1 option
598 if (!$conf->{smbios1
}) {
599 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
602 PVE
::QemuConfig-
>write_config($vmid, $conf);
608 foreach my $volid (@$vollist) {
609 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
615 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
618 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
620 if ($start_after_create) {
621 print "Execute autostart\n";
622 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
627 my ($code, $worker_name);
629 $worker_name = 'qmrestore';
631 eval { $restorefn->() };
633 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
639 $worker_name = 'qmcreate';
641 eval { $createfn->() };
644 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
646 or die "failed to remove config file: $@\n";
654 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
657 __PACKAGE__-
>register_method({
662 description
=> "Directory index",
667 additionalProperties
=> 0,
669 node
=> get_standard_option
('pve-node'),
670 vmid
=> get_standard_option
('pve-vmid'),
678 subdir
=> { type
=> 'string' },
681 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
687 { subdir
=> 'config' },
688 { subdir
=> 'pending' },
689 { subdir
=> 'status' },
690 { subdir
=> 'unlink' },
691 { subdir
=> 'vncproxy' },
692 { subdir
=> 'termproxy' },
693 { subdir
=> 'migrate' },
694 { subdir
=> 'resize' },
695 { subdir
=> 'move' },
697 { subdir
=> 'rrddata' },
698 { subdir
=> 'monitor' },
699 { subdir
=> 'agent' },
700 { subdir
=> 'snapshot' },
701 { subdir
=> 'spiceproxy' },
702 { subdir
=> 'sendkey' },
703 { subdir
=> 'firewall' },
709 __PACKAGE__-
>register_method ({
710 subclass
=> "PVE::API2::Firewall::VM",
711 path
=> '{vmid}/firewall',
714 __PACKAGE__-
>register_method ({
715 subclass
=> "PVE::API2::Qemu::Agent",
716 path
=> '{vmid}/agent',
719 __PACKAGE__-
>register_method({
721 path
=> '{vmid}/rrd',
723 protected
=> 1, # fixme: can we avoid that?
725 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
727 description
=> "Read VM RRD statistics (returns PNG)",
729 additionalProperties
=> 0,
731 node
=> get_standard_option
('pve-node'),
732 vmid
=> get_standard_option
('pve-vmid'),
734 description
=> "Specify the time frame you are interested in.",
736 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
739 description
=> "The list of datasources you want to display.",
740 type
=> 'string', format
=> 'pve-configid-list',
743 description
=> "The RRD consolidation function",
745 enum
=> [ 'AVERAGE', 'MAX' ],
753 filename
=> { type
=> 'string' },
759 return PVE
::Cluster
::create_rrd_graph
(
760 "pve2-vm/$param->{vmid}", $param->{timeframe
},
761 $param->{ds
}, $param->{cf
});
765 __PACKAGE__-
>register_method({
767 path
=> '{vmid}/rrddata',
769 protected
=> 1, # fixme: can we avoid that?
771 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
773 description
=> "Read VM RRD statistics",
775 additionalProperties
=> 0,
777 node
=> get_standard_option
('pve-node'),
778 vmid
=> get_standard_option
('pve-vmid'),
780 description
=> "Specify the time frame you are interested in.",
782 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
785 description
=> "The RRD consolidation function",
787 enum
=> [ 'AVERAGE', 'MAX' ],
802 return PVE
::Cluster
::create_rrd_data
(
803 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
807 __PACKAGE__-
>register_method({
809 path
=> '{vmid}/config',
812 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
814 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
817 additionalProperties
=> 0,
819 node
=> get_standard_option
('pve-node'),
820 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
822 description
=> "Get current values (instead of pending values).",
830 description
=> "The current VM configuration.",
832 properties
=> PVE
::QemuServer
::json_config_properties
({
835 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
842 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
844 delete $conf->{snapshots
};
846 if (!$param->{current
}) {
847 foreach my $opt (keys %{$conf->{pending
}}) {
848 next if $opt eq 'delete';
849 my $value = $conf->{pending
}->{$opt};
850 next if ref($value); # just to be sure
851 $conf->{$opt} = $value;
853 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
854 foreach my $opt (keys %$pending_delete_hash) {
855 delete $conf->{$opt} if $conf->{$opt};
859 delete $conf->{pending
};
861 # hide cloudinit password
862 if ($conf->{cipassword
}) {
863 $conf->{cipassword
} = '**********';
869 __PACKAGE__-
>register_method({
870 name
=> 'vm_pending',
871 path
=> '{vmid}/pending',
874 description
=> "Get virtual machine configuration, including pending changes.",
876 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
879 additionalProperties
=> 0,
881 node
=> get_standard_option
('pve-node'),
882 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
891 description
=> "Configuration option name.",
895 description
=> "Current value.",
900 description
=> "Pending value.",
905 description
=> "Indicates a pending delete request if present and not 0. " .
906 "The value 2 indicates a force-delete request.",
918 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
920 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
924 foreach my $opt (keys %$conf) {
925 next if ref($conf->{$opt});
926 my $item = { key
=> $opt };
927 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
928 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
929 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
931 # hide cloudinit password
932 if ($opt eq 'cipassword') {
933 $item->{value
} = '**********' if defined($item->{value
});
934 # the trailing space so that the pending string is different
935 $item->{pending
} = '********** ' if defined($item->{pending
});
940 foreach my $opt (keys %{$conf->{pending
}}) {
941 next if $opt eq 'delete';
942 next if ref($conf->{pending
}->{$opt}); # just to be sure
943 next if defined($conf->{$opt});
944 my $item = { key
=> $opt };
945 $item->{pending
} = $conf->{pending
}->{$opt};
947 # hide cloudinit password
948 if ($opt eq 'cipassword') {
949 $item->{pending
} = '**********' if defined($item->{pending
});
954 while (my ($opt, $force) = each %$pending_delete_hash) {
955 next if $conf->{pending
}->{$opt}; # just to be sure
956 next if $conf->{$opt};
957 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
964 # POST/PUT {vmid}/config implementation
966 # The original API used PUT (idempotent) an we assumed that all operations
967 # are fast. But it turned out that almost any configuration change can
968 # involve hot-plug actions, or disk alloc/free. Such actions can take long
969 # time to complete and have side effects (not idempotent).
971 # The new implementation uses POST and forks a worker process. We added
972 # a new option 'background_delay'. If specified we wait up to
973 # 'background_delay' second for the worker task to complete. It returns null
974 # if the task is finished within that time, else we return the UPID.
976 my $update_vm_api = sub {
977 my ($param, $sync) = @_;
979 my $rpcenv = PVE
::RPCEnvironment
::get
();
981 my $authuser = $rpcenv->get_user();
983 my $node = extract_param
($param, 'node');
985 my $vmid = extract_param
($param, 'vmid');
987 my $digest = extract_param
($param, 'digest');
989 my $background_delay = extract_param
($param, 'background_delay');
991 if (defined(my $cipassword = $param->{cipassword
})) {
992 # Same logic as in cloud-init (but with the regex fixed...)
993 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
994 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
997 my @paramarr = (); # used for log message
998 foreach my $key (sort keys %$param) {
999 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1000 push @paramarr, "-$key", $value;
1003 my $skiplock = extract_param
($param, 'skiplock');
1004 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1005 if $skiplock && $authuser ne 'root@pam';
1007 my $delete_str = extract_param
($param, 'delete');
1009 my $revert_str = extract_param
($param, 'revert');
1011 my $force = extract_param
($param, 'force');
1013 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1014 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1015 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1018 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1020 my $storecfg = PVE
::Storage
::config
();
1022 my $defaults = PVE
::QemuServer
::load_defaults
();
1024 &$resolve_cdrom_alias($param);
1026 # now try to verify all parameters
1029 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1030 if (!PVE
::QemuServer
::option_exists
($opt)) {
1031 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1034 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1035 "-revert $opt' at the same time" })
1036 if defined($param->{$opt});
1038 $revert->{$opt} = 1;
1042 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1043 $opt = 'ide2' if $opt eq 'cdrom';
1045 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1046 "-delete $opt' at the same time" })
1047 if defined($param->{$opt});
1049 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1050 "-revert $opt' at the same time" })
1053 if (!PVE
::QemuServer
::option_exists
($opt)) {
1054 raise_param_exc
({ delete => "unknown option '$opt'" });
1060 my $repl_conf = PVE
::ReplicationConfig-
>new();
1061 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1062 my $check_replication = sub {
1064 return if !$is_replicated;
1065 my $volid = $drive->{file
};
1066 return if !$volid || !($drive->{replicate
}//1);
1067 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1068 my ($storeid, $format);
1069 if ($volid =~ $NEW_DISK_RE) {
1071 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1073 ($storeid, undef) = PVE
::Storage
::parse_volume_id
($volid, 1);
1074 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1076 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1077 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1078 return if $scfg->{shared
};
1079 die "cannot add non-replicatable volume to a replicated VM\n";
1082 foreach my $opt (keys %$param) {
1083 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1084 # cleanup drive path
1085 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1086 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1087 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1088 $check_replication->($drive);
1089 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1090 } elsif ($opt =~ m/^net(\d+)$/) {
1092 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1093 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1097 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1099 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1101 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1103 my $updatefn = sub {
1105 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1107 die "checksum missmatch (file change by other user?)\n"
1108 if $digest && $digest ne $conf->{digest
};
1110 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1112 foreach my $opt (keys %$revert) {
1113 if (defined($conf->{$opt})) {
1114 $param->{$opt} = $conf->{$opt};
1115 } elsif (defined($conf->{pending
}->{$opt})) {
1120 if ($param->{memory
} || defined($param->{balloon
})) {
1121 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1122 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1124 die "balloon value too large (must be smaller than assigned memory)\n"
1125 if $balloon && $balloon > $maxmem;
1128 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1132 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1134 # write updates to pending section
1136 my $modified = {}; # record what $option we modify
1138 foreach my $opt (@delete) {
1139 $modified->{$opt} = 1;
1140 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1141 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1142 warn "cannot delete '$opt' - not set in current configuration!\n";
1143 $modified->{$opt} = 0;
1147 if ($opt =~ m/^unused/) {
1148 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1149 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1150 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1151 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1152 delete $conf->{$opt};
1153 PVE
::QemuConfig-
>write_config($vmid, $conf);
1155 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1156 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1157 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1158 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1159 if defined($conf->{pending
}->{$opt});
1160 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1161 PVE
::QemuConfig-
>write_config($vmid, $conf);
1163 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1164 PVE
::QemuConfig-
>write_config($vmid, $conf);
1168 foreach my $opt (keys %$param) { # add/change
1169 $modified->{$opt} = 1;
1170 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1171 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1173 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1174 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1175 # FIXME: cloudinit: CDROM or Disk?
1176 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1177 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1179 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1181 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1182 if defined($conf->{pending
}->{$opt});
1184 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1186 $conf->{pending
}->{$opt} = $param->{$opt};
1188 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1189 PVE
::QemuConfig-
>write_config($vmid, $conf);
1192 # remove pending changes when nothing changed
1193 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1194 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1195 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1197 return if !scalar(keys %{$conf->{pending
}});
1199 my $running = PVE
::QemuServer
::check_running
($vmid);
1201 # apply pending changes
1203 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1207 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1208 raise_param_exc
($errors) if scalar(keys %$errors);
1210 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1220 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1222 if ($background_delay) {
1224 # Note: It would be better to do that in the Event based HTTPServer
1225 # to avoid blocking call to sleep.
1227 my $end_time = time() + $background_delay;
1229 my $task = PVE
::Tools
::upid_decode
($upid);
1232 while (time() < $end_time) {
1233 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1235 sleep(1); # this gets interrupted when child process ends
1239 my $status = PVE
::Tools
::upid_read_status
($upid);
1240 return undef if $status eq 'OK';
1249 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1252 my $vm_config_perm_list = [
1257 'VM.Config.Network',
1259 'VM.Config.Options',
1262 __PACKAGE__-
>register_method({
1263 name
=> 'update_vm_async',
1264 path
=> '{vmid}/config',
1268 description
=> "Set virtual machine options (asynchrounous API).",
1270 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1273 additionalProperties
=> 0,
1274 properties
=> PVE
::QemuServer
::json_config_properties
(
1276 node
=> get_standard_option
('pve-node'),
1277 vmid
=> get_standard_option
('pve-vmid'),
1278 skiplock
=> get_standard_option
('skiplock'),
1280 type
=> 'string', format
=> 'pve-configid-list',
1281 description
=> "A list of settings you want to delete.",
1285 type
=> 'string', format
=> 'pve-configid-list',
1286 description
=> "Revert a pending change.",
1291 description
=> $opt_force_description,
1293 requires
=> 'delete',
1297 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1301 background_delay
=> {
1303 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1314 code
=> $update_vm_api,
1317 __PACKAGE__-
>register_method({
1318 name
=> 'update_vm',
1319 path
=> '{vmid}/config',
1323 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1325 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1328 additionalProperties
=> 0,
1329 properties
=> PVE
::QemuServer
::json_config_properties
(
1331 node
=> get_standard_option
('pve-node'),
1332 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1333 skiplock
=> get_standard_option
('skiplock'),
1335 type
=> 'string', format
=> 'pve-configid-list',
1336 description
=> "A list of settings you want to delete.",
1340 type
=> 'string', format
=> 'pve-configid-list',
1341 description
=> "Revert a pending change.",
1346 description
=> $opt_force_description,
1348 requires
=> 'delete',
1352 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1358 returns
=> { type
=> 'null' },
1361 &$update_vm_api($param, 1);
1367 __PACKAGE__-
>register_method({
1368 name
=> 'destroy_vm',
1373 description
=> "Destroy the vm (also delete all used/owned volumes).",
1375 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1378 additionalProperties
=> 0,
1380 node
=> get_standard_option
('pve-node'),
1381 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1382 skiplock
=> get_standard_option
('skiplock'),
1391 my $rpcenv = PVE
::RPCEnvironment
::get
();
1393 my $authuser = $rpcenv->get_user();
1395 my $vmid = $param->{vmid
};
1397 my $skiplock = $param->{skiplock
};
1398 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1399 if $skiplock && $authuser ne 'root@pam';
1402 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1404 my $storecfg = PVE
::Storage
::config
();
1406 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1408 die "unable to remove VM $vmid - used in HA resources\n"
1409 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1411 # do not allow destroy if there are replication jobs
1412 my $repl_conf = PVE
::ReplicationConfig-
>new();
1413 $repl_conf->check_for_existing_jobs($vmid);
1415 # early tests (repeat after locking)
1416 die "VM $vmid is running - destroy failed\n"
1417 if PVE
::QemuServer
::check_running
($vmid);
1422 syslog
('info', "destroy VM $vmid: $upid\n");
1424 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1426 PVE
::AccessControl
::remove_vm_access
($vmid);
1428 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1431 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1434 __PACKAGE__-
>register_method({
1436 path
=> '{vmid}/unlink',
1440 description
=> "Unlink/delete disk images.",
1442 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1445 additionalProperties
=> 0,
1447 node
=> get_standard_option
('pve-node'),
1448 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1450 type
=> 'string', format
=> 'pve-configid-list',
1451 description
=> "A list of disk IDs you want to delete.",
1455 description
=> $opt_force_description,
1460 returns
=> { type
=> 'null'},
1464 $param->{delete} = extract_param
($param, 'idlist');
1466 __PACKAGE__-
>update_vm($param);
1473 __PACKAGE__-
>register_method({
1475 path
=> '{vmid}/vncproxy',
1479 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1481 description
=> "Creates a TCP VNC proxy connections.",
1483 additionalProperties
=> 0,
1485 node
=> get_standard_option
('pve-node'),
1486 vmid
=> get_standard_option
('pve-vmid'),
1490 description
=> "starts websockify instead of vncproxy",
1495 additionalProperties
=> 0,
1497 user
=> { type
=> 'string' },
1498 ticket
=> { type
=> 'string' },
1499 cert
=> { type
=> 'string' },
1500 port
=> { type
=> 'integer' },
1501 upid
=> { type
=> 'string' },
1507 my $rpcenv = PVE
::RPCEnvironment
::get
();
1509 my $authuser = $rpcenv->get_user();
1511 my $vmid = $param->{vmid
};
1512 my $node = $param->{node
};
1513 my $websocket = $param->{websocket
};
1515 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1517 my $authpath = "/vms/$vmid";
1519 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1521 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1524 my ($remip, $family);
1527 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1528 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1529 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1530 $remcmd = ['/usr/bin/ssh', '-e', 'none', '-T', '-o', 'BatchMode=yes', $remip];
1532 $family = PVE
::Tools
::get_host_address_family
($node);
1535 my $port = PVE
::Tools
::next_vnc_port
($family);
1542 syslog
('info', "starting vnc proxy $upid\n");
1546 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1549 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1551 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1552 '-timeout', $timeout, '-authpath', $authpath,
1553 '-perm', 'Sys.Console'];
1555 if ($param->{websocket
}) {
1556 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1557 push @$cmd, '-notls', '-listen', 'localhost';
1560 push @$cmd, '-c', @$remcmd, @$termcmd;
1562 PVE
::Tools
::run_command
($cmd);
1566 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1568 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1570 my $sock = IO
::Socket
::IP-
>new(
1575 GetAddrInfoFlags
=> 0,
1576 ) or die "failed to create socket: $!\n";
1577 # Inside the worker we shouldn't have any previous alarms
1578 # running anyway...:
1580 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1582 accept(my $cli, $sock) or die "connection failed: $!\n";
1585 if (PVE
::Tools
::run_command
($cmd,
1586 output
=> '>&'.fileno($cli),
1587 input
=> '<&'.fileno($cli),
1590 die "Failed to run vncproxy.\n";
1597 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1599 PVE
::Tools
::wait_for_vnc_port
($port);
1610 __PACKAGE__-
>register_method({
1611 name
=> 'termproxy',
1612 path
=> '{vmid}/termproxy',
1616 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1618 description
=> "Creates a TCP proxy connections.",
1620 additionalProperties
=> 0,
1622 node
=> get_standard_option
('pve-node'),
1623 vmid
=> get_standard_option
('pve-vmid'),
1627 enum
=> [qw(serial0 serial1 serial2 serial3)],
1628 description
=> "opens a serial terminal (defaults to display)",
1633 additionalProperties
=> 0,
1635 user
=> { type
=> 'string' },
1636 ticket
=> { type
=> 'string' },
1637 port
=> { type
=> 'integer' },
1638 upid
=> { type
=> 'string' },
1644 my $rpcenv = PVE
::RPCEnvironment
::get
();
1646 my $authuser = $rpcenv->get_user();
1648 my $vmid = $param->{vmid
};
1649 my $node = $param->{node
};
1650 my $serial = $param->{serial
};
1652 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1654 if (!defined($serial)) {
1655 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1656 $serial = $conf->{vga
};
1660 my $authpath = "/vms/$vmid";
1662 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1664 my ($remip, $family);
1666 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1667 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1669 $family = PVE
::Tools
::get_host_address_family
($node);
1672 my $port = PVE
::Tools
::next_vnc_port
($family);
1674 my $remcmd = $remip ?
1675 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
1677 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1678 push @$termcmd, '-iface', $serial if $serial;
1683 syslog
('info', "starting qemu termproxy $upid\n");
1685 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1686 '--perm', 'VM.Console', '--'];
1687 push @$cmd, @$remcmd, @$termcmd;
1689 PVE
::Tools
::run_command
($cmd);
1692 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1694 PVE
::Tools
::wait_for_vnc_port
($port);
1704 __PACKAGE__-
>register_method({
1705 name
=> 'vncwebsocket',
1706 path
=> '{vmid}/vncwebsocket',
1709 description
=> "You also need to pass a valid ticket (vncticket).",
1710 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1712 description
=> "Opens a weksocket for VNC traffic.",
1714 additionalProperties
=> 0,
1716 node
=> get_standard_option
('pve-node'),
1717 vmid
=> get_standard_option
('pve-vmid'),
1719 description
=> "Ticket from previous call to vncproxy.",
1724 description
=> "Port number returned by previous vncproxy call.",
1734 port
=> { type
=> 'string' },
1740 my $rpcenv = PVE
::RPCEnvironment
::get
();
1742 my $authuser = $rpcenv->get_user();
1744 my $vmid = $param->{vmid
};
1745 my $node = $param->{node
};
1747 my $authpath = "/vms/$vmid";
1749 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1751 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1753 # Note: VNC ports are acessible from outside, so we do not gain any
1754 # security if we verify that $param->{port} belongs to VM $vmid. This
1755 # check is done by verifying the VNC ticket (inside VNC protocol).
1757 my $port = $param->{port
};
1759 return { port
=> $port };
1762 __PACKAGE__-
>register_method({
1763 name
=> 'spiceproxy',
1764 path
=> '{vmid}/spiceproxy',
1769 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1771 description
=> "Returns a SPICE configuration to connect to the VM.",
1773 additionalProperties
=> 0,
1775 node
=> get_standard_option
('pve-node'),
1776 vmid
=> get_standard_option
('pve-vmid'),
1777 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1780 returns
=> get_standard_option
('remote-viewer-config'),
1784 my $rpcenv = PVE
::RPCEnvironment
::get
();
1786 my $authuser = $rpcenv->get_user();
1788 my $vmid = $param->{vmid
};
1789 my $node = $param->{node
};
1790 my $proxy = $param->{proxy
};
1792 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1793 my $title = "VM $vmid";
1794 $title .= " - ". $conf->{name
} if $conf->{name
};
1796 my $port = PVE
::QemuServer
::spice_port
($vmid);
1798 my ($ticket, undef, $remote_viewer_config) =
1799 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1801 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1802 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1804 return $remote_viewer_config;
1807 __PACKAGE__-
>register_method({
1809 path
=> '{vmid}/status',
1812 description
=> "Directory index",
1817 additionalProperties
=> 0,
1819 node
=> get_standard_option
('pve-node'),
1820 vmid
=> get_standard_option
('pve-vmid'),
1828 subdir
=> { type
=> 'string' },
1831 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1837 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1840 { subdir
=> 'current' },
1841 { subdir
=> 'start' },
1842 { subdir
=> 'stop' },
1848 __PACKAGE__-
>register_method({
1849 name
=> 'vm_status',
1850 path
=> '{vmid}/status/current',
1853 protected
=> 1, # qemu pid files are only readable by root
1854 description
=> "Get virtual machine status.",
1856 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1859 additionalProperties
=> 0,
1861 node
=> get_standard_option
('pve-node'),
1862 vmid
=> get_standard_option
('pve-vmid'),
1868 %$PVE::QemuServer
::vmstatus_return_properties
,
1870 description
=> "HA manager service status.",
1874 description
=> "Qemu VGA configuration supports spice.",
1879 description
=> "Qemu GuestAgent enabled in config.",
1889 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1891 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1892 my $status = $vmstatus->{$param->{vmid
}};
1894 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1896 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1897 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1902 __PACKAGE__-
>register_method({
1904 path
=> '{vmid}/status/start',
1908 description
=> "Start virtual machine.",
1910 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1913 additionalProperties
=> 0,
1915 node
=> get_standard_option
('pve-node'),
1916 vmid
=> get_standard_option
('pve-vmid',
1917 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1918 skiplock
=> get_standard_option
('skiplock'),
1919 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1920 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1923 enum
=> ['secure', 'insecure'],
1924 description
=> "Migration traffic is encrypted using an SSH " .
1925 "tunnel by default. On secure, completely private networks " .
1926 "this can be disabled to increase performance.",
1929 migration_network
=> {
1930 type
=> 'string', format
=> 'CIDR',
1931 description
=> "CIDR of the (sub) network that is used for migration.",
1934 machine
=> get_standard_option
('pve-qm-machine'),
1936 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1948 my $rpcenv = PVE
::RPCEnvironment
::get
();
1950 my $authuser = $rpcenv->get_user();
1952 my $node = extract_param
($param, 'node');
1954 my $vmid = extract_param
($param, 'vmid');
1956 my $machine = extract_param
($param, 'machine');
1958 my $stateuri = extract_param
($param, 'stateuri');
1959 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1960 if $stateuri && $authuser ne 'root@pam';
1962 my $skiplock = extract_param
($param, 'skiplock');
1963 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1964 if $skiplock && $authuser ne 'root@pam';
1966 my $migratedfrom = extract_param
($param, 'migratedfrom');
1967 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1968 if $migratedfrom && $authuser ne 'root@pam';
1970 my $migration_type = extract_param
($param, 'migration_type');
1971 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1972 if $migration_type && $authuser ne 'root@pam';
1974 my $migration_network = extract_param
($param, 'migration_network');
1975 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1976 if $migration_network && $authuser ne 'root@pam';
1978 my $targetstorage = extract_param
($param, 'targetstorage');
1979 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1980 if $targetstorage && $authuser ne 'root@pam';
1982 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1983 if $targetstorage && !$migratedfrom;
1985 # read spice ticket from STDIN
1987 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1988 if (defined(my $line = <STDIN
>)) {
1990 $spice_ticket = $line;
1994 PVE
::Cluster
::check_cfs_quorum
();
1996 my $storecfg = PVE
::Storage
::config
();
1998 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1999 $rpcenv->{type
} ne 'ha') {
2004 my $service = "vm:$vmid";
2006 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
2008 print "Requesting HA start for VM $vmid\n";
2010 PVE
::Tools
::run_command
($cmd);
2015 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2022 syslog
('info', "start VM $vmid: $upid\n");
2024 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2025 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
2030 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2034 __PACKAGE__-
>register_method({
2036 path
=> '{vmid}/status/stop',
2040 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2041 "is akin to pulling the power plug of a running computer and may damage the VM data",
2043 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2046 additionalProperties
=> 0,
2048 node
=> get_standard_option
('pve-node'),
2049 vmid
=> get_standard_option
('pve-vmid',
2050 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2051 skiplock
=> get_standard_option
('skiplock'),
2052 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2054 description
=> "Wait maximal timeout seconds.",
2060 description
=> "Do not deactivate storage volumes.",
2073 my $rpcenv = PVE
::RPCEnvironment
::get
();
2075 my $authuser = $rpcenv->get_user();
2077 my $node = extract_param
($param, 'node');
2079 my $vmid = extract_param
($param, 'vmid');
2081 my $skiplock = extract_param
($param, 'skiplock');
2082 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2083 if $skiplock && $authuser ne 'root@pam';
2085 my $keepActive = extract_param
($param, 'keepActive');
2086 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2087 if $keepActive && $authuser ne 'root@pam';
2089 my $migratedfrom = extract_param
($param, 'migratedfrom');
2090 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2091 if $migratedfrom && $authuser ne 'root@pam';
2094 my $storecfg = PVE
::Storage
::config
();
2096 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2101 my $service = "vm:$vmid";
2103 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2105 print "Requesting HA stop for VM $vmid\n";
2107 PVE
::Tools
::run_command
($cmd);
2112 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2118 syslog
('info', "stop VM $vmid: $upid\n");
2120 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2121 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2126 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2130 __PACKAGE__-
>register_method({
2132 path
=> '{vmid}/status/reset',
2136 description
=> "Reset virtual machine.",
2138 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2141 additionalProperties
=> 0,
2143 node
=> get_standard_option
('pve-node'),
2144 vmid
=> get_standard_option
('pve-vmid',
2145 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2146 skiplock
=> get_standard_option
('skiplock'),
2155 my $rpcenv = PVE
::RPCEnvironment
::get
();
2157 my $authuser = $rpcenv->get_user();
2159 my $node = extract_param
($param, 'node');
2161 my $vmid = extract_param
($param, 'vmid');
2163 my $skiplock = extract_param
($param, 'skiplock');
2164 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2165 if $skiplock && $authuser ne 'root@pam';
2167 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2172 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2177 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2180 __PACKAGE__-
>register_method({
2181 name
=> 'vm_shutdown',
2182 path
=> '{vmid}/status/shutdown',
2186 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2187 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2189 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2192 additionalProperties
=> 0,
2194 node
=> get_standard_option
('pve-node'),
2195 vmid
=> get_standard_option
('pve-vmid',
2196 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2197 skiplock
=> get_standard_option
('skiplock'),
2199 description
=> "Wait maximal timeout seconds.",
2205 description
=> "Make sure the VM stops.",
2211 description
=> "Do not deactivate storage volumes.",
2224 my $rpcenv = PVE
::RPCEnvironment
::get
();
2226 my $authuser = $rpcenv->get_user();
2228 my $node = extract_param
($param, 'node');
2230 my $vmid = extract_param
($param, 'vmid');
2232 my $skiplock = extract_param
($param, 'skiplock');
2233 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2234 if $skiplock && $authuser ne 'root@pam';
2236 my $keepActive = extract_param
($param, 'keepActive');
2237 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2238 if $keepActive && $authuser ne 'root@pam';
2240 my $storecfg = PVE
::Storage
::config
();
2244 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2245 # otherwise, we will infer a shutdown command, but run into the timeout,
2246 # then when the vm is resumed, it will instantly shutdown
2248 # checking the qmp status here to get feedback to the gui/cli/api
2249 # and the status query should not take too long
2252 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2256 if (!$err && $qmpstatus->{status
} eq "paused") {
2257 if ($param->{forceStop
}) {
2258 warn "VM is paused - stop instead of shutdown\n";
2261 die "VM is paused - cannot shutdown\n";
2265 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2266 ($rpcenv->{type
} ne 'ha')) {
2271 my $service = "vm:$vmid";
2273 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2275 print "Requesting HA stop for VM $vmid\n";
2277 PVE
::Tools
::run_command
($cmd);
2282 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2289 syslog
('info', "shutdown VM $vmid: $upid\n");
2291 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2292 $shutdown, $param->{forceStop
}, $keepActive);
2297 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2301 __PACKAGE__-
>register_method({
2302 name
=> 'vm_suspend',
2303 path
=> '{vmid}/status/suspend',
2307 description
=> "Suspend virtual machine.",
2309 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2312 additionalProperties
=> 0,
2314 node
=> get_standard_option
('pve-node'),
2315 vmid
=> get_standard_option
('pve-vmid',
2316 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2317 skiplock
=> get_standard_option
('skiplock'),
2326 my $rpcenv = PVE
::RPCEnvironment
::get
();
2328 my $authuser = $rpcenv->get_user();
2330 my $node = extract_param
($param, 'node');
2332 my $vmid = extract_param
($param, 'vmid');
2334 my $skiplock = extract_param
($param, 'skiplock');
2335 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2336 if $skiplock && $authuser ne 'root@pam';
2338 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2343 syslog
('info', "suspend VM $vmid: $upid\n");
2345 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2350 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2353 __PACKAGE__-
>register_method({
2354 name
=> 'vm_resume',
2355 path
=> '{vmid}/status/resume',
2359 description
=> "Resume virtual machine.",
2361 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2364 additionalProperties
=> 0,
2366 node
=> get_standard_option
('pve-node'),
2367 vmid
=> get_standard_option
('pve-vmid',
2368 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2369 skiplock
=> get_standard_option
('skiplock'),
2370 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2380 my $rpcenv = PVE
::RPCEnvironment
::get
();
2382 my $authuser = $rpcenv->get_user();
2384 my $node = extract_param
($param, 'node');
2386 my $vmid = extract_param
($param, 'vmid');
2388 my $skiplock = extract_param
($param, 'skiplock');
2389 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2390 if $skiplock && $authuser ne 'root@pam';
2392 my $nocheck = extract_param
($param, 'nocheck');
2394 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2399 syslog
('info', "resume VM $vmid: $upid\n");
2401 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2406 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2409 __PACKAGE__-
>register_method({
2410 name
=> 'vm_sendkey',
2411 path
=> '{vmid}/sendkey',
2415 description
=> "Send key event to virtual machine.",
2417 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2420 additionalProperties
=> 0,
2422 node
=> get_standard_option
('pve-node'),
2423 vmid
=> get_standard_option
('pve-vmid',
2424 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2425 skiplock
=> get_standard_option
('skiplock'),
2427 description
=> "The key (qemu monitor encoding).",
2432 returns
=> { type
=> 'null'},
2436 my $rpcenv = PVE
::RPCEnvironment
::get
();
2438 my $authuser = $rpcenv->get_user();
2440 my $node = extract_param
($param, 'node');
2442 my $vmid = extract_param
($param, 'vmid');
2444 my $skiplock = extract_param
($param, 'skiplock');
2445 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2446 if $skiplock && $authuser ne 'root@pam';
2448 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2453 __PACKAGE__-
>register_method({
2454 name
=> 'vm_feature',
2455 path
=> '{vmid}/feature',
2459 description
=> "Check if feature for virtual machine is available.",
2461 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2464 additionalProperties
=> 0,
2466 node
=> get_standard_option
('pve-node'),
2467 vmid
=> get_standard_option
('pve-vmid'),
2469 description
=> "Feature to check.",
2471 enum
=> [ 'snapshot', 'clone', 'copy' ],
2473 snapname
=> get_standard_option
('pve-snapshot-name', {
2481 hasFeature
=> { type
=> 'boolean' },
2484 items
=> { type
=> 'string' },
2491 my $node = extract_param
($param, 'node');
2493 my $vmid = extract_param
($param, 'vmid');
2495 my $snapname = extract_param
($param, 'snapname');
2497 my $feature = extract_param
($param, 'feature');
2499 my $running = PVE
::QemuServer
::check_running
($vmid);
2501 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2504 my $snap = $conf->{snapshots
}->{$snapname};
2505 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2508 my $storecfg = PVE
::Storage
::config
();
2510 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2511 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2514 hasFeature
=> $hasFeature,
2515 nodes
=> [ keys %$nodelist ],
2519 __PACKAGE__-
>register_method({
2521 path
=> '{vmid}/clone',
2525 description
=> "Create a copy of virtual machine/template.",
2527 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2528 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2529 "'Datastore.AllocateSpace' on any used storage.",
2532 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2534 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2535 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2540 additionalProperties
=> 0,
2542 node
=> get_standard_option
('pve-node'),
2543 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2544 newid
=> get_standard_option
('pve-vmid', {
2545 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2546 description
=> 'VMID for the clone.' }),
2549 type
=> 'string', format
=> 'dns-name',
2550 description
=> "Set a name for the new VM.",
2555 description
=> "Description for the new VM.",
2559 type
=> 'string', format
=> 'pve-poolid',
2560 description
=> "Add the new VM to the specified pool.",
2562 snapname
=> get_standard_option
('pve-snapshot-name', {
2565 storage
=> get_standard_option
('pve-storage-id', {
2566 description
=> "Target storage for full clone.",
2570 description
=> "Target format for file storage. Only valid for full clone.",
2573 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2578 description
=> "Create a full copy of all disks. This is always done when " .
2579 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2581 target
=> get_standard_option
('pve-node', {
2582 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2593 my $rpcenv = PVE
::RPCEnvironment
::get
();
2595 my $authuser = $rpcenv->get_user();
2597 my $node = extract_param
($param, 'node');
2599 my $vmid = extract_param
($param, 'vmid');
2601 my $newid = extract_param
($param, 'newid');
2603 my $pool = extract_param
($param, 'pool');
2605 if (defined($pool)) {
2606 $rpcenv->check_pool_exist($pool);
2609 my $snapname = extract_param
($param, 'snapname');
2611 my $storage = extract_param
($param, 'storage');
2613 my $format = extract_param
($param, 'format');
2615 my $target = extract_param
($param, 'target');
2617 my $localnode = PVE
::INotify
::nodename
();
2619 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2621 PVE
::Cluster
::check_node_exists
($target) if $target;
2623 my $storecfg = PVE
::Storage
::config
();
2626 # check if storage is enabled on local node
2627 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2629 # check if storage is available on target node
2630 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2631 # clone only works if target storage is shared
2632 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2633 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2637 PVE
::Cluster
::check_cfs_quorum
();
2639 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2641 # exclusive lock if VM is running - else shared lock is enough;
2642 my $shared_lock = $running ?
0 : 1;
2646 # do all tests after lock
2647 # we also try to do all tests before we fork the worker
2649 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2651 PVE
::QemuConfig-
>check_lock($conf);
2653 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2655 die "unexpected state change\n" if $verify_running != $running;
2657 die "snapshot '$snapname' does not exist\n"
2658 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2660 my $full = extract_param
($param, 'full');
2661 if (!defined($full)) {
2662 $full = !PVE
::QemuConfig-
>is_template($conf);
2665 die "parameter 'storage' not allowed for linked clones\n"
2666 if defined($storage) && !$full;
2668 die "parameter 'format' not allowed for linked clones\n"
2669 if defined($format) && !$full;
2671 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2673 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2675 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2677 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2679 die "unable to create VM $newid: config file already exists\n"
2682 my $newconf = { lock => 'clone' };
2687 foreach my $opt (keys %$oldconf) {
2688 my $value = $oldconf->{$opt};
2690 # do not copy snapshot related info
2691 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2692 $opt eq 'vmstate' || $opt eq 'snapstate';
2694 # no need to copy unused images, because VMID(owner) changes anyways
2695 next if $opt =~ m/^unused\d+$/;
2697 # always change MAC! address
2698 if ($opt =~ m/^net(\d+)$/) {
2699 my $net = PVE
::QemuServer
::parse_net
($value);
2700 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2701 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2702 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2703 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2704 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2705 die "unable to parse drive options for '$opt'\n" if !$drive;
2706 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2707 $newconf->{$opt} = $value; # simply copy configuration
2709 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2710 die "Full clone feature is not supported for drive '$opt'\n"
2711 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2712 $fullclone->{$opt} = 1;
2714 # not full means clone instead of copy
2715 die "Linked clone feature is not supported for drive '$opt'\n"
2716 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2718 $drives->{$opt} = $drive;
2719 push @$vollist, $drive->{file
};
2722 # copy everything else
2723 $newconf->{$opt} = $value;
2727 # auto generate a new uuid
2728 my ($uuid, $uuid_str);
2729 UUID
::generate
($uuid);
2730 UUID
::unparse
($uuid, $uuid_str);
2731 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2732 $smbios1->{uuid
} = $uuid_str;
2733 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2735 delete $newconf->{template
};
2737 if ($param->{name
}) {
2738 $newconf->{name
} = $param->{name
};
2740 if ($oldconf->{name
}) {
2741 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2743 $newconf->{name
} = "Copy-of-VM-$vmid";
2747 if ($param->{description
}) {
2748 $newconf->{description
} = $param->{description
};
2751 # create empty/temp config - this fails if VM already exists on other node
2752 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2757 my $newvollist = [];
2764 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2766 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2768 my $total_jobs = scalar(keys %{$drives});
2771 foreach my $opt (keys %$drives) {
2772 my $drive = $drives->{$opt};
2773 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2775 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2776 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2777 $jobs, $skipcomplete, $oldconf->{agent
});
2779 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2781 PVE
::QemuConfig-
>write_config($newid, $newconf);
2785 delete $newconf->{lock};
2787 # do not write pending changes
2788 if ($newconf->{pending
}) {
2789 warn "found pending changes, discarding for clone\n";
2790 delete $newconf->{pending
};
2793 PVE
::QemuConfig-
>write_config($newid, $newconf);
2796 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2797 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2798 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2800 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2801 die "Failed to move config to node '$target' - rename failed: $!\n"
2802 if !rename($conffile, $newconffile);
2805 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2810 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2812 sleep 1; # some storage like rbd need to wait before release volume - really?
2814 foreach my $volid (@$newvollist) {
2815 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2818 die "clone failed: $err";
2824 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2826 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2829 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2830 # Aquire exclusive lock lock for $newid
2831 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2836 __PACKAGE__-
>register_method({
2837 name
=> 'move_vm_disk',
2838 path
=> '{vmid}/move_disk',
2842 description
=> "Move volume to different storage.",
2844 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2846 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2847 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2851 additionalProperties
=> 0,
2853 node
=> get_standard_option
('pve-node'),
2854 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2857 description
=> "The disk you want to move.",
2858 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2860 storage
=> get_standard_option
('pve-storage-id', {
2861 description
=> "Target storage.",
2862 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2866 description
=> "Target Format.",
2867 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2872 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2878 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2886 description
=> "the task ID.",
2891 my $rpcenv = PVE
::RPCEnvironment
::get
();
2893 my $authuser = $rpcenv->get_user();
2895 my $node = extract_param
($param, 'node');
2897 my $vmid = extract_param
($param, 'vmid');
2899 my $digest = extract_param
($param, 'digest');
2901 my $disk = extract_param
($param, 'disk');
2903 my $storeid = extract_param
($param, 'storage');
2905 my $format = extract_param
($param, 'format');
2907 my $storecfg = PVE
::Storage
::config
();
2909 my $updatefn = sub {
2911 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2913 PVE
::QemuConfig-
>check_lock($conf);
2915 die "checksum missmatch (file change by other user?)\n"
2916 if $digest && $digest ne $conf->{digest
};
2918 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2920 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2922 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2924 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
2927 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2928 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2932 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2933 (!$format || !$oldfmt || $oldfmt eq $format);
2935 # this only checks snapshots because $disk is passed!
2936 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2937 die "you can't move a disk with snapshots and delete the source\n"
2938 if $snapshotted && $param->{delete};
2940 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2942 my $running = PVE
::QemuServer
::check_running
($vmid);
2944 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2948 my $newvollist = [];
2954 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2956 warn "moving disk with snapshots, snapshots will not be moved!\n"
2959 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2960 $vmid, $storeid, $format, 1, $newvollist);
2962 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2964 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2966 # convert moved disk to base if part of template
2967 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2968 if PVE
::QemuConfig-
>is_template($conf);
2970 PVE
::QemuConfig-
>write_config($vmid, $conf);
2972 if ($running && PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
} && PVE
::QemuServer
::qga_check_running
($vmid)) {
2973 eval { PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-fstrim"); };
2977 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2978 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2985 foreach my $volid (@$newvollist) {
2986 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2989 die "storage migration failed: $err";
2992 if ($param->{delete}) {
2994 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2995 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3001 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3004 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3007 __PACKAGE__-
>register_method({
3008 name
=> 'migrate_vm',
3009 path
=> '{vmid}/migrate',
3013 description
=> "Migrate virtual machine. Creates a new migration task.",
3015 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3018 additionalProperties
=> 0,
3020 node
=> get_standard_option
('pve-node'),
3021 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3022 target
=> get_standard_option
('pve-node', {
3023 description
=> "Target node.",
3024 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3028 description
=> "Use online/live migration.",
3033 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3038 enum
=> ['secure', 'insecure'],
3039 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3042 migration_network
=> {
3043 type
=> 'string', format
=> 'CIDR',
3044 description
=> "CIDR of the (sub) network that is used for migration.",
3047 "with-local-disks" => {
3049 description
=> "Enable live storage migration for local disk",
3052 targetstorage
=> get_standard_option
('pve-storage-id', {
3053 description
=> "Default target storage.",
3055 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3061 description
=> "the task ID.",
3066 my $rpcenv = PVE
::RPCEnvironment
::get
();
3068 my $authuser = $rpcenv->get_user();
3070 my $target = extract_param
($param, 'target');
3072 my $localnode = PVE
::INotify
::nodename
();
3073 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3075 PVE
::Cluster
::check_cfs_quorum
();
3077 PVE
::Cluster
::check_node_exists
($target);
3079 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3081 my $vmid = extract_param
($param, 'vmid');
3083 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3084 if !$param->{online
} && $param->{targetstorage
};
3086 raise_param_exc
({ force
=> "Only root may use this option." })
3087 if $param->{force
} && $authuser ne 'root@pam';
3089 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3090 if $param->{migration_type
} && $authuser ne 'root@pam';
3092 # allow root only until better network permissions are available
3093 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3094 if $param->{migration_network
} && $authuser ne 'root@pam';
3097 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3099 # try to detect errors early
3101 PVE
::QemuConfig-
>check_lock($conf);
3103 if (PVE
::QemuServer
::check_running
($vmid)) {
3104 die "cant migrate running VM without --online\n"
3105 if !$param->{online
};
3108 my $storecfg = PVE
::Storage
::config
();
3110 if( $param->{targetstorage
}) {
3111 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3113 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3116 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3121 my $service = "vm:$vmid";
3123 my $cmd = ['ha-manager', 'migrate', $service, $target];
3125 print "Requesting HA migration for VM $vmid to node $target\n";
3127 PVE
::Tools
::run_command
($cmd);
3132 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3137 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3141 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3144 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3149 __PACKAGE__-
>register_method({
3151 path
=> '{vmid}/monitor',
3155 description
=> "Execute Qemu monitor commands.",
3157 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3158 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3161 additionalProperties
=> 0,
3163 node
=> get_standard_option
('pve-node'),
3164 vmid
=> get_standard_option
('pve-vmid'),
3167 description
=> "The monitor command.",
3171 returns
=> { type
=> 'string'},
3175 my $rpcenv = PVE
::RPCEnvironment
::get
();
3176 my $authuser = $rpcenv->get_user();
3179 my $command = shift;
3180 return $command =~ m/^\s*info(\s+|$)/
3181 || $command =~ m/^\s*help\s*$/;
3184 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3185 if !&$is_ro($param->{command
});
3187 my $vmid = $param->{vmid
};
3189 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3193 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3195 $res = "ERROR: $@" if $@;
3200 __PACKAGE__-
>register_method({
3201 name
=> 'resize_vm',
3202 path
=> '{vmid}/resize',
3206 description
=> "Extend volume size.",
3208 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3211 additionalProperties
=> 0,
3213 node
=> get_standard_option
('pve-node'),
3214 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3215 skiplock
=> get_standard_option
('skiplock'),
3218 description
=> "The disk you want to resize.",
3219 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3223 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3224 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.",
3228 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3234 returns
=> { type
=> 'null'},
3238 my $rpcenv = PVE
::RPCEnvironment
::get
();
3240 my $authuser = $rpcenv->get_user();
3242 my $node = extract_param
($param, 'node');
3244 my $vmid = extract_param
($param, 'vmid');
3246 my $digest = extract_param
($param, 'digest');
3248 my $disk = extract_param
($param, 'disk');
3250 my $sizestr = extract_param
($param, 'size');
3252 my $skiplock = extract_param
($param, 'skiplock');
3253 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3254 if $skiplock && $authuser ne 'root@pam';
3256 my $storecfg = PVE
::Storage
::config
();
3258 my $updatefn = sub {
3260 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3262 die "checksum missmatch (file change by other user?)\n"
3263 if $digest && $digest ne $conf->{digest
};
3264 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3266 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3268 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3270 my (undef, undef, undef, undef, undef, undef, $format) =
3271 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3273 die "can't resize volume: $disk if snapshot exists\n"
3274 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3276 my $volid = $drive->{file
};
3278 die "disk '$disk' has no associated volume\n" if !$volid;
3280 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3282 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3284 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3286 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3287 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3289 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3290 my ($ext, $newsize, $unit) = ($1, $2, $4);
3293 $newsize = $newsize * 1024;
3294 } elsif ($unit eq 'M') {
3295 $newsize = $newsize * 1024 * 1024;
3296 } elsif ($unit eq 'G') {
3297 $newsize = $newsize * 1024 * 1024 * 1024;
3298 } elsif ($unit eq 'T') {
3299 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3302 $newsize += $size if $ext;
3303 $newsize = int($newsize);
3305 die "shrinking disks is not supported\n" if $newsize < $size;
3307 return if $size == $newsize;
3309 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3311 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3313 $drive->{size
} = $newsize;
3314 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3316 PVE
::QemuConfig-
>write_config($vmid, $conf);
3319 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3323 __PACKAGE__-
>register_method({
3324 name
=> 'snapshot_list',
3325 path
=> '{vmid}/snapshot',
3327 description
=> "List all snapshots.",
3329 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3332 protected
=> 1, # qemu pid files are only readable by root
3334 additionalProperties
=> 0,
3336 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3337 node
=> get_standard_option
('pve-node'),
3346 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3350 description
=> "Snapshot includes RAM.",
3355 description
=> "Snapshot description.",
3359 description
=> "Snapshot creation time",
3361 renderer
=> 'timestamp',
3365 description
=> "Parent snapshot identifier.",
3371 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3376 my $vmid = $param->{vmid
};
3378 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3379 my $snaphash = $conf->{snapshots
} || {};
3383 foreach my $name (keys %$snaphash) {
3384 my $d = $snaphash->{$name};
3387 snaptime
=> $d->{snaptime
} || 0,
3388 vmstate
=> $d->{vmstate
} ?
1 : 0,
3389 description
=> $d->{description
} || '',
3391 $item->{parent
} = $d->{parent
} if $d->{parent
};
3392 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3396 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3399 digest
=> $conf->{digest
},
3400 running
=> $running,
3401 description
=> "You are here!",
3403 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3405 push @$res, $current;
3410 __PACKAGE__-
>register_method({
3412 path
=> '{vmid}/snapshot',
3416 description
=> "Snapshot a VM.",
3418 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3421 additionalProperties
=> 0,
3423 node
=> get_standard_option
('pve-node'),
3424 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3425 snapname
=> get_standard_option
('pve-snapshot-name'),
3429 description
=> "Save the vmstate",
3434 description
=> "A textual description or comment.",
3440 description
=> "the task ID.",
3445 my $rpcenv = PVE
::RPCEnvironment
::get
();
3447 my $authuser = $rpcenv->get_user();
3449 my $node = extract_param
($param, 'node');
3451 my $vmid = extract_param
($param, 'vmid');
3453 my $snapname = extract_param
($param, 'snapname');
3455 die "unable to use snapshot name 'current' (reserved name)\n"
3456 if $snapname eq 'current';
3459 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3460 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3461 $param->{description
});
3464 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3467 __PACKAGE__-
>register_method({
3468 name
=> 'snapshot_cmd_idx',
3469 path
=> '{vmid}/snapshot/{snapname}',
3476 additionalProperties
=> 0,
3478 vmid
=> get_standard_option
('pve-vmid'),
3479 node
=> get_standard_option
('pve-node'),
3480 snapname
=> get_standard_option
('pve-snapshot-name'),
3489 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3496 push @$res, { cmd
=> 'rollback' };
3497 push @$res, { cmd
=> 'config' };
3502 __PACKAGE__-
>register_method({
3503 name
=> 'update_snapshot_config',
3504 path
=> '{vmid}/snapshot/{snapname}/config',
3508 description
=> "Update snapshot metadata.",
3510 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3513 additionalProperties
=> 0,
3515 node
=> get_standard_option
('pve-node'),
3516 vmid
=> get_standard_option
('pve-vmid'),
3517 snapname
=> get_standard_option
('pve-snapshot-name'),
3521 description
=> "A textual description or comment.",
3525 returns
=> { type
=> 'null' },
3529 my $rpcenv = PVE
::RPCEnvironment
::get
();
3531 my $authuser = $rpcenv->get_user();
3533 my $vmid = extract_param
($param, 'vmid');
3535 my $snapname = extract_param
($param, 'snapname');
3537 return undef if !defined($param->{description
});
3539 my $updatefn = sub {
3541 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3543 PVE
::QemuConfig-
>check_lock($conf);
3545 my $snap = $conf->{snapshots
}->{$snapname};
3547 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3549 $snap->{description
} = $param->{description
} if defined($param->{description
});
3551 PVE
::QemuConfig-
>write_config($vmid, $conf);
3554 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3559 __PACKAGE__-
>register_method({
3560 name
=> 'get_snapshot_config',
3561 path
=> '{vmid}/snapshot/{snapname}/config',
3564 description
=> "Get snapshot configuration",
3566 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3569 additionalProperties
=> 0,
3571 node
=> get_standard_option
('pve-node'),
3572 vmid
=> get_standard_option
('pve-vmid'),
3573 snapname
=> get_standard_option
('pve-snapshot-name'),
3576 returns
=> { type
=> "object" },
3580 my $rpcenv = PVE
::RPCEnvironment
::get
();
3582 my $authuser = $rpcenv->get_user();
3584 my $vmid = extract_param
($param, 'vmid');
3586 my $snapname = extract_param
($param, 'snapname');
3588 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3590 my $snap = $conf->{snapshots
}->{$snapname};
3592 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3597 __PACKAGE__-
>register_method({
3599 path
=> '{vmid}/snapshot/{snapname}/rollback',
3603 description
=> "Rollback VM state to specified snapshot.",
3605 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3608 additionalProperties
=> 0,
3610 node
=> get_standard_option
('pve-node'),
3611 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3612 snapname
=> get_standard_option
('pve-snapshot-name'),
3617 description
=> "the task ID.",
3622 my $rpcenv = PVE
::RPCEnvironment
::get
();
3624 my $authuser = $rpcenv->get_user();
3626 my $node = extract_param
($param, 'node');
3628 my $vmid = extract_param
($param, 'vmid');
3630 my $snapname = extract_param
($param, 'snapname');
3633 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3634 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3638 # hold migration lock, this makes sure that nobody create replication snapshots
3639 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3642 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3645 __PACKAGE__-
>register_method({
3646 name
=> 'delsnapshot',
3647 path
=> '{vmid}/snapshot/{snapname}',
3651 description
=> "Delete a VM snapshot.",
3653 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3656 additionalProperties
=> 0,
3658 node
=> get_standard_option
('pve-node'),
3659 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3660 snapname
=> get_standard_option
('pve-snapshot-name'),
3664 description
=> "For removal from config file, even if removing disk snapshots fails.",
3670 description
=> "the task ID.",
3675 my $rpcenv = PVE
::RPCEnvironment
::get
();
3677 my $authuser = $rpcenv->get_user();
3679 my $node = extract_param
($param, 'node');
3681 my $vmid = extract_param
($param, 'vmid');
3683 my $snapname = extract_param
($param, 'snapname');
3686 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3687 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3690 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3693 __PACKAGE__-
>register_method({
3695 path
=> '{vmid}/template',
3699 description
=> "Create a Template.",
3701 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3702 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3705 additionalProperties
=> 0,
3707 node
=> get_standard_option
('pve-node'),
3708 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3712 description
=> "If you want to convert only 1 disk to base image.",
3713 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3718 returns
=> { type
=> 'null'},
3722 my $rpcenv = PVE
::RPCEnvironment
::get
();
3724 my $authuser = $rpcenv->get_user();
3726 my $node = extract_param
($param, 'node');
3728 my $vmid = extract_param
($param, 'vmid');
3730 my $disk = extract_param
($param, 'disk');
3732 my $updatefn = sub {
3734 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3736 PVE
::QemuConfig-
>check_lock($conf);
3738 die "unable to create template, because VM contains snapshots\n"
3739 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3741 die "you can't convert a template to a template\n"
3742 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3744 die "you can't convert a VM to template if VM is running\n"
3745 if PVE
::QemuServer
::check_running
($vmid);
3748 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3751 $conf->{template
} = 1;
3752 PVE
::QemuConfig-
>write_config($vmid, $conf);
3754 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3757 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);