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
1529 my $authpath = "/vms/$vmid";
1531 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1533 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1536 my ($remip, $family);
1539 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1540 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1541 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1542 $remcmd = ['/usr/bin/ssh', '-e', 'none', '-T', '-o', 'BatchMode=yes', $remip];
1544 $family = PVE
::Tools
::get_host_address_family
($node);
1547 my $port = PVE
::Tools
::next_vnc_port
($family);
1554 syslog
('info', "starting vnc proxy $upid\n");
1558 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1561 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1563 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1564 '-timeout', $timeout, '-authpath', $authpath,
1565 '-perm', 'Sys.Console'];
1567 if ($param->{websocket
}) {
1568 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1569 push @$cmd, '-notls', '-listen', 'localhost';
1572 push @$cmd, '-c', @$remcmd, @$termcmd;
1574 PVE
::Tools
::run_command
($cmd);
1578 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1580 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1582 my $sock = IO
::Socket
::IP-
>new(
1587 GetAddrInfoFlags
=> 0,
1588 ) or die "failed to create socket: $!\n";
1589 # Inside the worker we shouldn't have any previous alarms
1590 # running anyway...:
1592 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1594 accept(my $cli, $sock) or die "connection failed: $!\n";
1597 if (PVE
::Tools
::run_command
($cmd,
1598 output
=> '>&'.fileno($cli),
1599 input
=> '<&'.fileno($cli),
1602 die "Failed to run vncproxy.\n";
1609 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1611 PVE
::Tools
::wait_for_vnc_port
($port);
1622 __PACKAGE__-
>register_method({
1623 name
=> 'termproxy',
1624 path
=> '{vmid}/termproxy',
1628 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1630 description
=> "Creates a TCP proxy connections.",
1632 additionalProperties
=> 0,
1634 node
=> get_standard_option
('pve-node'),
1635 vmid
=> get_standard_option
('pve-vmid'),
1639 enum
=> [qw(serial0 serial1 serial2 serial3)],
1640 description
=> "opens a serial terminal (defaults to display)",
1645 additionalProperties
=> 0,
1647 user
=> { type
=> 'string' },
1648 ticket
=> { type
=> 'string' },
1649 port
=> { type
=> 'integer' },
1650 upid
=> { type
=> 'string' },
1656 my $rpcenv = PVE
::RPCEnvironment
::get
();
1658 my $authuser = $rpcenv->get_user();
1660 my $vmid = $param->{vmid
};
1661 my $node = $param->{node
};
1662 my $serial = $param->{serial
};
1664 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1666 if (!defined($serial)) {
1667 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1668 $serial = $conf->{vga
};
1672 my $authpath = "/vms/$vmid";
1674 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1676 my ($remip, $family);
1678 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1679 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1681 $family = PVE
::Tools
::get_host_address_family
($node);
1684 my $port = PVE
::Tools
::next_vnc_port
($family);
1686 my $remcmd = $remip ?
1687 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
1689 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1690 push @$termcmd, '-iface', $serial if $serial;
1695 syslog
('info', "starting qemu termproxy $upid\n");
1697 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1698 '--perm', 'VM.Console', '--'];
1699 push @$cmd, @$remcmd, @$termcmd;
1701 PVE
::Tools
::run_command
($cmd);
1704 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1706 PVE
::Tools
::wait_for_vnc_port
($port);
1716 __PACKAGE__-
>register_method({
1717 name
=> 'vncwebsocket',
1718 path
=> '{vmid}/vncwebsocket',
1721 description
=> "You also need to pass a valid ticket (vncticket).",
1722 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1724 description
=> "Opens a weksocket for VNC traffic.",
1726 additionalProperties
=> 0,
1728 node
=> get_standard_option
('pve-node'),
1729 vmid
=> get_standard_option
('pve-vmid'),
1731 description
=> "Ticket from previous call to vncproxy.",
1736 description
=> "Port number returned by previous vncproxy call.",
1746 port
=> { type
=> 'string' },
1752 my $rpcenv = PVE
::RPCEnvironment
::get
();
1754 my $authuser = $rpcenv->get_user();
1756 my $vmid = $param->{vmid
};
1757 my $node = $param->{node
};
1759 my $authpath = "/vms/$vmid";
1761 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1763 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1765 # Note: VNC ports are acessible from outside, so we do not gain any
1766 # security if we verify that $param->{port} belongs to VM $vmid. This
1767 # check is done by verifying the VNC ticket (inside VNC protocol).
1769 my $port = $param->{port
};
1771 return { port
=> $port };
1774 __PACKAGE__-
>register_method({
1775 name
=> 'spiceproxy',
1776 path
=> '{vmid}/spiceproxy',
1781 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1783 description
=> "Returns a SPICE configuration to connect to the VM.",
1785 additionalProperties
=> 0,
1787 node
=> get_standard_option
('pve-node'),
1788 vmid
=> get_standard_option
('pve-vmid'),
1789 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1792 returns
=> get_standard_option
('remote-viewer-config'),
1796 my $rpcenv = PVE
::RPCEnvironment
::get
();
1798 my $authuser = $rpcenv->get_user();
1800 my $vmid = $param->{vmid
};
1801 my $node = $param->{node
};
1802 my $proxy = $param->{proxy
};
1804 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1805 my $title = "VM $vmid";
1806 $title .= " - ". $conf->{name
} if $conf->{name
};
1808 my $port = PVE
::QemuServer
::spice_port
($vmid);
1810 my ($ticket, undef, $remote_viewer_config) =
1811 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1813 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1814 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1816 return $remote_viewer_config;
1819 __PACKAGE__-
>register_method({
1821 path
=> '{vmid}/status',
1824 description
=> "Directory index",
1829 additionalProperties
=> 0,
1831 node
=> get_standard_option
('pve-node'),
1832 vmid
=> get_standard_option
('pve-vmid'),
1840 subdir
=> { type
=> 'string' },
1843 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1849 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1852 { subdir
=> 'current' },
1853 { subdir
=> 'start' },
1854 { subdir
=> 'stop' },
1860 __PACKAGE__-
>register_method({
1861 name
=> 'vm_status',
1862 path
=> '{vmid}/status/current',
1865 protected
=> 1, # qemu pid files are only readable by root
1866 description
=> "Get virtual machine status.",
1868 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1871 additionalProperties
=> 0,
1873 node
=> get_standard_option
('pve-node'),
1874 vmid
=> get_standard_option
('pve-vmid'),
1880 %$PVE::QemuServer
::vmstatus_return_properties
,
1882 description
=> "HA manager service status.",
1886 description
=> "Qemu VGA configuration supports spice.",
1891 description
=> "Qemu GuestAgent enabled in config.",
1901 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1903 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1904 my $status = $vmstatus->{$param->{vmid
}};
1906 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1908 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1909 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1914 __PACKAGE__-
>register_method({
1916 path
=> '{vmid}/status/start',
1920 description
=> "Start virtual machine.",
1922 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1925 additionalProperties
=> 0,
1927 node
=> get_standard_option
('pve-node'),
1928 vmid
=> get_standard_option
('pve-vmid',
1929 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1930 skiplock
=> get_standard_option
('skiplock'),
1931 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1932 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1935 enum
=> ['secure', 'insecure'],
1936 description
=> "Migration traffic is encrypted using an SSH " .
1937 "tunnel by default. On secure, completely private networks " .
1938 "this can be disabled to increase performance.",
1941 migration_network
=> {
1942 type
=> 'string', format
=> 'CIDR',
1943 description
=> "CIDR of the (sub) network that is used for migration.",
1946 machine
=> get_standard_option
('pve-qm-machine'),
1948 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1960 my $rpcenv = PVE
::RPCEnvironment
::get
();
1962 my $authuser = $rpcenv->get_user();
1964 my $node = extract_param
($param, 'node');
1966 my $vmid = extract_param
($param, 'vmid');
1968 my $machine = extract_param
($param, 'machine');
1970 my $stateuri = extract_param
($param, 'stateuri');
1971 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1972 if $stateuri && $authuser ne 'root@pam';
1974 my $skiplock = extract_param
($param, 'skiplock');
1975 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1976 if $skiplock && $authuser ne 'root@pam';
1978 my $migratedfrom = extract_param
($param, 'migratedfrom');
1979 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1980 if $migratedfrom && $authuser ne 'root@pam';
1982 my $migration_type = extract_param
($param, 'migration_type');
1983 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1984 if $migration_type && $authuser ne 'root@pam';
1986 my $migration_network = extract_param
($param, 'migration_network');
1987 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1988 if $migration_network && $authuser ne 'root@pam';
1990 my $targetstorage = extract_param
($param, 'targetstorage');
1991 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1992 if $targetstorage && $authuser ne 'root@pam';
1994 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1995 if $targetstorage && !$migratedfrom;
1997 # read spice ticket from STDIN
1999 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2000 if (defined(my $line = <STDIN
>)) {
2002 $spice_ticket = $line;
2006 PVE
::Cluster
::check_cfs_quorum
();
2008 my $storecfg = PVE
::Storage
::config
();
2010 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
2011 $rpcenv->{type
} ne 'ha') {
2016 my $service = "vm:$vmid";
2018 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
2020 print "Requesting HA start for VM $vmid\n";
2022 PVE
::Tools
::run_command
($cmd);
2027 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2034 syslog
('info', "start VM $vmid: $upid\n");
2036 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2037 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
2042 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2046 __PACKAGE__-
>register_method({
2048 path
=> '{vmid}/status/stop',
2052 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2053 "is akin to pulling the power plug of a running computer and may damage the VM data",
2055 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2058 additionalProperties
=> 0,
2060 node
=> get_standard_option
('pve-node'),
2061 vmid
=> get_standard_option
('pve-vmid',
2062 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2063 skiplock
=> get_standard_option
('skiplock'),
2064 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2066 description
=> "Wait maximal timeout seconds.",
2072 description
=> "Do not deactivate storage volumes.",
2085 my $rpcenv = PVE
::RPCEnvironment
::get
();
2087 my $authuser = $rpcenv->get_user();
2089 my $node = extract_param
($param, 'node');
2091 my $vmid = extract_param
($param, 'vmid');
2093 my $skiplock = extract_param
($param, 'skiplock');
2094 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2095 if $skiplock && $authuser ne 'root@pam';
2097 my $keepActive = extract_param
($param, 'keepActive');
2098 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2099 if $keepActive && $authuser ne 'root@pam';
2101 my $migratedfrom = extract_param
($param, 'migratedfrom');
2102 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2103 if $migratedfrom && $authuser ne 'root@pam';
2106 my $storecfg = PVE
::Storage
::config
();
2108 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2113 my $service = "vm:$vmid";
2115 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2117 print "Requesting HA stop for VM $vmid\n";
2119 PVE
::Tools
::run_command
($cmd);
2124 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2130 syslog
('info', "stop VM $vmid: $upid\n");
2132 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2133 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2138 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2142 __PACKAGE__-
>register_method({
2144 path
=> '{vmid}/status/reset',
2148 description
=> "Reset virtual machine.",
2150 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2153 additionalProperties
=> 0,
2155 node
=> get_standard_option
('pve-node'),
2156 vmid
=> get_standard_option
('pve-vmid',
2157 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2158 skiplock
=> get_standard_option
('skiplock'),
2167 my $rpcenv = PVE
::RPCEnvironment
::get
();
2169 my $authuser = $rpcenv->get_user();
2171 my $node = extract_param
($param, 'node');
2173 my $vmid = extract_param
($param, 'vmid');
2175 my $skiplock = extract_param
($param, 'skiplock');
2176 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2177 if $skiplock && $authuser ne 'root@pam';
2179 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2184 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2189 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2192 __PACKAGE__-
>register_method({
2193 name
=> 'vm_shutdown',
2194 path
=> '{vmid}/status/shutdown',
2198 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2199 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2201 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2204 additionalProperties
=> 0,
2206 node
=> get_standard_option
('pve-node'),
2207 vmid
=> get_standard_option
('pve-vmid',
2208 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2209 skiplock
=> get_standard_option
('skiplock'),
2211 description
=> "Wait maximal timeout seconds.",
2217 description
=> "Make sure the VM stops.",
2223 description
=> "Do not deactivate storage volumes.",
2236 my $rpcenv = PVE
::RPCEnvironment
::get
();
2238 my $authuser = $rpcenv->get_user();
2240 my $node = extract_param
($param, 'node');
2242 my $vmid = extract_param
($param, 'vmid');
2244 my $skiplock = extract_param
($param, 'skiplock');
2245 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2246 if $skiplock && $authuser ne 'root@pam';
2248 my $keepActive = extract_param
($param, 'keepActive');
2249 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2250 if $keepActive && $authuser ne 'root@pam';
2252 my $storecfg = PVE
::Storage
::config
();
2256 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2257 # otherwise, we will infer a shutdown command, but run into the timeout,
2258 # then when the vm is resumed, it will instantly shutdown
2260 # checking the qmp status here to get feedback to the gui/cli/api
2261 # and the status query should not take too long
2264 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2268 if (!$err && $qmpstatus->{status
} eq "paused") {
2269 if ($param->{forceStop
}) {
2270 warn "VM is paused - stop instead of shutdown\n";
2273 die "VM is paused - cannot shutdown\n";
2277 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2278 ($rpcenv->{type
} ne 'ha')) {
2283 my $service = "vm:$vmid";
2285 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2287 print "Requesting HA stop for VM $vmid\n";
2289 PVE
::Tools
::run_command
($cmd);
2294 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2301 syslog
('info', "shutdown VM $vmid: $upid\n");
2303 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2304 $shutdown, $param->{forceStop
}, $keepActive);
2309 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2313 __PACKAGE__-
>register_method({
2314 name
=> 'vm_suspend',
2315 path
=> '{vmid}/status/suspend',
2319 description
=> "Suspend virtual machine.",
2321 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2324 additionalProperties
=> 0,
2326 node
=> get_standard_option
('pve-node'),
2327 vmid
=> get_standard_option
('pve-vmid',
2328 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2329 skiplock
=> get_standard_option
('skiplock'),
2338 my $rpcenv = PVE
::RPCEnvironment
::get
();
2340 my $authuser = $rpcenv->get_user();
2342 my $node = extract_param
($param, 'node');
2344 my $vmid = extract_param
($param, 'vmid');
2346 my $skiplock = extract_param
($param, 'skiplock');
2347 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2348 if $skiplock && $authuser ne 'root@pam';
2350 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2355 syslog
('info', "suspend VM $vmid: $upid\n");
2357 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2362 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2365 __PACKAGE__-
>register_method({
2366 name
=> 'vm_resume',
2367 path
=> '{vmid}/status/resume',
2371 description
=> "Resume virtual machine.",
2373 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2376 additionalProperties
=> 0,
2378 node
=> get_standard_option
('pve-node'),
2379 vmid
=> get_standard_option
('pve-vmid',
2380 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2381 skiplock
=> get_standard_option
('skiplock'),
2382 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2392 my $rpcenv = PVE
::RPCEnvironment
::get
();
2394 my $authuser = $rpcenv->get_user();
2396 my $node = extract_param
($param, 'node');
2398 my $vmid = extract_param
($param, 'vmid');
2400 my $skiplock = extract_param
($param, 'skiplock');
2401 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2402 if $skiplock && $authuser ne 'root@pam';
2404 my $nocheck = extract_param
($param, 'nocheck');
2406 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2411 syslog
('info', "resume VM $vmid: $upid\n");
2413 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2418 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2421 __PACKAGE__-
>register_method({
2422 name
=> 'vm_sendkey',
2423 path
=> '{vmid}/sendkey',
2427 description
=> "Send key event to virtual machine.",
2429 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2432 additionalProperties
=> 0,
2434 node
=> get_standard_option
('pve-node'),
2435 vmid
=> get_standard_option
('pve-vmid',
2436 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2437 skiplock
=> get_standard_option
('skiplock'),
2439 description
=> "The key (qemu monitor encoding).",
2444 returns
=> { type
=> 'null'},
2448 my $rpcenv = PVE
::RPCEnvironment
::get
();
2450 my $authuser = $rpcenv->get_user();
2452 my $node = extract_param
($param, 'node');
2454 my $vmid = extract_param
($param, 'vmid');
2456 my $skiplock = extract_param
($param, 'skiplock');
2457 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2458 if $skiplock && $authuser ne 'root@pam';
2460 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2465 __PACKAGE__-
>register_method({
2466 name
=> 'vm_feature',
2467 path
=> '{vmid}/feature',
2471 description
=> "Check if feature for virtual machine is available.",
2473 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2476 additionalProperties
=> 0,
2478 node
=> get_standard_option
('pve-node'),
2479 vmid
=> get_standard_option
('pve-vmid'),
2481 description
=> "Feature to check.",
2483 enum
=> [ 'snapshot', 'clone', 'copy' ],
2485 snapname
=> get_standard_option
('pve-snapshot-name', {
2493 hasFeature
=> { type
=> 'boolean' },
2496 items
=> { type
=> 'string' },
2503 my $node = extract_param
($param, 'node');
2505 my $vmid = extract_param
($param, 'vmid');
2507 my $snapname = extract_param
($param, 'snapname');
2509 my $feature = extract_param
($param, 'feature');
2511 my $running = PVE
::QemuServer
::check_running
($vmid);
2513 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2516 my $snap = $conf->{snapshots
}->{$snapname};
2517 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2520 my $storecfg = PVE
::Storage
::config
();
2522 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2523 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2526 hasFeature
=> $hasFeature,
2527 nodes
=> [ keys %$nodelist ],
2531 __PACKAGE__-
>register_method({
2533 path
=> '{vmid}/clone',
2537 description
=> "Create a copy of virtual machine/template.",
2539 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2540 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2541 "'Datastore.AllocateSpace' on any used storage.",
2544 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2546 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2547 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2552 additionalProperties
=> 0,
2554 node
=> get_standard_option
('pve-node'),
2555 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2556 newid
=> get_standard_option
('pve-vmid', {
2557 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2558 description
=> 'VMID for the clone.' }),
2561 type
=> 'string', format
=> 'dns-name',
2562 description
=> "Set a name for the new VM.",
2567 description
=> "Description for the new VM.",
2571 type
=> 'string', format
=> 'pve-poolid',
2572 description
=> "Add the new VM to the specified pool.",
2574 snapname
=> get_standard_option
('pve-snapshot-name', {
2577 storage
=> get_standard_option
('pve-storage-id', {
2578 description
=> "Target storage for full clone.",
2582 description
=> "Target format for file storage. Only valid for full clone.",
2585 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2590 description
=> "Create a full copy of all disks. This is always done when " .
2591 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2593 target
=> get_standard_option
('pve-node', {
2594 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2605 my $rpcenv = PVE
::RPCEnvironment
::get
();
2607 my $authuser = $rpcenv->get_user();
2609 my $node = extract_param
($param, 'node');
2611 my $vmid = extract_param
($param, 'vmid');
2613 my $newid = extract_param
($param, 'newid');
2615 my $pool = extract_param
($param, 'pool');
2617 if (defined($pool)) {
2618 $rpcenv->check_pool_exist($pool);
2621 my $snapname = extract_param
($param, 'snapname');
2623 my $storage = extract_param
($param, 'storage');
2625 my $format = extract_param
($param, 'format');
2627 my $target = extract_param
($param, 'target');
2629 my $localnode = PVE
::INotify
::nodename
();
2631 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2633 PVE
::Cluster
::check_node_exists
($target) if $target;
2635 my $storecfg = PVE
::Storage
::config
();
2638 # check if storage is enabled on local node
2639 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2641 # check if storage is available on target node
2642 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2643 # clone only works if target storage is shared
2644 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2645 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2649 PVE
::Cluster
::check_cfs_quorum
();
2651 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2653 # exclusive lock if VM is running - else shared lock is enough;
2654 my $shared_lock = $running ?
0 : 1;
2658 # do all tests after lock
2659 # we also try to do all tests before we fork the worker
2661 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2663 PVE
::QemuConfig-
>check_lock($conf);
2665 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2667 die "unexpected state change\n" if $verify_running != $running;
2669 die "snapshot '$snapname' does not exist\n"
2670 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2672 my $full = extract_param
($param, 'full');
2673 if (!defined($full)) {
2674 $full = !PVE
::QemuConfig-
>is_template($conf);
2677 die "parameter 'storage' not allowed for linked clones\n"
2678 if defined($storage) && !$full;
2680 die "parameter 'format' not allowed for linked clones\n"
2681 if defined($format) && !$full;
2683 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2685 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2687 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2689 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2691 die "unable to create VM $newid: config file already exists\n"
2694 my $newconf = { lock => 'clone' };
2699 foreach my $opt (keys %$oldconf) {
2700 my $value = $oldconf->{$opt};
2702 # do not copy snapshot related info
2703 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2704 $opt eq 'vmstate' || $opt eq 'snapstate';
2706 # no need to copy unused images, because VMID(owner) changes anyways
2707 next if $opt =~ m/^unused\d+$/;
2709 # always change MAC! address
2710 if ($opt =~ m/^net(\d+)$/) {
2711 my $net = PVE
::QemuServer
::parse_net
($value);
2712 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2713 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2714 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2715 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2716 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2717 die "unable to parse drive options for '$opt'\n" if !$drive;
2718 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2719 $newconf->{$opt} = $value; # simply copy configuration
2721 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2722 die "Full clone feature is not supported for drive '$opt'\n"
2723 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2724 $fullclone->{$opt} = 1;
2726 # not full means clone instead of copy
2727 die "Linked clone feature is not supported for drive '$opt'\n"
2728 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2730 $drives->{$opt} = $drive;
2731 push @$vollist, $drive->{file
};
2734 # copy everything else
2735 $newconf->{$opt} = $value;
2739 # auto generate a new uuid
2740 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2741 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2742 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2744 # auto generate a new vmgenid if the option was set
2745 if ($newconf->{vmgenid
}) {
2746 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2749 delete $newconf->{template
};
2751 if ($param->{name
}) {
2752 $newconf->{name
} = $param->{name
};
2754 if ($oldconf->{name
}) {
2755 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2757 $newconf->{name
} = "Copy-of-VM-$vmid";
2761 if ($param->{description
}) {
2762 $newconf->{description
} = $param->{description
};
2765 # create empty/temp config - this fails if VM already exists on other node
2766 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2771 my $newvollist = [];
2778 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2780 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2782 my $total_jobs = scalar(keys %{$drives});
2785 foreach my $opt (keys %$drives) {
2786 my $drive = $drives->{$opt};
2787 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2789 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2790 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2791 $jobs, $skipcomplete, $oldconf->{agent
});
2793 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2795 PVE
::QemuConfig-
>write_config($newid, $newconf);
2799 delete $newconf->{lock};
2801 # do not write pending changes
2802 if (my @changes = keys %{$newconf->{pending
}}) {
2803 my $pending = join(',', @changes);
2804 warn "found pending changes for '$pending', discarding for clone\n";
2805 delete $newconf->{pending
};
2808 PVE
::QemuConfig-
>write_config($newid, $newconf);
2811 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2812 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2813 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2815 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2816 die "Failed to move config to node '$target' - rename failed: $!\n"
2817 if !rename($conffile, $newconffile);
2820 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2825 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2827 sleep 1; # some storage like rbd need to wait before release volume - really?
2829 foreach my $volid (@$newvollist) {
2830 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2833 die "clone failed: $err";
2839 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2841 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2844 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2845 # Aquire exclusive lock lock for $newid
2846 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2851 __PACKAGE__-
>register_method({
2852 name
=> 'move_vm_disk',
2853 path
=> '{vmid}/move_disk',
2857 description
=> "Move volume to different storage.",
2859 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2861 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2862 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2866 additionalProperties
=> 0,
2868 node
=> get_standard_option
('pve-node'),
2869 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2872 description
=> "The disk you want to move.",
2873 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2875 storage
=> get_standard_option
('pve-storage-id', {
2876 description
=> "Target storage.",
2877 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2881 description
=> "Target Format.",
2882 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2887 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2893 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2901 description
=> "the task ID.",
2906 my $rpcenv = PVE
::RPCEnvironment
::get
();
2908 my $authuser = $rpcenv->get_user();
2910 my $node = extract_param
($param, 'node');
2912 my $vmid = extract_param
($param, 'vmid');
2914 my $digest = extract_param
($param, 'digest');
2916 my $disk = extract_param
($param, 'disk');
2918 my $storeid = extract_param
($param, 'storage');
2920 my $format = extract_param
($param, 'format');
2922 my $storecfg = PVE
::Storage
::config
();
2924 my $updatefn = sub {
2926 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2928 PVE
::QemuConfig-
>check_lock($conf);
2930 die "checksum missmatch (file change by other user?)\n"
2931 if $digest && $digest ne $conf->{digest
};
2933 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2935 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2937 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2939 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
2942 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2943 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2947 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2948 (!$format || !$oldfmt || $oldfmt eq $format);
2950 # this only checks snapshots because $disk is passed!
2951 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2952 die "you can't move a disk with snapshots and delete the source\n"
2953 if $snapshotted && $param->{delete};
2955 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2957 my $running = PVE
::QemuServer
::check_running
($vmid);
2959 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2963 my $newvollist = [];
2969 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2971 warn "moving disk with snapshots, snapshots will not be moved!\n"
2974 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2975 $vmid, $storeid, $format, 1, $newvollist);
2977 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2979 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2981 # convert moved disk to base if part of template
2982 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2983 if PVE
::QemuConfig-
>is_template($conf);
2985 PVE
::QemuConfig-
>write_config($vmid, $conf);
2987 if ($running && PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
} && PVE
::QemuServer
::qga_check_running
($vmid)) {
2988 eval { PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-fstrim"); };
2992 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2993 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3000 foreach my $volid (@$newvollist) {
3001 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3004 die "storage migration failed: $err";
3007 if ($param->{delete}) {
3009 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3010 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3016 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3019 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3022 __PACKAGE__-
>register_method({
3023 name
=> 'migrate_vm',
3024 path
=> '{vmid}/migrate',
3028 description
=> "Migrate virtual machine. Creates a new migration task.",
3030 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3033 additionalProperties
=> 0,
3035 node
=> get_standard_option
('pve-node'),
3036 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3037 target
=> get_standard_option
('pve-node', {
3038 description
=> "Target node.",
3039 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3043 description
=> "Use online/live migration.",
3048 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3053 enum
=> ['secure', 'insecure'],
3054 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3057 migration_network
=> {
3058 type
=> 'string', format
=> 'CIDR',
3059 description
=> "CIDR of the (sub) network that is used for migration.",
3062 "with-local-disks" => {
3064 description
=> "Enable live storage migration for local disk",
3067 targetstorage
=> get_standard_option
('pve-storage-id', {
3068 description
=> "Default target storage.",
3070 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3076 description
=> "the task ID.",
3081 my $rpcenv = PVE
::RPCEnvironment
::get
();
3083 my $authuser = $rpcenv->get_user();
3085 my $target = extract_param
($param, 'target');
3087 my $localnode = PVE
::INotify
::nodename
();
3088 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3090 PVE
::Cluster
::check_cfs_quorum
();
3092 PVE
::Cluster
::check_node_exists
($target);
3094 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3096 my $vmid = extract_param
($param, 'vmid');
3098 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3099 if !$param->{online
} && $param->{targetstorage
};
3101 raise_param_exc
({ force
=> "Only root may use this option." })
3102 if $param->{force
} && $authuser ne 'root@pam';
3104 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3105 if $param->{migration_type
} && $authuser ne 'root@pam';
3107 # allow root only until better network permissions are available
3108 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3109 if $param->{migration_network
} && $authuser ne 'root@pam';
3112 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3114 # try to detect errors early
3116 PVE
::QemuConfig-
>check_lock($conf);
3118 if (PVE
::QemuServer
::check_running
($vmid)) {
3119 die "cant migrate running VM without --online\n"
3120 if !$param->{online
};
3123 my $storecfg = PVE
::Storage
::config
();
3125 if( $param->{targetstorage
}) {
3126 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3128 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3131 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3136 my $service = "vm:$vmid";
3138 my $cmd = ['ha-manager', 'migrate', $service, $target];
3140 print "Requesting HA migration for VM $vmid to node $target\n";
3142 PVE
::Tools
::run_command
($cmd);
3147 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3152 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3156 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3159 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3164 __PACKAGE__-
>register_method({
3166 path
=> '{vmid}/monitor',
3170 description
=> "Execute Qemu monitor commands.",
3172 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3173 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3176 additionalProperties
=> 0,
3178 node
=> get_standard_option
('pve-node'),
3179 vmid
=> get_standard_option
('pve-vmid'),
3182 description
=> "The monitor command.",
3186 returns
=> { type
=> 'string'},
3190 my $rpcenv = PVE
::RPCEnvironment
::get
();
3191 my $authuser = $rpcenv->get_user();
3194 my $command = shift;
3195 return $command =~ m/^\s*info(\s+|$)/
3196 || $command =~ m/^\s*help\s*$/;
3199 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3200 if !&$is_ro($param->{command
});
3202 my $vmid = $param->{vmid
};
3204 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3208 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3210 $res = "ERROR: $@" if $@;
3215 __PACKAGE__-
>register_method({
3216 name
=> 'resize_vm',
3217 path
=> '{vmid}/resize',
3221 description
=> "Extend volume size.",
3223 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3226 additionalProperties
=> 0,
3228 node
=> get_standard_option
('pve-node'),
3229 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3230 skiplock
=> get_standard_option
('skiplock'),
3233 description
=> "The disk you want to resize.",
3234 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3238 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3239 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.",
3243 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3249 returns
=> { type
=> 'null'},
3253 my $rpcenv = PVE
::RPCEnvironment
::get
();
3255 my $authuser = $rpcenv->get_user();
3257 my $node = extract_param
($param, 'node');
3259 my $vmid = extract_param
($param, 'vmid');
3261 my $digest = extract_param
($param, 'digest');
3263 my $disk = extract_param
($param, 'disk');
3265 my $sizestr = extract_param
($param, 'size');
3267 my $skiplock = extract_param
($param, 'skiplock');
3268 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3269 if $skiplock && $authuser ne 'root@pam';
3271 my $storecfg = PVE
::Storage
::config
();
3273 my $updatefn = sub {
3275 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3277 die "checksum missmatch (file change by other user?)\n"
3278 if $digest && $digest ne $conf->{digest
};
3279 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3281 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3283 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3285 my (undef, undef, undef, undef, undef, undef, $format) =
3286 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3288 die "can't resize volume: $disk if snapshot exists\n"
3289 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3291 my $volid = $drive->{file
};
3293 die "disk '$disk' has no associated volume\n" if !$volid;
3295 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3297 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3299 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3301 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3302 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3304 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3305 my ($ext, $newsize, $unit) = ($1, $2, $4);
3308 $newsize = $newsize * 1024;
3309 } elsif ($unit eq 'M') {
3310 $newsize = $newsize * 1024 * 1024;
3311 } elsif ($unit eq 'G') {
3312 $newsize = $newsize * 1024 * 1024 * 1024;
3313 } elsif ($unit eq 'T') {
3314 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3317 $newsize += $size if $ext;
3318 $newsize = int($newsize);
3320 die "shrinking disks is not supported\n" if $newsize < $size;
3322 return if $size == $newsize;
3324 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3326 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3328 $drive->{size
} = $newsize;
3329 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3331 PVE
::QemuConfig-
>write_config($vmid, $conf);
3334 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3338 __PACKAGE__-
>register_method({
3339 name
=> 'snapshot_list',
3340 path
=> '{vmid}/snapshot',
3342 description
=> "List all snapshots.",
3344 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3347 protected
=> 1, # qemu pid files are only readable by root
3349 additionalProperties
=> 0,
3351 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3352 node
=> get_standard_option
('pve-node'),
3361 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3365 description
=> "Snapshot includes RAM.",
3370 description
=> "Snapshot description.",
3374 description
=> "Snapshot creation time",
3376 renderer
=> 'timestamp',
3380 description
=> "Parent snapshot identifier.",
3386 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3391 my $vmid = $param->{vmid
};
3393 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3394 my $snaphash = $conf->{snapshots
} || {};
3398 foreach my $name (keys %$snaphash) {
3399 my $d = $snaphash->{$name};
3402 snaptime
=> $d->{snaptime
} || 0,
3403 vmstate
=> $d->{vmstate
} ?
1 : 0,
3404 description
=> $d->{description
} || '',
3406 $item->{parent
} = $d->{parent
} if $d->{parent
};
3407 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3411 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3414 digest
=> $conf->{digest
},
3415 running
=> $running,
3416 description
=> "You are here!",
3418 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3420 push @$res, $current;
3425 __PACKAGE__-
>register_method({
3427 path
=> '{vmid}/snapshot',
3431 description
=> "Snapshot a VM.",
3433 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3436 additionalProperties
=> 0,
3438 node
=> get_standard_option
('pve-node'),
3439 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3440 snapname
=> get_standard_option
('pve-snapshot-name'),
3444 description
=> "Save the vmstate",
3449 description
=> "A textual description or comment.",
3455 description
=> "the task ID.",
3460 my $rpcenv = PVE
::RPCEnvironment
::get
();
3462 my $authuser = $rpcenv->get_user();
3464 my $node = extract_param
($param, 'node');
3466 my $vmid = extract_param
($param, 'vmid');
3468 my $snapname = extract_param
($param, 'snapname');
3470 die "unable to use snapshot name 'current' (reserved name)\n"
3471 if $snapname eq 'current';
3474 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3475 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3476 $param->{description
});
3479 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3482 __PACKAGE__-
>register_method({
3483 name
=> 'snapshot_cmd_idx',
3484 path
=> '{vmid}/snapshot/{snapname}',
3491 additionalProperties
=> 0,
3493 vmid
=> get_standard_option
('pve-vmid'),
3494 node
=> get_standard_option
('pve-node'),
3495 snapname
=> get_standard_option
('pve-snapshot-name'),
3504 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3511 push @$res, { cmd
=> 'rollback' };
3512 push @$res, { cmd
=> 'config' };
3517 __PACKAGE__-
>register_method({
3518 name
=> 'update_snapshot_config',
3519 path
=> '{vmid}/snapshot/{snapname}/config',
3523 description
=> "Update snapshot metadata.",
3525 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3528 additionalProperties
=> 0,
3530 node
=> get_standard_option
('pve-node'),
3531 vmid
=> get_standard_option
('pve-vmid'),
3532 snapname
=> get_standard_option
('pve-snapshot-name'),
3536 description
=> "A textual description or comment.",
3540 returns
=> { type
=> 'null' },
3544 my $rpcenv = PVE
::RPCEnvironment
::get
();
3546 my $authuser = $rpcenv->get_user();
3548 my $vmid = extract_param
($param, 'vmid');
3550 my $snapname = extract_param
($param, 'snapname');
3552 return undef if !defined($param->{description
});
3554 my $updatefn = sub {
3556 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3558 PVE
::QemuConfig-
>check_lock($conf);
3560 my $snap = $conf->{snapshots
}->{$snapname};
3562 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3564 $snap->{description
} = $param->{description
} if defined($param->{description
});
3566 PVE
::QemuConfig-
>write_config($vmid, $conf);
3569 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3574 __PACKAGE__-
>register_method({
3575 name
=> 'get_snapshot_config',
3576 path
=> '{vmid}/snapshot/{snapname}/config',
3579 description
=> "Get snapshot configuration",
3581 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3584 additionalProperties
=> 0,
3586 node
=> get_standard_option
('pve-node'),
3587 vmid
=> get_standard_option
('pve-vmid'),
3588 snapname
=> get_standard_option
('pve-snapshot-name'),
3591 returns
=> { type
=> "object" },
3595 my $rpcenv = PVE
::RPCEnvironment
::get
();
3597 my $authuser = $rpcenv->get_user();
3599 my $vmid = extract_param
($param, 'vmid');
3601 my $snapname = extract_param
($param, 'snapname');
3603 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3605 my $snap = $conf->{snapshots
}->{$snapname};
3607 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3612 __PACKAGE__-
>register_method({
3614 path
=> '{vmid}/snapshot/{snapname}/rollback',
3618 description
=> "Rollback VM state to specified snapshot.",
3620 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3623 additionalProperties
=> 0,
3625 node
=> get_standard_option
('pve-node'),
3626 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3627 snapname
=> get_standard_option
('pve-snapshot-name'),
3632 description
=> "the task ID.",
3637 my $rpcenv = PVE
::RPCEnvironment
::get
();
3639 my $authuser = $rpcenv->get_user();
3641 my $node = extract_param
($param, 'node');
3643 my $vmid = extract_param
($param, 'vmid');
3645 my $snapname = extract_param
($param, 'snapname');
3648 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3649 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3653 # hold migration lock, this makes sure that nobody create replication snapshots
3654 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3657 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3660 __PACKAGE__-
>register_method({
3661 name
=> 'delsnapshot',
3662 path
=> '{vmid}/snapshot/{snapname}',
3666 description
=> "Delete a VM snapshot.",
3668 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3671 additionalProperties
=> 0,
3673 node
=> get_standard_option
('pve-node'),
3674 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3675 snapname
=> get_standard_option
('pve-snapshot-name'),
3679 description
=> "For removal from config file, even if removing disk snapshots fails.",
3685 description
=> "the task ID.",
3690 my $rpcenv = PVE
::RPCEnvironment
::get
();
3692 my $authuser = $rpcenv->get_user();
3694 my $node = extract_param
($param, 'node');
3696 my $vmid = extract_param
($param, 'vmid');
3698 my $snapname = extract_param
($param, 'snapname');
3701 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3702 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3705 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3708 __PACKAGE__-
>register_method({
3710 path
=> '{vmid}/template',
3714 description
=> "Create a Template.",
3716 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3717 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3720 additionalProperties
=> 0,
3722 node
=> get_standard_option
('pve-node'),
3723 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3727 description
=> "If you want to convert only 1 disk to base image.",
3728 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3733 returns
=> { type
=> 'null'},
3737 my $rpcenv = PVE
::RPCEnvironment
::get
();
3739 my $authuser = $rpcenv->get_user();
3741 my $node = extract_param
($param, 'node');
3743 my $vmid = extract_param
($param, 'vmid');
3745 my $disk = extract_param
($param, 'disk');
3747 my $updatefn = sub {
3749 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3751 PVE
::QemuConfig-
>check_lock($conf);
3753 die "unable to create template, because VM contains snapshots\n"
3754 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3756 die "you can't convert a template to a template\n"
3757 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3759 die "you can't convert a VM to template if VM is running\n"
3760 if PVE
::QemuServer
::check_running
($vmid);
3763 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3766 $conf->{template
} = 1;
3767 PVE
::QemuConfig-
>write_config($vmid, $conf);
3769 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3772 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);