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
();
2025 my $authuser = $rpcenv->get_user();
2027 my $node = extract_param
($param, 'node');
2029 my $vmid = extract_param
($param, 'vmid');
2031 my $machine = extract_param
($param, 'machine');
2033 my $stateuri = extract_param
($param, 'stateuri');
2034 raise_param_exc
({ stateuri
=> "Only root may use this option." })
2035 if $stateuri && $authuser ne 'root@pam';
2037 my $skiplock = extract_param
($param, 'skiplock');
2038 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2039 if $skiplock && $authuser ne 'root@pam';
2041 my $migratedfrom = extract_param
($param, 'migratedfrom');
2042 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2043 if $migratedfrom && $authuser ne 'root@pam';
2045 my $migration_type = extract_param
($param, 'migration_type');
2046 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2047 if $migration_type && $authuser ne 'root@pam';
2049 my $migration_network = extract_param
($param, 'migration_network');
2050 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2051 if $migration_network && $authuser ne 'root@pam';
2053 my $targetstorage = extract_param
($param, 'targetstorage');
2054 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
2055 if $targetstorage && $authuser ne 'root@pam';
2057 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2058 if $targetstorage && !$migratedfrom;
2060 # read spice ticket from STDIN
2062 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2063 if (defined(my $line = <STDIN
>)) {
2065 $spice_ticket = $line;
2069 PVE
::Cluster
::check_cfs_quorum
();
2071 my $storecfg = PVE
::Storage
::config
();
2073 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
2074 $rpcenv->{type
} ne 'ha') {
2079 my $service = "vm:$vmid";
2081 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
2083 print "Requesting HA start for VM $vmid\n";
2085 PVE
::Tools
::run_command
($cmd);
2090 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2097 syslog
('info', "start VM $vmid: $upid\n");
2099 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2100 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
2105 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2109 __PACKAGE__-
>register_method({
2111 path
=> '{vmid}/status/stop',
2115 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2116 "is akin to pulling the power plug of a running computer and may damage the VM data",
2118 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2121 additionalProperties
=> 0,
2123 node
=> get_standard_option
('pve-node'),
2124 vmid
=> get_standard_option
('pve-vmid',
2125 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2126 skiplock
=> get_standard_option
('skiplock'),
2127 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2129 description
=> "Wait maximal timeout seconds.",
2135 description
=> "Do not deactivate storage volumes.",
2148 my $rpcenv = PVE
::RPCEnvironment
::get
();
2150 my $authuser = $rpcenv->get_user();
2152 my $node = extract_param
($param, 'node');
2154 my $vmid = extract_param
($param, 'vmid');
2156 my $skiplock = extract_param
($param, 'skiplock');
2157 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2158 if $skiplock && $authuser ne 'root@pam';
2160 my $keepActive = extract_param
($param, 'keepActive');
2161 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2162 if $keepActive && $authuser ne 'root@pam';
2164 my $migratedfrom = extract_param
($param, 'migratedfrom');
2165 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2166 if $migratedfrom && $authuser ne 'root@pam';
2169 my $storecfg = PVE
::Storage
::config
();
2171 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2176 my $service = "vm:$vmid";
2178 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2180 print "Requesting HA stop for VM $vmid\n";
2182 PVE
::Tools
::run_command
($cmd);
2187 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2193 syslog
('info', "stop VM $vmid: $upid\n");
2195 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2196 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2201 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2205 __PACKAGE__-
>register_method({
2207 path
=> '{vmid}/status/reset',
2211 description
=> "Reset virtual machine.",
2213 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2216 additionalProperties
=> 0,
2218 node
=> get_standard_option
('pve-node'),
2219 vmid
=> get_standard_option
('pve-vmid',
2220 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2221 skiplock
=> get_standard_option
('skiplock'),
2230 my $rpcenv = PVE
::RPCEnvironment
::get
();
2232 my $authuser = $rpcenv->get_user();
2234 my $node = extract_param
($param, 'node');
2236 my $vmid = extract_param
($param, 'vmid');
2238 my $skiplock = extract_param
($param, 'skiplock');
2239 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2240 if $skiplock && $authuser ne 'root@pam';
2242 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2247 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2252 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2255 __PACKAGE__-
>register_method({
2256 name
=> 'vm_shutdown',
2257 path
=> '{vmid}/status/shutdown',
2261 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2262 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2264 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2267 additionalProperties
=> 0,
2269 node
=> get_standard_option
('pve-node'),
2270 vmid
=> get_standard_option
('pve-vmid',
2271 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2272 skiplock
=> get_standard_option
('skiplock'),
2274 description
=> "Wait maximal timeout seconds.",
2280 description
=> "Make sure the VM stops.",
2286 description
=> "Do not deactivate storage volumes.",
2299 my $rpcenv = PVE
::RPCEnvironment
::get
();
2301 my $authuser = $rpcenv->get_user();
2303 my $node = extract_param
($param, 'node');
2305 my $vmid = extract_param
($param, 'vmid');
2307 my $skiplock = extract_param
($param, 'skiplock');
2308 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2309 if $skiplock && $authuser ne 'root@pam';
2311 my $keepActive = extract_param
($param, 'keepActive');
2312 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2313 if $keepActive && $authuser ne 'root@pam';
2315 my $storecfg = PVE
::Storage
::config
();
2319 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2320 # otherwise, we will infer a shutdown command, but run into the timeout,
2321 # then when the vm is resumed, it will instantly shutdown
2323 # checking the qmp status here to get feedback to the gui/cli/api
2324 # and the status query should not take too long
2327 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2331 if (!$err && $qmpstatus->{status
} eq "paused") {
2332 if ($param->{forceStop
}) {
2333 warn "VM is paused - stop instead of shutdown\n";
2336 die "VM is paused - cannot shutdown\n";
2340 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2341 ($rpcenv->{type
} ne 'ha')) {
2346 my $service = "vm:$vmid";
2348 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2350 print "Requesting HA stop for VM $vmid\n";
2352 PVE
::Tools
::run_command
($cmd);
2357 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2364 syslog
('info', "shutdown VM $vmid: $upid\n");
2366 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2367 $shutdown, $param->{forceStop
}, $keepActive);
2372 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2376 __PACKAGE__-
>register_method({
2377 name
=> 'vm_suspend',
2378 path
=> '{vmid}/status/suspend',
2382 description
=> "Suspend virtual machine.",
2384 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2387 additionalProperties
=> 0,
2389 node
=> get_standard_option
('pve-node'),
2390 vmid
=> get_standard_option
('pve-vmid',
2391 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2392 skiplock
=> get_standard_option
('skiplock'),
2397 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2399 statestorage
=> get_standard_option
('pve-storage-id', {
2400 description
=> "The storage for the VM state",
2401 requires
=> 'todisk',
2403 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2413 my $rpcenv = PVE
::RPCEnvironment
::get
();
2415 my $authuser = $rpcenv->get_user();
2417 my $node = extract_param
($param, 'node');
2419 my $vmid = extract_param
($param, 'vmid');
2421 my $todisk = extract_param
($param, 'todisk') // 0;
2423 my $statestorage = extract_param
($param, 'statestorage');
2425 my $skiplock = extract_param
($param, 'skiplock');
2426 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2427 if $skiplock && $authuser ne 'root@pam';
2429 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2431 die "Cannot suspend HA managed VM to disk\n"
2432 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2434 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2439 syslog
('info', "suspend VM $vmid: $upid\n");
2441 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2446 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2449 __PACKAGE__-
>register_method({
2450 name
=> 'vm_resume',
2451 path
=> '{vmid}/status/resume',
2455 description
=> "Resume virtual machine.",
2457 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2460 additionalProperties
=> 0,
2462 node
=> get_standard_option
('pve-node'),
2463 vmid
=> get_standard_option
('pve-vmid',
2464 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2465 skiplock
=> get_standard_option
('skiplock'),
2466 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2476 my $rpcenv = PVE
::RPCEnvironment
::get
();
2478 my $authuser = $rpcenv->get_user();
2480 my $node = extract_param
($param, 'node');
2482 my $vmid = extract_param
($param, 'vmid');
2484 my $skiplock = extract_param
($param, 'skiplock');
2485 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2486 if $skiplock && $authuser ne 'root@pam';
2488 my $nocheck = extract_param
($param, 'nocheck');
2490 my $to_disk_suspended;
2492 PVE
::QemuConfig-
>lock_config($vmid, sub {
2493 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2494 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2498 die "VM $vmid not running\n"
2499 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2504 syslog
('info', "resume VM $vmid: $upid\n");
2506 if (!$to_disk_suspended) {
2507 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2509 my $storecfg = PVE
::Storage
::config
();
2510 PVE
::QemuServer
::vm_start
($storecfg, $vmid, undef, $skiplock);
2516 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2519 __PACKAGE__-
>register_method({
2520 name
=> 'vm_sendkey',
2521 path
=> '{vmid}/sendkey',
2525 description
=> "Send key event to virtual machine.",
2527 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2530 additionalProperties
=> 0,
2532 node
=> get_standard_option
('pve-node'),
2533 vmid
=> get_standard_option
('pve-vmid',
2534 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2535 skiplock
=> get_standard_option
('skiplock'),
2537 description
=> "The key (qemu monitor encoding).",
2542 returns
=> { type
=> 'null'},
2546 my $rpcenv = PVE
::RPCEnvironment
::get
();
2548 my $authuser = $rpcenv->get_user();
2550 my $node = extract_param
($param, 'node');
2552 my $vmid = extract_param
($param, 'vmid');
2554 my $skiplock = extract_param
($param, 'skiplock');
2555 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2556 if $skiplock && $authuser ne 'root@pam';
2558 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2563 __PACKAGE__-
>register_method({
2564 name
=> 'vm_feature',
2565 path
=> '{vmid}/feature',
2569 description
=> "Check if feature for virtual machine is available.",
2571 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2574 additionalProperties
=> 0,
2576 node
=> get_standard_option
('pve-node'),
2577 vmid
=> get_standard_option
('pve-vmid'),
2579 description
=> "Feature to check.",
2581 enum
=> [ 'snapshot', 'clone', 'copy' ],
2583 snapname
=> get_standard_option
('pve-snapshot-name', {
2591 hasFeature
=> { type
=> 'boolean' },
2594 items
=> { type
=> 'string' },
2601 my $node = extract_param
($param, 'node');
2603 my $vmid = extract_param
($param, 'vmid');
2605 my $snapname = extract_param
($param, 'snapname');
2607 my $feature = extract_param
($param, 'feature');
2609 my $running = PVE
::QemuServer
::check_running
($vmid);
2611 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2614 my $snap = $conf->{snapshots
}->{$snapname};
2615 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2618 my $storecfg = PVE
::Storage
::config
();
2620 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2621 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2624 hasFeature
=> $hasFeature,
2625 nodes
=> [ keys %$nodelist ],
2629 __PACKAGE__-
>register_method({
2631 path
=> '{vmid}/clone',
2635 description
=> "Create a copy of virtual machine/template.",
2637 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2638 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2639 "'Datastore.AllocateSpace' on any used storage.",
2642 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2644 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2645 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2650 additionalProperties
=> 0,
2652 node
=> get_standard_option
('pve-node'),
2653 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2654 newid
=> get_standard_option
('pve-vmid', {
2655 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2656 description
=> 'VMID for the clone.' }),
2659 type
=> 'string', format
=> 'dns-name',
2660 description
=> "Set a name for the new VM.",
2665 description
=> "Description for the new VM.",
2669 type
=> 'string', format
=> 'pve-poolid',
2670 description
=> "Add the new VM to the specified pool.",
2672 snapname
=> get_standard_option
('pve-snapshot-name', {
2675 storage
=> get_standard_option
('pve-storage-id', {
2676 description
=> "Target storage for full clone.",
2680 description
=> "Target format for file storage. Only valid for full clone.",
2683 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2688 description
=> "Create a full copy of all disks. This is always done when " .
2689 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2691 target
=> get_standard_option
('pve-node', {
2692 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2696 description
=> "Override I/O bandwidth limit (in KiB/s).",
2700 default => 'clone limit from datacenter or storage config',
2710 my $rpcenv = PVE
::RPCEnvironment
::get
();
2712 my $authuser = $rpcenv->get_user();
2714 my $node = extract_param
($param, 'node');
2716 my $vmid = extract_param
($param, 'vmid');
2718 my $newid = extract_param
($param, 'newid');
2720 my $pool = extract_param
($param, 'pool');
2722 if (defined($pool)) {
2723 $rpcenv->check_pool_exist($pool);
2726 my $snapname = extract_param
($param, 'snapname');
2728 my $storage = extract_param
($param, 'storage');
2730 my $format = extract_param
($param, 'format');
2732 my $target = extract_param
($param, 'target');
2734 my $localnode = PVE
::INotify
::nodename
();
2736 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2738 PVE
::Cluster
::check_node_exists
($target) if $target;
2740 my $storecfg = PVE
::Storage
::config
();
2743 # check if storage is enabled on local node
2744 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2746 # check if storage is available on target node
2747 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2748 # clone only works if target storage is shared
2749 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2750 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2754 PVE
::Cluster
::check_cfs_quorum
();
2756 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2758 # exclusive lock if VM is running - else shared lock is enough;
2759 my $shared_lock = $running ?
0 : 1;
2763 # do all tests after lock
2764 # we also try to do all tests before we fork the worker
2766 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2768 PVE
::QemuConfig-
>check_lock($conf);
2770 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2772 die "unexpected state change\n" if $verify_running != $running;
2774 die "snapshot '$snapname' does not exist\n"
2775 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2777 my $full = extract_param
($param, 'full');
2778 if (!defined($full)) {
2779 $full = !PVE
::QemuConfig-
>is_template($conf);
2782 die "parameter 'storage' not allowed for linked clones\n"
2783 if defined($storage) && !$full;
2785 die "parameter 'format' not allowed for linked clones\n"
2786 if defined($format) && !$full;
2788 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2790 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2792 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2794 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2796 die "unable to create VM $newid: config file already exists\n"
2799 my $newconf = { lock => 'clone' };
2804 foreach my $opt (keys %$oldconf) {
2805 my $value = $oldconf->{$opt};
2807 # do not copy snapshot related info
2808 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2809 $opt eq 'vmstate' || $opt eq 'snapstate';
2811 # no need to copy unused images, because VMID(owner) changes anyways
2812 next if $opt =~ m/^unused\d+$/;
2814 # always change MAC! address
2815 if ($opt =~ m/^net(\d+)$/) {
2816 my $net = PVE
::QemuServer
::parse_net
($value);
2817 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2818 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2819 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2820 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2821 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2822 die "unable to parse drive options for '$opt'\n" if !$drive;
2823 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2824 $newconf->{$opt} = $value; # simply copy configuration
2826 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2827 die "Full clone feature is not supported for drive '$opt'\n"
2828 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2829 $fullclone->{$opt} = 1;
2831 # not full means clone instead of copy
2832 die "Linked clone feature is not supported for drive '$opt'\n"
2833 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2835 $drives->{$opt} = $drive;
2836 push @$vollist, $drive->{file
};
2839 # copy everything else
2840 $newconf->{$opt} = $value;
2844 # auto generate a new uuid
2845 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2846 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2847 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2849 # auto generate a new vmgenid if the option was set
2850 if ($newconf->{vmgenid
}) {
2851 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2854 delete $newconf->{template
};
2856 if ($param->{name
}) {
2857 $newconf->{name
} = $param->{name
};
2859 if ($oldconf->{name
}) {
2860 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2862 $newconf->{name
} = "Copy-of-VM-$vmid";
2866 if ($param->{description
}) {
2867 $newconf->{description
} = $param->{description
};
2870 # create empty/temp config - this fails if VM already exists on other node
2871 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2876 my $newvollist = [];
2883 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2885 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2887 my $bwlimit = extract_param
($param, 'bwlimit');
2889 my $total_jobs = scalar(keys %{$drives});
2892 foreach my $opt (keys %$drives) {
2893 my $drive = $drives->{$opt};
2894 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2896 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
2897 my $storage_list = [ $src_sid ];
2898 push @$storage_list, $storage if defined($storage);
2899 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
2901 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2902 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2903 $jobs, $skipcomplete, $oldconf->{agent
}, $clonelimit);
2905 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2907 PVE
::QemuConfig-
>write_config($newid, $newconf);
2911 delete $newconf->{lock};
2913 # do not write pending changes
2914 if (my @changes = keys %{$newconf->{pending
}}) {
2915 my $pending = join(',', @changes);
2916 warn "found pending changes for '$pending', discarding for clone\n";
2917 delete $newconf->{pending
};
2920 PVE
::QemuConfig-
>write_config($newid, $newconf);
2923 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2924 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2925 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2927 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2928 die "Failed to move config to node '$target' - rename failed: $!\n"
2929 if !rename($conffile, $newconffile);
2932 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2937 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2939 sleep 1; # some storage like rbd need to wait before release volume - really?
2941 foreach my $volid (@$newvollist) {
2942 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2945 die "clone failed: $err";
2951 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2953 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2956 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2957 # Aquire exclusive lock lock for $newid
2958 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2963 __PACKAGE__-
>register_method({
2964 name
=> 'move_vm_disk',
2965 path
=> '{vmid}/move_disk',
2969 description
=> "Move volume to different storage.",
2971 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2973 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2974 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2978 additionalProperties
=> 0,
2980 node
=> get_standard_option
('pve-node'),
2981 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2984 description
=> "The disk you want to move.",
2985 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2987 storage
=> get_standard_option
('pve-storage-id', {
2988 description
=> "Target storage.",
2989 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2993 description
=> "Target Format.",
2994 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2999 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3005 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3010 description
=> "Override I/O bandwidth limit (in KiB/s).",
3014 default => 'move limit from datacenter or storage config',
3020 description
=> "the task ID.",
3025 my $rpcenv = PVE
::RPCEnvironment
::get
();
3027 my $authuser = $rpcenv->get_user();
3029 my $node = extract_param
($param, 'node');
3031 my $vmid = extract_param
($param, 'vmid');
3033 my $digest = extract_param
($param, 'digest');
3035 my $disk = extract_param
($param, 'disk');
3037 my $storeid = extract_param
($param, 'storage');
3039 my $format = extract_param
($param, 'format');
3041 my $storecfg = PVE
::Storage
::config
();
3043 my $updatefn = sub {
3045 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3047 PVE
::QemuConfig-
>check_lock($conf);
3049 die "checksum missmatch (file change by other user?)\n"
3050 if $digest && $digest ne $conf->{digest
};
3052 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3054 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3056 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
3058 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3061 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3062 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3066 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
3067 (!$format || !$oldfmt || $oldfmt eq $format);
3069 # this only checks snapshots because $disk is passed!
3070 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3071 die "you can't move a disk with snapshots and delete the source\n"
3072 if $snapshotted && $param->{delete};
3074 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3076 my $running = PVE
::QemuServer
::check_running
($vmid);
3078 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3082 my $newvollist = [];
3088 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3090 warn "moving disk with snapshots, snapshots will not be moved!\n"
3093 my $bwlimit = extract_param
($param, 'bwlimit');
3094 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3096 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3097 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit);
3099 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
3101 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3103 # convert moved disk to base if part of template
3104 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3105 if PVE
::QemuConfig-
>is_template($conf);
3107 PVE
::QemuConfig-
>write_config($vmid, $conf);
3109 if ($running && PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
} && PVE
::QemuServer
::qga_check_running
($vmid)) {
3110 eval { PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-fstrim"); };
3114 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3115 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3122 foreach my $volid (@$newvollist) {
3123 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3126 die "storage migration failed: $err";
3129 if ($param->{delete}) {
3131 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3132 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3138 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3141 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3144 __PACKAGE__-
>register_method({
3145 name
=> 'migrate_vm',
3146 path
=> '{vmid}/migrate',
3150 description
=> "Migrate virtual machine. Creates a new migration task.",
3152 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3155 additionalProperties
=> 0,
3157 node
=> get_standard_option
('pve-node'),
3158 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3159 target
=> get_standard_option
('pve-node', {
3160 description
=> "Target node.",
3161 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3165 description
=> "Use online/live migration.",
3170 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3175 enum
=> ['secure', 'insecure'],
3176 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3179 migration_network
=> {
3180 type
=> 'string', format
=> 'CIDR',
3181 description
=> "CIDR of the (sub) network that is used for migration.",
3184 "with-local-disks" => {
3186 description
=> "Enable live storage migration for local disk",
3189 targetstorage
=> get_standard_option
('pve-storage-id', {
3190 description
=> "Default target storage.",
3192 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3195 description
=> "Override I/O bandwidth limit (in KiB/s).",
3199 default => 'migrate limit from datacenter or storage config',
3205 description
=> "the task ID.",
3210 my $rpcenv = PVE
::RPCEnvironment
::get
();
3212 my $authuser = $rpcenv->get_user();
3214 my $target = extract_param
($param, 'target');
3216 my $localnode = PVE
::INotify
::nodename
();
3217 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3219 PVE
::Cluster
::check_cfs_quorum
();
3221 PVE
::Cluster
::check_node_exists
($target);
3223 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3225 my $vmid = extract_param
($param, 'vmid');
3227 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3228 if !$param->{online
} && $param->{targetstorage
};
3230 raise_param_exc
({ force
=> "Only root may use this option." })
3231 if $param->{force
} && $authuser ne 'root@pam';
3233 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3234 if $param->{migration_type
} && $authuser ne 'root@pam';
3236 # allow root only until better network permissions are available
3237 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3238 if $param->{migration_network
} && $authuser ne 'root@pam';
3241 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3243 # try to detect errors early
3245 PVE
::QemuConfig-
>check_lock($conf);
3247 if (PVE
::QemuServer
::check_running
($vmid)) {
3248 die "cant migrate running VM without --online\n"
3249 if !$param->{online
};
3252 my $storecfg = PVE
::Storage
::config
();
3254 if( $param->{targetstorage
}) {
3255 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3257 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3260 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3265 my $service = "vm:$vmid";
3267 my $cmd = ['ha-manager', 'migrate', $service, $target];
3269 print "Requesting HA migration for VM $vmid to node $target\n";
3271 PVE
::Tools
::run_command
($cmd);
3276 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3281 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3285 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3288 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3293 __PACKAGE__-
>register_method({
3295 path
=> '{vmid}/monitor',
3299 description
=> "Execute Qemu monitor commands.",
3301 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3302 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3305 additionalProperties
=> 0,
3307 node
=> get_standard_option
('pve-node'),
3308 vmid
=> get_standard_option
('pve-vmid'),
3311 description
=> "The monitor command.",
3315 returns
=> { type
=> 'string'},
3319 my $rpcenv = PVE
::RPCEnvironment
::get
();
3320 my $authuser = $rpcenv->get_user();
3323 my $command = shift;
3324 return $command =~ m/^\s*info(\s+|$)/
3325 || $command =~ m/^\s*help\s*$/;
3328 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3329 if !&$is_ro($param->{command
});
3331 my $vmid = $param->{vmid
};
3333 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3337 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3339 $res = "ERROR: $@" if $@;
3344 __PACKAGE__-
>register_method({
3345 name
=> 'resize_vm',
3346 path
=> '{vmid}/resize',
3350 description
=> "Extend volume size.",
3352 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3355 additionalProperties
=> 0,
3357 node
=> get_standard_option
('pve-node'),
3358 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3359 skiplock
=> get_standard_option
('skiplock'),
3362 description
=> "The disk you want to resize.",
3363 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3367 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3368 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.",
3372 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3378 returns
=> { type
=> 'null'},
3382 my $rpcenv = PVE
::RPCEnvironment
::get
();
3384 my $authuser = $rpcenv->get_user();
3386 my $node = extract_param
($param, 'node');
3388 my $vmid = extract_param
($param, 'vmid');
3390 my $digest = extract_param
($param, 'digest');
3392 my $disk = extract_param
($param, 'disk');
3394 my $sizestr = extract_param
($param, 'size');
3396 my $skiplock = extract_param
($param, 'skiplock');
3397 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3398 if $skiplock && $authuser ne 'root@pam';
3400 my $storecfg = PVE
::Storage
::config
();
3402 my $updatefn = sub {
3404 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3406 die "checksum missmatch (file change by other user?)\n"
3407 if $digest && $digest ne $conf->{digest
};
3408 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3410 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3412 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3414 my (undef, undef, undef, undef, undef, undef, $format) =
3415 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3417 die "can't resize volume: $disk if snapshot exists\n"
3418 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3420 my $volid = $drive->{file
};
3422 die "disk '$disk' has no associated volume\n" if !$volid;
3424 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3426 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3428 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3430 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3431 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3433 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3434 my ($ext, $newsize, $unit) = ($1, $2, $4);
3437 $newsize = $newsize * 1024;
3438 } elsif ($unit eq 'M') {
3439 $newsize = $newsize * 1024 * 1024;
3440 } elsif ($unit eq 'G') {
3441 $newsize = $newsize * 1024 * 1024 * 1024;
3442 } elsif ($unit eq 'T') {
3443 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3446 $newsize += $size if $ext;
3447 $newsize = int($newsize);
3449 die "shrinking disks is not supported\n" if $newsize < $size;
3451 return if $size == $newsize;
3453 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3455 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3457 $drive->{size
} = $newsize;
3458 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3460 PVE
::QemuConfig-
>write_config($vmid, $conf);
3463 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3467 __PACKAGE__-
>register_method({
3468 name
=> 'snapshot_list',
3469 path
=> '{vmid}/snapshot',
3471 description
=> "List all snapshots.",
3473 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3476 protected
=> 1, # qemu pid files are only readable by root
3478 additionalProperties
=> 0,
3480 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3481 node
=> get_standard_option
('pve-node'),
3490 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3494 description
=> "Snapshot includes RAM.",
3499 description
=> "Snapshot description.",
3503 description
=> "Snapshot creation time",
3505 renderer
=> 'timestamp',
3509 description
=> "Parent snapshot identifier.",
3515 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3520 my $vmid = $param->{vmid
};
3522 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3523 my $snaphash = $conf->{snapshots
} || {};
3527 foreach my $name (keys %$snaphash) {
3528 my $d = $snaphash->{$name};
3531 snaptime
=> $d->{snaptime
} || 0,
3532 vmstate
=> $d->{vmstate
} ?
1 : 0,
3533 description
=> $d->{description
} || '',
3535 $item->{parent
} = $d->{parent
} if $d->{parent
};
3536 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3540 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3543 digest
=> $conf->{digest
},
3544 running
=> $running,
3545 description
=> "You are here!",
3547 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3549 push @$res, $current;
3554 __PACKAGE__-
>register_method({
3556 path
=> '{vmid}/snapshot',
3560 description
=> "Snapshot a VM.",
3562 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3565 additionalProperties
=> 0,
3567 node
=> get_standard_option
('pve-node'),
3568 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3569 snapname
=> get_standard_option
('pve-snapshot-name'),
3573 description
=> "Save the vmstate",
3578 description
=> "A textual description or comment.",
3584 description
=> "the task ID.",
3589 my $rpcenv = PVE
::RPCEnvironment
::get
();
3591 my $authuser = $rpcenv->get_user();
3593 my $node = extract_param
($param, 'node');
3595 my $vmid = extract_param
($param, 'vmid');
3597 my $snapname = extract_param
($param, 'snapname');
3599 die "unable to use snapshot name 'current' (reserved name)\n"
3600 if $snapname eq 'current';
3603 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3604 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3605 $param->{description
});
3608 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3611 __PACKAGE__-
>register_method({
3612 name
=> 'snapshot_cmd_idx',
3613 path
=> '{vmid}/snapshot/{snapname}',
3620 additionalProperties
=> 0,
3622 vmid
=> get_standard_option
('pve-vmid'),
3623 node
=> get_standard_option
('pve-node'),
3624 snapname
=> get_standard_option
('pve-snapshot-name'),
3633 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3640 push @$res, { cmd
=> 'rollback' };
3641 push @$res, { cmd
=> 'config' };
3646 __PACKAGE__-
>register_method({
3647 name
=> 'update_snapshot_config',
3648 path
=> '{vmid}/snapshot/{snapname}/config',
3652 description
=> "Update snapshot metadata.",
3654 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3657 additionalProperties
=> 0,
3659 node
=> get_standard_option
('pve-node'),
3660 vmid
=> get_standard_option
('pve-vmid'),
3661 snapname
=> get_standard_option
('pve-snapshot-name'),
3665 description
=> "A textual description or comment.",
3669 returns
=> { type
=> 'null' },
3673 my $rpcenv = PVE
::RPCEnvironment
::get
();
3675 my $authuser = $rpcenv->get_user();
3677 my $vmid = extract_param
($param, 'vmid');
3679 my $snapname = extract_param
($param, 'snapname');
3681 return undef if !defined($param->{description
});
3683 my $updatefn = sub {
3685 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3687 PVE
::QemuConfig-
>check_lock($conf);
3689 my $snap = $conf->{snapshots
}->{$snapname};
3691 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3693 $snap->{description
} = $param->{description
} if defined($param->{description
});
3695 PVE
::QemuConfig-
>write_config($vmid, $conf);
3698 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3703 __PACKAGE__-
>register_method({
3704 name
=> 'get_snapshot_config',
3705 path
=> '{vmid}/snapshot/{snapname}/config',
3708 description
=> "Get snapshot configuration",
3710 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3713 additionalProperties
=> 0,
3715 node
=> get_standard_option
('pve-node'),
3716 vmid
=> get_standard_option
('pve-vmid'),
3717 snapname
=> get_standard_option
('pve-snapshot-name'),
3720 returns
=> { type
=> "object" },
3724 my $rpcenv = PVE
::RPCEnvironment
::get
();
3726 my $authuser = $rpcenv->get_user();
3728 my $vmid = extract_param
($param, 'vmid');
3730 my $snapname = extract_param
($param, 'snapname');
3732 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3734 my $snap = $conf->{snapshots
}->{$snapname};
3736 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3741 __PACKAGE__-
>register_method({
3743 path
=> '{vmid}/snapshot/{snapname}/rollback',
3747 description
=> "Rollback VM state to specified snapshot.",
3749 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3752 additionalProperties
=> 0,
3754 node
=> get_standard_option
('pve-node'),
3755 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3756 snapname
=> get_standard_option
('pve-snapshot-name'),
3761 description
=> "the task ID.",
3766 my $rpcenv = PVE
::RPCEnvironment
::get
();
3768 my $authuser = $rpcenv->get_user();
3770 my $node = extract_param
($param, 'node');
3772 my $vmid = extract_param
($param, 'vmid');
3774 my $snapname = extract_param
($param, 'snapname');
3777 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3778 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3782 # hold migration lock, this makes sure that nobody create replication snapshots
3783 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3786 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3789 __PACKAGE__-
>register_method({
3790 name
=> 'delsnapshot',
3791 path
=> '{vmid}/snapshot/{snapname}',
3795 description
=> "Delete a VM snapshot.",
3797 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3800 additionalProperties
=> 0,
3802 node
=> get_standard_option
('pve-node'),
3803 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3804 snapname
=> get_standard_option
('pve-snapshot-name'),
3808 description
=> "For removal from config file, even if removing disk snapshots fails.",
3814 description
=> "the task ID.",
3819 my $rpcenv = PVE
::RPCEnvironment
::get
();
3821 my $authuser = $rpcenv->get_user();
3823 my $node = extract_param
($param, 'node');
3825 my $vmid = extract_param
($param, 'vmid');
3827 my $snapname = extract_param
($param, 'snapname');
3830 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3831 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3834 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3837 __PACKAGE__-
>register_method({
3839 path
=> '{vmid}/template',
3843 description
=> "Create a Template.",
3845 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3846 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3849 additionalProperties
=> 0,
3851 node
=> get_standard_option
('pve-node'),
3852 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3856 description
=> "If you want to convert only 1 disk to base image.",
3857 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3862 returns
=> { type
=> 'null'},
3866 my $rpcenv = PVE
::RPCEnvironment
::get
();
3868 my $authuser = $rpcenv->get_user();
3870 my $node = extract_param
($param, 'node');
3872 my $vmid = extract_param
($param, 'vmid');
3874 my $disk = extract_param
($param, 'disk');
3876 my $updatefn = sub {
3878 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3880 PVE
::QemuConfig-
>check_lock($conf);
3882 die "unable to create template, because VM contains snapshots\n"
3883 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3885 die "you can't convert a template to a template\n"
3886 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3888 die "you can't convert a VM to template if VM is running\n"
3889 if PVE
::QemuServer
::check_running
($vmid);
3892 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3895 $conf->{template
} = 1;
3896 PVE
::QemuConfig-
>write_config($vmid, $conf);
3898 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3901 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);