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";
155 $fmt = $disk->{format
} // "qcow2";
158 $fmt = $disk->{format
} // "raw";
161 # Initial disk created with 4 MB and aligned to 4MB on regeneration
162 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
163 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
164 $disk->{file
} = $volid;
165 $disk->{media
} = 'cdrom';
166 push @$vollist, $volid;
167 delete $disk->{format
}; # no longer needed
168 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
169 } elsif ($volid =~ $NEW_DISK_RE) {
170 my ($storeid, $size) = ($2 || $default_storage, $3);
171 die "no storage ID specified (and no default storage)\n" if !$storeid;
172 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
173 my $fmt = $disk->{format
} || $defformat;
175 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
178 if ($ds eq 'efidisk0') {
179 ($volid, $size) = PVE
::QemuServer
::create_efidisk
($storecfg, $storeid, $vmid, $fmt, $arch);
181 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
183 push @$vollist, $volid;
184 $disk->{file
} = $volid;
185 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
186 delete $disk->{format
}; # no longer needed
187 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
190 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
192 my $volid_is_new = 1;
195 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
196 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
201 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
203 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
205 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
207 die "volume $volid does not exists\n" if !$size;
209 $disk->{size
} = $size;
212 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
216 eval { PVE
::QemuServer
::foreach_drive
($settings, $code); };
218 # free allocated images on error
220 syslog
('err', "VM $vmid creating disks failed");
221 foreach my $volid (@$vollist) {
222 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
228 # modify vm config if everything went well
229 foreach my $ds (keys %$res) {
230 $conf->{$ds} = $res->{$ds};
247 my $memoryoptions = {
253 my $hwtypeoptions = {
265 my $generaloptions = {
272 'migrate_downtime' => 1,
273 'migrate_speed' => 1,
285 my $vmpoweroptions = {
292 'vmstatestorage' => 1,
295 my $cloudinitoptions = {
305 my $check_vm_modify_config_perm = sub {
306 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
308 return 1 if $authuser eq 'root@pam';
310 foreach my $opt (@$key_list) {
311 # some checks (e.g., disk, serial port, usb) need to be done somewhere
312 # else, as there the permission can be value dependend
313 next if PVE
::QemuServer
::is_valid_drivename
($opt);
314 next if $opt eq 'cdrom';
315 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
318 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
319 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
320 } elsif ($memoryoptions->{$opt}) {
321 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
322 } elsif ($hwtypeoptions->{$opt}) {
323 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
324 } elsif ($generaloptions->{$opt}) {
325 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
326 # special case for startup since it changes host behaviour
327 if ($opt eq 'startup') {
328 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
330 } elsif ($vmpoweroptions->{$opt}) {
331 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
332 } elsif ($diskoptions->{$opt}) {
333 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
334 } elsif ($cloudinitoptions->{$opt} || ($opt =~ m/^(?:net|ipconfig)\d+$/)) {
335 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
337 # catches hostpci\d+, args, lock, etc.
338 # new options will be checked here
339 die "only root can set '$opt' config\n";
346 __PACKAGE__-
>register_method({
350 description
=> "Virtual machine index (per node).",
352 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
356 protected
=> 1, # qemu pid files are only readable by root
358 additionalProperties
=> 0,
360 node
=> get_standard_option
('pve-node'),
364 description
=> "Determine the full status of active VMs.",
372 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
374 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
379 my $rpcenv = PVE
::RPCEnvironment
::get
();
380 my $authuser = $rpcenv->get_user();
382 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
385 foreach my $vmid (keys %$vmstatus) {
386 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
388 my $data = $vmstatus->{$vmid};
397 __PACKAGE__-
>register_method({
401 description
=> "Create or restore a virtual machine.",
403 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
404 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
405 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
406 user
=> 'all', # check inside
411 additionalProperties
=> 0,
412 properties
=> PVE
::QemuServer
::json_config_properties
(
414 node
=> get_standard_option
('pve-node'),
415 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
417 description
=> "The backup file.",
421 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
423 storage
=> get_standard_option
('pve-storage-id', {
424 description
=> "Default storage.",
426 completion
=> \
&PVE
::QemuServer
::complete_storage
,
431 description
=> "Allow to overwrite existing VM.",
432 requires
=> 'archive',
437 description
=> "Assign a unique random ethernet address.",
438 requires
=> 'archive',
442 type
=> 'string', format
=> 'pve-poolid',
443 description
=> "Add the VM to the specified pool.",
446 description
=> "Override I/O bandwidth limit (in KiB/s).",
450 default => 'restore limit from datacenter or storage config',
456 description
=> "Start VM after it was created successfully.",
466 my $rpcenv = PVE
::RPCEnvironment
::get
();
468 my $authuser = $rpcenv->get_user();
470 my $node = extract_param
($param, 'node');
472 my $vmid = extract_param
($param, 'vmid');
474 my $archive = extract_param
($param, 'archive');
475 my $is_restore = !!$archive;
477 my $storage = extract_param
($param, 'storage');
479 my $force = extract_param
($param, 'force');
481 my $unique = extract_param
($param, 'unique');
483 my $pool = extract_param
($param, 'pool');
485 my $bwlimit = extract_param
($param, 'bwlimit');
487 my $start_after_create = extract_param
($param, 'start');
489 my $filename = PVE
::QemuConfig-
>config_file($vmid);
491 my $storecfg = PVE
::Storage
::config
();
493 if (defined(my $ssh_keys = $param->{sshkeys
})) {
494 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
495 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
498 PVE
::Cluster
::check_cfs_quorum
();
500 if (defined($pool)) {
501 $rpcenv->check_pool_exist($pool);
504 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
505 if defined($storage);
507 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
509 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
511 } elsif ($archive && $force && (-f
$filename) &&
512 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
513 # OK: user has VM.Backup permissions, and want to restore an existing VM
519 &$resolve_cdrom_alias($param);
521 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
523 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
525 foreach my $opt (keys %$param) {
526 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
527 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
528 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
530 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
531 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
535 PVE
::QemuServer
::add_random_macs
($param);
537 my $keystr = join(' ', keys %$param);
538 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
540 if ($archive eq '-') {
541 die "pipe requires cli environment\n"
542 if $rpcenv->{type
} ne 'cli';
544 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
545 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
549 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
551 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
552 die "$emsg $@" if $@;
554 my $restorefn = sub {
555 my $conf = PVE
::QemuConfig-
>load_config($vmid);
557 PVE
::QemuConfig-
>check_protection($conf, $emsg);
559 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
562 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
568 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
569 # Convert restored VM to template if backup was VM template
570 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
571 warn "Convert to template.\n";
572 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
576 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
578 if ($start_after_create) {
579 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
584 # ensure no old replication state are exists
585 PVE
::ReplicationState
::delete_guest_states
($vmid);
587 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
591 # ensure no old replication state are exists
592 PVE
::ReplicationState
::delete_guest_states
($vmid);
600 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
604 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
606 if (!$conf->{bootdisk
}) {
607 my $firstdisk = PVE
::QemuServer
::resolve_first_disk
($conf);
608 $conf->{bootdisk
} = $firstdisk if $firstdisk;
611 # auto generate uuid if user did not specify smbios1 option
612 if (!$conf->{smbios1
}) {
613 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
616 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
617 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
620 PVE
::QemuConfig-
>write_config($vmid, $conf);
626 foreach my $volid (@$vollist) {
627 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
633 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
636 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
638 if ($start_after_create) {
639 print "Execute autostart\n";
640 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
645 my ($code, $worker_name);
647 $worker_name = 'qmrestore';
649 eval { $restorefn->() };
651 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
657 $worker_name = 'qmcreate';
659 eval { $createfn->() };
662 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
663 unlink($conffile) or die "failed to remove config file: $!\n";
671 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
674 __PACKAGE__-
>register_method({
679 description
=> "Directory index",
684 additionalProperties
=> 0,
686 node
=> get_standard_option
('pve-node'),
687 vmid
=> get_standard_option
('pve-vmid'),
695 subdir
=> { type
=> 'string' },
698 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
704 { subdir
=> 'config' },
705 { subdir
=> 'pending' },
706 { subdir
=> 'status' },
707 { subdir
=> 'unlink' },
708 { subdir
=> 'vncproxy' },
709 { subdir
=> 'termproxy' },
710 { subdir
=> 'migrate' },
711 { subdir
=> 'resize' },
712 { subdir
=> 'move' },
714 { subdir
=> 'rrddata' },
715 { subdir
=> 'monitor' },
716 { subdir
=> 'agent' },
717 { subdir
=> 'snapshot' },
718 { subdir
=> 'spiceproxy' },
719 { subdir
=> 'sendkey' },
720 { subdir
=> 'firewall' },
726 __PACKAGE__-
>register_method ({
727 subclass
=> "PVE::API2::Firewall::VM",
728 path
=> '{vmid}/firewall',
731 __PACKAGE__-
>register_method ({
732 subclass
=> "PVE::API2::Qemu::Agent",
733 path
=> '{vmid}/agent',
736 __PACKAGE__-
>register_method({
738 path
=> '{vmid}/rrd',
740 protected
=> 1, # fixme: can we avoid that?
742 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
744 description
=> "Read VM RRD statistics (returns PNG)",
746 additionalProperties
=> 0,
748 node
=> get_standard_option
('pve-node'),
749 vmid
=> get_standard_option
('pve-vmid'),
751 description
=> "Specify the time frame you are interested in.",
753 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
756 description
=> "The list of datasources you want to display.",
757 type
=> 'string', format
=> 'pve-configid-list',
760 description
=> "The RRD consolidation function",
762 enum
=> [ 'AVERAGE', 'MAX' ],
770 filename
=> { type
=> 'string' },
776 return PVE
::Cluster
::create_rrd_graph
(
777 "pve2-vm/$param->{vmid}", $param->{timeframe
},
778 $param->{ds
}, $param->{cf
});
782 __PACKAGE__-
>register_method({
784 path
=> '{vmid}/rrddata',
786 protected
=> 1, # fixme: can we avoid that?
788 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
790 description
=> "Read VM RRD statistics",
792 additionalProperties
=> 0,
794 node
=> get_standard_option
('pve-node'),
795 vmid
=> get_standard_option
('pve-vmid'),
797 description
=> "Specify the time frame you are interested in.",
799 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
802 description
=> "The RRD consolidation function",
804 enum
=> [ 'AVERAGE', 'MAX' ],
819 return PVE
::Cluster
::create_rrd_data
(
820 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
824 __PACKAGE__-
>register_method({
826 path
=> '{vmid}/config',
829 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
831 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
834 additionalProperties
=> 0,
836 node
=> get_standard_option
('pve-node'),
837 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
839 description
=> "Get current values (instead of pending values).",
844 snapshot
=> get_standard_option
('pve-snapshot-name', {
845 description
=> "Fetch config values from given snapshot.",
848 my ($cmd, $pname, $cur, $args) = @_;
849 PVE
::QemuConfig-
>snapshot_list($args->[0]);
855 description
=> "The current VM configuration.",
857 properties
=> PVE
::QemuServer
::json_config_properties
({
860 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
867 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
869 if (my $snapname = $param->{snapshot
}) {
870 my $snapshot = $conf->{snapshots
}->{$snapname};
871 die "snapshot '$snapname' does not exist\n" if !defined($snapshot);
873 $snapshot->{digest
} = $conf->{digest
}; # keep file digest for API
878 delete $conf->{snapshots
};
880 if (!$param->{current
}) {
881 foreach my $opt (keys %{$conf->{pending
}}) {
882 next if $opt eq 'delete';
883 my $value = $conf->{pending
}->{$opt};
884 next if ref($value); # just to be sure
885 $conf->{$opt} = $value;
887 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
888 foreach my $opt (keys %$pending_delete_hash) {
889 delete $conf->{$opt} if $conf->{$opt};
893 delete $conf->{pending
};
895 # hide cloudinit password
896 if ($conf->{cipassword
}) {
897 $conf->{cipassword
} = '**********';
903 __PACKAGE__-
>register_method({
904 name
=> 'vm_pending',
905 path
=> '{vmid}/pending',
908 description
=> "Get virtual machine configuration, including pending changes.",
910 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
913 additionalProperties
=> 0,
915 node
=> get_standard_option
('pve-node'),
916 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
925 description
=> "Configuration option name.",
929 description
=> "Current value.",
934 description
=> "Pending value.",
939 description
=> "Indicates a pending delete request if present and not 0. " .
940 "The value 2 indicates a force-delete request.",
952 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
954 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
958 foreach my $opt (keys %$conf) {
959 next if ref($conf->{$opt});
960 my $item = { key
=> $opt };
961 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
962 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
963 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
965 # hide cloudinit password
966 if ($opt eq 'cipassword') {
967 $item->{value
} = '**********' if defined($item->{value
});
968 # the trailing space so that the pending string is different
969 $item->{pending
} = '********** ' if defined($item->{pending
});
974 foreach my $opt (keys %{$conf->{pending
}}) {
975 next if $opt eq 'delete';
976 next if ref($conf->{pending
}->{$opt}); # just to be sure
977 next if defined($conf->{$opt});
978 my $item = { key
=> $opt };
979 $item->{pending
} = $conf->{pending
}->{$opt};
981 # hide cloudinit password
982 if ($opt eq 'cipassword') {
983 $item->{pending
} = '**********' if defined($item->{pending
});
988 while (my ($opt, $force) = each %$pending_delete_hash) {
989 next if $conf->{pending
}->{$opt}; # just to be sure
990 next if $conf->{$opt};
991 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
998 # POST/PUT {vmid}/config implementation
1000 # The original API used PUT (idempotent) an we assumed that all operations
1001 # are fast. But it turned out that almost any configuration change can
1002 # involve hot-plug actions, or disk alloc/free. Such actions can take long
1003 # time to complete and have side effects (not idempotent).
1005 # The new implementation uses POST and forks a worker process. We added
1006 # a new option 'background_delay'. If specified we wait up to
1007 # 'background_delay' second for the worker task to complete. It returns null
1008 # if the task is finished within that time, else we return the UPID.
1010 my $update_vm_api = sub {
1011 my ($param, $sync) = @_;
1013 my $rpcenv = PVE
::RPCEnvironment
::get
();
1015 my $authuser = $rpcenv->get_user();
1017 my $node = extract_param
($param, 'node');
1019 my $vmid = extract_param
($param, 'vmid');
1021 my $digest = extract_param
($param, 'digest');
1023 my $background_delay = extract_param
($param, 'background_delay');
1025 if (defined(my $cipassword = $param->{cipassword
})) {
1026 # Same logic as in cloud-init (but with the regex fixed...)
1027 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
1028 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1031 my @paramarr = (); # used for log message
1032 foreach my $key (sort keys %$param) {
1033 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1034 push @paramarr, "-$key", $value;
1037 my $skiplock = extract_param
($param, 'skiplock');
1038 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1039 if $skiplock && $authuser ne 'root@pam';
1041 my $delete_str = extract_param
($param, 'delete');
1043 my $revert_str = extract_param
($param, 'revert');
1045 my $force = extract_param
($param, 'force');
1047 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1048 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1049 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1052 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1054 my $storecfg = PVE
::Storage
::config
();
1056 my $defaults = PVE
::QemuServer
::load_defaults
();
1058 &$resolve_cdrom_alias($param);
1060 # now try to verify all parameters
1063 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1064 if (!PVE
::QemuServer
::option_exists
($opt)) {
1065 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1068 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1069 "-revert $opt' at the same time" })
1070 if defined($param->{$opt});
1072 $revert->{$opt} = 1;
1076 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1077 $opt = 'ide2' if $opt eq 'cdrom';
1079 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1080 "-delete $opt' at the same time" })
1081 if defined($param->{$opt});
1083 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1084 "-revert $opt' at the same time" })
1087 if (!PVE
::QemuServer
::option_exists
($opt)) {
1088 raise_param_exc
({ delete => "unknown option '$opt'" });
1094 my $repl_conf = PVE
::ReplicationConfig-
>new();
1095 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1096 my $check_replication = sub {
1098 return if !$is_replicated;
1099 my $volid = $drive->{file
};
1100 return if !$volid || !($drive->{replicate
}//1);
1101 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1102 my ($storeid, $format);
1103 if ($volid =~ $NEW_DISK_RE) {
1105 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1107 ($storeid, undef) = PVE
::Storage
::parse_volume_id
($volid, 1);
1108 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1110 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1111 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1112 return if $scfg->{shared
};
1113 die "cannot add non-replicatable volume to a replicated VM\n";
1116 foreach my $opt (keys %$param) {
1117 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1118 # cleanup drive path
1119 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1120 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1121 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1122 $check_replication->($drive);
1123 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1124 } elsif ($opt =~ m/^net(\d+)$/) {
1126 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1127 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1128 } elsif ($opt eq 'vmgenid') {
1129 if ($param->{$opt} eq '1') {
1130 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1132 } elsif ($opt eq 'hookscript') {
1133 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1134 raise_param_exc
({ $opt => $@ }) if $@;
1138 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1140 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1142 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1144 my $updatefn = sub {
1146 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1148 die "checksum missmatch (file change by other user?)\n"
1149 if $digest && $digest ne $conf->{digest
};
1151 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1153 foreach my $opt (keys %$revert) {
1154 if (defined($conf->{$opt})) {
1155 $param->{$opt} = $conf->{$opt};
1156 } elsif (defined($conf->{pending
}->{$opt})) {
1161 if ($param->{memory
} || defined($param->{balloon
})) {
1162 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1163 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1165 die "balloon value too large (must be smaller than assigned memory)\n"
1166 if $balloon && $balloon > $maxmem;
1169 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1173 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1175 # write updates to pending section
1177 my $modified = {}; # record what $option we modify
1179 foreach my $opt (@delete) {
1180 $modified->{$opt} = 1;
1181 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1182 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1183 warn "cannot delete '$opt' - not set in current configuration!\n";
1184 $modified->{$opt} = 0;
1188 if ($opt =~ m/^unused/) {
1189 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1190 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1191 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1192 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1193 delete $conf->{$opt};
1194 PVE
::QemuConfig-
>write_config($vmid, $conf);
1196 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1197 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1198 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1199 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1200 if defined($conf->{pending
}->{$opt});
1201 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1202 PVE
::QemuConfig-
>write_config($vmid, $conf);
1203 } elsif ($opt =~ m/^serial\d+$/) {
1204 if ($conf->{$opt} eq 'socket') {
1205 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1206 } elsif ($authuser ne 'root@pam') {
1207 die "only root can delete '$opt' config for real devices\n";
1209 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1210 PVE
::QemuConfig-
>write_config($vmid, $conf);
1211 } elsif ($opt =~ m/^usb\d+$/) {
1212 if ($conf->{$opt} =~ m/spice/) {
1213 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1214 } elsif ($authuser ne 'root@pam') {
1215 die "only root can delete '$opt' config for real devices\n";
1217 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1218 PVE
::QemuConfig-
>write_config($vmid, $conf);
1220 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1221 PVE
::QemuConfig-
>write_config($vmid, $conf);
1225 foreach my $opt (keys %$param) { # add/change
1226 $modified->{$opt} = 1;
1227 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1228 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1230 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
1232 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1233 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1234 # FIXME: cloudinit: CDROM or Disk?
1235 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1236 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1238 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1240 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1241 if defined($conf->{pending
}->{$opt});
1243 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1244 } elsif ($opt =~ m/^serial\d+/) {
1245 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1246 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1247 } elsif ($authuser ne 'root@pam') {
1248 die "only root can modify '$opt' config for real devices\n";
1250 $conf->{pending
}->{$opt} = $param->{$opt};
1251 } elsif ($opt =~ m/^usb\d+/) {
1252 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1253 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1254 } elsif ($authuser ne 'root@pam') {
1255 die "only root can modify '$opt' config for real devices\n";
1257 $conf->{pending
}->{$opt} = $param->{$opt};
1259 $conf->{pending
}->{$opt} = $param->{$opt};
1261 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1262 PVE
::QemuConfig-
>write_config($vmid, $conf);
1265 # remove pending changes when nothing changed
1266 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1267 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1268 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1270 return if !scalar(keys %{$conf->{pending
}});
1272 my $running = PVE
::QemuServer
::check_running
($vmid);
1274 # apply pending changes
1276 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1280 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1281 raise_param_exc
($errors) if scalar(keys %$errors);
1283 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1293 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1295 if ($background_delay) {
1297 # Note: It would be better to do that in the Event based HTTPServer
1298 # to avoid blocking call to sleep.
1300 my $end_time = time() + $background_delay;
1302 my $task = PVE
::Tools
::upid_decode
($upid);
1305 while (time() < $end_time) {
1306 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1308 sleep(1); # this gets interrupted when child process ends
1312 my $status = PVE
::Tools
::upid_read_status
($upid);
1313 return undef if $status eq 'OK';
1322 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1325 my $vm_config_perm_list = [
1330 'VM.Config.Network',
1332 'VM.Config.Options',
1335 __PACKAGE__-
>register_method({
1336 name
=> 'update_vm_async',
1337 path
=> '{vmid}/config',
1341 description
=> "Set virtual machine options (asynchrounous API).",
1343 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1346 additionalProperties
=> 0,
1347 properties
=> PVE
::QemuServer
::json_config_properties
(
1349 node
=> get_standard_option
('pve-node'),
1350 vmid
=> get_standard_option
('pve-vmid'),
1351 skiplock
=> get_standard_option
('skiplock'),
1353 type
=> 'string', format
=> 'pve-configid-list',
1354 description
=> "A list of settings you want to delete.",
1358 type
=> 'string', format
=> 'pve-configid-list',
1359 description
=> "Revert a pending change.",
1364 description
=> $opt_force_description,
1366 requires
=> 'delete',
1370 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1374 background_delay
=> {
1376 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1387 code
=> $update_vm_api,
1390 __PACKAGE__-
>register_method({
1391 name
=> 'update_vm',
1392 path
=> '{vmid}/config',
1396 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1398 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1401 additionalProperties
=> 0,
1402 properties
=> PVE
::QemuServer
::json_config_properties
(
1404 node
=> get_standard_option
('pve-node'),
1405 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1406 skiplock
=> get_standard_option
('skiplock'),
1408 type
=> 'string', format
=> 'pve-configid-list',
1409 description
=> "A list of settings you want to delete.",
1413 type
=> 'string', format
=> 'pve-configid-list',
1414 description
=> "Revert a pending change.",
1419 description
=> $opt_force_description,
1421 requires
=> 'delete',
1425 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1431 returns
=> { type
=> 'null' },
1434 &$update_vm_api($param, 1);
1440 __PACKAGE__-
>register_method({
1441 name
=> 'destroy_vm',
1446 description
=> "Destroy the vm (also delete all used/owned volumes).",
1448 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1451 additionalProperties
=> 0,
1453 node
=> get_standard_option
('pve-node'),
1454 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1455 skiplock
=> get_standard_option
('skiplock'),
1464 my $rpcenv = PVE
::RPCEnvironment
::get
();
1466 my $authuser = $rpcenv->get_user();
1468 my $vmid = $param->{vmid
};
1470 my $skiplock = $param->{skiplock
};
1471 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1472 if $skiplock && $authuser ne 'root@pam';
1475 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1477 my $storecfg = PVE
::Storage
::config
();
1479 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1481 die "unable to remove VM $vmid - used in HA resources\n"
1482 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1484 # do not allow destroy if there are replication jobs
1485 my $repl_conf = PVE
::ReplicationConfig-
>new();
1486 $repl_conf->check_for_existing_jobs($vmid);
1488 # early tests (repeat after locking)
1489 die "VM $vmid is running - destroy failed\n"
1490 if PVE
::QemuServer
::check_running
($vmid);
1495 syslog
('info', "destroy VM $vmid: $upid\n");
1497 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1499 PVE
::AccessControl
::remove_vm_access
($vmid);
1501 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1504 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1507 __PACKAGE__-
>register_method({
1509 path
=> '{vmid}/unlink',
1513 description
=> "Unlink/delete disk images.",
1515 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1518 additionalProperties
=> 0,
1520 node
=> get_standard_option
('pve-node'),
1521 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1523 type
=> 'string', format
=> 'pve-configid-list',
1524 description
=> "A list of disk IDs you want to delete.",
1528 description
=> $opt_force_description,
1533 returns
=> { type
=> 'null'},
1537 $param->{delete} = extract_param
($param, 'idlist');
1539 __PACKAGE__-
>update_vm($param);
1546 __PACKAGE__-
>register_method({
1548 path
=> '{vmid}/vncproxy',
1552 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1554 description
=> "Creates a TCP VNC proxy connections.",
1556 additionalProperties
=> 0,
1558 node
=> get_standard_option
('pve-node'),
1559 vmid
=> get_standard_option
('pve-vmid'),
1563 description
=> "starts websockify instead of vncproxy",
1568 additionalProperties
=> 0,
1570 user
=> { type
=> 'string' },
1571 ticket
=> { type
=> 'string' },
1572 cert
=> { type
=> 'string' },
1573 port
=> { type
=> 'integer' },
1574 upid
=> { type
=> 'string' },
1580 my $rpcenv = PVE
::RPCEnvironment
::get
();
1582 my $authuser = $rpcenv->get_user();
1584 my $vmid = $param->{vmid
};
1585 my $node = $param->{node
};
1586 my $websocket = $param->{websocket
};
1588 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1589 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1591 my $authpath = "/vms/$vmid";
1593 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1595 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1601 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1602 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1603 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1604 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1605 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1607 $family = PVE
::Tools
::get_host_address_family
($node);
1610 my $port = PVE
::Tools
::next_vnc_port
($family);
1617 syslog
('info', "starting vnc proxy $upid\n");
1623 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1625 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1626 '-timeout', $timeout, '-authpath', $authpath,
1627 '-perm', 'Sys.Console'];
1629 if ($param->{websocket
}) {
1630 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1631 push @$cmd, '-notls', '-listen', 'localhost';
1634 push @$cmd, '-c', @$remcmd, @$termcmd;
1636 PVE
::Tools
::run_command
($cmd);
1640 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1642 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1644 my $sock = IO
::Socket
::IP-
>new(
1649 GetAddrInfoFlags
=> 0,
1650 ) or die "failed to create socket: $!\n";
1651 # Inside the worker we shouldn't have any previous alarms
1652 # running anyway...:
1654 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1656 accept(my $cli, $sock) or die "connection failed: $!\n";
1659 if (PVE
::Tools
::run_command
($cmd,
1660 output
=> '>&'.fileno($cli),
1661 input
=> '<&'.fileno($cli),
1664 die "Failed to run vncproxy.\n";
1671 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1673 PVE
::Tools
::wait_for_vnc_port
($port);
1684 __PACKAGE__-
>register_method({
1685 name
=> 'termproxy',
1686 path
=> '{vmid}/termproxy',
1690 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1692 description
=> "Creates a TCP proxy connections.",
1694 additionalProperties
=> 0,
1696 node
=> get_standard_option
('pve-node'),
1697 vmid
=> get_standard_option
('pve-vmid'),
1701 enum
=> [qw(serial0 serial1 serial2 serial3)],
1702 description
=> "opens a serial terminal (defaults to display)",
1707 additionalProperties
=> 0,
1709 user
=> { type
=> 'string' },
1710 ticket
=> { type
=> 'string' },
1711 port
=> { type
=> 'integer' },
1712 upid
=> { type
=> 'string' },
1718 my $rpcenv = PVE
::RPCEnvironment
::get
();
1720 my $authuser = $rpcenv->get_user();
1722 my $vmid = $param->{vmid
};
1723 my $node = $param->{node
};
1724 my $serial = $param->{serial
};
1726 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1728 if (!defined($serial)) {
1729 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1730 $serial = $conf->{vga
};
1734 my $authpath = "/vms/$vmid";
1736 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1741 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1742 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1743 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1744 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, '-t');
1745 push @$remcmd, '--';
1747 $family = PVE
::Tools
::get_host_address_family
($node);
1750 my $port = PVE
::Tools
::next_vnc_port
($family);
1752 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1753 push @$termcmd, '-iface', $serial if $serial;
1758 syslog
('info', "starting qemu termproxy $upid\n");
1760 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1761 '--perm', 'VM.Console', '--'];
1762 push @$cmd, @$remcmd, @$termcmd;
1764 PVE
::Tools
::run_command
($cmd);
1767 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1769 PVE
::Tools
::wait_for_vnc_port
($port);
1779 __PACKAGE__-
>register_method({
1780 name
=> 'vncwebsocket',
1781 path
=> '{vmid}/vncwebsocket',
1784 description
=> "You also need to pass a valid ticket (vncticket).",
1785 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1787 description
=> "Opens a weksocket for VNC traffic.",
1789 additionalProperties
=> 0,
1791 node
=> get_standard_option
('pve-node'),
1792 vmid
=> get_standard_option
('pve-vmid'),
1794 description
=> "Ticket from previous call to vncproxy.",
1799 description
=> "Port number returned by previous vncproxy call.",
1809 port
=> { type
=> 'string' },
1815 my $rpcenv = PVE
::RPCEnvironment
::get
();
1817 my $authuser = $rpcenv->get_user();
1819 my $vmid = $param->{vmid
};
1820 my $node = $param->{node
};
1822 my $authpath = "/vms/$vmid";
1824 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1826 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1828 # Note: VNC ports are acessible from outside, so we do not gain any
1829 # security if we verify that $param->{port} belongs to VM $vmid. This
1830 # check is done by verifying the VNC ticket (inside VNC protocol).
1832 my $port = $param->{port
};
1834 return { port
=> $port };
1837 __PACKAGE__-
>register_method({
1838 name
=> 'spiceproxy',
1839 path
=> '{vmid}/spiceproxy',
1844 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1846 description
=> "Returns a SPICE configuration to connect to the VM.",
1848 additionalProperties
=> 0,
1850 node
=> get_standard_option
('pve-node'),
1851 vmid
=> get_standard_option
('pve-vmid'),
1852 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1855 returns
=> get_standard_option
('remote-viewer-config'),
1859 my $rpcenv = PVE
::RPCEnvironment
::get
();
1861 my $authuser = $rpcenv->get_user();
1863 my $vmid = $param->{vmid
};
1864 my $node = $param->{node
};
1865 my $proxy = $param->{proxy
};
1867 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1868 my $title = "VM $vmid";
1869 $title .= " - ". $conf->{name
} if $conf->{name
};
1871 my $port = PVE
::QemuServer
::spice_port
($vmid);
1873 my ($ticket, undef, $remote_viewer_config) =
1874 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1876 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1877 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1879 return $remote_viewer_config;
1882 __PACKAGE__-
>register_method({
1884 path
=> '{vmid}/status',
1887 description
=> "Directory index",
1892 additionalProperties
=> 0,
1894 node
=> get_standard_option
('pve-node'),
1895 vmid
=> get_standard_option
('pve-vmid'),
1903 subdir
=> { type
=> 'string' },
1906 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1912 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1915 { subdir
=> 'current' },
1916 { subdir
=> 'start' },
1917 { subdir
=> 'stop' },
1923 __PACKAGE__-
>register_method({
1924 name
=> 'vm_status',
1925 path
=> '{vmid}/status/current',
1928 protected
=> 1, # qemu pid files are only readable by root
1929 description
=> "Get virtual machine status.",
1931 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1934 additionalProperties
=> 0,
1936 node
=> get_standard_option
('pve-node'),
1937 vmid
=> get_standard_option
('pve-vmid'),
1943 %$PVE::QemuServer
::vmstatus_return_properties
,
1945 description
=> "HA manager service status.",
1949 description
=> "Qemu VGA configuration supports spice.",
1954 description
=> "Qemu GuestAgent enabled in config.",
1964 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1966 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1967 my $status = $vmstatus->{$param->{vmid
}};
1969 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1971 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1972 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1977 __PACKAGE__-
>register_method({
1979 path
=> '{vmid}/status/start',
1983 description
=> "Start virtual machine.",
1985 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1988 additionalProperties
=> 0,
1990 node
=> get_standard_option
('pve-node'),
1991 vmid
=> get_standard_option
('pve-vmid',
1992 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1993 skiplock
=> get_standard_option
('skiplock'),
1994 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1995 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1998 enum
=> ['secure', 'insecure'],
1999 description
=> "Migration traffic is encrypted using an SSH " .
2000 "tunnel by default. On secure, completely private networks " .
2001 "this can be disabled to increase performance.",
2004 migration_network
=> {
2005 type
=> 'string', format
=> 'CIDR',
2006 description
=> "CIDR of the (sub) network that is used for migration.",
2009 machine
=> get_standard_option
('pve-qm-machine'),
2011 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
2023 my $rpcenv = PVE
::RPCEnvironment
::get
();
2024 my $authuser = $rpcenv->get_user();
2026 my $node = extract_param
($param, 'node');
2027 my $vmid = extract_param
($param, 'vmid');
2029 my $machine = extract_param
($param, 'machine');
2031 my $stateuri = extract_param
($param, 'stateuri');
2032 raise_param_exc
({ stateuri
=> "Only root may use this option." })
2033 if $stateuri && $authuser ne 'root@pam';
2035 my $skiplock = extract_param
($param, 'skiplock');
2036 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2037 if $skiplock && $authuser ne 'root@pam';
2039 my $migratedfrom = extract_param
($param, 'migratedfrom');
2040 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2041 if $migratedfrom && $authuser ne 'root@pam';
2043 my $migration_type = extract_param
($param, 'migration_type');
2044 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2045 if $migration_type && $authuser ne 'root@pam';
2047 my $migration_network = extract_param
($param, 'migration_network');
2048 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2049 if $migration_network && $authuser ne 'root@pam';
2051 my $targetstorage = extract_param
($param, 'targetstorage');
2052 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
2053 if $targetstorage && $authuser ne 'root@pam';
2055 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2056 if $targetstorage && !$migratedfrom;
2058 # read spice ticket from STDIN
2060 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2061 if (defined(my $line = <STDIN
>)) {
2063 $spice_ticket = $line;
2067 PVE
::Cluster
::check_cfs_quorum
();
2069 my $storecfg = PVE
::Storage
::config
();
2071 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2075 print "Requesting HA start for VM $vmid\n";
2077 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2078 PVE
::Tools
::run_command
($cmd);
2082 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2089 syslog
('info', "start VM $vmid: $upid\n");
2091 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2092 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
2096 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2100 __PACKAGE__-
>register_method({
2102 path
=> '{vmid}/status/stop',
2106 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2107 "is akin to pulling the power plug of a running computer and may damage the VM data",
2109 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2112 additionalProperties
=> 0,
2114 node
=> get_standard_option
('pve-node'),
2115 vmid
=> get_standard_option
('pve-vmid',
2116 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2117 skiplock
=> get_standard_option
('skiplock'),
2118 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2120 description
=> "Wait maximal timeout seconds.",
2126 description
=> "Do not deactivate storage volumes.",
2139 my $rpcenv = PVE
::RPCEnvironment
::get
();
2140 my $authuser = $rpcenv->get_user();
2142 my $node = extract_param
($param, 'node');
2143 my $vmid = extract_param
($param, 'vmid');
2145 my $skiplock = extract_param
($param, 'skiplock');
2146 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2147 if $skiplock && $authuser ne 'root@pam';
2149 my $keepActive = extract_param
($param, 'keepActive');
2150 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2151 if $keepActive && $authuser ne 'root@pam';
2153 my $migratedfrom = extract_param
($param, 'migratedfrom');
2154 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2155 if $migratedfrom && $authuser ne 'root@pam';
2158 my $storecfg = PVE
::Storage
::config
();
2160 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2165 print "Requesting HA stop for VM $vmid\n";
2167 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'stopped'];
2168 PVE
::Tools
::run_command
($cmd);
2172 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2178 syslog
('info', "stop VM $vmid: $upid\n");
2180 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2181 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2185 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2189 __PACKAGE__-
>register_method({
2191 path
=> '{vmid}/status/reset',
2195 description
=> "Reset virtual machine.",
2197 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2200 additionalProperties
=> 0,
2202 node
=> get_standard_option
('pve-node'),
2203 vmid
=> get_standard_option
('pve-vmid',
2204 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2205 skiplock
=> get_standard_option
('skiplock'),
2214 my $rpcenv = PVE
::RPCEnvironment
::get
();
2216 my $authuser = $rpcenv->get_user();
2218 my $node = extract_param
($param, 'node');
2220 my $vmid = extract_param
($param, 'vmid');
2222 my $skiplock = extract_param
($param, 'skiplock');
2223 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2224 if $skiplock && $authuser ne 'root@pam';
2226 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2231 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2236 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2239 __PACKAGE__-
>register_method({
2240 name
=> 'vm_shutdown',
2241 path
=> '{vmid}/status/shutdown',
2245 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2246 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2248 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2251 additionalProperties
=> 0,
2253 node
=> get_standard_option
('pve-node'),
2254 vmid
=> get_standard_option
('pve-vmid',
2255 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2256 skiplock
=> get_standard_option
('skiplock'),
2258 description
=> "Wait maximal timeout seconds.",
2264 description
=> "Make sure the VM stops.",
2270 description
=> "Do not deactivate storage volumes.",
2283 my $rpcenv = PVE
::RPCEnvironment
::get
();
2284 my $authuser = $rpcenv->get_user();
2286 my $node = extract_param
($param, 'node');
2287 my $vmid = extract_param
($param, 'vmid');
2289 my $skiplock = extract_param
($param, 'skiplock');
2290 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2291 if $skiplock && $authuser ne 'root@pam';
2293 my $keepActive = extract_param
($param, 'keepActive');
2294 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2295 if $keepActive && $authuser ne 'root@pam';
2297 my $storecfg = PVE
::Storage
::config
();
2301 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2302 # otherwise, we will infer a shutdown command, but run into the timeout,
2303 # then when the vm is resumed, it will instantly shutdown
2305 # checking the qmp status here to get feedback to the gui/cli/api
2306 # and the status query should not take too long
2307 my $qmpstatus = eval {
2308 PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2312 if (!$err && $qmpstatus->{status
} eq "paused") {
2313 if ($param->{forceStop
}) {
2314 warn "VM is paused - stop instead of shutdown\n";
2317 die "VM is paused - cannot shutdown\n";
2321 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2326 print "Requesting HA stop for VM $vmid\n";
2328 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'stopped'];
2329 PVE
::Tools
::run_command
($cmd);
2333 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2340 syslog
('info', "shutdown VM $vmid: $upid\n");
2342 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2343 $shutdown, $param->{forceStop
}, $keepActive);
2347 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2351 __PACKAGE__-
>register_method({
2352 name
=> 'vm_suspend',
2353 path
=> '{vmid}/status/suspend',
2357 description
=> "Suspend virtual machine.",
2359 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2362 additionalProperties
=> 0,
2364 node
=> get_standard_option
('pve-node'),
2365 vmid
=> get_standard_option
('pve-vmid',
2366 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2367 skiplock
=> get_standard_option
('skiplock'),
2372 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2374 statestorage
=> get_standard_option
('pve-storage-id', {
2375 description
=> "The storage for the VM state",
2376 requires
=> 'todisk',
2378 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2388 my $rpcenv = PVE
::RPCEnvironment
::get
();
2389 my $authuser = $rpcenv->get_user();
2391 my $node = extract_param
($param, 'node');
2392 my $vmid = extract_param
($param, 'vmid');
2394 my $todisk = extract_param
($param, 'todisk') // 0;
2396 my $statestorage = extract_param
($param, 'statestorage');
2398 my $skiplock = extract_param
($param, 'skiplock');
2399 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2400 if $skiplock && $authuser ne 'root@pam';
2402 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2404 die "Cannot suspend HA managed VM to disk\n"
2405 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2410 syslog
('info', "suspend VM $vmid: $upid\n");
2412 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2417 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2418 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2421 __PACKAGE__-
>register_method({
2422 name
=> 'vm_resume',
2423 path
=> '{vmid}/status/resume',
2427 description
=> "Resume virtual machine.",
2429 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
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'),
2438 nocheck
=> { type
=> 'boolean', optional
=> 1 },
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 my $nocheck = extract_param
($param, 'nocheck');
2462 my $to_disk_suspended;
2464 PVE
::QemuConfig-
>lock_config($vmid, sub {
2465 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2466 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2470 die "VM $vmid not running\n"
2471 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2476 syslog
('info', "resume VM $vmid: $upid\n");
2478 if (!$to_disk_suspended) {
2479 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2481 my $storecfg = PVE
::Storage
::config
();
2482 PVE
::QemuServer
::vm_start
($storecfg, $vmid, undef, $skiplock);
2488 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2491 __PACKAGE__-
>register_method({
2492 name
=> 'vm_sendkey',
2493 path
=> '{vmid}/sendkey',
2497 description
=> "Send key event to virtual machine.",
2499 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2502 additionalProperties
=> 0,
2504 node
=> get_standard_option
('pve-node'),
2505 vmid
=> get_standard_option
('pve-vmid',
2506 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2507 skiplock
=> get_standard_option
('skiplock'),
2509 description
=> "The key (qemu monitor encoding).",
2514 returns
=> { type
=> 'null'},
2518 my $rpcenv = PVE
::RPCEnvironment
::get
();
2520 my $authuser = $rpcenv->get_user();
2522 my $node = extract_param
($param, 'node');
2524 my $vmid = extract_param
($param, 'vmid');
2526 my $skiplock = extract_param
($param, 'skiplock');
2527 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2528 if $skiplock && $authuser ne 'root@pam';
2530 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2535 __PACKAGE__-
>register_method({
2536 name
=> 'vm_feature',
2537 path
=> '{vmid}/feature',
2541 description
=> "Check if feature for virtual machine is available.",
2543 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2546 additionalProperties
=> 0,
2548 node
=> get_standard_option
('pve-node'),
2549 vmid
=> get_standard_option
('pve-vmid'),
2551 description
=> "Feature to check.",
2553 enum
=> [ 'snapshot', 'clone', 'copy' ],
2555 snapname
=> get_standard_option
('pve-snapshot-name', {
2563 hasFeature
=> { type
=> 'boolean' },
2566 items
=> { type
=> 'string' },
2573 my $node = extract_param
($param, 'node');
2575 my $vmid = extract_param
($param, 'vmid');
2577 my $snapname = extract_param
($param, 'snapname');
2579 my $feature = extract_param
($param, 'feature');
2581 my $running = PVE
::QemuServer
::check_running
($vmid);
2583 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2586 my $snap = $conf->{snapshots
}->{$snapname};
2587 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2590 my $storecfg = PVE
::Storage
::config
();
2592 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2593 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2596 hasFeature
=> $hasFeature,
2597 nodes
=> [ keys %$nodelist ],
2601 __PACKAGE__-
>register_method({
2603 path
=> '{vmid}/clone',
2607 description
=> "Create a copy of virtual machine/template.",
2609 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2610 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2611 "'Datastore.AllocateSpace' on any used storage.",
2614 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2616 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2617 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2622 additionalProperties
=> 0,
2624 node
=> get_standard_option
('pve-node'),
2625 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2626 newid
=> get_standard_option
('pve-vmid', {
2627 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2628 description
=> 'VMID for the clone.' }),
2631 type
=> 'string', format
=> 'dns-name',
2632 description
=> "Set a name for the new VM.",
2637 description
=> "Description for the new VM.",
2641 type
=> 'string', format
=> 'pve-poolid',
2642 description
=> "Add the new VM to the specified pool.",
2644 snapname
=> get_standard_option
('pve-snapshot-name', {
2647 storage
=> get_standard_option
('pve-storage-id', {
2648 description
=> "Target storage for full clone.",
2652 description
=> "Target format for file storage. Only valid for full clone.",
2655 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2660 description
=> "Create a full copy of all disks. This is always done when " .
2661 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2663 target
=> get_standard_option
('pve-node', {
2664 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2668 description
=> "Override I/O bandwidth limit (in KiB/s).",
2672 default => 'clone limit from datacenter or storage config',
2682 my $rpcenv = PVE
::RPCEnvironment
::get
();
2684 my $authuser = $rpcenv->get_user();
2686 my $node = extract_param
($param, 'node');
2688 my $vmid = extract_param
($param, 'vmid');
2690 my $newid = extract_param
($param, 'newid');
2692 my $pool = extract_param
($param, 'pool');
2694 if (defined($pool)) {
2695 $rpcenv->check_pool_exist($pool);
2698 my $snapname = extract_param
($param, 'snapname');
2700 my $storage = extract_param
($param, 'storage');
2702 my $format = extract_param
($param, 'format');
2704 my $target = extract_param
($param, 'target');
2706 my $localnode = PVE
::INotify
::nodename
();
2708 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2710 PVE
::Cluster
::check_node_exists
($target) if $target;
2712 my $storecfg = PVE
::Storage
::config
();
2715 # check if storage is enabled on local node
2716 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2718 # check if storage is available on target node
2719 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2720 # clone only works if target storage is shared
2721 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2722 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2726 PVE
::Cluster
::check_cfs_quorum
();
2728 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2730 # exclusive lock if VM is running - else shared lock is enough;
2731 my $shared_lock = $running ?
0 : 1;
2735 # do all tests after lock
2736 # we also try to do all tests before we fork the worker
2738 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2740 PVE
::QemuConfig-
>check_lock($conf);
2742 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2744 die "unexpected state change\n" if $verify_running != $running;
2746 die "snapshot '$snapname' does not exist\n"
2747 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2749 my $full = extract_param
($param, 'full');
2750 if (!defined($full)) {
2751 $full = !PVE
::QemuConfig-
>is_template($conf);
2754 die "parameter 'storage' not allowed for linked clones\n"
2755 if defined($storage) && !$full;
2757 die "parameter 'format' not allowed for linked clones\n"
2758 if defined($format) && !$full;
2760 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2762 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2764 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2766 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2768 die "unable to create VM $newid: config file already exists\n"
2771 my $newconf = { lock => 'clone' };
2776 foreach my $opt (keys %$oldconf) {
2777 my $value = $oldconf->{$opt};
2779 # do not copy snapshot related info
2780 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2781 $opt eq 'vmstate' || $opt eq 'snapstate';
2783 # no need to copy unused images, because VMID(owner) changes anyways
2784 next if $opt =~ m/^unused\d+$/;
2786 # always change MAC! address
2787 if ($opt =~ m/^net(\d+)$/) {
2788 my $net = PVE
::QemuServer
::parse_net
($value);
2789 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2790 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2791 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2792 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2793 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2794 die "unable to parse drive options for '$opt'\n" if !$drive;
2795 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2796 $newconf->{$opt} = $value; # simply copy configuration
2798 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2799 die "Full clone feature is not supported for drive '$opt'\n"
2800 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2801 $fullclone->{$opt} = 1;
2803 # not full means clone instead of copy
2804 die "Linked clone feature is not supported for drive '$opt'\n"
2805 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2807 $drives->{$opt} = $drive;
2808 push @$vollist, $drive->{file
};
2811 # copy everything else
2812 $newconf->{$opt} = $value;
2816 # auto generate a new uuid
2817 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2818 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2819 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2821 # auto generate a new vmgenid if the option was set
2822 if ($newconf->{vmgenid
}) {
2823 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2826 delete $newconf->{template
};
2828 if ($param->{name
}) {
2829 $newconf->{name
} = $param->{name
};
2831 if ($oldconf->{name
}) {
2832 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2834 $newconf->{name
} = "Copy-of-VM-$vmid";
2838 if ($param->{description
}) {
2839 $newconf->{description
} = $param->{description
};
2842 # create empty/temp config - this fails if VM already exists on other node
2843 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2848 my $newvollist = [];
2855 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2857 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2859 my $bwlimit = extract_param
($param, 'bwlimit');
2861 my $total_jobs = scalar(keys %{$drives});
2864 foreach my $opt (keys %$drives) {
2865 my $drive = $drives->{$opt};
2866 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2868 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
2869 my $storage_list = [ $src_sid ];
2870 push @$storage_list, $storage if defined($storage);
2871 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
2873 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2874 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2875 $jobs, $skipcomplete, $oldconf->{agent
}, $clonelimit);
2877 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2879 PVE
::QemuConfig-
>write_config($newid, $newconf);
2883 delete $newconf->{lock};
2885 # do not write pending changes
2886 if (my @changes = keys %{$newconf->{pending
}}) {
2887 my $pending = join(',', @changes);
2888 warn "found pending changes for '$pending', discarding for clone\n";
2889 delete $newconf->{pending
};
2892 PVE
::QemuConfig-
>write_config($newid, $newconf);
2895 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2896 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2897 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2899 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2900 die "Failed to move config to node '$target' - rename failed: $!\n"
2901 if !rename($conffile, $newconffile);
2904 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2909 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2911 sleep 1; # some storage like rbd need to wait before release volume - really?
2913 foreach my $volid (@$newvollist) {
2914 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2917 die "clone failed: $err";
2923 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2925 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2928 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2929 # Aquire exclusive lock lock for $newid
2930 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2935 __PACKAGE__-
>register_method({
2936 name
=> 'move_vm_disk',
2937 path
=> '{vmid}/move_disk',
2941 description
=> "Move volume to different storage.",
2943 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2945 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2946 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2950 additionalProperties
=> 0,
2952 node
=> get_standard_option
('pve-node'),
2953 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2956 description
=> "The disk you want to move.",
2957 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2959 storage
=> get_standard_option
('pve-storage-id', {
2960 description
=> "Target storage.",
2961 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2965 description
=> "Target Format.",
2966 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2971 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2977 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2982 description
=> "Override I/O bandwidth limit (in KiB/s).",
2986 default => 'move limit from datacenter or storage config',
2992 description
=> "the task ID.",
2997 my $rpcenv = PVE
::RPCEnvironment
::get
();
2999 my $authuser = $rpcenv->get_user();
3001 my $node = extract_param
($param, 'node');
3003 my $vmid = extract_param
($param, 'vmid');
3005 my $digest = extract_param
($param, 'digest');
3007 my $disk = extract_param
($param, 'disk');
3009 my $storeid = extract_param
($param, 'storage');
3011 my $format = extract_param
($param, 'format');
3013 my $storecfg = PVE
::Storage
::config
();
3015 my $updatefn = sub {
3017 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3019 PVE
::QemuConfig-
>check_lock($conf);
3021 die "checksum missmatch (file change by other user?)\n"
3022 if $digest && $digest ne $conf->{digest
};
3024 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3026 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3028 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
3030 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3033 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3034 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3038 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
3039 (!$format || !$oldfmt || $oldfmt eq $format);
3041 # this only checks snapshots because $disk is passed!
3042 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3043 die "you can't move a disk with snapshots and delete the source\n"
3044 if $snapshotted && $param->{delete};
3046 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3048 my $running = PVE
::QemuServer
::check_running
($vmid);
3050 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3054 my $newvollist = [];
3060 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3062 warn "moving disk with snapshots, snapshots will not be moved!\n"
3065 my $bwlimit = extract_param
($param, 'bwlimit');
3066 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3068 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3069 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit);
3071 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
3073 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3075 # convert moved disk to base if part of template
3076 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3077 if PVE
::QemuConfig-
>is_template($conf);
3079 PVE
::QemuConfig-
>write_config($vmid, $conf);
3081 if ($running && PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
} && PVE
::QemuServer
::qga_check_running
($vmid)) {
3082 eval { PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-fstrim"); };
3086 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3087 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3094 foreach my $volid (@$newvollist) {
3095 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3098 die "storage migration failed: $err";
3101 if ($param->{delete}) {
3103 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3104 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3110 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3113 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3116 __PACKAGE__-
>register_method({
3117 name
=> 'migrate_vm',
3118 path
=> '{vmid}/migrate',
3122 description
=> "Migrate virtual machine. Creates a new migration task.",
3124 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3127 additionalProperties
=> 0,
3129 node
=> get_standard_option
('pve-node'),
3130 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3131 target
=> get_standard_option
('pve-node', {
3132 description
=> "Target node.",
3133 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3137 description
=> "Use online/live migration.",
3142 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3147 enum
=> ['secure', 'insecure'],
3148 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3151 migration_network
=> {
3152 type
=> 'string', format
=> 'CIDR',
3153 description
=> "CIDR of the (sub) network that is used for migration.",
3156 "with-local-disks" => {
3158 description
=> "Enable live storage migration for local disk",
3161 targetstorage
=> get_standard_option
('pve-storage-id', {
3162 description
=> "Default target storage.",
3164 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3167 description
=> "Override I/O bandwidth limit (in KiB/s).",
3171 default => 'migrate limit from datacenter or storage config',
3177 description
=> "the task ID.",
3182 my $rpcenv = PVE
::RPCEnvironment
::get
();
3183 my $authuser = $rpcenv->get_user();
3185 my $target = extract_param
($param, 'target');
3187 my $localnode = PVE
::INotify
::nodename
();
3188 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3190 PVE
::Cluster
::check_cfs_quorum
();
3192 PVE
::Cluster
::check_node_exists
($target);
3194 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3196 my $vmid = extract_param
($param, 'vmid');
3198 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3199 if !$param->{online
} && $param->{targetstorage
};
3201 raise_param_exc
({ force
=> "Only root may use this option." })
3202 if $param->{force
} && $authuser ne 'root@pam';
3204 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3205 if $param->{migration_type
} && $authuser ne 'root@pam';
3207 # allow root only until better network permissions are available
3208 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3209 if $param->{migration_network
} && $authuser ne 'root@pam';
3212 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3214 # try to detect errors early
3216 PVE
::QemuConfig-
>check_lock($conf);
3218 if (PVE
::QemuServer
::check_running
($vmid)) {
3219 die "cant migrate running VM without --online\n"
3220 if !$param->{online
};
3223 my $storecfg = PVE
::Storage
::config
();
3225 if( $param->{targetstorage
}) {
3226 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3228 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3231 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3236 print "Requesting HA migration for VM $vmid to node $target\n";
3238 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3239 PVE
::Tools
::run_command
($cmd);
3243 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3248 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3252 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3255 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3260 __PACKAGE__-
>register_method({
3262 path
=> '{vmid}/monitor',
3266 description
=> "Execute Qemu monitor commands.",
3268 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3269 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3272 additionalProperties
=> 0,
3274 node
=> get_standard_option
('pve-node'),
3275 vmid
=> get_standard_option
('pve-vmid'),
3278 description
=> "The monitor command.",
3282 returns
=> { type
=> 'string'},
3286 my $rpcenv = PVE
::RPCEnvironment
::get
();
3287 my $authuser = $rpcenv->get_user();
3290 my $command = shift;
3291 return $command =~ m/^\s*info(\s+|$)/
3292 || $command =~ m/^\s*help\s*$/;
3295 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3296 if !&$is_ro($param->{command
});
3298 my $vmid = $param->{vmid
};
3300 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3304 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3306 $res = "ERROR: $@" if $@;
3311 __PACKAGE__-
>register_method({
3312 name
=> 'resize_vm',
3313 path
=> '{vmid}/resize',
3317 description
=> "Extend volume size.",
3319 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3322 additionalProperties
=> 0,
3324 node
=> get_standard_option
('pve-node'),
3325 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3326 skiplock
=> get_standard_option
('skiplock'),
3329 description
=> "The disk you want to resize.",
3330 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3334 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3335 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.",
3339 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3345 returns
=> { type
=> 'null'},
3349 my $rpcenv = PVE
::RPCEnvironment
::get
();
3351 my $authuser = $rpcenv->get_user();
3353 my $node = extract_param
($param, 'node');
3355 my $vmid = extract_param
($param, 'vmid');
3357 my $digest = extract_param
($param, 'digest');
3359 my $disk = extract_param
($param, 'disk');
3361 my $sizestr = extract_param
($param, 'size');
3363 my $skiplock = extract_param
($param, 'skiplock');
3364 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3365 if $skiplock && $authuser ne 'root@pam';
3367 my $storecfg = PVE
::Storage
::config
();
3369 my $updatefn = sub {
3371 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3373 die "checksum missmatch (file change by other user?)\n"
3374 if $digest && $digest ne $conf->{digest
};
3375 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3377 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3379 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3381 my (undef, undef, undef, undef, undef, undef, $format) =
3382 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3384 die "can't resize volume: $disk if snapshot exists\n"
3385 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3387 my $volid = $drive->{file
};
3389 die "disk '$disk' has no associated volume\n" if !$volid;
3391 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3393 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3395 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3397 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3398 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3400 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3401 my ($ext, $newsize, $unit) = ($1, $2, $4);
3404 $newsize = $newsize * 1024;
3405 } elsif ($unit eq 'M') {
3406 $newsize = $newsize * 1024 * 1024;
3407 } elsif ($unit eq 'G') {
3408 $newsize = $newsize * 1024 * 1024 * 1024;
3409 } elsif ($unit eq 'T') {
3410 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3413 $newsize += $size if $ext;
3414 $newsize = int($newsize);
3416 die "shrinking disks is not supported\n" if $newsize < $size;
3418 return if $size == $newsize;
3420 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3422 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3424 $drive->{size
} = $newsize;
3425 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3427 PVE
::QemuConfig-
>write_config($vmid, $conf);
3430 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3434 __PACKAGE__-
>register_method({
3435 name
=> 'snapshot_list',
3436 path
=> '{vmid}/snapshot',
3438 description
=> "List all snapshots.",
3440 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3443 protected
=> 1, # qemu pid files are only readable by root
3445 additionalProperties
=> 0,
3447 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3448 node
=> get_standard_option
('pve-node'),
3457 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3461 description
=> "Snapshot includes RAM.",
3466 description
=> "Snapshot description.",
3470 description
=> "Snapshot creation time",
3472 renderer
=> 'timestamp',
3476 description
=> "Parent snapshot identifier.",
3482 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3487 my $vmid = $param->{vmid
};
3489 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3490 my $snaphash = $conf->{snapshots
} || {};
3494 foreach my $name (keys %$snaphash) {
3495 my $d = $snaphash->{$name};
3498 snaptime
=> $d->{snaptime
} || 0,
3499 vmstate
=> $d->{vmstate
} ?
1 : 0,
3500 description
=> $d->{description
} || '',
3502 $item->{parent
} = $d->{parent
} if $d->{parent
};
3503 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3507 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3510 digest
=> $conf->{digest
},
3511 running
=> $running,
3512 description
=> "You are here!",
3514 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3516 push @$res, $current;
3521 __PACKAGE__-
>register_method({
3523 path
=> '{vmid}/snapshot',
3527 description
=> "Snapshot a VM.",
3529 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3532 additionalProperties
=> 0,
3534 node
=> get_standard_option
('pve-node'),
3535 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3536 snapname
=> get_standard_option
('pve-snapshot-name'),
3540 description
=> "Save the vmstate",
3545 description
=> "A textual description or comment.",
3551 description
=> "the task ID.",
3556 my $rpcenv = PVE
::RPCEnvironment
::get
();
3558 my $authuser = $rpcenv->get_user();
3560 my $node = extract_param
($param, 'node');
3562 my $vmid = extract_param
($param, 'vmid');
3564 my $snapname = extract_param
($param, 'snapname');
3566 die "unable to use snapshot name 'current' (reserved name)\n"
3567 if $snapname eq 'current';
3570 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3571 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3572 $param->{description
});
3575 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3578 __PACKAGE__-
>register_method({
3579 name
=> 'snapshot_cmd_idx',
3580 path
=> '{vmid}/snapshot/{snapname}',
3587 additionalProperties
=> 0,
3589 vmid
=> get_standard_option
('pve-vmid'),
3590 node
=> get_standard_option
('pve-node'),
3591 snapname
=> get_standard_option
('pve-snapshot-name'),
3600 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3607 push @$res, { cmd
=> 'rollback' };
3608 push @$res, { cmd
=> 'config' };
3613 __PACKAGE__-
>register_method({
3614 name
=> 'update_snapshot_config',
3615 path
=> '{vmid}/snapshot/{snapname}/config',
3619 description
=> "Update snapshot metadata.",
3621 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3624 additionalProperties
=> 0,
3626 node
=> get_standard_option
('pve-node'),
3627 vmid
=> get_standard_option
('pve-vmid'),
3628 snapname
=> get_standard_option
('pve-snapshot-name'),
3632 description
=> "A textual description or comment.",
3636 returns
=> { type
=> 'null' },
3640 my $rpcenv = PVE
::RPCEnvironment
::get
();
3642 my $authuser = $rpcenv->get_user();
3644 my $vmid = extract_param
($param, 'vmid');
3646 my $snapname = extract_param
($param, 'snapname');
3648 return undef if !defined($param->{description
});
3650 my $updatefn = sub {
3652 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3654 PVE
::QemuConfig-
>check_lock($conf);
3656 my $snap = $conf->{snapshots
}->{$snapname};
3658 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3660 $snap->{description
} = $param->{description
} if defined($param->{description
});
3662 PVE
::QemuConfig-
>write_config($vmid, $conf);
3665 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3670 __PACKAGE__-
>register_method({
3671 name
=> 'get_snapshot_config',
3672 path
=> '{vmid}/snapshot/{snapname}/config',
3675 description
=> "Get snapshot configuration",
3677 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3680 additionalProperties
=> 0,
3682 node
=> get_standard_option
('pve-node'),
3683 vmid
=> get_standard_option
('pve-vmid'),
3684 snapname
=> get_standard_option
('pve-snapshot-name'),
3687 returns
=> { type
=> "object" },
3691 my $rpcenv = PVE
::RPCEnvironment
::get
();
3693 my $authuser = $rpcenv->get_user();
3695 my $vmid = extract_param
($param, 'vmid');
3697 my $snapname = extract_param
($param, 'snapname');
3699 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3701 my $snap = $conf->{snapshots
}->{$snapname};
3703 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3708 __PACKAGE__-
>register_method({
3710 path
=> '{vmid}/snapshot/{snapname}/rollback',
3714 description
=> "Rollback VM state to specified snapshot.",
3716 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3719 additionalProperties
=> 0,
3721 node
=> get_standard_option
('pve-node'),
3722 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3723 snapname
=> get_standard_option
('pve-snapshot-name'),
3728 description
=> "the task ID.",
3733 my $rpcenv = PVE
::RPCEnvironment
::get
();
3735 my $authuser = $rpcenv->get_user();
3737 my $node = extract_param
($param, 'node');
3739 my $vmid = extract_param
($param, 'vmid');
3741 my $snapname = extract_param
($param, 'snapname');
3744 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3745 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3749 # hold migration lock, this makes sure that nobody create replication snapshots
3750 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3753 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3756 __PACKAGE__-
>register_method({
3757 name
=> 'delsnapshot',
3758 path
=> '{vmid}/snapshot/{snapname}',
3762 description
=> "Delete a VM snapshot.",
3764 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3767 additionalProperties
=> 0,
3769 node
=> get_standard_option
('pve-node'),
3770 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3771 snapname
=> get_standard_option
('pve-snapshot-name'),
3775 description
=> "For removal from config file, even if removing disk snapshots fails.",
3781 description
=> "the task ID.",
3786 my $rpcenv = PVE
::RPCEnvironment
::get
();
3788 my $authuser = $rpcenv->get_user();
3790 my $node = extract_param
($param, 'node');
3792 my $vmid = extract_param
($param, 'vmid');
3794 my $snapname = extract_param
($param, 'snapname');
3797 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3798 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3801 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3804 __PACKAGE__-
>register_method({
3806 path
=> '{vmid}/template',
3810 description
=> "Create a Template.",
3812 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3813 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3816 additionalProperties
=> 0,
3818 node
=> get_standard_option
('pve-node'),
3819 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3823 description
=> "If you want to convert only 1 disk to base image.",
3824 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3829 returns
=> { type
=> 'null'},
3833 my $rpcenv = PVE
::RPCEnvironment
::get
();
3835 my $authuser = $rpcenv->get_user();
3837 my $node = extract_param
($param, 'node');
3839 my $vmid = extract_param
($param, 'vmid');
3841 my $disk = extract_param
($param, 'disk');
3843 my $updatefn = sub {
3845 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3847 PVE
::QemuConfig-
>check_lock($conf);
3849 die "unable to create template, because VM contains snapshots\n"
3850 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3852 die "you can't convert a template to a template\n"
3853 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3855 die "you can't convert a VM to template if VM is running\n"
3856 if PVE
::QemuServer
::check_running
($vmid);
3859 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3862 $conf->{template
} = 1;
3863 PVE
::QemuConfig-
>write_config($vmid, $conf);
3865 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3868 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);