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);
651 unlink($conffile) or die "failed to remove config file: $!\n";
659 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
662 __PACKAGE__-
>register_method({
667 description
=> "Directory index",
672 additionalProperties
=> 0,
674 node
=> get_standard_option
('pve-node'),
675 vmid
=> get_standard_option
('pve-vmid'),
683 subdir
=> { type
=> 'string' },
686 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
692 { subdir
=> 'config' },
693 { subdir
=> 'pending' },
694 { subdir
=> 'status' },
695 { subdir
=> 'unlink' },
696 { subdir
=> 'vncproxy' },
697 { subdir
=> 'termproxy' },
698 { subdir
=> 'migrate' },
699 { subdir
=> 'resize' },
700 { subdir
=> 'move' },
702 { subdir
=> 'rrddata' },
703 { subdir
=> 'monitor' },
704 { subdir
=> 'agent' },
705 { subdir
=> 'snapshot' },
706 { subdir
=> 'spiceproxy' },
707 { subdir
=> 'sendkey' },
708 { subdir
=> 'firewall' },
714 __PACKAGE__-
>register_method ({
715 subclass
=> "PVE::API2::Firewall::VM",
716 path
=> '{vmid}/firewall',
719 __PACKAGE__-
>register_method ({
720 subclass
=> "PVE::API2::Qemu::Agent",
721 path
=> '{vmid}/agent',
724 __PACKAGE__-
>register_method({
726 path
=> '{vmid}/rrd',
728 protected
=> 1, # fixme: can we avoid that?
730 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
732 description
=> "Read VM RRD statistics (returns PNG)",
734 additionalProperties
=> 0,
736 node
=> get_standard_option
('pve-node'),
737 vmid
=> get_standard_option
('pve-vmid'),
739 description
=> "Specify the time frame you are interested in.",
741 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
744 description
=> "The list of datasources you want to display.",
745 type
=> 'string', format
=> 'pve-configid-list',
748 description
=> "The RRD consolidation function",
750 enum
=> [ 'AVERAGE', 'MAX' ],
758 filename
=> { type
=> 'string' },
764 return PVE
::Cluster
::create_rrd_graph
(
765 "pve2-vm/$param->{vmid}", $param->{timeframe
},
766 $param->{ds
}, $param->{cf
});
770 __PACKAGE__-
>register_method({
772 path
=> '{vmid}/rrddata',
774 protected
=> 1, # fixme: can we avoid that?
776 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
778 description
=> "Read VM RRD statistics",
780 additionalProperties
=> 0,
782 node
=> get_standard_option
('pve-node'),
783 vmid
=> get_standard_option
('pve-vmid'),
785 description
=> "Specify the time frame you are interested in.",
787 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
790 description
=> "The RRD consolidation function",
792 enum
=> [ 'AVERAGE', 'MAX' ],
807 return PVE
::Cluster
::create_rrd_data
(
808 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
812 __PACKAGE__-
>register_method({
814 path
=> '{vmid}/config',
817 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
819 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
822 additionalProperties
=> 0,
824 node
=> get_standard_option
('pve-node'),
825 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
827 description
=> "Get current values (instead of pending values).",
832 snapshot
=> get_standard_option
('pve-snapshot-name', {
833 description
=> "Fetch config values from given snapshot.",
836 my ($cmd, $pname, $cur, $args) = @_;
837 PVE
::QemuConfig-
>snapshot_list($args->[0]);
843 description
=> "The current VM configuration.",
845 properties
=> PVE
::QemuServer
::json_config_properties
({
848 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
855 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
857 if (my $snapname = $param->{snapshot
}) {
858 my $snapshot = $conf->{snapshots
}->{$snapname};
859 die "snapshot '$snapname' does not exist\n" if !defined($snapshot);
861 $snapshot->{digest
} = $conf->{digest
}; # keep file digest for API
866 delete $conf->{snapshots
};
868 if (!$param->{current
}) {
869 foreach my $opt (keys %{$conf->{pending
}}) {
870 next if $opt eq 'delete';
871 my $value = $conf->{pending
}->{$opt};
872 next if ref($value); # just to be sure
873 $conf->{$opt} = $value;
875 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
876 foreach my $opt (keys %$pending_delete_hash) {
877 delete $conf->{$opt} if $conf->{$opt};
881 delete $conf->{pending
};
883 # hide cloudinit password
884 if ($conf->{cipassword
}) {
885 $conf->{cipassword
} = '**********';
891 __PACKAGE__-
>register_method({
892 name
=> 'vm_pending',
893 path
=> '{vmid}/pending',
896 description
=> "Get virtual machine configuration, including pending changes.",
898 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
901 additionalProperties
=> 0,
903 node
=> get_standard_option
('pve-node'),
904 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
913 description
=> "Configuration option name.",
917 description
=> "Current value.",
922 description
=> "Pending value.",
927 description
=> "Indicates a pending delete request if present and not 0. " .
928 "The value 2 indicates a force-delete request.",
940 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
942 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
946 foreach my $opt (keys %$conf) {
947 next if ref($conf->{$opt});
948 my $item = { key
=> $opt };
949 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
950 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
951 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
953 # hide cloudinit password
954 if ($opt eq 'cipassword') {
955 $item->{value
} = '**********' if defined($item->{value
});
956 # the trailing space so that the pending string is different
957 $item->{pending
} = '********** ' if defined($item->{pending
});
962 foreach my $opt (keys %{$conf->{pending
}}) {
963 next if $opt eq 'delete';
964 next if ref($conf->{pending
}->{$opt}); # just to be sure
965 next if defined($conf->{$opt});
966 my $item = { key
=> $opt };
967 $item->{pending
} = $conf->{pending
}->{$opt};
969 # hide cloudinit password
970 if ($opt eq 'cipassword') {
971 $item->{pending
} = '**********' if defined($item->{pending
});
976 while (my ($opt, $force) = each %$pending_delete_hash) {
977 next if $conf->{pending
}->{$opt}; # just to be sure
978 next if $conf->{$opt};
979 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
986 # POST/PUT {vmid}/config implementation
988 # The original API used PUT (idempotent) an we assumed that all operations
989 # are fast. But it turned out that almost any configuration change can
990 # involve hot-plug actions, or disk alloc/free. Such actions can take long
991 # time to complete and have side effects (not idempotent).
993 # The new implementation uses POST and forks a worker process. We added
994 # a new option 'background_delay'. If specified we wait up to
995 # 'background_delay' second for the worker task to complete. It returns null
996 # if the task is finished within that time, else we return the UPID.
998 my $update_vm_api = sub {
999 my ($param, $sync) = @_;
1001 my $rpcenv = PVE
::RPCEnvironment
::get
();
1003 my $authuser = $rpcenv->get_user();
1005 my $node = extract_param
($param, 'node');
1007 my $vmid = extract_param
($param, 'vmid');
1009 my $digest = extract_param
($param, 'digest');
1011 my $background_delay = extract_param
($param, 'background_delay');
1013 if (defined(my $cipassword = $param->{cipassword
})) {
1014 # Same logic as in cloud-init (but with the regex fixed...)
1015 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
1016 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1019 my @paramarr = (); # used for log message
1020 foreach my $key (sort keys %$param) {
1021 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1022 push @paramarr, "-$key", $value;
1025 my $skiplock = extract_param
($param, 'skiplock');
1026 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1027 if $skiplock && $authuser ne 'root@pam';
1029 my $delete_str = extract_param
($param, 'delete');
1031 my $revert_str = extract_param
($param, 'revert');
1033 my $force = extract_param
($param, 'force');
1035 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1036 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1037 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1040 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1042 my $storecfg = PVE
::Storage
::config
();
1044 my $defaults = PVE
::QemuServer
::load_defaults
();
1046 &$resolve_cdrom_alias($param);
1048 # now try to verify all parameters
1051 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1052 if (!PVE
::QemuServer
::option_exists
($opt)) {
1053 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1056 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1057 "-revert $opt' at the same time" })
1058 if defined($param->{$opt});
1060 $revert->{$opt} = 1;
1064 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1065 $opt = 'ide2' if $opt eq 'cdrom';
1067 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1068 "-delete $opt' at the same time" })
1069 if defined($param->{$opt});
1071 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1072 "-revert $opt' at the same time" })
1075 if (!PVE
::QemuServer
::option_exists
($opt)) {
1076 raise_param_exc
({ delete => "unknown option '$opt'" });
1082 my $repl_conf = PVE
::ReplicationConfig-
>new();
1083 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1084 my $check_replication = sub {
1086 return if !$is_replicated;
1087 my $volid = $drive->{file
};
1088 return if !$volid || !($drive->{replicate
}//1);
1089 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1090 my ($storeid, $format);
1091 if ($volid =~ $NEW_DISK_RE) {
1093 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1095 ($storeid, undef) = PVE
::Storage
::parse_volume_id
($volid, 1);
1096 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1098 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1099 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1100 return if $scfg->{shared
};
1101 die "cannot add non-replicatable volume to a replicated VM\n";
1104 foreach my $opt (keys %$param) {
1105 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1106 # cleanup drive path
1107 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1108 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1109 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1110 $check_replication->($drive);
1111 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1112 } elsif ($opt =~ m/^net(\d+)$/) {
1114 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1115 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1116 } elsif ($opt eq 'vmgenid') {
1117 if ($param->{$opt} eq '1') {
1118 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1120 } elsif ($opt eq 'hookscript') {
1121 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1122 raise_param_exc
({ $opt => $@ }) if $@;
1126 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1128 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1130 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1132 my $updatefn = sub {
1134 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1136 die "checksum missmatch (file change by other user?)\n"
1137 if $digest && $digest ne $conf->{digest
};
1139 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1141 foreach my $opt (keys %$revert) {
1142 if (defined($conf->{$opt})) {
1143 $param->{$opt} = $conf->{$opt};
1144 } elsif (defined($conf->{pending
}->{$opt})) {
1149 if ($param->{memory
} || defined($param->{balloon
})) {
1150 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1151 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1153 die "balloon value too large (must be smaller than assigned memory)\n"
1154 if $balloon && $balloon > $maxmem;
1157 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1161 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1163 # write updates to pending section
1165 my $modified = {}; # record what $option we modify
1167 foreach my $opt (@delete) {
1168 $modified->{$opt} = 1;
1169 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1170 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1171 warn "cannot delete '$opt' - not set in current configuration!\n";
1172 $modified->{$opt} = 0;
1176 if ($opt =~ m/^unused/) {
1177 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1178 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1179 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1180 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1181 delete $conf->{$opt};
1182 PVE
::QemuConfig-
>write_config($vmid, $conf);
1184 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1185 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1186 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1187 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1188 if defined($conf->{pending
}->{$opt});
1189 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1190 PVE
::QemuConfig-
>write_config($vmid, $conf);
1192 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1193 PVE
::QemuConfig-
>write_config($vmid, $conf);
1197 foreach my $opt (keys %$param) { # add/change
1198 $modified->{$opt} = 1;
1199 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1200 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1202 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
1204 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1205 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1206 # FIXME: cloudinit: CDROM or Disk?
1207 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1208 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1210 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1212 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1213 if defined($conf->{pending
}->{$opt});
1215 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1217 $conf->{pending
}->{$opt} = $param->{$opt};
1219 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1220 PVE
::QemuConfig-
>write_config($vmid, $conf);
1223 # remove pending changes when nothing changed
1224 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1225 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1226 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1228 return if !scalar(keys %{$conf->{pending
}});
1230 my $running = PVE
::QemuServer
::check_running
($vmid);
1232 # apply pending changes
1234 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1238 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1239 raise_param_exc
($errors) if scalar(keys %$errors);
1241 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1251 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1253 if ($background_delay) {
1255 # Note: It would be better to do that in the Event based HTTPServer
1256 # to avoid blocking call to sleep.
1258 my $end_time = time() + $background_delay;
1260 my $task = PVE
::Tools
::upid_decode
($upid);
1263 while (time() < $end_time) {
1264 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1266 sleep(1); # this gets interrupted when child process ends
1270 my $status = PVE
::Tools
::upid_read_status
($upid);
1271 return undef if $status eq 'OK';
1280 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1283 my $vm_config_perm_list = [
1288 'VM.Config.Network',
1290 'VM.Config.Options',
1293 __PACKAGE__-
>register_method({
1294 name
=> 'update_vm_async',
1295 path
=> '{vmid}/config',
1299 description
=> "Set virtual machine options (asynchrounous API).",
1301 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1304 additionalProperties
=> 0,
1305 properties
=> PVE
::QemuServer
::json_config_properties
(
1307 node
=> get_standard_option
('pve-node'),
1308 vmid
=> get_standard_option
('pve-vmid'),
1309 skiplock
=> get_standard_option
('skiplock'),
1311 type
=> 'string', format
=> 'pve-configid-list',
1312 description
=> "A list of settings you want to delete.",
1316 type
=> 'string', format
=> 'pve-configid-list',
1317 description
=> "Revert a pending change.",
1322 description
=> $opt_force_description,
1324 requires
=> 'delete',
1328 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1332 background_delay
=> {
1334 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1345 code
=> $update_vm_api,
1348 __PACKAGE__-
>register_method({
1349 name
=> 'update_vm',
1350 path
=> '{vmid}/config',
1354 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1356 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1359 additionalProperties
=> 0,
1360 properties
=> PVE
::QemuServer
::json_config_properties
(
1362 node
=> get_standard_option
('pve-node'),
1363 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1364 skiplock
=> get_standard_option
('skiplock'),
1366 type
=> 'string', format
=> 'pve-configid-list',
1367 description
=> "A list of settings you want to delete.",
1371 type
=> 'string', format
=> 'pve-configid-list',
1372 description
=> "Revert a pending change.",
1377 description
=> $opt_force_description,
1379 requires
=> 'delete',
1383 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1389 returns
=> { type
=> 'null' },
1392 &$update_vm_api($param, 1);
1398 __PACKAGE__-
>register_method({
1399 name
=> 'destroy_vm',
1404 description
=> "Destroy the vm (also delete all used/owned volumes).",
1406 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1409 additionalProperties
=> 0,
1411 node
=> get_standard_option
('pve-node'),
1412 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1413 skiplock
=> get_standard_option
('skiplock'),
1422 my $rpcenv = PVE
::RPCEnvironment
::get
();
1424 my $authuser = $rpcenv->get_user();
1426 my $vmid = $param->{vmid
};
1428 my $skiplock = $param->{skiplock
};
1429 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1430 if $skiplock && $authuser ne 'root@pam';
1433 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1435 my $storecfg = PVE
::Storage
::config
();
1437 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1439 die "unable to remove VM $vmid - used in HA resources\n"
1440 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1442 # do not allow destroy if there are replication jobs
1443 my $repl_conf = PVE
::ReplicationConfig-
>new();
1444 $repl_conf->check_for_existing_jobs($vmid);
1446 # early tests (repeat after locking)
1447 die "VM $vmid is running - destroy failed\n"
1448 if PVE
::QemuServer
::check_running
($vmid);
1453 syslog
('info', "destroy VM $vmid: $upid\n");
1455 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1457 PVE
::AccessControl
::remove_vm_access
($vmid);
1459 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1462 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1465 __PACKAGE__-
>register_method({
1467 path
=> '{vmid}/unlink',
1471 description
=> "Unlink/delete disk images.",
1473 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1476 additionalProperties
=> 0,
1478 node
=> get_standard_option
('pve-node'),
1479 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1481 type
=> 'string', format
=> 'pve-configid-list',
1482 description
=> "A list of disk IDs you want to delete.",
1486 description
=> $opt_force_description,
1491 returns
=> { type
=> 'null'},
1495 $param->{delete} = extract_param
($param, 'idlist');
1497 __PACKAGE__-
>update_vm($param);
1504 __PACKAGE__-
>register_method({
1506 path
=> '{vmid}/vncproxy',
1510 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1512 description
=> "Creates a TCP VNC proxy connections.",
1514 additionalProperties
=> 0,
1516 node
=> get_standard_option
('pve-node'),
1517 vmid
=> get_standard_option
('pve-vmid'),
1521 description
=> "starts websockify instead of vncproxy",
1526 additionalProperties
=> 0,
1528 user
=> { type
=> 'string' },
1529 ticket
=> { type
=> 'string' },
1530 cert
=> { type
=> 'string' },
1531 port
=> { type
=> 'integer' },
1532 upid
=> { type
=> 'string' },
1538 my $rpcenv = PVE
::RPCEnvironment
::get
();
1540 my $authuser = $rpcenv->get_user();
1542 my $vmid = $param->{vmid
};
1543 my $node = $param->{node
};
1544 my $websocket = $param->{websocket
};
1546 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1547 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1549 my $authpath = "/vms/$vmid";
1551 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1553 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1559 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1560 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1561 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1562 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1563 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1565 $family = PVE
::Tools
::get_host_address_family
($node);
1568 my $port = PVE
::Tools
::next_vnc_port
($family);
1575 syslog
('info', "starting vnc proxy $upid\n");
1581 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1583 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1584 '-timeout', $timeout, '-authpath', $authpath,
1585 '-perm', 'Sys.Console'];
1587 if ($param->{websocket
}) {
1588 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1589 push @$cmd, '-notls', '-listen', 'localhost';
1592 push @$cmd, '-c', @$remcmd, @$termcmd;
1594 PVE
::Tools
::run_command
($cmd);
1598 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1600 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1602 my $sock = IO
::Socket
::IP-
>new(
1607 GetAddrInfoFlags
=> 0,
1608 ) or die "failed to create socket: $!\n";
1609 # Inside the worker we shouldn't have any previous alarms
1610 # running anyway...:
1612 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1614 accept(my $cli, $sock) or die "connection failed: $!\n";
1617 if (PVE
::Tools
::run_command
($cmd,
1618 output
=> '>&'.fileno($cli),
1619 input
=> '<&'.fileno($cli),
1622 die "Failed to run vncproxy.\n";
1629 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1631 PVE
::Tools
::wait_for_vnc_port
($port);
1642 __PACKAGE__-
>register_method({
1643 name
=> 'termproxy',
1644 path
=> '{vmid}/termproxy',
1648 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1650 description
=> "Creates a TCP proxy connections.",
1652 additionalProperties
=> 0,
1654 node
=> get_standard_option
('pve-node'),
1655 vmid
=> get_standard_option
('pve-vmid'),
1659 enum
=> [qw(serial0 serial1 serial2 serial3)],
1660 description
=> "opens a serial terminal (defaults to display)",
1665 additionalProperties
=> 0,
1667 user
=> { type
=> 'string' },
1668 ticket
=> { type
=> 'string' },
1669 port
=> { type
=> 'integer' },
1670 upid
=> { type
=> 'string' },
1676 my $rpcenv = PVE
::RPCEnvironment
::get
();
1678 my $authuser = $rpcenv->get_user();
1680 my $vmid = $param->{vmid
};
1681 my $node = $param->{node
};
1682 my $serial = $param->{serial
};
1684 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1686 if (!defined($serial)) {
1687 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1688 $serial = $conf->{vga
};
1692 my $authpath = "/vms/$vmid";
1694 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1699 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1700 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1701 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1702 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, '-t');
1703 push @$remcmd, '--';
1705 $family = PVE
::Tools
::get_host_address_family
($node);
1708 my $port = PVE
::Tools
::next_vnc_port
($family);
1710 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1711 push @$termcmd, '-iface', $serial if $serial;
1716 syslog
('info', "starting qemu termproxy $upid\n");
1718 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1719 '--perm', 'VM.Console', '--'];
1720 push @$cmd, @$remcmd, @$termcmd;
1722 PVE
::Tools
::run_command
($cmd);
1725 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1727 PVE
::Tools
::wait_for_vnc_port
($port);
1737 __PACKAGE__-
>register_method({
1738 name
=> 'vncwebsocket',
1739 path
=> '{vmid}/vncwebsocket',
1742 description
=> "You also need to pass a valid ticket (vncticket).",
1743 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1745 description
=> "Opens a weksocket for VNC traffic.",
1747 additionalProperties
=> 0,
1749 node
=> get_standard_option
('pve-node'),
1750 vmid
=> get_standard_option
('pve-vmid'),
1752 description
=> "Ticket from previous call to vncproxy.",
1757 description
=> "Port number returned by previous vncproxy call.",
1767 port
=> { type
=> 'string' },
1773 my $rpcenv = PVE
::RPCEnvironment
::get
();
1775 my $authuser = $rpcenv->get_user();
1777 my $vmid = $param->{vmid
};
1778 my $node = $param->{node
};
1780 my $authpath = "/vms/$vmid";
1782 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1784 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1786 # Note: VNC ports are acessible from outside, so we do not gain any
1787 # security if we verify that $param->{port} belongs to VM $vmid. This
1788 # check is done by verifying the VNC ticket (inside VNC protocol).
1790 my $port = $param->{port
};
1792 return { port
=> $port };
1795 __PACKAGE__-
>register_method({
1796 name
=> 'spiceproxy',
1797 path
=> '{vmid}/spiceproxy',
1802 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1804 description
=> "Returns a SPICE configuration to connect to the VM.",
1806 additionalProperties
=> 0,
1808 node
=> get_standard_option
('pve-node'),
1809 vmid
=> get_standard_option
('pve-vmid'),
1810 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1813 returns
=> get_standard_option
('remote-viewer-config'),
1817 my $rpcenv = PVE
::RPCEnvironment
::get
();
1819 my $authuser = $rpcenv->get_user();
1821 my $vmid = $param->{vmid
};
1822 my $node = $param->{node
};
1823 my $proxy = $param->{proxy
};
1825 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1826 my $title = "VM $vmid";
1827 $title .= " - ". $conf->{name
} if $conf->{name
};
1829 my $port = PVE
::QemuServer
::spice_port
($vmid);
1831 my ($ticket, undef, $remote_viewer_config) =
1832 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1834 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1835 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1837 return $remote_viewer_config;
1840 __PACKAGE__-
>register_method({
1842 path
=> '{vmid}/status',
1845 description
=> "Directory index",
1850 additionalProperties
=> 0,
1852 node
=> get_standard_option
('pve-node'),
1853 vmid
=> get_standard_option
('pve-vmid'),
1861 subdir
=> { type
=> 'string' },
1864 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1870 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1873 { subdir
=> 'current' },
1874 { subdir
=> 'start' },
1875 { subdir
=> 'stop' },
1881 __PACKAGE__-
>register_method({
1882 name
=> 'vm_status',
1883 path
=> '{vmid}/status/current',
1886 protected
=> 1, # qemu pid files are only readable by root
1887 description
=> "Get virtual machine status.",
1889 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1892 additionalProperties
=> 0,
1894 node
=> get_standard_option
('pve-node'),
1895 vmid
=> get_standard_option
('pve-vmid'),
1901 %$PVE::QemuServer
::vmstatus_return_properties
,
1903 description
=> "HA manager service status.",
1907 description
=> "Qemu VGA configuration supports spice.",
1912 description
=> "Qemu GuestAgent enabled in config.",
1922 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1924 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1925 my $status = $vmstatus->{$param->{vmid
}};
1927 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1929 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1930 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1935 __PACKAGE__-
>register_method({
1937 path
=> '{vmid}/status/start',
1941 description
=> "Start virtual machine.",
1943 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1946 additionalProperties
=> 0,
1948 node
=> get_standard_option
('pve-node'),
1949 vmid
=> get_standard_option
('pve-vmid',
1950 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1951 skiplock
=> get_standard_option
('skiplock'),
1952 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1953 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1956 enum
=> ['secure', 'insecure'],
1957 description
=> "Migration traffic is encrypted using an SSH " .
1958 "tunnel by default. On secure, completely private networks " .
1959 "this can be disabled to increase performance.",
1962 migration_network
=> {
1963 type
=> 'string', format
=> 'CIDR',
1964 description
=> "CIDR of the (sub) network that is used for migration.",
1967 machine
=> get_standard_option
('pve-qm-machine'),
1969 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1981 my $rpcenv = PVE
::RPCEnvironment
::get
();
1983 my $authuser = $rpcenv->get_user();
1985 my $node = extract_param
($param, 'node');
1987 my $vmid = extract_param
($param, 'vmid');
1989 my $machine = extract_param
($param, 'machine');
1991 my $stateuri = extract_param
($param, 'stateuri');
1992 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1993 if $stateuri && $authuser ne 'root@pam';
1995 my $skiplock = extract_param
($param, 'skiplock');
1996 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1997 if $skiplock && $authuser ne 'root@pam';
1999 my $migratedfrom = extract_param
($param, 'migratedfrom');
2000 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2001 if $migratedfrom && $authuser ne 'root@pam';
2003 my $migration_type = extract_param
($param, 'migration_type');
2004 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2005 if $migration_type && $authuser ne 'root@pam';
2007 my $migration_network = extract_param
($param, 'migration_network');
2008 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2009 if $migration_network && $authuser ne 'root@pam';
2011 my $targetstorage = extract_param
($param, 'targetstorage');
2012 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
2013 if $targetstorage && $authuser ne 'root@pam';
2015 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2016 if $targetstorage && !$migratedfrom;
2018 # read spice ticket from STDIN
2020 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2021 if (defined(my $line = <STDIN
>)) {
2023 $spice_ticket = $line;
2027 PVE
::Cluster
::check_cfs_quorum
();
2029 my $storecfg = PVE
::Storage
::config
();
2031 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
2032 $rpcenv->{type
} ne 'ha') {
2037 my $service = "vm:$vmid";
2039 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
2041 print "Requesting HA start for VM $vmid\n";
2043 PVE
::Tools
::run_command
($cmd);
2048 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2055 syslog
('info', "start VM $vmid: $upid\n");
2057 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2058 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
2063 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2067 __PACKAGE__-
>register_method({
2069 path
=> '{vmid}/status/stop',
2073 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2074 "is akin to pulling the power plug of a running computer and may damage the VM data",
2076 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2079 additionalProperties
=> 0,
2081 node
=> get_standard_option
('pve-node'),
2082 vmid
=> get_standard_option
('pve-vmid',
2083 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2084 skiplock
=> get_standard_option
('skiplock'),
2085 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2087 description
=> "Wait maximal timeout seconds.",
2093 description
=> "Do not deactivate storage volumes.",
2106 my $rpcenv = PVE
::RPCEnvironment
::get
();
2108 my $authuser = $rpcenv->get_user();
2110 my $node = extract_param
($param, 'node');
2112 my $vmid = extract_param
($param, 'vmid');
2114 my $skiplock = extract_param
($param, 'skiplock');
2115 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2116 if $skiplock && $authuser ne 'root@pam';
2118 my $keepActive = extract_param
($param, 'keepActive');
2119 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2120 if $keepActive && $authuser ne 'root@pam';
2122 my $migratedfrom = extract_param
($param, 'migratedfrom');
2123 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2124 if $migratedfrom && $authuser ne 'root@pam';
2127 my $storecfg = PVE
::Storage
::config
();
2129 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2134 my $service = "vm:$vmid";
2136 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2138 print "Requesting HA stop for VM $vmid\n";
2140 PVE
::Tools
::run_command
($cmd);
2145 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2151 syslog
('info', "stop VM $vmid: $upid\n");
2153 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2154 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2159 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2163 __PACKAGE__-
>register_method({
2165 path
=> '{vmid}/status/reset',
2169 description
=> "Reset virtual machine.",
2171 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2174 additionalProperties
=> 0,
2176 node
=> get_standard_option
('pve-node'),
2177 vmid
=> get_standard_option
('pve-vmid',
2178 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2179 skiplock
=> get_standard_option
('skiplock'),
2188 my $rpcenv = PVE
::RPCEnvironment
::get
();
2190 my $authuser = $rpcenv->get_user();
2192 my $node = extract_param
($param, 'node');
2194 my $vmid = extract_param
($param, 'vmid');
2196 my $skiplock = extract_param
($param, 'skiplock');
2197 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2198 if $skiplock && $authuser ne 'root@pam';
2200 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2205 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2210 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2213 __PACKAGE__-
>register_method({
2214 name
=> 'vm_shutdown',
2215 path
=> '{vmid}/status/shutdown',
2219 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2220 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2222 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2225 additionalProperties
=> 0,
2227 node
=> get_standard_option
('pve-node'),
2228 vmid
=> get_standard_option
('pve-vmid',
2229 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2230 skiplock
=> get_standard_option
('skiplock'),
2232 description
=> "Wait maximal timeout seconds.",
2238 description
=> "Make sure the VM stops.",
2244 description
=> "Do not deactivate storage volumes.",
2257 my $rpcenv = PVE
::RPCEnvironment
::get
();
2259 my $authuser = $rpcenv->get_user();
2261 my $node = extract_param
($param, 'node');
2263 my $vmid = extract_param
($param, 'vmid');
2265 my $skiplock = extract_param
($param, 'skiplock');
2266 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2267 if $skiplock && $authuser ne 'root@pam';
2269 my $keepActive = extract_param
($param, 'keepActive');
2270 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2271 if $keepActive && $authuser ne 'root@pam';
2273 my $storecfg = PVE
::Storage
::config
();
2277 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2278 # otherwise, we will infer a shutdown command, but run into the timeout,
2279 # then when the vm is resumed, it will instantly shutdown
2281 # checking the qmp status here to get feedback to the gui/cli/api
2282 # and the status query should not take too long
2285 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2289 if (!$err && $qmpstatus->{status
} eq "paused") {
2290 if ($param->{forceStop
}) {
2291 warn "VM is paused - stop instead of shutdown\n";
2294 die "VM is paused - cannot shutdown\n";
2298 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2299 ($rpcenv->{type
} ne 'ha')) {
2304 my $service = "vm:$vmid";
2306 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2308 print "Requesting HA stop for VM $vmid\n";
2310 PVE
::Tools
::run_command
($cmd);
2315 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2322 syslog
('info', "shutdown VM $vmid: $upid\n");
2324 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2325 $shutdown, $param->{forceStop
}, $keepActive);
2330 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2334 __PACKAGE__-
>register_method({
2335 name
=> 'vm_suspend',
2336 path
=> '{vmid}/status/suspend',
2340 description
=> "Suspend virtual machine.",
2342 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2345 additionalProperties
=> 0,
2347 node
=> get_standard_option
('pve-node'),
2348 vmid
=> get_standard_option
('pve-vmid',
2349 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2350 skiplock
=> get_standard_option
('skiplock'),
2359 my $rpcenv = PVE
::RPCEnvironment
::get
();
2361 my $authuser = $rpcenv->get_user();
2363 my $node = extract_param
($param, 'node');
2365 my $vmid = extract_param
($param, 'vmid');
2367 my $skiplock = extract_param
($param, 'skiplock');
2368 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2369 if $skiplock && $authuser ne 'root@pam';
2371 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2376 syslog
('info', "suspend VM $vmid: $upid\n");
2378 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2383 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2386 __PACKAGE__-
>register_method({
2387 name
=> 'vm_resume',
2388 path
=> '{vmid}/status/resume',
2392 description
=> "Resume virtual machine.",
2394 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2397 additionalProperties
=> 0,
2399 node
=> get_standard_option
('pve-node'),
2400 vmid
=> get_standard_option
('pve-vmid',
2401 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2402 skiplock
=> get_standard_option
('skiplock'),
2403 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2413 my $rpcenv = PVE
::RPCEnvironment
::get
();
2415 my $authuser = $rpcenv->get_user();
2417 my $node = extract_param
($param, 'node');
2419 my $vmid = extract_param
($param, 'vmid');
2421 my $skiplock = extract_param
($param, 'skiplock');
2422 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2423 if $skiplock && $authuser ne 'root@pam';
2425 my $nocheck = extract_param
($param, 'nocheck');
2427 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2432 syslog
('info', "resume VM $vmid: $upid\n");
2434 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2439 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2442 __PACKAGE__-
>register_method({
2443 name
=> 'vm_sendkey',
2444 path
=> '{vmid}/sendkey',
2448 description
=> "Send key event to virtual machine.",
2450 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2453 additionalProperties
=> 0,
2455 node
=> get_standard_option
('pve-node'),
2456 vmid
=> get_standard_option
('pve-vmid',
2457 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2458 skiplock
=> get_standard_option
('skiplock'),
2460 description
=> "The key (qemu monitor encoding).",
2465 returns
=> { type
=> 'null'},
2469 my $rpcenv = PVE
::RPCEnvironment
::get
();
2471 my $authuser = $rpcenv->get_user();
2473 my $node = extract_param
($param, 'node');
2475 my $vmid = extract_param
($param, 'vmid');
2477 my $skiplock = extract_param
($param, 'skiplock');
2478 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2479 if $skiplock && $authuser ne 'root@pam';
2481 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2486 __PACKAGE__-
>register_method({
2487 name
=> 'vm_feature',
2488 path
=> '{vmid}/feature',
2492 description
=> "Check if feature for virtual machine is available.",
2494 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2497 additionalProperties
=> 0,
2499 node
=> get_standard_option
('pve-node'),
2500 vmid
=> get_standard_option
('pve-vmid'),
2502 description
=> "Feature to check.",
2504 enum
=> [ 'snapshot', 'clone', 'copy' ],
2506 snapname
=> get_standard_option
('pve-snapshot-name', {
2514 hasFeature
=> { type
=> 'boolean' },
2517 items
=> { type
=> 'string' },
2524 my $node = extract_param
($param, 'node');
2526 my $vmid = extract_param
($param, 'vmid');
2528 my $snapname = extract_param
($param, 'snapname');
2530 my $feature = extract_param
($param, 'feature');
2532 my $running = PVE
::QemuServer
::check_running
($vmid);
2534 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2537 my $snap = $conf->{snapshots
}->{$snapname};
2538 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2541 my $storecfg = PVE
::Storage
::config
();
2543 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2544 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2547 hasFeature
=> $hasFeature,
2548 nodes
=> [ keys %$nodelist ],
2552 __PACKAGE__-
>register_method({
2554 path
=> '{vmid}/clone',
2558 description
=> "Create a copy of virtual machine/template.",
2560 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2561 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2562 "'Datastore.AllocateSpace' on any used storage.",
2565 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2567 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2568 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2573 additionalProperties
=> 0,
2575 node
=> get_standard_option
('pve-node'),
2576 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2577 newid
=> get_standard_option
('pve-vmid', {
2578 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2579 description
=> 'VMID for the clone.' }),
2582 type
=> 'string', format
=> 'dns-name',
2583 description
=> "Set a name for the new VM.",
2588 description
=> "Description for the new VM.",
2592 type
=> 'string', format
=> 'pve-poolid',
2593 description
=> "Add the new VM to the specified pool.",
2595 snapname
=> get_standard_option
('pve-snapshot-name', {
2598 storage
=> get_standard_option
('pve-storage-id', {
2599 description
=> "Target storage for full clone.",
2603 description
=> "Target format for file storage. Only valid for full clone.",
2606 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2611 description
=> "Create a full copy of all disks. This is always done when " .
2612 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2614 target
=> get_standard_option
('pve-node', {
2615 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2626 my $rpcenv = PVE
::RPCEnvironment
::get
();
2628 my $authuser = $rpcenv->get_user();
2630 my $node = extract_param
($param, 'node');
2632 my $vmid = extract_param
($param, 'vmid');
2634 my $newid = extract_param
($param, 'newid');
2636 my $pool = extract_param
($param, 'pool');
2638 if (defined($pool)) {
2639 $rpcenv->check_pool_exist($pool);
2642 my $snapname = extract_param
($param, 'snapname');
2644 my $storage = extract_param
($param, 'storage');
2646 my $format = extract_param
($param, 'format');
2648 my $target = extract_param
($param, 'target');
2650 my $localnode = PVE
::INotify
::nodename
();
2652 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2654 PVE
::Cluster
::check_node_exists
($target) if $target;
2656 my $storecfg = PVE
::Storage
::config
();
2659 # check if storage is enabled on local node
2660 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2662 # check if storage is available on target node
2663 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2664 # clone only works if target storage is shared
2665 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2666 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2670 PVE
::Cluster
::check_cfs_quorum
();
2672 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2674 # exclusive lock if VM is running - else shared lock is enough;
2675 my $shared_lock = $running ?
0 : 1;
2679 # do all tests after lock
2680 # we also try to do all tests before we fork the worker
2682 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2684 PVE
::QemuConfig-
>check_lock($conf);
2686 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2688 die "unexpected state change\n" if $verify_running != $running;
2690 die "snapshot '$snapname' does not exist\n"
2691 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2693 my $full = extract_param
($param, 'full');
2694 if (!defined($full)) {
2695 $full = !PVE
::QemuConfig-
>is_template($conf);
2698 die "parameter 'storage' not allowed for linked clones\n"
2699 if defined($storage) && !$full;
2701 die "parameter 'format' not allowed for linked clones\n"
2702 if defined($format) && !$full;
2704 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2706 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2708 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2710 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2712 die "unable to create VM $newid: config file already exists\n"
2715 my $newconf = { lock => 'clone' };
2720 foreach my $opt (keys %$oldconf) {
2721 my $value = $oldconf->{$opt};
2723 # do not copy snapshot related info
2724 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2725 $opt eq 'vmstate' || $opt eq 'snapstate';
2727 # no need to copy unused images, because VMID(owner) changes anyways
2728 next if $opt =~ m/^unused\d+$/;
2730 # always change MAC! address
2731 if ($opt =~ m/^net(\d+)$/) {
2732 my $net = PVE
::QemuServer
::parse_net
($value);
2733 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2734 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2735 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2736 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2737 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2738 die "unable to parse drive options for '$opt'\n" if !$drive;
2739 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2740 $newconf->{$opt} = $value; # simply copy configuration
2742 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2743 die "Full clone feature is not supported for drive '$opt'\n"
2744 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2745 $fullclone->{$opt} = 1;
2747 # not full means clone instead of copy
2748 die "Linked clone feature is not supported for drive '$opt'\n"
2749 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2751 $drives->{$opt} = $drive;
2752 push @$vollist, $drive->{file
};
2755 # copy everything else
2756 $newconf->{$opt} = $value;
2760 # auto generate a new uuid
2761 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2762 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2763 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2765 # auto generate a new vmgenid if the option was set
2766 if ($newconf->{vmgenid
}) {
2767 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2770 delete $newconf->{template
};
2772 if ($param->{name
}) {
2773 $newconf->{name
} = $param->{name
};
2775 if ($oldconf->{name
}) {
2776 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2778 $newconf->{name
} = "Copy-of-VM-$vmid";
2782 if ($param->{description
}) {
2783 $newconf->{description
} = $param->{description
};
2786 # create empty/temp config - this fails if VM already exists on other node
2787 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2792 my $newvollist = [];
2799 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2801 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2803 my $total_jobs = scalar(keys %{$drives});
2806 foreach my $opt (keys %$drives) {
2807 my $drive = $drives->{$opt};
2808 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2810 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2811 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2812 $jobs, $skipcomplete, $oldconf->{agent
});
2814 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2816 PVE
::QemuConfig-
>write_config($newid, $newconf);
2820 delete $newconf->{lock};
2822 # do not write pending changes
2823 if (my @changes = keys %{$newconf->{pending
}}) {
2824 my $pending = join(',', @changes);
2825 warn "found pending changes for '$pending', discarding for clone\n";
2826 delete $newconf->{pending
};
2829 PVE
::QemuConfig-
>write_config($newid, $newconf);
2832 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2833 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2834 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2836 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2837 die "Failed to move config to node '$target' - rename failed: $!\n"
2838 if !rename($conffile, $newconffile);
2841 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2846 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2848 sleep 1; # some storage like rbd need to wait before release volume - really?
2850 foreach my $volid (@$newvollist) {
2851 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2854 die "clone failed: $err";
2860 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2862 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2865 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2866 # Aquire exclusive lock lock for $newid
2867 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2872 __PACKAGE__-
>register_method({
2873 name
=> 'move_vm_disk',
2874 path
=> '{vmid}/move_disk',
2878 description
=> "Move volume to different storage.",
2880 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2882 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2883 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2887 additionalProperties
=> 0,
2889 node
=> get_standard_option
('pve-node'),
2890 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2893 description
=> "The disk you want to move.",
2894 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2896 storage
=> get_standard_option
('pve-storage-id', {
2897 description
=> "Target storage.",
2898 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2902 description
=> "Target Format.",
2903 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2908 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2914 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2922 description
=> "the task ID.",
2927 my $rpcenv = PVE
::RPCEnvironment
::get
();
2929 my $authuser = $rpcenv->get_user();
2931 my $node = extract_param
($param, 'node');
2933 my $vmid = extract_param
($param, 'vmid');
2935 my $digest = extract_param
($param, 'digest');
2937 my $disk = extract_param
($param, 'disk');
2939 my $storeid = extract_param
($param, 'storage');
2941 my $format = extract_param
($param, 'format');
2943 my $storecfg = PVE
::Storage
::config
();
2945 my $updatefn = sub {
2947 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2949 PVE
::QemuConfig-
>check_lock($conf);
2951 die "checksum missmatch (file change by other user?)\n"
2952 if $digest && $digest ne $conf->{digest
};
2954 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2956 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2958 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2960 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
2963 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2964 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2968 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2969 (!$format || !$oldfmt || $oldfmt eq $format);
2971 # this only checks snapshots because $disk is passed!
2972 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2973 die "you can't move a disk with snapshots and delete the source\n"
2974 if $snapshotted && $param->{delete};
2976 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2978 my $running = PVE
::QemuServer
::check_running
($vmid);
2980 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2984 my $newvollist = [];
2990 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2992 warn "moving disk with snapshots, snapshots will not be moved!\n"
2995 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2996 $vmid, $storeid, $format, 1, $newvollist);
2998 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
3000 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3002 # convert moved disk to base if part of template
3003 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3004 if PVE
::QemuConfig-
>is_template($conf);
3006 PVE
::QemuConfig-
>write_config($vmid, $conf);
3008 if ($running && PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
} && PVE
::QemuServer
::qga_check_running
($vmid)) {
3009 eval { PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-fstrim"); };
3013 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3014 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3021 foreach my $volid (@$newvollist) {
3022 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3025 die "storage migration failed: $err";
3028 if ($param->{delete}) {
3030 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3031 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3037 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3040 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3043 __PACKAGE__-
>register_method({
3044 name
=> 'migrate_vm',
3045 path
=> '{vmid}/migrate',
3049 description
=> "Migrate virtual machine. Creates a new migration task.",
3051 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3054 additionalProperties
=> 0,
3056 node
=> get_standard_option
('pve-node'),
3057 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3058 target
=> get_standard_option
('pve-node', {
3059 description
=> "Target node.",
3060 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3064 description
=> "Use online/live migration.",
3069 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3074 enum
=> ['secure', 'insecure'],
3075 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3078 migration_network
=> {
3079 type
=> 'string', format
=> 'CIDR',
3080 description
=> "CIDR of the (sub) network that is used for migration.",
3083 "with-local-disks" => {
3085 description
=> "Enable live storage migration for local disk",
3088 targetstorage
=> get_standard_option
('pve-storage-id', {
3089 description
=> "Default target storage.",
3091 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3097 description
=> "the task ID.",
3102 my $rpcenv = PVE
::RPCEnvironment
::get
();
3104 my $authuser = $rpcenv->get_user();
3106 my $target = extract_param
($param, 'target');
3108 my $localnode = PVE
::INotify
::nodename
();
3109 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3111 PVE
::Cluster
::check_cfs_quorum
();
3113 PVE
::Cluster
::check_node_exists
($target);
3115 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3117 my $vmid = extract_param
($param, 'vmid');
3119 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3120 if !$param->{online
} && $param->{targetstorage
};
3122 raise_param_exc
({ force
=> "Only root may use this option." })
3123 if $param->{force
} && $authuser ne 'root@pam';
3125 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3126 if $param->{migration_type
} && $authuser ne 'root@pam';
3128 # allow root only until better network permissions are available
3129 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3130 if $param->{migration_network
} && $authuser ne 'root@pam';
3133 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3135 # try to detect errors early
3137 PVE
::QemuConfig-
>check_lock($conf);
3139 if (PVE
::QemuServer
::check_running
($vmid)) {
3140 die "cant migrate running VM without --online\n"
3141 if !$param->{online
};
3144 my $storecfg = PVE
::Storage
::config
();
3146 if( $param->{targetstorage
}) {
3147 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3149 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3152 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3157 my $service = "vm:$vmid";
3159 my $cmd = ['ha-manager', 'migrate', $service, $target];
3161 print "Requesting HA migration for VM $vmid to node $target\n";
3163 PVE
::Tools
::run_command
($cmd);
3168 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3173 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3177 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3180 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3185 __PACKAGE__-
>register_method({
3187 path
=> '{vmid}/monitor',
3191 description
=> "Execute Qemu monitor commands.",
3193 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3194 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3197 additionalProperties
=> 0,
3199 node
=> get_standard_option
('pve-node'),
3200 vmid
=> get_standard_option
('pve-vmid'),
3203 description
=> "The monitor command.",
3207 returns
=> { type
=> 'string'},
3211 my $rpcenv = PVE
::RPCEnvironment
::get
();
3212 my $authuser = $rpcenv->get_user();
3215 my $command = shift;
3216 return $command =~ m/^\s*info(\s+|$)/
3217 || $command =~ m/^\s*help\s*$/;
3220 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3221 if !&$is_ro($param->{command
});
3223 my $vmid = $param->{vmid
};
3225 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3229 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3231 $res = "ERROR: $@" if $@;
3236 __PACKAGE__-
>register_method({
3237 name
=> 'resize_vm',
3238 path
=> '{vmid}/resize',
3242 description
=> "Extend volume size.",
3244 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3247 additionalProperties
=> 0,
3249 node
=> get_standard_option
('pve-node'),
3250 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3251 skiplock
=> get_standard_option
('skiplock'),
3254 description
=> "The disk you want to resize.",
3255 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3259 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3260 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.",
3264 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3270 returns
=> { type
=> 'null'},
3274 my $rpcenv = PVE
::RPCEnvironment
::get
();
3276 my $authuser = $rpcenv->get_user();
3278 my $node = extract_param
($param, 'node');
3280 my $vmid = extract_param
($param, 'vmid');
3282 my $digest = extract_param
($param, 'digest');
3284 my $disk = extract_param
($param, 'disk');
3286 my $sizestr = extract_param
($param, 'size');
3288 my $skiplock = extract_param
($param, 'skiplock');
3289 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3290 if $skiplock && $authuser ne 'root@pam';
3292 my $storecfg = PVE
::Storage
::config
();
3294 my $updatefn = sub {
3296 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3298 die "checksum missmatch (file change by other user?)\n"
3299 if $digest && $digest ne $conf->{digest
};
3300 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3302 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3304 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3306 my (undef, undef, undef, undef, undef, undef, $format) =
3307 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3309 die "can't resize volume: $disk if snapshot exists\n"
3310 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3312 my $volid = $drive->{file
};
3314 die "disk '$disk' has no associated volume\n" if !$volid;
3316 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3318 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3320 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3322 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3323 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3325 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3326 my ($ext, $newsize, $unit) = ($1, $2, $4);
3329 $newsize = $newsize * 1024;
3330 } elsif ($unit eq 'M') {
3331 $newsize = $newsize * 1024 * 1024;
3332 } elsif ($unit eq 'G') {
3333 $newsize = $newsize * 1024 * 1024 * 1024;
3334 } elsif ($unit eq 'T') {
3335 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3338 $newsize += $size if $ext;
3339 $newsize = int($newsize);
3341 die "shrinking disks is not supported\n" if $newsize < $size;
3343 return if $size == $newsize;
3345 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3347 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3349 $drive->{size
} = $newsize;
3350 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3352 PVE
::QemuConfig-
>write_config($vmid, $conf);
3355 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3359 __PACKAGE__-
>register_method({
3360 name
=> 'snapshot_list',
3361 path
=> '{vmid}/snapshot',
3363 description
=> "List all snapshots.",
3365 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3368 protected
=> 1, # qemu pid files are only readable by root
3370 additionalProperties
=> 0,
3372 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3373 node
=> get_standard_option
('pve-node'),
3382 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3386 description
=> "Snapshot includes RAM.",
3391 description
=> "Snapshot description.",
3395 description
=> "Snapshot creation time",
3397 renderer
=> 'timestamp',
3401 description
=> "Parent snapshot identifier.",
3407 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3412 my $vmid = $param->{vmid
};
3414 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3415 my $snaphash = $conf->{snapshots
} || {};
3419 foreach my $name (keys %$snaphash) {
3420 my $d = $snaphash->{$name};
3423 snaptime
=> $d->{snaptime
} || 0,
3424 vmstate
=> $d->{vmstate
} ?
1 : 0,
3425 description
=> $d->{description
} || '',
3427 $item->{parent
} = $d->{parent
} if $d->{parent
};
3428 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3432 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3435 digest
=> $conf->{digest
},
3436 running
=> $running,
3437 description
=> "You are here!",
3439 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3441 push @$res, $current;
3446 __PACKAGE__-
>register_method({
3448 path
=> '{vmid}/snapshot',
3452 description
=> "Snapshot a VM.",
3454 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3457 additionalProperties
=> 0,
3459 node
=> get_standard_option
('pve-node'),
3460 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3461 snapname
=> get_standard_option
('pve-snapshot-name'),
3465 description
=> "Save the vmstate",
3470 description
=> "A textual description or comment.",
3476 description
=> "the task ID.",
3481 my $rpcenv = PVE
::RPCEnvironment
::get
();
3483 my $authuser = $rpcenv->get_user();
3485 my $node = extract_param
($param, 'node');
3487 my $vmid = extract_param
($param, 'vmid');
3489 my $snapname = extract_param
($param, 'snapname');
3491 die "unable to use snapshot name 'current' (reserved name)\n"
3492 if $snapname eq 'current';
3495 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3496 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3497 $param->{description
});
3500 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3503 __PACKAGE__-
>register_method({
3504 name
=> 'snapshot_cmd_idx',
3505 path
=> '{vmid}/snapshot/{snapname}',
3512 additionalProperties
=> 0,
3514 vmid
=> get_standard_option
('pve-vmid'),
3515 node
=> get_standard_option
('pve-node'),
3516 snapname
=> get_standard_option
('pve-snapshot-name'),
3525 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3532 push @$res, { cmd
=> 'rollback' };
3533 push @$res, { cmd
=> 'config' };
3538 __PACKAGE__-
>register_method({
3539 name
=> 'update_snapshot_config',
3540 path
=> '{vmid}/snapshot/{snapname}/config',
3544 description
=> "Update snapshot metadata.",
3546 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3549 additionalProperties
=> 0,
3551 node
=> get_standard_option
('pve-node'),
3552 vmid
=> get_standard_option
('pve-vmid'),
3553 snapname
=> get_standard_option
('pve-snapshot-name'),
3557 description
=> "A textual description or comment.",
3561 returns
=> { type
=> 'null' },
3565 my $rpcenv = PVE
::RPCEnvironment
::get
();
3567 my $authuser = $rpcenv->get_user();
3569 my $vmid = extract_param
($param, 'vmid');
3571 my $snapname = extract_param
($param, 'snapname');
3573 return undef if !defined($param->{description
});
3575 my $updatefn = sub {
3577 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3579 PVE
::QemuConfig-
>check_lock($conf);
3581 my $snap = $conf->{snapshots
}->{$snapname};
3583 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3585 $snap->{description
} = $param->{description
} if defined($param->{description
});
3587 PVE
::QemuConfig-
>write_config($vmid, $conf);
3590 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3595 __PACKAGE__-
>register_method({
3596 name
=> 'get_snapshot_config',
3597 path
=> '{vmid}/snapshot/{snapname}/config',
3600 description
=> "Get snapshot configuration",
3602 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3605 additionalProperties
=> 0,
3607 node
=> get_standard_option
('pve-node'),
3608 vmid
=> get_standard_option
('pve-vmid'),
3609 snapname
=> get_standard_option
('pve-snapshot-name'),
3612 returns
=> { type
=> "object" },
3616 my $rpcenv = PVE
::RPCEnvironment
::get
();
3618 my $authuser = $rpcenv->get_user();
3620 my $vmid = extract_param
($param, 'vmid');
3622 my $snapname = extract_param
($param, 'snapname');
3624 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3626 my $snap = $conf->{snapshots
}->{$snapname};
3628 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3633 __PACKAGE__-
>register_method({
3635 path
=> '{vmid}/snapshot/{snapname}/rollback',
3639 description
=> "Rollback VM state to specified snapshot.",
3641 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3644 additionalProperties
=> 0,
3646 node
=> get_standard_option
('pve-node'),
3647 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3648 snapname
=> get_standard_option
('pve-snapshot-name'),
3653 description
=> "the task ID.",
3658 my $rpcenv = PVE
::RPCEnvironment
::get
();
3660 my $authuser = $rpcenv->get_user();
3662 my $node = extract_param
($param, 'node');
3664 my $vmid = extract_param
($param, 'vmid');
3666 my $snapname = extract_param
($param, 'snapname');
3669 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3670 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3674 # hold migration lock, this makes sure that nobody create replication snapshots
3675 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3678 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3681 __PACKAGE__-
>register_method({
3682 name
=> 'delsnapshot',
3683 path
=> '{vmid}/snapshot/{snapname}',
3687 description
=> "Delete a VM snapshot.",
3689 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3692 additionalProperties
=> 0,
3694 node
=> get_standard_option
('pve-node'),
3695 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3696 snapname
=> get_standard_option
('pve-snapshot-name'),
3700 description
=> "For removal from config file, even if removing disk snapshots fails.",
3706 description
=> "the task ID.",
3711 my $rpcenv = PVE
::RPCEnvironment
::get
();
3713 my $authuser = $rpcenv->get_user();
3715 my $node = extract_param
($param, 'node');
3717 my $vmid = extract_param
($param, 'vmid');
3719 my $snapname = extract_param
($param, 'snapname');
3722 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3723 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3726 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3729 __PACKAGE__-
>register_method({
3731 path
=> '{vmid}/template',
3735 description
=> "Create a Template.",
3737 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3738 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3741 additionalProperties
=> 0,
3743 node
=> get_standard_option
('pve-node'),
3744 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3748 description
=> "If you want to convert only 1 disk to base image.",
3749 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3754 returns
=> { type
=> 'null'},
3758 my $rpcenv = PVE
::RPCEnvironment
::get
();
3760 my $authuser = $rpcenv->get_user();
3762 my $node = extract_param
($param, 'node');
3764 my $vmid = extract_param
($param, 'vmid');
3766 my $disk = extract_param
($param, 'disk');
3768 my $updatefn = sub {
3770 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3772 PVE
::QemuConfig-
>check_lock($conf);
3774 die "unable to create template, because VM contains snapshots\n"
3775 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3777 die "you can't convert a template to a template\n"
3778 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3780 die "you can't convert a VM to template if VM is running\n"
3781 if PVE
::QemuServer
::check_running
($vmid);
3784 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3787 $conf->{template
} = 1;
3788 PVE
::QemuConfig-
>write_config($vmid, $conf);
3790 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3793 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);