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, $arch, $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, $arch);
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);
588 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
592 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $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 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
605 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
608 PVE
::QemuConfig-
>write_config($vmid, $conf);
614 foreach my $volid (@$vollist) {
615 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
621 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
624 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
626 if ($start_after_create) {
627 print "Execute autostart\n";
628 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
633 my ($code, $worker_name);
635 $worker_name = 'qmrestore';
637 eval { $restorefn->() };
639 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
645 $worker_name = 'qmcreate';
647 eval { $createfn->() };
650 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
652 or die "failed to remove config file: $@\n";
660 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
663 __PACKAGE__-
>register_method({
668 description
=> "Directory index",
673 additionalProperties
=> 0,
675 node
=> get_standard_option
('pve-node'),
676 vmid
=> get_standard_option
('pve-vmid'),
684 subdir
=> { type
=> 'string' },
687 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
693 { subdir
=> 'config' },
694 { subdir
=> 'pending' },
695 { subdir
=> 'status' },
696 { subdir
=> 'unlink' },
697 { subdir
=> 'vncproxy' },
698 { subdir
=> 'termproxy' },
699 { subdir
=> 'migrate' },
700 { subdir
=> 'resize' },
701 { subdir
=> 'move' },
703 { subdir
=> 'rrddata' },
704 { subdir
=> 'monitor' },
705 { subdir
=> 'agent' },
706 { subdir
=> 'snapshot' },
707 { subdir
=> 'spiceproxy' },
708 { subdir
=> 'sendkey' },
709 { subdir
=> 'firewall' },
715 __PACKAGE__-
>register_method ({
716 subclass
=> "PVE::API2::Firewall::VM",
717 path
=> '{vmid}/firewall',
720 __PACKAGE__-
>register_method ({
721 subclass
=> "PVE::API2::Qemu::Agent",
722 path
=> '{vmid}/agent',
725 __PACKAGE__-
>register_method({
727 path
=> '{vmid}/rrd',
729 protected
=> 1, # fixme: can we avoid that?
731 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
733 description
=> "Read VM RRD statistics (returns PNG)",
735 additionalProperties
=> 0,
737 node
=> get_standard_option
('pve-node'),
738 vmid
=> get_standard_option
('pve-vmid'),
740 description
=> "Specify the time frame you are interested in.",
742 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
745 description
=> "The list of datasources you want to display.",
746 type
=> 'string', format
=> 'pve-configid-list',
749 description
=> "The RRD consolidation function",
751 enum
=> [ 'AVERAGE', 'MAX' ],
759 filename
=> { type
=> 'string' },
765 return PVE
::Cluster
::create_rrd_graph
(
766 "pve2-vm/$param->{vmid}", $param->{timeframe
},
767 $param->{ds
}, $param->{cf
});
771 __PACKAGE__-
>register_method({
773 path
=> '{vmid}/rrddata',
775 protected
=> 1, # fixme: can we avoid that?
777 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
779 description
=> "Read VM RRD statistics",
781 additionalProperties
=> 0,
783 node
=> get_standard_option
('pve-node'),
784 vmid
=> get_standard_option
('pve-vmid'),
786 description
=> "Specify the time frame you are interested in.",
788 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
791 description
=> "The RRD consolidation function",
793 enum
=> [ 'AVERAGE', 'MAX' ],
808 return PVE
::Cluster
::create_rrd_data
(
809 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
813 __PACKAGE__-
>register_method({
815 path
=> '{vmid}/config',
818 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
820 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
823 additionalProperties
=> 0,
825 node
=> get_standard_option
('pve-node'),
826 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
828 description
=> "Get current values (instead of pending values).",
836 description
=> "The current VM configuration.",
838 properties
=> PVE
::QemuServer
::json_config_properties
({
841 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
848 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
850 delete $conf->{snapshots
};
852 if (!$param->{current
}) {
853 foreach my $opt (keys %{$conf->{pending
}}) {
854 next if $opt eq 'delete';
855 my $value = $conf->{pending
}->{$opt};
856 next if ref($value); # just to be sure
857 $conf->{$opt} = $value;
859 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
860 foreach my $opt (keys %$pending_delete_hash) {
861 delete $conf->{$opt} if $conf->{$opt};
865 delete $conf->{pending
};
867 # hide cloudinit password
868 if ($conf->{cipassword
}) {
869 $conf->{cipassword
} = '**********';
875 __PACKAGE__-
>register_method({
876 name
=> 'vm_pending',
877 path
=> '{vmid}/pending',
880 description
=> "Get virtual machine configuration, including pending changes.",
882 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
885 additionalProperties
=> 0,
887 node
=> get_standard_option
('pve-node'),
888 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
897 description
=> "Configuration option name.",
901 description
=> "Current value.",
906 description
=> "Pending value.",
911 description
=> "Indicates a pending delete request if present and not 0. " .
912 "The value 2 indicates a force-delete request.",
924 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
926 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
930 foreach my $opt (keys %$conf) {
931 next if ref($conf->{$opt});
932 my $item = { key
=> $opt };
933 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
934 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
935 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
937 # hide cloudinit password
938 if ($opt eq 'cipassword') {
939 $item->{value
} = '**********' if defined($item->{value
});
940 # the trailing space so that the pending string is different
941 $item->{pending
} = '********** ' if defined($item->{pending
});
946 foreach my $opt (keys %{$conf->{pending
}}) {
947 next if $opt eq 'delete';
948 next if ref($conf->{pending
}->{$opt}); # just to be sure
949 next if defined($conf->{$opt});
950 my $item = { key
=> $opt };
951 $item->{pending
} = $conf->{pending
}->{$opt};
953 # hide cloudinit password
954 if ($opt eq 'cipassword') {
955 $item->{pending
} = '**********' if defined($item->{pending
});
960 while (my ($opt, $force) = each %$pending_delete_hash) {
961 next if $conf->{pending
}->{$opt}; # just to be sure
962 next if $conf->{$opt};
963 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
970 # POST/PUT {vmid}/config implementation
972 # The original API used PUT (idempotent) an we assumed that all operations
973 # are fast. But it turned out that almost any configuration change can
974 # involve hot-plug actions, or disk alloc/free. Such actions can take long
975 # time to complete and have side effects (not idempotent).
977 # The new implementation uses POST and forks a worker process. We added
978 # a new option 'background_delay'. If specified we wait up to
979 # 'background_delay' second for the worker task to complete. It returns null
980 # if the task is finished within that time, else we return the UPID.
982 my $update_vm_api = sub {
983 my ($param, $sync) = @_;
985 my $rpcenv = PVE
::RPCEnvironment
::get
();
987 my $authuser = $rpcenv->get_user();
989 my $node = extract_param
($param, 'node');
991 my $vmid = extract_param
($param, 'vmid');
993 my $digest = extract_param
($param, 'digest');
995 my $background_delay = extract_param
($param, 'background_delay');
997 if (defined(my $cipassword = $param->{cipassword
})) {
998 # Same logic as in cloud-init (but with the regex fixed...)
999 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
1000 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1003 my @paramarr = (); # used for log message
1004 foreach my $key (sort keys %$param) {
1005 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1006 push @paramarr, "-$key", $value;
1009 my $skiplock = extract_param
($param, 'skiplock');
1010 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1011 if $skiplock && $authuser ne 'root@pam';
1013 my $delete_str = extract_param
($param, 'delete');
1015 my $revert_str = extract_param
($param, 'revert');
1017 my $force = extract_param
($param, 'force');
1019 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1020 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1021 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1024 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1026 my $storecfg = PVE
::Storage
::config
();
1028 my $defaults = PVE
::QemuServer
::load_defaults
();
1030 &$resolve_cdrom_alias($param);
1032 # now try to verify all parameters
1035 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1036 if (!PVE
::QemuServer
::option_exists
($opt)) {
1037 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1040 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1041 "-revert $opt' at the same time" })
1042 if defined($param->{$opt});
1044 $revert->{$opt} = 1;
1048 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1049 $opt = 'ide2' if $opt eq 'cdrom';
1051 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1052 "-delete $opt' at the same time" })
1053 if defined($param->{$opt});
1055 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1056 "-revert $opt' at the same time" })
1059 if (!PVE
::QemuServer
::option_exists
($opt)) {
1060 raise_param_exc
({ delete => "unknown option '$opt'" });
1066 my $repl_conf = PVE
::ReplicationConfig-
>new();
1067 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1068 my $check_replication = sub {
1070 return if !$is_replicated;
1071 my $volid = $drive->{file
};
1072 return if !$volid || !($drive->{replicate
}//1);
1073 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1074 my ($storeid, $format);
1075 if ($volid =~ $NEW_DISK_RE) {
1077 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1079 ($storeid, undef) = PVE
::Storage
::parse_volume_id
($volid, 1);
1080 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1082 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1083 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1084 return if $scfg->{shared
};
1085 die "cannot add non-replicatable volume to a replicated VM\n";
1088 foreach my $opt (keys %$param) {
1089 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1090 # cleanup drive path
1091 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1092 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1093 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1094 $check_replication->($drive);
1095 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1096 } elsif ($opt =~ m/^net(\d+)$/) {
1098 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1099 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1100 } elsif ($opt eq 'vmgenid') {
1101 if ($param->{$opt} eq '1') {
1102 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1107 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1109 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1111 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1113 my $updatefn = sub {
1115 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1117 die "checksum missmatch (file change by other user?)\n"
1118 if $digest && $digest ne $conf->{digest
};
1120 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1122 foreach my $opt (keys %$revert) {
1123 if (defined($conf->{$opt})) {
1124 $param->{$opt} = $conf->{$opt};
1125 } elsif (defined($conf->{pending
}->{$opt})) {
1130 if ($param->{memory
} || defined($param->{balloon
})) {
1131 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1132 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1134 die "balloon value too large (must be smaller than assigned memory)\n"
1135 if $balloon && $balloon > $maxmem;
1138 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1142 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1144 # write updates to pending section
1146 my $modified = {}; # record what $option we modify
1148 foreach my $opt (@delete) {
1149 $modified->{$opt} = 1;
1150 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1151 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1152 warn "cannot delete '$opt' - not set in current configuration!\n";
1153 $modified->{$opt} = 0;
1157 if ($opt =~ m/^unused/) {
1158 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1159 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1160 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1161 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1162 delete $conf->{$opt};
1163 PVE
::QemuConfig-
>write_config($vmid, $conf);
1165 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1166 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1167 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1168 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1169 if defined($conf->{pending
}->{$opt});
1170 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1171 PVE
::QemuConfig-
>write_config($vmid, $conf);
1173 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1174 PVE
::QemuConfig-
>write_config($vmid, $conf);
1178 foreach my $opt (keys %$param) { # add/change
1179 $modified->{$opt} = 1;
1180 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1181 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1183 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
1185 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1186 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1187 # FIXME: cloudinit: CDROM or Disk?
1188 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1189 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1191 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1193 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1194 if defined($conf->{pending
}->{$opt});
1196 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1198 $conf->{pending
}->{$opt} = $param->{$opt};
1200 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1201 PVE
::QemuConfig-
>write_config($vmid, $conf);
1204 # remove pending changes when nothing changed
1205 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1206 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1207 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1209 return if !scalar(keys %{$conf->{pending
}});
1211 my $running = PVE
::QemuServer
::check_running
($vmid);
1213 # apply pending changes
1215 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1219 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1220 raise_param_exc
($errors) if scalar(keys %$errors);
1222 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1232 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1234 if ($background_delay) {
1236 # Note: It would be better to do that in the Event based HTTPServer
1237 # to avoid blocking call to sleep.
1239 my $end_time = time() + $background_delay;
1241 my $task = PVE
::Tools
::upid_decode
($upid);
1244 while (time() < $end_time) {
1245 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1247 sleep(1); # this gets interrupted when child process ends
1251 my $status = PVE
::Tools
::upid_read_status
($upid);
1252 return undef if $status eq 'OK';
1261 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1264 my $vm_config_perm_list = [
1269 'VM.Config.Network',
1271 'VM.Config.Options',
1274 __PACKAGE__-
>register_method({
1275 name
=> 'update_vm_async',
1276 path
=> '{vmid}/config',
1280 description
=> "Set virtual machine options (asynchrounous API).",
1282 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1285 additionalProperties
=> 0,
1286 properties
=> PVE
::QemuServer
::json_config_properties
(
1288 node
=> get_standard_option
('pve-node'),
1289 vmid
=> get_standard_option
('pve-vmid'),
1290 skiplock
=> get_standard_option
('skiplock'),
1292 type
=> 'string', format
=> 'pve-configid-list',
1293 description
=> "A list of settings you want to delete.",
1297 type
=> 'string', format
=> 'pve-configid-list',
1298 description
=> "Revert a pending change.",
1303 description
=> $opt_force_description,
1305 requires
=> 'delete',
1309 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1313 background_delay
=> {
1315 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1326 code
=> $update_vm_api,
1329 __PACKAGE__-
>register_method({
1330 name
=> 'update_vm',
1331 path
=> '{vmid}/config',
1335 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1337 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1340 additionalProperties
=> 0,
1341 properties
=> PVE
::QemuServer
::json_config_properties
(
1343 node
=> get_standard_option
('pve-node'),
1344 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1345 skiplock
=> get_standard_option
('skiplock'),
1347 type
=> 'string', format
=> 'pve-configid-list',
1348 description
=> "A list of settings you want to delete.",
1352 type
=> 'string', format
=> 'pve-configid-list',
1353 description
=> "Revert a pending change.",
1358 description
=> $opt_force_description,
1360 requires
=> 'delete',
1364 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1370 returns
=> { type
=> 'null' },
1373 &$update_vm_api($param, 1);
1379 __PACKAGE__-
>register_method({
1380 name
=> 'destroy_vm',
1385 description
=> "Destroy the vm (also delete all used/owned volumes).",
1387 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1390 additionalProperties
=> 0,
1392 node
=> get_standard_option
('pve-node'),
1393 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1394 skiplock
=> get_standard_option
('skiplock'),
1403 my $rpcenv = PVE
::RPCEnvironment
::get
();
1405 my $authuser = $rpcenv->get_user();
1407 my $vmid = $param->{vmid
};
1409 my $skiplock = $param->{skiplock
};
1410 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1411 if $skiplock && $authuser ne 'root@pam';
1414 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1416 my $storecfg = PVE
::Storage
::config
();
1418 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1420 die "unable to remove VM $vmid - used in HA resources\n"
1421 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1423 # do not allow destroy if there are replication jobs
1424 my $repl_conf = PVE
::ReplicationConfig-
>new();
1425 $repl_conf->check_for_existing_jobs($vmid);
1427 # early tests (repeat after locking)
1428 die "VM $vmid is running - destroy failed\n"
1429 if PVE
::QemuServer
::check_running
($vmid);
1434 syslog
('info', "destroy VM $vmid: $upid\n");
1436 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1438 PVE
::AccessControl
::remove_vm_access
($vmid);
1440 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1443 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1446 __PACKAGE__-
>register_method({
1448 path
=> '{vmid}/unlink',
1452 description
=> "Unlink/delete disk images.",
1454 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1457 additionalProperties
=> 0,
1459 node
=> get_standard_option
('pve-node'),
1460 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1462 type
=> 'string', format
=> 'pve-configid-list',
1463 description
=> "A list of disk IDs you want to delete.",
1467 description
=> $opt_force_description,
1472 returns
=> { type
=> 'null'},
1476 $param->{delete} = extract_param
($param, 'idlist');
1478 __PACKAGE__-
>update_vm($param);
1485 __PACKAGE__-
>register_method({
1487 path
=> '{vmid}/vncproxy',
1491 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1493 description
=> "Creates a TCP VNC proxy connections.",
1495 additionalProperties
=> 0,
1497 node
=> get_standard_option
('pve-node'),
1498 vmid
=> get_standard_option
('pve-vmid'),
1502 description
=> "starts websockify instead of vncproxy",
1507 additionalProperties
=> 0,
1509 user
=> { type
=> 'string' },
1510 ticket
=> { type
=> 'string' },
1511 cert
=> { type
=> 'string' },
1512 port
=> { type
=> 'integer' },
1513 upid
=> { type
=> 'string' },
1519 my $rpcenv = PVE
::RPCEnvironment
::get
();
1521 my $authuser = $rpcenv->get_user();
1523 my $vmid = $param->{vmid
};
1524 my $node = $param->{node
};
1525 my $websocket = $param->{websocket
};
1527 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1528 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1530 my $authpath = "/vms/$vmid";
1532 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1534 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1540 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1541 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1542 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1543 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1544 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, '-T');
1546 $family = PVE
::Tools
::get_host_address_family
($node);
1549 my $port = PVE
::Tools
::next_vnc_port
($family);
1556 syslog
('info', "starting vnc proxy $upid\n");
1562 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1564 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1565 '-timeout', $timeout, '-authpath', $authpath,
1566 '-perm', 'Sys.Console'];
1568 if ($param->{websocket
}) {
1569 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1570 push @$cmd, '-notls', '-listen', 'localhost';
1573 push @$cmd, '-c', @$remcmd, @$termcmd;
1575 PVE
::Tools
::run_command
($cmd);
1579 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1581 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1583 my $sock = IO
::Socket
::IP-
>new(
1588 GetAddrInfoFlags
=> 0,
1589 ) or die "failed to create socket: $!\n";
1590 # Inside the worker we shouldn't have any previous alarms
1591 # running anyway...:
1593 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1595 accept(my $cli, $sock) or die "connection failed: $!\n";
1598 if (PVE
::Tools
::run_command
($cmd,
1599 output
=> '>&'.fileno($cli),
1600 input
=> '<&'.fileno($cli),
1603 die "Failed to run vncproxy.\n";
1610 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1612 PVE
::Tools
::wait_for_vnc_port
($port);
1623 __PACKAGE__-
>register_method({
1624 name
=> 'termproxy',
1625 path
=> '{vmid}/termproxy',
1629 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1631 description
=> "Creates a TCP proxy connections.",
1633 additionalProperties
=> 0,
1635 node
=> get_standard_option
('pve-node'),
1636 vmid
=> get_standard_option
('pve-vmid'),
1640 enum
=> [qw(serial0 serial1 serial2 serial3)],
1641 description
=> "opens a serial terminal (defaults to display)",
1646 additionalProperties
=> 0,
1648 user
=> { type
=> 'string' },
1649 ticket
=> { type
=> 'string' },
1650 port
=> { type
=> 'integer' },
1651 upid
=> { type
=> 'string' },
1657 my $rpcenv = PVE
::RPCEnvironment
::get
();
1659 my $authuser = $rpcenv->get_user();
1661 my $vmid = $param->{vmid
};
1662 my $node = $param->{node
};
1663 my $serial = $param->{serial
};
1665 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1667 if (!defined($serial)) {
1668 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1669 $serial = $conf->{vga
};
1673 my $authpath = "/vms/$vmid";
1675 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1680 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1681 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1682 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1683 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, '-t');
1684 push @$remcmd, '--';
1686 $family = PVE
::Tools
::get_host_address_family
($node);
1689 my $port = PVE
::Tools
::next_vnc_port
($family);
1691 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1692 push @$termcmd, '-iface', $serial if $serial;
1697 syslog
('info', "starting qemu termproxy $upid\n");
1699 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1700 '--perm', 'VM.Console', '--'];
1701 push @$cmd, @$remcmd, @$termcmd;
1703 PVE
::Tools
::run_command
($cmd);
1706 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1708 PVE
::Tools
::wait_for_vnc_port
($port);
1718 __PACKAGE__-
>register_method({
1719 name
=> 'vncwebsocket',
1720 path
=> '{vmid}/vncwebsocket',
1723 description
=> "You also need to pass a valid ticket (vncticket).",
1724 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1726 description
=> "Opens a weksocket for VNC traffic.",
1728 additionalProperties
=> 0,
1730 node
=> get_standard_option
('pve-node'),
1731 vmid
=> get_standard_option
('pve-vmid'),
1733 description
=> "Ticket from previous call to vncproxy.",
1738 description
=> "Port number returned by previous vncproxy call.",
1748 port
=> { type
=> 'string' },
1754 my $rpcenv = PVE
::RPCEnvironment
::get
();
1756 my $authuser = $rpcenv->get_user();
1758 my $vmid = $param->{vmid
};
1759 my $node = $param->{node
};
1761 my $authpath = "/vms/$vmid";
1763 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1765 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1767 # Note: VNC ports are acessible from outside, so we do not gain any
1768 # security if we verify that $param->{port} belongs to VM $vmid. This
1769 # check is done by verifying the VNC ticket (inside VNC protocol).
1771 my $port = $param->{port
};
1773 return { port
=> $port };
1776 __PACKAGE__-
>register_method({
1777 name
=> 'spiceproxy',
1778 path
=> '{vmid}/spiceproxy',
1783 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1785 description
=> "Returns a SPICE configuration to connect to the VM.",
1787 additionalProperties
=> 0,
1789 node
=> get_standard_option
('pve-node'),
1790 vmid
=> get_standard_option
('pve-vmid'),
1791 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1794 returns
=> get_standard_option
('remote-viewer-config'),
1798 my $rpcenv = PVE
::RPCEnvironment
::get
();
1800 my $authuser = $rpcenv->get_user();
1802 my $vmid = $param->{vmid
};
1803 my $node = $param->{node
};
1804 my $proxy = $param->{proxy
};
1806 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1807 my $title = "VM $vmid";
1808 $title .= " - ". $conf->{name
} if $conf->{name
};
1810 my $port = PVE
::QemuServer
::spice_port
($vmid);
1812 my ($ticket, undef, $remote_viewer_config) =
1813 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1815 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1816 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1818 return $remote_viewer_config;
1821 __PACKAGE__-
>register_method({
1823 path
=> '{vmid}/status',
1826 description
=> "Directory index",
1831 additionalProperties
=> 0,
1833 node
=> get_standard_option
('pve-node'),
1834 vmid
=> get_standard_option
('pve-vmid'),
1842 subdir
=> { type
=> 'string' },
1845 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1851 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1854 { subdir
=> 'current' },
1855 { subdir
=> 'start' },
1856 { subdir
=> 'stop' },
1862 __PACKAGE__-
>register_method({
1863 name
=> 'vm_status',
1864 path
=> '{vmid}/status/current',
1867 protected
=> 1, # qemu pid files are only readable by root
1868 description
=> "Get virtual machine status.",
1870 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1873 additionalProperties
=> 0,
1875 node
=> get_standard_option
('pve-node'),
1876 vmid
=> get_standard_option
('pve-vmid'),
1882 %$PVE::QemuServer
::vmstatus_return_properties
,
1884 description
=> "HA manager service status.",
1888 description
=> "Qemu VGA configuration supports spice.",
1893 description
=> "Qemu GuestAgent enabled in config.",
1903 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1905 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1906 my $status = $vmstatus->{$param->{vmid
}};
1908 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1910 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1911 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1916 __PACKAGE__-
>register_method({
1918 path
=> '{vmid}/status/start',
1922 description
=> "Start virtual machine.",
1924 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1927 additionalProperties
=> 0,
1929 node
=> get_standard_option
('pve-node'),
1930 vmid
=> get_standard_option
('pve-vmid',
1931 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1932 skiplock
=> get_standard_option
('skiplock'),
1933 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1934 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1937 enum
=> ['secure', 'insecure'],
1938 description
=> "Migration traffic is encrypted using an SSH " .
1939 "tunnel by default. On secure, completely private networks " .
1940 "this can be disabled to increase performance.",
1943 migration_network
=> {
1944 type
=> 'string', format
=> 'CIDR',
1945 description
=> "CIDR of the (sub) network that is used for migration.",
1948 machine
=> get_standard_option
('pve-qm-machine'),
1950 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1962 my $rpcenv = PVE
::RPCEnvironment
::get
();
1964 my $authuser = $rpcenv->get_user();
1966 my $node = extract_param
($param, 'node');
1968 my $vmid = extract_param
($param, 'vmid');
1970 my $machine = extract_param
($param, 'machine');
1972 my $stateuri = extract_param
($param, 'stateuri');
1973 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1974 if $stateuri && $authuser ne 'root@pam';
1976 my $skiplock = extract_param
($param, 'skiplock');
1977 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1978 if $skiplock && $authuser ne 'root@pam';
1980 my $migratedfrom = extract_param
($param, 'migratedfrom');
1981 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1982 if $migratedfrom && $authuser ne 'root@pam';
1984 my $migration_type = extract_param
($param, 'migration_type');
1985 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1986 if $migration_type && $authuser ne 'root@pam';
1988 my $migration_network = extract_param
($param, 'migration_network');
1989 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1990 if $migration_network && $authuser ne 'root@pam';
1992 my $targetstorage = extract_param
($param, 'targetstorage');
1993 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1994 if $targetstorage && $authuser ne 'root@pam';
1996 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1997 if $targetstorage && !$migratedfrom;
1999 # read spice ticket from STDIN
2001 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2002 if (defined(my $line = <STDIN
>)) {
2004 $spice_ticket = $line;
2008 PVE
::Cluster
::check_cfs_quorum
();
2010 my $storecfg = PVE
::Storage
::config
();
2012 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
2013 $rpcenv->{type
} ne 'ha') {
2018 my $service = "vm:$vmid";
2020 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
2022 print "Requesting HA start for VM $vmid\n";
2024 PVE
::Tools
::run_command
($cmd);
2029 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2036 syslog
('info', "start VM $vmid: $upid\n");
2038 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2039 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
2044 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2048 __PACKAGE__-
>register_method({
2050 path
=> '{vmid}/status/stop',
2054 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2055 "is akin to pulling the power plug of a running computer and may damage the VM data",
2057 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2060 additionalProperties
=> 0,
2062 node
=> get_standard_option
('pve-node'),
2063 vmid
=> get_standard_option
('pve-vmid',
2064 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2065 skiplock
=> get_standard_option
('skiplock'),
2066 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2068 description
=> "Wait maximal timeout seconds.",
2074 description
=> "Do not deactivate storage volumes.",
2087 my $rpcenv = PVE
::RPCEnvironment
::get
();
2089 my $authuser = $rpcenv->get_user();
2091 my $node = extract_param
($param, 'node');
2093 my $vmid = extract_param
($param, 'vmid');
2095 my $skiplock = extract_param
($param, 'skiplock');
2096 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2097 if $skiplock && $authuser ne 'root@pam';
2099 my $keepActive = extract_param
($param, 'keepActive');
2100 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2101 if $keepActive && $authuser ne 'root@pam';
2103 my $migratedfrom = extract_param
($param, 'migratedfrom');
2104 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2105 if $migratedfrom && $authuser ne 'root@pam';
2108 my $storecfg = PVE
::Storage
::config
();
2110 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2115 my $service = "vm:$vmid";
2117 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2119 print "Requesting HA stop for VM $vmid\n";
2121 PVE
::Tools
::run_command
($cmd);
2126 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2132 syslog
('info', "stop VM $vmid: $upid\n");
2134 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2135 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2140 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2144 __PACKAGE__-
>register_method({
2146 path
=> '{vmid}/status/reset',
2150 description
=> "Reset virtual machine.",
2152 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2155 additionalProperties
=> 0,
2157 node
=> get_standard_option
('pve-node'),
2158 vmid
=> get_standard_option
('pve-vmid',
2159 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2160 skiplock
=> get_standard_option
('skiplock'),
2169 my $rpcenv = PVE
::RPCEnvironment
::get
();
2171 my $authuser = $rpcenv->get_user();
2173 my $node = extract_param
($param, 'node');
2175 my $vmid = extract_param
($param, 'vmid');
2177 my $skiplock = extract_param
($param, 'skiplock');
2178 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2179 if $skiplock && $authuser ne 'root@pam';
2181 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2186 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2191 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2194 __PACKAGE__-
>register_method({
2195 name
=> 'vm_shutdown',
2196 path
=> '{vmid}/status/shutdown',
2200 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2201 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2203 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2206 additionalProperties
=> 0,
2208 node
=> get_standard_option
('pve-node'),
2209 vmid
=> get_standard_option
('pve-vmid',
2210 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2211 skiplock
=> get_standard_option
('skiplock'),
2213 description
=> "Wait maximal timeout seconds.",
2219 description
=> "Make sure the VM stops.",
2225 description
=> "Do not deactivate storage volumes.",
2238 my $rpcenv = PVE
::RPCEnvironment
::get
();
2240 my $authuser = $rpcenv->get_user();
2242 my $node = extract_param
($param, 'node');
2244 my $vmid = extract_param
($param, 'vmid');
2246 my $skiplock = extract_param
($param, 'skiplock');
2247 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2248 if $skiplock && $authuser ne 'root@pam';
2250 my $keepActive = extract_param
($param, 'keepActive');
2251 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2252 if $keepActive && $authuser ne 'root@pam';
2254 my $storecfg = PVE
::Storage
::config
();
2258 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2259 # otherwise, we will infer a shutdown command, but run into the timeout,
2260 # then when the vm is resumed, it will instantly shutdown
2262 # checking the qmp status here to get feedback to the gui/cli/api
2263 # and the status query should not take too long
2266 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2270 if (!$err && $qmpstatus->{status
} eq "paused") {
2271 if ($param->{forceStop
}) {
2272 warn "VM is paused - stop instead of shutdown\n";
2275 die "VM is paused - cannot shutdown\n";
2279 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2280 ($rpcenv->{type
} ne 'ha')) {
2285 my $service = "vm:$vmid";
2287 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2289 print "Requesting HA stop for VM $vmid\n";
2291 PVE
::Tools
::run_command
($cmd);
2296 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2303 syslog
('info', "shutdown VM $vmid: $upid\n");
2305 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2306 $shutdown, $param->{forceStop
}, $keepActive);
2311 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2315 __PACKAGE__-
>register_method({
2316 name
=> 'vm_suspend',
2317 path
=> '{vmid}/status/suspend',
2321 description
=> "Suspend virtual machine.",
2323 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2326 additionalProperties
=> 0,
2328 node
=> get_standard_option
('pve-node'),
2329 vmid
=> get_standard_option
('pve-vmid',
2330 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2331 skiplock
=> get_standard_option
('skiplock'),
2340 my $rpcenv = PVE
::RPCEnvironment
::get
();
2342 my $authuser = $rpcenv->get_user();
2344 my $node = extract_param
($param, 'node');
2346 my $vmid = extract_param
($param, 'vmid');
2348 my $skiplock = extract_param
($param, 'skiplock');
2349 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2350 if $skiplock && $authuser ne 'root@pam';
2352 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2357 syslog
('info', "suspend VM $vmid: $upid\n");
2359 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2364 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2367 __PACKAGE__-
>register_method({
2368 name
=> 'vm_resume',
2369 path
=> '{vmid}/status/resume',
2373 description
=> "Resume virtual machine.",
2375 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2378 additionalProperties
=> 0,
2380 node
=> get_standard_option
('pve-node'),
2381 vmid
=> get_standard_option
('pve-vmid',
2382 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2383 skiplock
=> get_standard_option
('skiplock'),
2384 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2394 my $rpcenv = PVE
::RPCEnvironment
::get
();
2396 my $authuser = $rpcenv->get_user();
2398 my $node = extract_param
($param, 'node');
2400 my $vmid = extract_param
($param, 'vmid');
2402 my $skiplock = extract_param
($param, 'skiplock');
2403 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2404 if $skiplock && $authuser ne 'root@pam';
2406 my $nocheck = extract_param
($param, 'nocheck');
2408 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2413 syslog
('info', "resume VM $vmid: $upid\n");
2415 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2420 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2423 __PACKAGE__-
>register_method({
2424 name
=> 'vm_sendkey',
2425 path
=> '{vmid}/sendkey',
2429 description
=> "Send key event to virtual machine.",
2431 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2434 additionalProperties
=> 0,
2436 node
=> get_standard_option
('pve-node'),
2437 vmid
=> get_standard_option
('pve-vmid',
2438 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2439 skiplock
=> get_standard_option
('skiplock'),
2441 description
=> "The key (qemu monitor encoding).",
2446 returns
=> { type
=> 'null'},
2450 my $rpcenv = PVE
::RPCEnvironment
::get
();
2452 my $authuser = $rpcenv->get_user();
2454 my $node = extract_param
($param, 'node');
2456 my $vmid = extract_param
($param, 'vmid');
2458 my $skiplock = extract_param
($param, 'skiplock');
2459 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2460 if $skiplock && $authuser ne 'root@pam';
2462 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2467 __PACKAGE__-
>register_method({
2468 name
=> 'vm_feature',
2469 path
=> '{vmid}/feature',
2473 description
=> "Check if feature for virtual machine is available.",
2475 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2478 additionalProperties
=> 0,
2480 node
=> get_standard_option
('pve-node'),
2481 vmid
=> get_standard_option
('pve-vmid'),
2483 description
=> "Feature to check.",
2485 enum
=> [ 'snapshot', 'clone', 'copy' ],
2487 snapname
=> get_standard_option
('pve-snapshot-name', {
2495 hasFeature
=> { type
=> 'boolean' },
2498 items
=> { type
=> 'string' },
2505 my $node = extract_param
($param, 'node');
2507 my $vmid = extract_param
($param, 'vmid');
2509 my $snapname = extract_param
($param, 'snapname');
2511 my $feature = extract_param
($param, 'feature');
2513 my $running = PVE
::QemuServer
::check_running
($vmid);
2515 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2518 my $snap = $conf->{snapshots
}->{$snapname};
2519 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2522 my $storecfg = PVE
::Storage
::config
();
2524 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2525 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2528 hasFeature
=> $hasFeature,
2529 nodes
=> [ keys %$nodelist ],
2533 __PACKAGE__-
>register_method({
2535 path
=> '{vmid}/clone',
2539 description
=> "Create a copy of virtual machine/template.",
2541 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2542 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2543 "'Datastore.AllocateSpace' on any used storage.",
2546 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2548 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2549 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2554 additionalProperties
=> 0,
2556 node
=> get_standard_option
('pve-node'),
2557 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2558 newid
=> get_standard_option
('pve-vmid', {
2559 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2560 description
=> 'VMID for the clone.' }),
2563 type
=> 'string', format
=> 'dns-name',
2564 description
=> "Set a name for the new VM.",
2569 description
=> "Description for the new VM.",
2573 type
=> 'string', format
=> 'pve-poolid',
2574 description
=> "Add the new VM to the specified pool.",
2576 snapname
=> get_standard_option
('pve-snapshot-name', {
2579 storage
=> get_standard_option
('pve-storage-id', {
2580 description
=> "Target storage for full clone.",
2584 description
=> "Target format for file storage. Only valid for full clone.",
2587 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2592 description
=> "Create a full copy of all disks. This is always done when " .
2593 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2595 target
=> get_standard_option
('pve-node', {
2596 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2607 my $rpcenv = PVE
::RPCEnvironment
::get
();
2609 my $authuser = $rpcenv->get_user();
2611 my $node = extract_param
($param, 'node');
2613 my $vmid = extract_param
($param, 'vmid');
2615 my $newid = extract_param
($param, 'newid');
2617 my $pool = extract_param
($param, 'pool');
2619 if (defined($pool)) {
2620 $rpcenv->check_pool_exist($pool);
2623 my $snapname = extract_param
($param, 'snapname');
2625 my $storage = extract_param
($param, 'storage');
2627 my $format = extract_param
($param, 'format');
2629 my $target = extract_param
($param, 'target');
2631 my $localnode = PVE
::INotify
::nodename
();
2633 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2635 PVE
::Cluster
::check_node_exists
($target) if $target;
2637 my $storecfg = PVE
::Storage
::config
();
2640 # check if storage is enabled on local node
2641 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2643 # check if storage is available on target node
2644 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2645 # clone only works if target storage is shared
2646 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2647 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2651 PVE
::Cluster
::check_cfs_quorum
();
2653 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2655 # exclusive lock if VM is running - else shared lock is enough;
2656 my $shared_lock = $running ?
0 : 1;
2660 # do all tests after lock
2661 # we also try to do all tests before we fork the worker
2663 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2665 PVE
::QemuConfig-
>check_lock($conf);
2667 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2669 die "unexpected state change\n" if $verify_running != $running;
2671 die "snapshot '$snapname' does not exist\n"
2672 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2674 my $full = extract_param
($param, 'full');
2675 if (!defined($full)) {
2676 $full = !PVE
::QemuConfig-
>is_template($conf);
2679 die "parameter 'storage' not allowed for linked clones\n"
2680 if defined($storage) && !$full;
2682 die "parameter 'format' not allowed for linked clones\n"
2683 if defined($format) && !$full;
2685 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2687 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2689 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2691 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2693 die "unable to create VM $newid: config file already exists\n"
2696 my $newconf = { lock => 'clone' };
2701 foreach my $opt (keys %$oldconf) {
2702 my $value = $oldconf->{$opt};
2704 # do not copy snapshot related info
2705 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2706 $opt eq 'vmstate' || $opt eq 'snapstate';
2708 # no need to copy unused images, because VMID(owner) changes anyways
2709 next if $opt =~ m/^unused\d+$/;
2711 # always change MAC! address
2712 if ($opt =~ m/^net(\d+)$/) {
2713 my $net = PVE
::QemuServer
::parse_net
($value);
2714 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2715 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2716 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2717 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2718 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2719 die "unable to parse drive options for '$opt'\n" if !$drive;
2720 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2721 $newconf->{$opt} = $value; # simply copy configuration
2723 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2724 die "Full clone feature is not supported for drive '$opt'\n"
2725 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2726 $fullclone->{$opt} = 1;
2728 # not full means clone instead of copy
2729 die "Linked clone feature is not supported for drive '$opt'\n"
2730 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2732 $drives->{$opt} = $drive;
2733 push @$vollist, $drive->{file
};
2736 # copy everything else
2737 $newconf->{$opt} = $value;
2741 # auto generate a new uuid
2742 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2743 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2744 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2746 # auto generate a new vmgenid if the option was set
2747 if ($newconf->{vmgenid
}) {
2748 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2751 delete $newconf->{template
};
2753 if ($param->{name
}) {
2754 $newconf->{name
} = $param->{name
};
2756 if ($oldconf->{name
}) {
2757 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2759 $newconf->{name
} = "Copy-of-VM-$vmid";
2763 if ($param->{description
}) {
2764 $newconf->{description
} = $param->{description
};
2767 # create empty/temp config - this fails if VM already exists on other node
2768 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2773 my $newvollist = [];
2780 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2782 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2784 my $total_jobs = scalar(keys %{$drives});
2787 foreach my $opt (keys %$drives) {
2788 my $drive = $drives->{$opt};
2789 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2791 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2792 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2793 $jobs, $skipcomplete, $oldconf->{agent
});
2795 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2797 PVE
::QemuConfig-
>write_config($newid, $newconf);
2801 delete $newconf->{lock};
2803 # do not write pending changes
2804 if (my @changes = keys %{$newconf->{pending
}}) {
2805 my $pending = join(',', @changes);
2806 warn "found pending changes for '$pending', discarding for clone\n";
2807 delete $newconf->{pending
};
2810 PVE
::QemuConfig-
>write_config($newid, $newconf);
2813 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2814 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2815 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2817 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2818 die "Failed to move config to node '$target' - rename failed: $!\n"
2819 if !rename($conffile, $newconffile);
2822 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2827 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2829 sleep 1; # some storage like rbd need to wait before release volume - really?
2831 foreach my $volid (@$newvollist) {
2832 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2835 die "clone failed: $err";
2841 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2843 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2846 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2847 # Aquire exclusive lock lock for $newid
2848 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2853 __PACKAGE__-
>register_method({
2854 name
=> 'move_vm_disk',
2855 path
=> '{vmid}/move_disk',
2859 description
=> "Move volume to different storage.",
2861 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2863 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2864 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2868 additionalProperties
=> 0,
2870 node
=> get_standard_option
('pve-node'),
2871 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2874 description
=> "The disk you want to move.",
2875 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2877 storage
=> get_standard_option
('pve-storage-id', {
2878 description
=> "Target storage.",
2879 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2883 description
=> "Target Format.",
2884 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2889 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2895 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2903 description
=> "the task ID.",
2908 my $rpcenv = PVE
::RPCEnvironment
::get
();
2910 my $authuser = $rpcenv->get_user();
2912 my $node = extract_param
($param, 'node');
2914 my $vmid = extract_param
($param, 'vmid');
2916 my $digest = extract_param
($param, 'digest');
2918 my $disk = extract_param
($param, 'disk');
2920 my $storeid = extract_param
($param, 'storage');
2922 my $format = extract_param
($param, 'format');
2924 my $storecfg = PVE
::Storage
::config
();
2926 my $updatefn = sub {
2928 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2930 PVE
::QemuConfig-
>check_lock($conf);
2932 die "checksum missmatch (file change by other user?)\n"
2933 if $digest && $digest ne $conf->{digest
};
2935 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2937 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2939 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2941 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
2944 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2945 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2949 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2950 (!$format || !$oldfmt || $oldfmt eq $format);
2952 # this only checks snapshots because $disk is passed!
2953 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2954 die "you can't move a disk with snapshots and delete the source\n"
2955 if $snapshotted && $param->{delete};
2957 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2959 my $running = PVE
::QemuServer
::check_running
($vmid);
2961 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2965 my $newvollist = [];
2971 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2973 warn "moving disk with snapshots, snapshots will not be moved!\n"
2976 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2977 $vmid, $storeid, $format, 1, $newvollist);
2979 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2981 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2983 # convert moved disk to base if part of template
2984 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2985 if PVE
::QemuConfig-
>is_template($conf);
2987 PVE
::QemuConfig-
>write_config($vmid, $conf);
2989 if ($running && PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
} && PVE
::QemuServer
::qga_check_running
($vmid)) {
2990 eval { PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-fstrim"); };
2994 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2995 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3002 foreach my $volid (@$newvollist) {
3003 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3006 die "storage migration failed: $err";
3009 if ($param->{delete}) {
3011 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3012 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3018 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3021 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3024 __PACKAGE__-
>register_method({
3025 name
=> 'migrate_vm',
3026 path
=> '{vmid}/migrate',
3030 description
=> "Migrate virtual machine. Creates a new migration task.",
3032 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3035 additionalProperties
=> 0,
3037 node
=> get_standard_option
('pve-node'),
3038 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3039 target
=> get_standard_option
('pve-node', {
3040 description
=> "Target node.",
3041 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3045 description
=> "Use online/live migration.",
3050 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3055 enum
=> ['secure', 'insecure'],
3056 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3059 migration_network
=> {
3060 type
=> 'string', format
=> 'CIDR',
3061 description
=> "CIDR of the (sub) network that is used for migration.",
3064 "with-local-disks" => {
3066 description
=> "Enable live storage migration for local disk",
3069 targetstorage
=> get_standard_option
('pve-storage-id', {
3070 description
=> "Default target storage.",
3072 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3078 description
=> "the task ID.",
3083 my $rpcenv = PVE
::RPCEnvironment
::get
();
3085 my $authuser = $rpcenv->get_user();
3087 my $target = extract_param
($param, 'target');
3089 my $localnode = PVE
::INotify
::nodename
();
3090 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3092 PVE
::Cluster
::check_cfs_quorum
();
3094 PVE
::Cluster
::check_node_exists
($target);
3096 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3098 my $vmid = extract_param
($param, 'vmid');
3100 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3101 if !$param->{online
} && $param->{targetstorage
};
3103 raise_param_exc
({ force
=> "Only root may use this option." })
3104 if $param->{force
} && $authuser ne 'root@pam';
3106 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3107 if $param->{migration_type
} && $authuser ne 'root@pam';
3109 # allow root only until better network permissions are available
3110 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3111 if $param->{migration_network
} && $authuser ne 'root@pam';
3114 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3116 # try to detect errors early
3118 PVE
::QemuConfig-
>check_lock($conf);
3120 if (PVE
::QemuServer
::check_running
($vmid)) {
3121 die "cant migrate running VM without --online\n"
3122 if !$param->{online
};
3125 my $storecfg = PVE
::Storage
::config
();
3127 if( $param->{targetstorage
}) {
3128 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3130 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3133 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3138 my $service = "vm:$vmid";
3140 my $cmd = ['ha-manager', 'migrate', $service, $target];
3142 print "Requesting HA migration for VM $vmid to node $target\n";
3144 PVE
::Tools
::run_command
($cmd);
3149 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3154 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3158 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3161 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3166 __PACKAGE__-
>register_method({
3168 path
=> '{vmid}/monitor',
3172 description
=> "Execute Qemu monitor commands.",
3174 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3175 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3178 additionalProperties
=> 0,
3180 node
=> get_standard_option
('pve-node'),
3181 vmid
=> get_standard_option
('pve-vmid'),
3184 description
=> "The monitor command.",
3188 returns
=> { type
=> 'string'},
3192 my $rpcenv = PVE
::RPCEnvironment
::get
();
3193 my $authuser = $rpcenv->get_user();
3196 my $command = shift;
3197 return $command =~ m/^\s*info(\s+|$)/
3198 || $command =~ m/^\s*help\s*$/;
3201 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3202 if !&$is_ro($param->{command
});
3204 my $vmid = $param->{vmid
};
3206 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3210 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3212 $res = "ERROR: $@" if $@;
3217 __PACKAGE__-
>register_method({
3218 name
=> 'resize_vm',
3219 path
=> '{vmid}/resize',
3223 description
=> "Extend volume size.",
3225 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3228 additionalProperties
=> 0,
3230 node
=> get_standard_option
('pve-node'),
3231 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3232 skiplock
=> get_standard_option
('skiplock'),
3235 description
=> "The disk you want to resize.",
3236 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3240 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3241 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.",
3245 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3251 returns
=> { type
=> 'null'},
3255 my $rpcenv = PVE
::RPCEnvironment
::get
();
3257 my $authuser = $rpcenv->get_user();
3259 my $node = extract_param
($param, 'node');
3261 my $vmid = extract_param
($param, 'vmid');
3263 my $digest = extract_param
($param, 'digest');
3265 my $disk = extract_param
($param, 'disk');
3267 my $sizestr = extract_param
($param, 'size');
3269 my $skiplock = extract_param
($param, 'skiplock');
3270 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3271 if $skiplock && $authuser ne 'root@pam';
3273 my $storecfg = PVE
::Storage
::config
();
3275 my $updatefn = sub {
3277 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3279 die "checksum missmatch (file change by other user?)\n"
3280 if $digest && $digest ne $conf->{digest
};
3281 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3283 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3285 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3287 my (undef, undef, undef, undef, undef, undef, $format) =
3288 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3290 die "can't resize volume: $disk if snapshot exists\n"
3291 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3293 my $volid = $drive->{file
};
3295 die "disk '$disk' has no associated volume\n" if !$volid;
3297 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3299 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3301 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3303 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3304 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3306 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3307 my ($ext, $newsize, $unit) = ($1, $2, $4);
3310 $newsize = $newsize * 1024;
3311 } elsif ($unit eq 'M') {
3312 $newsize = $newsize * 1024 * 1024;
3313 } elsif ($unit eq 'G') {
3314 $newsize = $newsize * 1024 * 1024 * 1024;
3315 } elsif ($unit eq 'T') {
3316 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3319 $newsize += $size if $ext;
3320 $newsize = int($newsize);
3322 die "shrinking disks is not supported\n" if $newsize < $size;
3324 return if $size == $newsize;
3326 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3328 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3330 $drive->{size
} = $newsize;
3331 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3333 PVE
::QemuConfig-
>write_config($vmid, $conf);
3336 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3340 __PACKAGE__-
>register_method({
3341 name
=> 'snapshot_list',
3342 path
=> '{vmid}/snapshot',
3344 description
=> "List all snapshots.",
3346 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3349 protected
=> 1, # qemu pid files are only readable by root
3351 additionalProperties
=> 0,
3353 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3354 node
=> get_standard_option
('pve-node'),
3363 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3367 description
=> "Snapshot includes RAM.",
3372 description
=> "Snapshot description.",
3376 description
=> "Snapshot creation time",
3378 renderer
=> 'timestamp',
3382 description
=> "Parent snapshot identifier.",
3388 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3393 my $vmid = $param->{vmid
};
3395 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3396 my $snaphash = $conf->{snapshots
} || {};
3400 foreach my $name (keys %$snaphash) {
3401 my $d = $snaphash->{$name};
3404 snaptime
=> $d->{snaptime
} || 0,
3405 vmstate
=> $d->{vmstate
} ?
1 : 0,
3406 description
=> $d->{description
} || '',
3408 $item->{parent
} = $d->{parent
} if $d->{parent
};
3409 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3413 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3416 digest
=> $conf->{digest
},
3417 running
=> $running,
3418 description
=> "You are here!",
3420 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3422 push @$res, $current;
3427 __PACKAGE__-
>register_method({
3429 path
=> '{vmid}/snapshot',
3433 description
=> "Snapshot a VM.",
3435 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3438 additionalProperties
=> 0,
3440 node
=> get_standard_option
('pve-node'),
3441 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3442 snapname
=> get_standard_option
('pve-snapshot-name'),
3446 description
=> "Save the vmstate",
3451 description
=> "A textual description or comment.",
3457 description
=> "the task ID.",
3462 my $rpcenv = PVE
::RPCEnvironment
::get
();
3464 my $authuser = $rpcenv->get_user();
3466 my $node = extract_param
($param, 'node');
3468 my $vmid = extract_param
($param, 'vmid');
3470 my $snapname = extract_param
($param, 'snapname');
3472 die "unable to use snapshot name 'current' (reserved name)\n"
3473 if $snapname eq 'current';
3476 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3477 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3478 $param->{description
});
3481 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3484 __PACKAGE__-
>register_method({
3485 name
=> 'snapshot_cmd_idx',
3486 path
=> '{vmid}/snapshot/{snapname}',
3493 additionalProperties
=> 0,
3495 vmid
=> get_standard_option
('pve-vmid'),
3496 node
=> get_standard_option
('pve-node'),
3497 snapname
=> get_standard_option
('pve-snapshot-name'),
3506 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3513 push @$res, { cmd
=> 'rollback' };
3514 push @$res, { cmd
=> 'config' };
3519 __PACKAGE__-
>register_method({
3520 name
=> 'update_snapshot_config',
3521 path
=> '{vmid}/snapshot/{snapname}/config',
3525 description
=> "Update snapshot metadata.",
3527 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3530 additionalProperties
=> 0,
3532 node
=> get_standard_option
('pve-node'),
3533 vmid
=> get_standard_option
('pve-vmid'),
3534 snapname
=> get_standard_option
('pve-snapshot-name'),
3538 description
=> "A textual description or comment.",
3542 returns
=> { type
=> 'null' },
3546 my $rpcenv = PVE
::RPCEnvironment
::get
();
3548 my $authuser = $rpcenv->get_user();
3550 my $vmid = extract_param
($param, 'vmid');
3552 my $snapname = extract_param
($param, 'snapname');
3554 return undef if !defined($param->{description
});
3556 my $updatefn = sub {
3558 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3560 PVE
::QemuConfig-
>check_lock($conf);
3562 my $snap = $conf->{snapshots
}->{$snapname};
3564 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3566 $snap->{description
} = $param->{description
} if defined($param->{description
});
3568 PVE
::QemuConfig-
>write_config($vmid, $conf);
3571 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3576 __PACKAGE__-
>register_method({
3577 name
=> 'get_snapshot_config',
3578 path
=> '{vmid}/snapshot/{snapname}/config',
3581 description
=> "Get snapshot configuration",
3583 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3586 additionalProperties
=> 0,
3588 node
=> get_standard_option
('pve-node'),
3589 vmid
=> get_standard_option
('pve-vmid'),
3590 snapname
=> get_standard_option
('pve-snapshot-name'),
3593 returns
=> { type
=> "object" },
3597 my $rpcenv = PVE
::RPCEnvironment
::get
();
3599 my $authuser = $rpcenv->get_user();
3601 my $vmid = extract_param
($param, 'vmid');
3603 my $snapname = extract_param
($param, 'snapname');
3605 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3607 my $snap = $conf->{snapshots
}->{$snapname};
3609 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3614 __PACKAGE__-
>register_method({
3616 path
=> '{vmid}/snapshot/{snapname}/rollback',
3620 description
=> "Rollback VM state to specified snapshot.",
3622 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3625 additionalProperties
=> 0,
3627 node
=> get_standard_option
('pve-node'),
3628 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3629 snapname
=> get_standard_option
('pve-snapshot-name'),
3634 description
=> "the task ID.",
3639 my $rpcenv = PVE
::RPCEnvironment
::get
();
3641 my $authuser = $rpcenv->get_user();
3643 my $node = extract_param
($param, 'node');
3645 my $vmid = extract_param
($param, 'vmid');
3647 my $snapname = extract_param
($param, 'snapname');
3650 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3651 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3655 # hold migration lock, this makes sure that nobody create replication snapshots
3656 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3659 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3662 __PACKAGE__-
>register_method({
3663 name
=> 'delsnapshot',
3664 path
=> '{vmid}/snapshot/{snapname}',
3668 description
=> "Delete a VM snapshot.",
3670 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3673 additionalProperties
=> 0,
3675 node
=> get_standard_option
('pve-node'),
3676 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3677 snapname
=> get_standard_option
('pve-snapshot-name'),
3681 description
=> "For removal from config file, even if removing disk snapshots fails.",
3687 description
=> "the task ID.",
3692 my $rpcenv = PVE
::RPCEnvironment
::get
();
3694 my $authuser = $rpcenv->get_user();
3696 my $node = extract_param
($param, 'node');
3698 my $vmid = extract_param
($param, 'vmid');
3700 my $snapname = extract_param
($param, 'snapname');
3703 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3704 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3707 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3710 __PACKAGE__-
>register_method({
3712 path
=> '{vmid}/template',
3716 description
=> "Create a Template.",
3718 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3719 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3722 additionalProperties
=> 0,
3724 node
=> get_standard_option
('pve-node'),
3725 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3729 description
=> "If you want to convert only 1 disk to base image.",
3730 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3735 returns
=> { type
=> 'null'},
3739 my $rpcenv = PVE
::RPCEnvironment
::get
();
3741 my $authuser = $rpcenv->get_user();
3743 my $node = extract_param
($param, 'node');
3745 my $vmid = extract_param
($param, 'vmid');
3747 my $disk = extract_param
($param, 'disk');
3749 my $updatefn = sub {
3751 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3753 PVE
::QemuConfig-
>check_lock($conf);
3755 die "unable to create template, because VM contains snapshots\n"
3756 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3758 die "you can't convert a template to a template\n"
3759 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3761 die "you can't convert a VM to template if VM is running\n"
3762 if PVE
::QemuServer
::check_running
($vmid);
3765 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3768 $conf->{template
} = 1;
3769 PVE
::QemuConfig-
>write_config($vmid, $conf);
3771 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3774 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);