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.",
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};
385 $data->{vmid
} = int($vmid);
394 __PACKAGE__-
>register_method({
398 description
=> "Create or restore a virtual machine.",
400 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
401 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
402 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
403 user
=> 'all', # check inside
408 additionalProperties
=> 0,
409 properties
=> PVE
::QemuServer
::json_config_properties
(
411 node
=> get_standard_option
('pve-node'),
412 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
414 description
=> "The backup file.",
418 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
420 storage
=> get_standard_option
('pve-storage-id', {
421 description
=> "Default storage.",
423 completion
=> \
&PVE
::QemuServer
::complete_storage
,
428 description
=> "Allow to overwrite existing VM.",
429 requires
=> 'archive',
434 description
=> "Assign a unique random ethernet address.",
435 requires
=> 'archive',
439 type
=> 'string', format
=> 'pve-poolid',
440 description
=> "Add the VM to the specified pool.",
443 description
=> "Override i/o bandwidth limit (in KiB/s).",
456 my $rpcenv = PVE
::RPCEnvironment
::get
();
458 my $authuser = $rpcenv->get_user();
460 my $node = extract_param
($param, 'node');
462 my $vmid = extract_param
($param, 'vmid');
464 my $archive = extract_param
($param, 'archive');
465 my $is_restore = !!$archive;
467 my $storage = extract_param
($param, 'storage');
469 my $force = extract_param
($param, 'force');
471 my $unique = extract_param
($param, 'unique');
473 my $pool = extract_param
($param, 'pool');
475 my $bwlimit = extract_param
($param, 'bwlimit');
477 my $filename = PVE
::QemuConfig-
>config_file($vmid);
479 my $storecfg = PVE
::Storage
::config
();
481 if (defined(my $ssh_keys = $param->{sshkeys
})) {
482 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
483 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
486 PVE
::Cluster
::check_cfs_quorum
();
488 if (defined($pool)) {
489 $rpcenv->check_pool_exist($pool);
492 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
493 if defined($storage);
495 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
497 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
499 } elsif ($archive && $force && (-f
$filename) &&
500 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
501 # OK: user has VM.Backup permissions, and want to restore an existing VM
507 &$resolve_cdrom_alias($param);
509 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
511 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
513 foreach my $opt (keys %$param) {
514 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
515 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
516 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
518 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
519 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
523 PVE
::QemuServer
::add_random_macs
($param);
525 my $keystr = join(' ', keys %$param);
526 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
528 if ($archive eq '-') {
529 die "pipe requires cli environment\n"
530 if $rpcenv->{type
} ne 'cli';
532 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
533 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
537 my $restorefn = sub {
538 my $vmlist = PVE
::Cluster
::get_vmlist
();
539 if ($vmlist->{ids
}->{$vmid}) {
540 my $current_node = $vmlist->{ids
}->{$vmid}->{node
};
541 if ($current_node eq $node) {
542 my $conf = PVE
::QemuConfig-
>load_config($vmid);
544 PVE
::QemuConfig-
>check_protection($conf, "unable to restore VM $vmid");
546 die "unable to restore vm $vmid - config file already exists\n"
549 die "unable to restore vm $vmid - vm is running\n"
550 if PVE
::QemuServer
::check_running
($vmid);
552 die "unable to restore vm $vmid - vm is a template\n"
553 if PVE
::QemuConfig-
>is_template($conf);
556 die "unable to restore vm $vmid - already existing on cluster node '$current_node'\n";
561 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
565 bwlimit
=> $bwlimit, });
567 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
570 # ensure no old replication state are exists
571 PVE
::ReplicationState
::delete_guest_states
($vmid);
573 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
579 PVE
::Cluster
::check_vmid_unused
($vmid);
581 # ensure no old replication state are exists
582 PVE
::ReplicationState
::delete_guest_states
($vmid);
592 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
594 if (!$conf->{bootdisk
}) {
595 my $firstdisk = PVE
::QemuServer
::resolve_first_disk
($conf);
596 $conf->{bootdisk
} = $firstdisk if $firstdisk;
599 # auto generate uuid if user did not specify smbios1 option
600 if (!$conf->{smbios1
}) {
601 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
604 PVE
::QemuConfig-
>write_config($vmid, $conf);
610 foreach my $volid (@$vollist) {
611 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
614 die "create failed - $err";
617 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
620 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
623 my $worker_name = $is_restore ?
'qmrestore' : 'qmcreate';
624 my $code = $is_restore ?
$restorefn : $createfn;
626 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
629 __PACKAGE__-
>register_method({
634 description
=> "Directory index",
639 additionalProperties
=> 0,
641 node
=> get_standard_option
('pve-node'),
642 vmid
=> get_standard_option
('pve-vmid'),
650 subdir
=> { type
=> 'string' },
653 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
659 { subdir
=> 'config' },
660 { subdir
=> 'pending' },
661 { subdir
=> 'status' },
662 { subdir
=> 'unlink' },
663 { subdir
=> 'vncproxy' },
664 { subdir
=> 'termproxy' },
665 { subdir
=> 'migrate' },
666 { subdir
=> 'resize' },
667 { subdir
=> 'move' },
669 { subdir
=> 'rrddata' },
670 { subdir
=> 'monitor' },
671 { subdir
=> 'agent' },
672 { subdir
=> 'snapshot' },
673 { subdir
=> 'spiceproxy' },
674 { subdir
=> 'sendkey' },
675 { subdir
=> 'firewall' },
681 __PACKAGE__-
>register_method ({
682 subclass
=> "PVE::API2::Firewall::VM",
683 path
=> '{vmid}/firewall',
686 __PACKAGE__-
>register_method ({
687 subclass
=> "PVE::API2::Qemu::Agent",
688 path
=> '{vmid}/agent',
691 __PACKAGE__-
>register_method({
693 path
=> '{vmid}/rrd',
695 protected
=> 1, # fixme: can we avoid that?
697 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
699 description
=> "Read VM RRD statistics (returns PNG)",
701 additionalProperties
=> 0,
703 node
=> get_standard_option
('pve-node'),
704 vmid
=> get_standard_option
('pve-vmid'),
706 description
=> "Specify the time frame you are interested in.",
708 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
711 description
=> "The list of datasources you want to display.",
712 type
=> 'string', format
=> 'pve-configid-list',
715 description
=> "The RRD consolidation function",
717 enum
=> [ 'AVERAGE', 'MAX' ],
725 filename
=> { type
=> 'string' },
731 return PVE
::Cluster
::create_rrd_graph
(
732 "pve2-vm/$param->{vmid}", $param->{timeframe
},
733 $param->{ds
}, $param->{cf
});
737 __PACKAGE__-
>register_method({
739 path
=> '{vmid}/rrddata',
741 protected
=> 1, # fixme: can we avoid that?
743 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
745 description
=> "Read VM RRD statistics",
747 additionalProperties
=> 0,
749 node
=> get_standard_option
('pve-node'),
750 vmid
=> get_standard_option
('pve-vmid'),
752 description
=> "Specify the time frame you are interested in.",
754 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
757 description
=> "The RRD consolidation function",
759 enum
=> [ 'AVERAGE', 'MAX' ],
774 return PVE
::Cluster
::create_rrd_data
(
775 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
779 __PACKAGE__-
>register_method({
781 path
=> '{vmid}/config',
784 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
786 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
789 additionalProperties
=> 0,
791 node
=> get_standard_option
('pve-node'),
792 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
794 description
=> "Get current values (instead of pending values).",
806 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
813 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
815 delete $conf->{snapshots
};
817 if (!$param->{current
}) {
818 foreach my $opt (keys %{$conf->{pending
}}) {
819 next if $opt eq 'delete';
820 my $value = $conf->{pending
}->{$opt};
821 next if ref($value); # just to be sure
822 $conf->{$opt} = $value;
824 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
825 foreach my $opt (keys %$pending_delete_hash) {
826 delete $conf->{$opt} if $conf->{$opt};
830 delete $conf->{pending
};
832 # hide cloudinit password
833 if ($conf->{cipassword
}) {
834 $conf->{cipassword
} = '**********';
840 __PACKAGE__-
>register_method({
841 name
=> 'vm_pending',
842 path
=> '{vmid}/pending',
845 description
=> "Get virtual machine configuration, including pending changes.",
847 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
850 additionalProperties
=> 0,
852 node
=> get_standard_option
('pve-node'),
853 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
862 description
=> "Configuration option name.",
866 description
=> "Current value.",
871 description
=> "Pending value.",
876 description
=> "Indicates a pending delete request if present and not 0. " .
877 "The value 2 indicates a force-delete request.",
889 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
891 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
895 foreach my $opt (keys %$conf) {
896 next if ref($conf->{$opt});
897 my $item = { key
=> $opt };
898 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
899 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
900 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
902 # hide cloudinit password
903 if ($opt eq 'cipassword') {
904 $item->{value
} = '**********' if defined($item->{value
});
905 # the trailing space so that the pending string is different
906 $item->{pending
} = '********** ' if defined($item->{pending
});
911 foreach my $opt (keys %{$conf->{pending
}}) {
912 next if $opt eq 'delete';
913 next if ref($conf->{pending
}->{$opt}); # just to be sure
914 next if defined($conf->{$opt});
915 my $item = { key
=> $opt };
916 $item->{pending
} = $conf->{pending
}->{$opt};
918 # hide cloudinit password
919 if ($opt eq 'cipassword') {
920 $item->{pending
} = '**********' if defined($item->{pending
});
925 while (my ($opt, $force) = each %$pending_delete_hash) {
926 next if $conf->{pending
}->{$opt}; # just to be sure
927 next if $conf->{$opt};
928 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
935 # POST/PUT {vmid}/config implementation
937 # The original API used PUT (idempotent) an we assumed that all operations
938 # are fast. But it turned out that almost any configuration change can
939 # involve hot-plug actions, or disk alloc/free. Such actions can take long
940 # time to complete and have side effects (not idempotent).
942 # The new implementation uses POST and forks a worker process. We added
943 # a new option 'background_delay'. If specified we wait up to
944 # 'background_delay' second for the worker task to complete. It returns null
945 # if the task is finished within that time, else we return the UPID.
947 my $update_vm_api = sub {
948 my ($param, $sync) = @_;
950 my $rpcenv = PVE
::RPCEnvironment
::get
();
952 my $authuser = $rpcenv->get_user();
954 my $node = extract_param
($param, 'node');
956 my $vmid = extract_param
($param, 'vmid');
958 my $digest = extract_param
($param, 'digest');
960 my $background_delay = extract_param
($param, 'background_delay');
962 if (defined(my $cipassword = $param->{cipassword
})) {
963 # Same logic as in cloud-init (but with the regex fixed...)
964 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
965 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
968 my @paramarr = (); # used for log message
969 foreach my $key (sort keys %$param) {
970 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
971 push @paramarr, "-$key", $value;
974 my $skiplock = extract_param
($param, 'skiplock');
975 raise_param_exc
({ skiplock
=> "Only root may use this option." })
976 if $skiplock && $authuser ne 'root@pam';
978 my $delete_str = extract_param
($param, 'delete');
980 my $revert_str = extract_param
($param, 'revert');
982 my $force = extract_param
($param, 'force');
984 if (defined(my $ssh_keys = $param->{sshkeys
})) {
985 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
986 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
989 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
991 my $storecfg = PVE
::Storage
::config
();
993 my $defaults = PVE
::QemuServer
::load_defaults
();
995 &$resolve_cdrom_alias($param);
997 # now try to verify all parameters
1000 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1001 if (!PVE
::QemuServer
::option_exists
($opt)) {
1002 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1005 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1006 "-revert $opt' at the same time" })
1007 if defined($param->{$opt});
1009 $revert->{$opt} = 1;
1013 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1014 $opt = 'ide2' if $opt eq 'cdrom';
1016 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1017 "-delete $opt' at the same time" })
1018 if defined($param->{$opt});
1020 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1021 "-revert $opt' at the same time" })
1024 if (!PVE
::QemuServer
::option_exists
($opt)) {
1025 raise_param_exc
({ delete => "unknown option '$opt'" });
1031 my $repl_conf = PVE
::ReplicationConfig-
>new();
1032 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1033 my $check_replication = sub {
1035 return if !$is_replicated;
1036 my $volid = $drive->{file
};
1037 return if !$volid || !($drive->{replicate
}//1);
1038 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1039 my ($storeid, $format);
1040 if ($volid =~ $NEW_DISK_RE) {
1042 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1044 ($storeid, undef) = PVE
::Storage
::parse_volume_id
($volid, 1);
1045 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1047 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1048 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1049 return if $scfg->{shared
};
1050 die "cannot add non-replicatable volume to a replicated VM\n";
1053 foreach my $opt (keys %$param) {
1054 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1055 # cleanup drive path
1056 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1057 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1058 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1059 $check_replication->($drive);
1060 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1061 } elsif ($opt =~ m/^net(\d+)$/) {
1063 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1064 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1068 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1070 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1072 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1074 my $updatefn = sub {
1076 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1078 die "checksum missmatch (file change by other user?)\n"
1079 if $digest && $digest ne $conf->{digest
};
1081 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1083 foreach my $opt (keys %$revert) {
1084 if (defined($conf->{$opt})) {
1085 $param->{$opt} = $conf->{$opt};
1086 } elsif (defined($conf->{pending
}->{$opt})) {
1091 if ($param->{memory
} || defined($param->{balloon
})) {
1092 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1093 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1095 die "balloon value too large (must be smaller than assigned memory)\n"
1096 if $balloon && $balloon > $maxmem;
1099 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1103 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1105 # write updates to pending section
1107 my $modified = {}; # record what $option we modify
1109 foreach my $opt (@delete) {
1110 $modified->{$opt} = 1;
1111 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1112 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1113 warn "cannot delete '$opt' - not set in current configuration!\n";
1114 $modified->{$opt} = 0;
1118 if ($opt =~ m/^unused/) {
1119 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1120 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1121 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1122 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1123 delete $conf->{$opt};
1124 PVE
::QemuConfig-
>write_config($vmid, $conf);
1126 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1127 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1128 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1129 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1130 if defined($conf->{pending
}->{$opt});
1131 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1132 PVE
::QemuConfig-
>write_config($vmid, $conf);
1134 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1135 PVE
::QemuConfig-
>write_config($vmid, $conf);
1139 foreach my $opt (keys %$param) { # add/change
1140 $modified->{$opt} = 1;
1141 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1142 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1144 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1145 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1146 # FIXME: cloudinit: CDROM or Disk?
1147 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1148 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1150 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1152 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1153 if defined($conf->{pending
}->{$opt});
1155 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1157 $conf->{pending
}->{$opt} = $param->{$opt};
1159 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1160 PVE
::QemuConfig-
>write_config($vmid, $conf);
1163 # remove pending changes when nothing changed
1164 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1165 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1166 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1168 return if !scalar(keys %{$conf->{pending
}});
1170 my $running = PVE
::QemuServer
::check_running
($vmid);
1172 # apply pending changes
1174 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1178 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1179 raise_param_exc
($errors) if scalar(keys %$errors);
1181 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1191 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1193 if ($background_delay) {
1195 # Note: It would be better to do that in the Event based HTTPServer
1196 # to avoid blocking call to sleep.
1198 my $end_time = time() + $background_delay;
1200 my $task = PVE
::Tools
::upid_decode
($upid);
1203 while (time() < $end_time) {
1204 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1206 sleep(1); # this gets interrupted when child process ends
1210 my $status = PVE
::Tools
::upid_read_status
($upid);
1211 return undef if $status eq 'OK';
1220 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1223 my $vm_config_perm_list = [
1228 'VM.Config.Network',
1230 'VM.Config.Options',
1233 __PACKAGE__-
>register_method({
1234 name
=> 'update_vm_async',
1235 path
=> '{vmid}/config',
1239 description
=> "Set virtual machine options (asynchrounous API).",
1241 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1244 additionalProperties
=> 0,
1245 properties
=> PVE
::QemuServer
::json_config_properties
(
1247 node
=> get_standard_option
('pve-node'),
1248 vmid
=> get_standard_option
('pve-vmid'),
1249 skiplock
=> get_standard_option
('skiplock'),
1251 type
=> 'string', format
=> 'pve-configid-list',
1252 description
=> "A list of settings you want to delete.",
1256 type
=> 'string', format
=> 'pve-configid-list',
1257 description
=> "Revert a pending change.",
1262 description
=> $opt_force_description,
1264 requires
=> 'delete',
1268 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1272 background_delay
=> {
1274 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1285 code
=> $update_vm_api,
1288 __PACKAGE__-
>register_method({
1289 name
=> 'update_vm',
1290 path
=> '{vmid}/config',
1294 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1296 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1299 additionalProperties
=> 0,
1300 properties
=> PVE
::QemuServer
::json_config_properties
(
1302 node
=> get_standard_option
('pve-node'),
1303 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1304 skiplock
=> get_standard_option
('skiplock'),
1306 type
=> 'string', format
=> 'pve-configid-list',
1307 description
=> "A list of settings you want to delete.",
1311 type
=> 'string', format
=> 'pve-configid-list',
1312 description
=> "Revert a pending change.",
1317 description
=> $opt_force_description,
1319 requires
=> 'delete',
1323 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1329 returns
=> { type
=> 'null' },
1332 &$update_vm_api($param, 1);
1338 __PACKAGE__-
>register_method({
1339 name
=> 'destroy_vm',
1344 description
=> "Destroy the vm (also delete all used/owned volumes).",
1346 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1349 additionalProperties
=> 0,
1351 node
=> get_standard_option
('pve-node'),
1352 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1353 skiplock
=> get_standard_option
('skiplock'),
1362 my $rpcenv = PVE
::RPCEnvironment
::get
();
1364 my $authuser = $rpcenv->get_user();
1366 my $vmid = $param->{vmid
};
1368 my $skiplock = $param->{skiplock
};
1369 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1370 if $skiplock && $authuser ne 'root@pam';
1373 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1375 my $storecfg = PVE
::Storage
::config
();
1377 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1379 die "unable to remove VM $vmid - used in HA resources\n"
1380 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1382 # do not allow destroy if there are replication jobs
1383 my $repl_conf = PVE
::ReplicationConfig-
>new();
1384 $repl_conf->check_for_existing_jobs($vmid);
1386 # early tests (repeat after locking)
1387 die "VM $vmid is running - destroy failed\n"
1388 if PVE
::QemuServer
::check_running
($vmid);
1393 syslog
('info', "destroy VM $vmid: $upid\n");
1395 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1397 PVE
::AccessControl
::remove_vm_access
($vmid);
1399 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1402 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1405 __PACKAGE__-
>register_method({
1407 path
=> '{vmid}/unlink',
1411 description
=> "Unlink/delete disk images.",
1413 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1416 additionalProperties
=> 0,
1418 node
=> get_standard_option
('pve-node'),
1419 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1421 type
=> 'string', format
=> 'pve-configid-list',
1422 description
=> "A list of disk IDs you want to delete.",
1426 description
=> $opt_force_description,
1431 returns
=> { type
=> 'null'},
1435 $param->{delete} = extract_param
($param, 'idlist');
1437 __PACKAGE__-
>update_vm($param);
1444 __PACKAGE__-
>register_method({
1446 path
=> '{vmid}/vncproxy',
1450 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1452 description
=> "Creates a TCP VNC proxy connections.",
1454 additionalProperties
=> 0,
1456 node
=> get_standard_option
('pve-node'),
1457 vmid
=> get_standard_option
('pve-vmid'),
1461 description
=> "starts websockify instead of vncproxy",
1466 additionalProperties
=> 0,
1468 user
=> { type
=> 'string' },
1469 ticket
=> { type
=> 'string' },
1470 cert
=> { type
=> 'string' },
1471 port
=> { type
=> 'integer' },
1472 upid
=> { type
=> 'string' },
1478 my $rpcenv = PVE
::RPCEnvironment
::get
();
1480 my $authuser = $rpcenv->get_user();
1482 my $vmid = $param->{vmid
};
1483 my $node = $param->{node
};
1484 my $websocket = $param->{websocket
};
1486 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1488 my $authpath = "/vms/$vmid";
1490 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1492 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1495 my ($remip, $family);
1498 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1499 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1500 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1501 $remcmd = ['/usr/bin/ssh', '-e', 'none', '-T', '-o', 'BatchMode=yes', $remip];
1503 $family = PVE
::Tools
::get_host_address_family
($node);
1506 my $port = PVE
::Tools
::next_vnc_port
($family);
1513 syslog
('info', "starting vnc proxy $upid\n");
1517 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1520 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1522 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1523 '-timeout', $timeout, '-authpath', $authpath,
1524 '-perm', 'Sys.Console'];
1526 if ($param->{websocket
}) {
1527 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1528 push @$cmd, '-notls', '-listen', 'localhost';
1531 push @$cmd, '-c', @$remcmd, @$termcmd;
1533 PVE
::Tools
::run_command
($cmd);
1537 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1539 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1541 my $sock = IO
::Socket
::IP-
>new(
1546 GetAddrInfoFlags
=> 0,
1547 ) or die "failed to create socket: $!\n";
1548 # Inside the worker we shouldn't have any previous alarms
1549 # running anyway...:
1551 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1553 accept(my $cli, $sock) or die "connection failed: $!\n";
1556 if (PVE
::Tools
::run_command
($cmd,
1557 output
=> '>&'.fileno($cli),
1558 input
=> '<&'.fileno($cli),
1561 die "Failed to run vncproxy.\n";
1568 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1570 PVE
::Tools
::wait_for_vnc_port
($port);
1581 __PACKAGE__-
>register_method({
1582 name
=> 'termproxy',
1583 path
=> '{vmid}/termproxy',
1587 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1589 description
=> "Creates a TCP proxy connections.",
1591 additionalProperties
=> 0,
1593 node
=> get_standard_option
('pve-node'),
1594 vmid
=> get_standard_option
('pve-vmid'),
1598 enum
=> [qw(serial0 serial1 serial2 serial3)],
1599 description
=> "opens a serial terminal (defaults to display)",
1604 additionalProperties
=> 0,
1606 user
=> { type
=> 'string' },
1607 ticket
=> { type
=> 'string' },
1608 port
=> { type
=> 'integer' },
1609 upid
=> { type
=> 'string' },
1615 my $rpcenv = PVE
::RPCEnvironment
::get
();
1617 my $authuser = $rpcenv->get_user();
1619 my $vmid = $param->{vmid
};
1620 my $node = $param->{node
};
1621 my $serial = $param->{serial
};
1623 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1625 if (!defined($serial)) {
1626 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1627 $serial = $conf->{vga
};
1631 my $authpath = "/vms/$vmid";
1633 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1635 my ($remip, $family);
1637 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1638 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1640 $family = PVE
::Tools
::get_host_address_family
($node);
1643 my $port = PVE
::Tools
::next_vnc_port
($family);
1645 my $remcmd = $remip ?
1646 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
1648 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1649 push @$termcmd, '-iface', $serial if $serial;
1654 syslog
('info', "starting qemu termproxy $upid\n");
1656 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1657 '--perm', 'VM.Console', '--'];
1658 push @$cmd, @$remcmd, @$termcmd;
1660 PVE
::Tools
::run_command
($cmd);
1663 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1665 PVE
::Tools
::wait_for_vnc_port
($port);
1675 __PACKAGE__-
>register_method({
1676 name
=> 'vncwebsocket',
1677 path
=> '{vmid}/vncwebsocket',
1680 description
=> "You also need to pass a valid ticket (vncticket).",
1681 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1683 description
=> "Opens a weksocket for VNC traffic.",
1685 additionalProperties
=> 0,
1687 node
=> get_standard_option
('pve-node'),
1688 vmid
=> get_standard_option
('pve-vmid'),
1690 description
=> "Ticket from previous call to vncproxy.",
1695 description
=> "Port number returned by previous vncproxy call.",
1705 port
=> { type
=> 'string' },
1711 my $rpcenv = PVE
::RPCEnvironment
::get
();
1713 my $authuser = $rpcenv->get_user();
1715 my $vmid = $param->{vmid
};
1716 my $node = $param->{node
};
1718 my $authpath = "/vms/$vmid";
1720 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1722 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1724 # Note: VNC ports are acessible from outside, so we do not gain any
1725 # security if we verify that $param->{port} belongs to VM $vmid. This
1726 # check is done by verifying the VNC ticket (inside VNC protocol).
1728 my $port = $param->{port
};
1730 return { port
=> $port };
1733 __PACKAGE__-
>register_method({
1734 name
=> 'spiceproxy',
1735 path
=> '{vmid}/spiceproxy',
1740 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1742 description
=> "Returns a SPICE configuration to connect to the VM.",
1744 additionalProperties
=> 0,
1746 node
=> get_standard_option
('pve-node'),
1747 vmid
=> get_standard_option
('pve-vmid'),
1748 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1751 returns
=> get_standard_option
('remote-viewer-config'),
1755 my $rpcenv = PVE
::RPCEnvironment
::get
();
1757 my $authuser = $rpcenv->get_user();
1759 my $vmid = $param->{vmid
};
1760 my $node = $param->{node
};
1761 my $proxy = $param->{proxy
};
1763 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1764 my $title = "VM $vmid";
1765 $title .= " - ". $conf->{name
} if $conf->{name
};
1767 my $port = PVE
::QemuServer
::spice_port
($vmid);
1769 my ($ticket, undef, $remote_viewer_config) =
1770 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1772 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1773 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1775 return $remote_viewer_config;
1778 __PACKAGE__-
>register_method({
1780 path
=> '{vmid}/status',
1783 description
=> "Directory index",
1788 additionalProperties
=> 0,
1790 node
=> get_standard_option
('pve-node'),
1791 vmid
=> get_standard_option
('pve-vmid'),
1799 subdir
=> { type
=> 'string' },
1802 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1808 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1811 { subdir
=> 'current' },
1812 { subdir
=> 'start' },
1813 { subdir
=> 'stop' },
1819 __PACKAGE__-
>register_method({
1820 name
=> 'vm_status',
1821 path
=> '{vmid}/status/current',
1824 protected
=> 1, # qemu pid files are only readable by root
1825 description
=> "Get virtual machine status.",
1827 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1830 additionalProperties
=> 0,
1832 node
=> get_standard_option
('pve-node'),
1833 vmid
=> get_standard_option
('pve-vmid'),
1836 returns
=> { type
=> 'object' },
1841 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1843 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1844 my $status = $vmstatus->{$param->{vmid
}};
1846 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1848 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1850 $status->{agent
} = 1 if $conf->{agent
};
1855 __PACKAGE__-
>register_method({
1857 path
=> '{vmid}/status/start',
1861 description
=> "Start virtual machine.",
1863 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1866 additionalProperties
=> 0,
1868 node
=> get_standard_option
('pve-node'),
1869 vmid
=> get_standard_option
('pve-vmid',
1870 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1871 skiplock
=> get_standard_option
('skiplock'),
1872 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1873 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1876 enum
=> ['secure', 'insecure'],
1877 description
=> "Migration traffic is encrypted using an SSH " .
1878 "tunnel by default. On secure, completely private networks " .
1879 "this can be disabled to increase performance.",
1882 migration_network
=> {
1883 type
=> 'string', format
=> 'CIDR',
1884 description
=> "CIDR of the (sub) network that is used for migration.",
1887 machine
=> get_standard_option
('pve-qm-machine'),
1889 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1901 my $rpcenv = PVE
::RPCEnvironment
::get
();
1903 my $authuser = $rpcenv->get_user();
1905 my $node = extract_param
($param, 'node');
1907 my $vmid = extract_param
($param, 'vmid');
1909 my $machine = extract_param
($param, 'machine');
1911 my $stateuri = extract_param
($param, 'stateuri');
1912 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1913 if $stateuri && $authuser ne 'root@pam';
1915 my $skiplock = extract_param
($param, 'skiplock');
1916 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1917 if $skiplock && $authuser ne 'root@pam';
1919 my $migratedfrom = extract_param
($param, 'migratedfrom');
1920 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1921 if $migratedfrom && $authuser ne 'root@pam';
1923 my $migration_type = extract_param
($param, 'migration_type');
1924 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1925 if $migration_type && $authuser ne 'root@pam';
1927 my $migration_network = extract_param
($param, 'migration_network');
1928 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1929 if $migration_network && $authuser ne 'root@pam';
1931 my $targetstorage = extract_param
($param, 'targetstorage');
1932 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1933 if $targetstorage && $authuser ne 'root@pam';
1935 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1936 if $targetstorage && !$migratedfrom;
1938 # read spice ticket from STDIN
1940 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1941 if (defined(my $line = <STDIN
>)) {
1943 $spice_ticket = $line;
1947 PVE
::Cluster
::check_cfs_quorum
();
1949 my $storecfg = PVE
::Storage
::config
();
1951 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1952 $rpcenv->{type
} ne 'ha') {
1957 my $service = "vm:$vmid";
1959 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
1961 print "Requesting HA start for VM $vmid\n";
1963 PVE
::Tools
::run_command
($cmd);
1968 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1975 syslog
('info', "start VM $vmid: $upid\n");
1977 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1978 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
1983 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1987 __PACKAGE__-
>register_method({
1989 path
=> '{vmid}/status/stop',
1993 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1994 "is akin to pulling the power plug of a running computer and may damage the VM data",
1996 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1999 additionalProperties
=> 0,
2001 node
=> get_standard_option
('pve-node'),
2002 vmid
=> get_standard_option
('pve-vmid',
2003 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2004 skiplock
=> get_standard_option
('skiplock'),
2005 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2007 description
=> "Wait maximal timeout seconds.",
2013 description
=> "Do not deactivate storage volumes.",
2026 my $rpcenv = PVE
::RPCEnvironment
::get
();
2028 my $authuser = $rpcenv->get_user();
2030 my $node = extract_param
($param, 'node');
2032 my $vmid = extract_param
($param, 'vmid');
2034 my $skiplock = extract_param
($param, 'skiplock');
2035 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2036 if $skiplock && $authuser ne 'root@pam';
2038 my $keepActive = extract_param
($param, 'keepActive');
2039 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2040 if $keepActive && $authuser ne 'root@pam';
2042 my $migratedfrom = extract_param
($param, 'migratedfrom');
2043 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2044 if $migratedfrom && $authuser ne 'root@pam';
2047 my $storecfg = PVE
::Storage
::config
();
2049 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2054 my $service = "vm:$vmid";
2056 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2058 print "Requesting HA stop for VM $vmid\n";
2060 PVE
::Tools
::run_command
($cmd);
2065 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2071 syslog
('info', "stop VM $vmid: $upid\n");
2073 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2074 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2079 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2083 __PACKAGE__-
>register_method({
2085 path
=> '{vmid}/status/reset',
2089 description
=> "Reset virtual machine.",
2091 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2094 additionalProperties
=> 0,
2096 node
=> get_standard_option
('pve-node'),
2097 vmid
=> get_standard_option
('pve-vmid',
2098 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2099 skiplock
=> get_standard_option
('skiplock'),
2108 my $rpcenv = PVE
::RPCEnvironment
::get
();
2110 my $authuser = $rpcenv->get_user();
2112 my $node = extract_param
($param, 'node');
2114 my $vmid = extract_param
($param, 'vmid');
2116 my $skiplock = extract_param
($param, 'skiplock');
2117 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2118 if $skiplock && $authuser ne 'root@pam';
2120 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2125 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2130 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2133 __PACKAGE__-
>register_method({
2134 name
=> 'vm_shutdown',
2135 path
=> '{vmid}/status/shutdown',
2139 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2140 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2142 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2145 additionalProperties
=> 0,
2147 node
=> get_standard_option
('pve-node'),
2148 vmid
=> get_standard_option
('pve-vmid',
2149 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2150 skiplock
=> get_standard_option
('skiplock'),
2152 description
=> "Wait maximal timeout seconds.",
2158 description
=> "Make sure the VM stops.",
2164 description
=> "Do not deactivate storage volumes.",
2177 my $rpcenv = PVE
::RPCEnvironment
::get
();
2179 my $authuser = $rpcenv->get_user();
2181 my $node = extract_param
($param, 'node');
2183 my $vmid = extract_param
($param, 'vmid');
2185 my $skiplock = extract_param
($param, 'skiplock');
2186 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2187 if $skiplock && $authuser ne 'root@pam';
2189 my $keepActive = extract_param
($param, 'keepActive');
2190 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2191 if $keepActive && $authuser ne 'root@pam';
2193 my $storecfg = PVE
::Storage
::config
();
2197 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2198 # otherwise, we will infer a shutdown command, but run into the timeout,
2199 # then when the vm is resumed, it will instantly shutdown
2201 # checking the qmp status here to get feedback to the gui/cli/api
2202 # and the status query should not take too long
2205 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2209 if (!$err && $qmpstatus->{status
} eq "paused") {
2210 if ($param->{forceStop
}) {
2211 warn "VM is paused - stop instead of shutdown\n";
2214 die "VM is paused - cannot shutdown\n";
2218 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2219 ($rpcenv->{type
} ne 'ha')) {
2224 my $service = "vm:$vmid";
2226 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2228 print "Requesting HA stop for VM $vmid\n";
2230 PVE
::Tools
::run_command
($cmd);
2235 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2242 syslog
('info', "shutdown VM $vmid: $upid\n");
2244 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2245 $shutdown, $param->{forceStop
}, $keepActive);
2250 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2254 __PACKAGE__-
>register_method({
2255 name
=> 'vm_suspend',
2256 path
=> '{vmid}/status/suspend',
2260 description
=> "Suspend virtual machine.",
2262 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2265 additionalProperties
=> 0,
2267 node
=> get_standard_option
('pve-node'),
2268 vmid
=> get_standard_option
('pve-vmid',
2269 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2270 skiplock
=> get_standard_option
('skiplock'),
2279 my $rpcenv = PVE
::RPCEnvironment
::get
();
2281 my $authuser = $rpcenv->get_user();
2283 my $node = extract_param
($param, 'node');
2285 my $vmid = extract_param
($param, 'vmid');
2287 my $skiplock = extract_param
($param, 'skiplock');
2288 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2289 if $skiplock && $authuser ne 'root@pam';
2291 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2296 syslog
('info', "suspend VM $vmid: $upid\n");
2298 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2303 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2306 __PACKAGE__-
>register_method({
2307 name
=> 'vm_resume',
2308 path
=> '{vmid}/status/resume',
2312 description
=> "Resume virtual machine.",
2314 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2317 additionalProperties
=> 0,
2319 node
=> get_standard_option
('pve-node'),
2320 vmid
=> get_standard_option
('pve-vmid',
2321 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2322 skiplock
=> get_standard_option
('skiplock'),
2323 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2333 my $rpcenv = PVE
::RPCEnvironment
::get
();
2335 my $authuser = $rpcenv->get_user();
2337 my $node = extract_param
($param, 'node');
2339 my $vmid = extract_param
($param, 'vmid');
2341 my $skiplock = extract_param
($param, 'skiplock');
2342 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2343 if $skiplock && $authuser ne 'root@pam';
2345 my $nocheck = extract_param
($param, 'nocheck');
2347 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2352 syslog
('info', "resume VM $vmid: $upid\n");
2354 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2359 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2362 __PACKAGE__-
>register_method({
2363 name
=> 'vm_sendkey',
2364 path
=> '{vmid}/sendkey',
2368 description
=> "Send key event to virtual machine.",
2370 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2373 additionalProperties
=> 0,
2375 node
=> get_standard_option
('pve-node'),
2376 vmid
=> get_standard_option
('pve-vmid',
2377 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2378 skiplock
=> get_standard_option
('skiplock'),
2380 description
=> "The key (qemu monitor encoding).",
2385 returns
=> { type
=> 'null'},
2389 my $rpcenv = PVE
::RPCEnvironment
::get
();
2391 my $authuser = $rpcenv->get_user();
2393 my $node = extract_param
($param, 'node');
2395 my $vmid = extract_param
($param, 'vmid');
2397 my $skiplock = extract_param
($param, 'skiplock');
2398 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2399 if $skiplock && $authuser ne 'root@pam';
2401 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2406 __PACKAGE__-
>register_method({
2407 name
=> 'vm_feature',
2408 path
=> '{vmid}/feature',
2412 description
=> "Check if feature for virtual machine is available.",
2414 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2417 additionalProperties
=> 0,
2419 node
=> get_standard_option
('pve-node'),
2420 vmid
=> get_standard_option
('pve-vmid'),
2422 description
=> "Feature to check.",
2424 enum
=> [ 'snapshot', 'clone', 'copy' ],
2426 snapname
=> get_standard_option
('pve-snapshot-name', {
2434 hasFeature
=> { type
=> 'boolean' },
2437 items
=> { type
=> 'string' },
2444 my $node = extract_param
($param, 'node');
2446 my $vmid = extract_param
($param, 'vmid');
2448 my $snapname = extract_param
($param, 'snapname');
2450 my $feature = extract_param
($param, 'feature');
2452 my $running = PVE
::QemuServer
::check_running
($vmid);
2454 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2457 my $snap = $conf->{snapshots
}->{$snapname};
2458 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2461 my $storecfg = PVE
::Storage
::config
();
2463 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2464 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2467 hasFeature
=> $hasFeature,
2468 nodes
=> [ keys %$nodelist ],
2472 __PACKAGE__-
>register_method({
2474 path
=> '{vmid}/clone',
2478 description
=> "Create a copy of virtual machine/template.",
2480 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2481 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2482 "'Datastore.AllocateSpace' on any used storage.",
2485 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2487 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2488 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2493 additionalProperties
=> 0,
2495 node
=> get_standard_option
('pve-node'),
2496 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2497 newid
=> get_standard_option
('pve-vmid', {
2498 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2499 description
=> 'VMID for the clone.' }),
2502 type
=> 'string', format
=> 'dns-name',
2503 description
=> "Set a name for the new VM.",
2508 description
=> "Description for the new VM.",
2512 type
=> 'string', format
=> 'pve-poolid',
2513 description
=> "Add the new VM to the specified pool.",
2515 snapname
=> get_standard_option
('pve-snapshot-name', {
2518 storage
=> get_standard_option
('pve-storage-id', {
2519 description
=> "Target storage for full clone.",
2523 description
=> "Target format for file storage. Only valid for full clone.",
2526 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2531 description
=> "Create a full copy of all disks. This is always done when " .
2532 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2534 target
=> get_standard_option
('pve-node', {
2535 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2546 my $rpcenv = PVE
::RPCEnvironment
::get
();
2548 my $authuser = $rpcenv->get_user();
2550 my $node = extract_param
($param, 'node');
2552 my $vmid = extract_param
($param, 'vmid');
2554 my $newid = extract_param
($param, 'newid');
2556 my $pool = extract_param
($param, 'pool');
2558 if (defined($pool)) {
2559 $rpcenv->check_pool_exist($pool);
2562 my $snapname = extract_param
($param, 'snapname');
2564 my $storage = extract_param
($param, 'storage');
2566 my $format = extract_param
($param, 'format');
2568 my $target = extract_param
($param, 'target');
2570 my $localnode = PVE
::INotify
::nodename
();
2572 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2574 PVE
::Cluster
::check_node_exists
($target) if $target;
2576 my $storecfg = PVE
::Storage
::config
();
2579 # check if storage is enabled on local node
2580 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2582 # check if storage is available on target node
2583 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2584 # clone only works if target storage is shared
2585 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2586 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2590 PVE
::Cluster
::check_cfs_quorum
();
2592 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2594 # exclusive lock if VM is running - else shared lock is enough;
2595 my $shared_lock = $running ?
0 : 1;
2599 # do all tests after lock
2600 # we also try to do all tests before we fork the worker
2602 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2604 PVE
::QemuConfig-
>check_lock($conf);
2606 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2608 die "unexpected state change\n" if $verify_running != $running;
2610 die "snapshot '$snapname' does not exist\n"
2611 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2613 my $full = extract_param
($param, 'full');
2614 if (!defined($full)) {
2615 $full = !PVE
::QemuConfig-
>is_template($conf);
2618 die "parameter 'storage' not allowed for linked clones\n"
2619 if defined($storage) && !$full;
2621 die "parameter 'format' not allowed for linked clones\n"
2622 if defined($format) && !$full;
2624 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2626 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2628 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2630 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2632 die "unable to create VM $newid: config file already exists\n"
2635 my $newconf = { lock => 'clone' };
2640 foreach my $opt (keys %$oldconf) {
2641 my $value = $oldconf->{$opt};
2643 # do not copy snapshot related info
2644 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2645 $opt eq 'vmstate' || $opt eq 'snapstate';
2647 # no need to copy unused images, because VMID(owner) changes anyways
2648 next if $opt =~ m/^unused\d+$/;
2650 # always change MAC! address
2651 if ($opt =~ m/^net(\d+)$/) {
2652 my $net = PVE
::QemuServer
::parse_net
($value);
2653 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2654 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2655 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2656 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2657 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2658 die "unable to parse drive options for '$opt'\n" if !$drive;
2659 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2660 $newconf->{$opt} = $value; # simply copy configuration
2662 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2663 die "Full clone feature is not supported for drive '$opt'\n"
2664 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2665 $fullclone->{$opt} = 1;
2667 # not full means clone instead of copy
2668 die "Linked clone feature is not supported for drive '$opt'\n"
2669 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2671 $drives->{$opt} = $drive;
2672 push @$vollist, $drive->{file
};
2675 # copy everything else
2676 $newconf->{$opt} = $value;
2680 # auto generate a new uuid
2681 my ($uuid, $uuid_str);
2682 UUID
::generate
($uuid);
2683 UUID
::unparse
($uuid, $uuid_str);
2684 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2685 $smbios1->{uuid
} = $uuid_str;
2686 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2688 delete $newconf->{template
};
2690 if ($param->{name
}) {
2691 $newconf->{name
} = $param->{name
};
2693 if ($oldconf->{name
}) {
2694 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2696 $newconf->{name
} = "Copy-of-VM-$vmid";
2700 if ($param->{description
}) {
2701 $newconf->{description
} = $param->{description
};
2704 # create empty/temp config - this fails if VM already exists on other node
2705 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2710 my $newvollist = [];
2717 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2719 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2721 my $total_jobs = scalar(keys %{$drives});
2724 foreach my $opt (keys %$drives) {
2725 my $drive = $drives->{$opt};
2726 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2728 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2729 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2730 $jobs, $skipcomplete, $oldconf->{agent
});
2732 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2734 PVE
::QemuConfig-
>write_config($newid, $newconf);
2738 delete $newconf->{lock};
2740 # do not write pending changes
2741 if ($newconf->{pending
}) {
2742 warn "found pending changes, discarding for clone\n";
2743 delete $newconf->{pending
};
2746 PVE
::QemuConfig-
>write_config($newid, $newconf);
2749 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2750 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2751 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2753 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2754 die "Failed to move config to node '$target' - rename failed: $!\n"
2755 if !rename($conffile, $newconffile);
2758 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2763 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2765 sleep 1; # some storage like rbd need to wait before release volume - really?
2767 foreach my $volid (@$newvollist) {
2768 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2771 die "clone failed: $err";
2777 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2779 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2782 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2783 # Aquire exclusive lock lock for $newid
2784 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2789 __PACKAGE__-
>register_method({
2790 name
=> 'move_vm_disk',
2791 path
=> '{vmid}/move_disk',
2795 description
=> "Move volume to different storage.",
2797 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2799 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2800 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2804 additionalProperties
=> 0,
2806 node
=> get_standard_option
('pve-node'),
2807 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2810 description
=> "The disk you want to move.",
2811 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2813 storage
=> get_standard_option
('pve-storage-id', {
2814 description
=> "Target storage.",
2815 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2819 description
=> "Target Format.",
2820 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2825 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2831 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2839 description
=> "the task ID.",
2844 my $rpcenv = PVE
::RPCEnvironment
::get
();
2846 my $authuser = $rpcenv->get_user();
2848 my $node = extract_param
($param, 'node');
2850 my $vmid = extract_param
($param, 'vmid');
2852 my $digest = extract_param
($param, 'digest');
2854 my $disk = extract_param
($param, 'disk');
2856 my $storeid = extract_param
($param, 'storage');
2858 my $format = extract_param
($param, 'format');
2860 my $storecfg = PVE
::Storage
::config
();
2862 my $updatefn = sub {
2864 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2866 PVE
::QemuConfig-
>check_lock($conf);
2868 die "checksum missmatch (file change by other user?)\n"
2869 if $digest && $digest ne $conf->{digest
};
2871 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2873 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2875 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2877 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
2880 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2881 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2885 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2886 (!$format || !$oldfmt || $oldfmt eq $format);
2888 # this only checks snapshots because $disk is passed!
2889 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2890 die "you can't move a disk with snapshots and delete the source\n"
2891 if $snapshotted && $param->{delete};
2893 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2895 my $running = PVE
::QemuServer
::check_running
($vmid);
2897 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2901 my $newvollist = [];
2907 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2909 warn "moving disk with snapshots, snapshots will not be moved!\n"
2912 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2913 $vmid, $storeid, $format, 1, $newvollist);
2915 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2917 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2919 # convert moved disk to base if part of template
2920 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2921 if PVE
::QemuConfig-
>is_template($conf);
2923 PVE
::QemuConfig-
>write_config($vmid, $conf);
2926 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2927 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2934 foreach my $volid (@$newvollist) {
2935 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2938 die "storage migration failed: $err";
2941 if ($param->{delete}) {
2943 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2944 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2950 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2953 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2956 __PACKAGE__-
>register_method({
2957 name
=> 'migrate_vm',
2958 path
=> '{vmid}/migrate',
2962 description
=> "Migrate virtual machine. Creates a new migration task.",
2964 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2967 additionalProperties
=> 0,
2969 node
=> get_standard_option
('pve-node'),
2970 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2971 target
=> get_standard_option
('pve-node', {
2972 description
=> "Target node.",
2973 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2977 description
=> "Use online/live migration.",
2982 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2987 enum
=> ['secure', 'insecure'],
2988 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
2991 migration_network
=> {
2992 type
=> 'string', format
=> 'CIDR',
2993 description
=> "CIDR of the (sub) network that is used for migration.",
2996 "with-local-disks" => {
2998 description
=> "Enable live storage migration for local disk",
3001 targetstorage
=> get_standard_option
('pve-storage-id', {
3002 description
=> "Default target storage.",
3004 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3010 description
=> "the task ID.",
3015 my $rpcenv = PVE
::RPCEnvironment
::get
();
3017 my $authuser = $rpcenv->get_user();
3019 my $target = extract_param
($param, 'target');
3021 my $localnode = PVE
::INotify
::nodename
();
3022 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3024 PVE
::Cluster
::check_cfs_quorum
();
3026 PVE
::Cluster
::check_node_exists
($target);
3028 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3030 my $vmid = extract_param
($param, 'vmid');
3032 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3033 if !$param->{online
} && $param->{targetstorage
};
3035 raise_param_exc
({ force
=> "Only root may use this option." })
3036 if $param->{force
} && $authuser ne 'root@pam';
3038 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3039 if $param->{migration_type
} && $authuser ne 'root@pam';
3041 # allow root only until better network permissions are available
3042 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3043 if $param->{migration_network
} && $authuser ne 'root@pam';
3046 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3048 # try to detect errors early
3050 PVE
::QemuConfig-
>check_lock($conf);
3052 if (PVE
::QemuServer
::check_running
($vmid)) {
3053 die "cant migrate running VM without --online\n"
3054 if !$param->{online
};
3057 my $storecfg = PVE
::Storage
::config
();
3059 if( $param->{targetstorage
}) {
3060 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3062 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3065 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3070 my $service = "vm:$vmid";
3072 my $cmd = ['ha-manager', 'migrate', $service, $target];
3074 print "Requesting HA migration for VM $vmid to node $target\n";
3076 PVE
::Tools
::run_command
($cmd);
3081 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3086 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3090 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3093 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3098 __PACKAGE__-
>register_method({
3100 path
=> '{vmid}/monitor',
3104 description
=> "Execute Qemu monitor commands.",
3106 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3107 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3110 additionalProperties
=> 0,
3112 node
=> get_standard_option
('pve-node'),
3113 vmid
=> get_standard_option
('pve-vmid'),
3116 description
=> "The monitor command.",
3120 returns
=> { type
=> 'string'},
3124 my $rpcenv = PVE
::RPCEnvironment
::get
();
3125 my $authuser = $rpcenv->get_user();
3128 my $command = shift;
3129 return $command =~ m/^\s*info(\s+|$)/
3130 || $command =~ m/^\s*help\s*$/;
3133 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3134 if !&$is_ro($param->{command
});
3136 my $vmid = $param->{vmid
};
3138 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3142 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3144 $res = "ERROR: $@" if $@;
3149 __PACKAGE__-
>register_method({
3150 name
=> 'resize_vm',
3151 path
=> '{vmid}/resize',
3155 description
=> "Extend volume size.",
3157 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3160 additionalProperties
=> 0,
3162 node
=> get_standard_option
('pve-node'),
3163 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3164 skiplock
=> get_standard_option
('skiplock'),
3167 description
=> "The disk you want to resize.",
3168 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3172 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3173 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.",
3177 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3183 returns
=> { type
=> 'null'},
3187 my $rpcenv = PVE
::RPCEnvironment
::get
();
3189 my $authuser = $rpcenv->get_user();
3191 my $node = extract_param
($param, 'node');
3193 my $vmid = extract_param
($param, 'vmid');
3195 my $digest = extract_param
($param, 'digest');
3197 my $disk = extract_param
($param, 'disk');
3199 my $sizestr = extract_param
($param, 'size');
3201 my $skiplock = extract_param
($param, 'skiplock');
3202 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3203 if $skiplock && $authuser ne 'root@pam';
3205 my $storecfg = PVE
::Storage
::config
();
3207 my $updatefn = sub {
3209 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3211 die "checksum missmatch (file change by other user?)\n"
3212 if $digest && $digest ne $conf->{digest
};
3213 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3215 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3217 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3219 my (undef, undef, undef, undef, undef, undef, $format) =
3220 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3222 die "can't resize volume: $disk if snapshot exists\n"
3223 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3225 my $volid = $drive->{file
};
3227 die "disk '$disk' has no associated volume\n" if !$volid;
3229 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3231 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3233 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3235 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3236 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3238 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3239 my ($ext, $newsize, $unit) = ($1, $2, $4);
3242 $newsize = $newsize * 1024;
3243 } elsif ($unit eq 'M') {
3244 $newsize = $newsize * 1024 * 1024;
3245 } elsif ($unit eq 'G') {
3246 $newsize = $newsize * 1024 * 1024 * 1024;
3247 } elsif ($unit eq 'T') {
3248 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3251 $newsize += $size if $ext;
3252 $newsize = int($newsize);
3254 die "shrinking disks is not supported\n" if $newsize < $size;
3256 return if $size == $newsize;
3258 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3260 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3262 $drive->{size
} = $newsize;
3263 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3265 PVE
::QemuConfig-
>write_config($vmid, $conf);
3268 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3272 __PACKAGE__-
>register_method({
3273 name
=> 'snapshot_list',
3274 path
=> '{vmid}/snapshot',
3276 description
=> "List all snapshots.",
3278 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3281 protected
=> 1, # qemu pid files are only readable by root
3283 additionalProperties
=> 0,
3285 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3286 node
=> get_standard_option
('pve-node'),
3295 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3300 my $vmid = $param->{vmid
};
3302 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3303 my $snaphash = $conf->{snapshots
} || {};
3307 foreach my $name (keys %$snaphash) {
3308 my $d = $snaphash->{$name};
3311 snaptime
=> $d->{snaptime
} || 0,
3312 vmstate
=> $d->{vmstate
} ?
1 : 0,
3313 description
=> $d->{description
} || '',
3315 $item->{parent
} = $d->{parent
} if $d->{parent
};
3316 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3320 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3321 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
3322 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3324 push @$res, $current;
3329 __PACKAGE__-
>register_method({
3331 path
=> '{vmid}/snapshot',
3335 description
=> "Snapshot a VM.",
3337 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3340 additionalProperties
=> 0,
3342 node
=> get_standard_option
('pve-node'),
3343 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3344 snapname
=> get_standard_option
('pve-snapshot-name'),
3348 description
=> "Save the vmstate",
3353 description
=> "A textual description or comment.",
3359 description
=> "the task ID.",
3364 my $rpcenv = PVE
::RPCEnvironment
::get
();
3366 my $authuser = $rpcenv->get_user();
3368 my $node = extract_param
($param, 'node');
3370 my $vmid = extract_param
($param, 'vmid');
3372 my $snapname = extract_param
($param, 'snapname');
3374 die "unable to use snapshot name 'current' (reserved name)\n"
3375 if $snapname eq 'current';
3378 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3379 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3380 $param->{description
});
3383 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3386 __PACKAGE__-
>register_method({
3387 name
=> 'snapshot_cmd_idx',
3388 path
=> '{vmid}/snapshot/{snapname}',
3395 additionalProperties
=> 0,
3397 vmid
=> get_standard_option
('pve-vmid'),
3398 node
=> get_standard_option
('pve-node'),
3399 snapname
=> get_standard_option
('pve-snapshot-name'),
3408 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3415 push @$res, { cmd
=> 'rollback' };
3416 push @$res, { cmd
=> 'config' };
3421 __PACKAGE__-
>register_method({
3422 name
=> 'update_snapshot_config',
3423 path
=> '{vmid}/snapshot/{snapname}/config',
3427 description
=> "Update snapshot metadata.",
3429 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3432 additionalProperties
=> 0,
3434 node
=> get_standard_option
('pve-node'),
3435 vmid
=> get_standard_option
('pve-vmid'),
3436 snapname
=> get_standard_option
('pve-snapshot-name'),
3440 description
=> "A textual description or comment.",
3444 returns
=> { type
=> 'null' },
3448 my $rpcenv = PVE
::RPCEnvironment
::get
();
3450 my $authuser = $rpcenv->get_user();
3452 my $vmid = extract_param
($param, 'vmid');
3454 my $snapname = extract_param
($param, 'snapname');
3456 return undef if !defined($param->{description
});
3458 my $updatefn = sub {
3460 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3462 PVE
::QemuConfig-
>check_lock($conf);
3464 my $snap = $conf->{snapshots
}->{$snapname};
3466 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3468 $snap->{description
} = $param->{description
} if defined($param->{description
});
3470 PVE
::QemuConfig-
>write_config($vmid, $conf);
3473 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3478 __PACKAGE__-
>register_method({
3479 name
=> 'get_snapshot_config',
3480 path
=> '{vmid}/snapshot/{snapname}/config',
3483 description
=> "Get snapshot configuration",
3485 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3488 additionalProperties
=> 0,
3490 node
=> get_standard_option
('pve-node'),
3491 vmid
=> get_standard_option
('pve-vmid'),
3492 snapname
=> get_standard_option
('pve-snapshot-name'),
3495 returns
=> { type
=> "object" },
3499 my $rpcenv = PVE
::RPCEnvironment
::get
();
3501 my $authuser = $rpcenv->get_user();
3503 my $vmid = extract_param
($param, 'vmid');
3505 my $snapname = extract_param
($param, 'snapname');
3507 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3509 my $snap = $conf->{snapshots
}->{$snapname};
3511 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3516 __PACKAGE__-
>register_method({
3518 path
=> '{vmid}/snapshot/{snapname}/rollback',
3522 description
=> "Rollback VM state to specified snapshot.",
3524 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3527 additionalProperties
=> 0,
3529 node
=> get_standard_option
('pve-node'),
3530 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3531 snapname
=> get_standard_option
('pve-snapshot-name'),
3536 description
=> "the task ID.",
3541 my $rpcenv = PVE
::RPCEnvironment
::get
();
3543 my $authuser = $rpcenv->get_user();
3545 my $node = extract_param
($param, 'node');
3547 my $vmid = extract_param
($param, 'vmid');
3549 my $snapname = extract_param
($param, 'snapname');
3552 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3553 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3557 # hold migration lock, this makes sure that nobody create replication snapshots
3558 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3561 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3564 __PACKAGE__-
>register_method({
3565 name
=> 'delsnapshot',
3566 path
=> '{vmid}/snapshot/{snapname}',
3570 description
=> "Delete a VM snapshot.",
3572 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3575 additionalProperties
=> 0,
3577 node
=> get_standard_option
('pve-node'),
3578 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3579 snapname
=> get_standard_option
('pve-snapshot-name'),
3583 description
=> "For removal from config file, even if removing disk snapshots fails.",
3589 description
=> "the task ID.",
3594 my $rpcenv = PVE
::RPCEnvironment
::get
();
3596 my $authuser = $rpcenv->get_user();
3598 my $node = extract_param
($param, 'node');
3600 my $vmid = extract_param
($param, 'vmid');
3602 my $snapname = extract_param
($param, 'snapname');
3605 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3606 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3609 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3612 __PACKAGE__-
>register_method({
3614 path
=> '{vmid}/template',
3618 description
=> "Create a Template.",
3620 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3621 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3624 additionalProperties
=> 0,
3626 node
=> get_standard_option
('pve-node'),
3627 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3631 description
=> "If you want to convert only 1 disk to base image.",
3632 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3637 returns
=> { type
=> 'null'},
3641 my $rpcenv = PVE
::RPCEnvironment
::get
();
3643 my $authuser = $rpcenv->get_user();
3645 my $node = extract_param
($param, 'node');
3647 my $vmid = extract_param
($param, 'vmid');
3649 my $disk = extract_param
($param, 'disk');
3651 my $updatefn = sub {
3653 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3655 PVE
::QemuConfig-
>check_lock($conf);
3657 die "unable to create template, because VM contains snapshots\n"
3658 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3660 die "you can't convert a template to a template\n"
3661 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3663 die "you can't convert a VM to template if VM is running\n"
3664 if PVE
::QemuServer
::check_running
($vmid);
3667 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3670 $conf->{template
} = 1;
3671 PVE
::QemuConfig-
>write_config($vmid, $conf);
3673 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3676 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);