1 package PVE
::API2
::Qemu
;
12 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
14 use PVE
::Tools
qw(extract_param);
15 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
17 use PVE
::JSONSchema
qw(get_standard_option);
19 use PVE
::ReplicationConfig
;
20 use PVE
::GuestHelpers
;
24 use PVE
::RPCEnvironment
;
25 use PVE
::AccessControl
;
29 use PVE
::API2
::Firewall
::VM
;
30 use PVE
::API2
::Qemu
::Agent
;
33 if (!$ENV{PVE_GENERATING_DOCS
}) {
34 require PVE
::HA
::Env
::PVE2
;
35 import PVE
::HA
::Env
::PVE2
;
36 require PVE
::HA
::Config
;
37 import PVE
::HA
::Config
;
41 use Data
::Dumper
; # fixme: remove
43 use base
qw(PVE::RESTHandler);
45 my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal.";
47 my $resolve_cdrom_alias = sub {
50 if (my $value = $param->{cdrom
}) {
51 $value .= ",media=cdrom" if $value !~ m/media=/;
52 $param->{ide2
} = $value;
53 delete $param->{cdrom
};
57 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
58 my $check_storage_access = sub {
59 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
61 PVE
::QemuServer
::foreach_drive
($settings, sub {
62 my ($ds, $drive) = @_;
64 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
66 my $volid = $drive->{file
};
68 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit')) {
70 } elsif ($volid =~ m/^(([^:\s]+):)?(cloudinit)$/) {
72 } elsif ($isCDROM && ($volid eq 'cdrom')) {
73 $rpcenv->check($authuser, "/", ['Sys.Console']);
74 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
75 my ($storeid, $size) = ($2 || $default_storage, $3);
76 die "no storage ID specified (and no default storage)\n" if !$storeid;
77 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
78 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
79 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
80 if !$scfg->{content
}->{images
};
82 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
86 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
87 if defined($settings->{vmstatestorage
});
90 my $check_storage_access_clone = sub {
91 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
95 PVE
::QemuServer
::foreach_drive
($conf, sub {
96 my ($ds, $drive) = @_;
98 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
100 my $volid = $drive->{file
};
102 return if !$volid || $volid eq 'none';
105 if ($volid eq 'cdrom') {
106 $rpcenv->check($authuser, "/", ['Sys.Console']);
108 # we simply allow access
109 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
110 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
111 $sharedvm = 0 if !$scfg->{shared
};
115 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
116 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
117 $sharedvm = 0 if !$scfg->{shared
};
119 $sid = $storage if $storage;
120 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
124 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
125 if defined($conf->{vmstatestorage
});
130 # Note: $pool is only needed when creating a VM, because pool permissions
131 # are automatically inherited if VM already exists inside a pool.
132 my $create_disks = sub {
133 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
140 my ($ds, $disk) = @_;
142 my $volid = $disk->{file
};
144 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
145 delete $disk->{size
};
146 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
147 } elsif ($volid =~ m!^(?:([^/:\s]+):)?cloudinit$!) {
148 my $storeid = $1 || $default_storage;
149 die "no storage ID specified (and no default storage)\n" if !$storeid;
150 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
151 my $name = "vm-$vmid-cloudinit";
155 $fmt = $disk->{format
} // "qcow2";
158 $fmt = $disk->{format
} // "raw";
161 # Initial disk created with 4 MB and aligned to 4MB on regeneration
162 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
163 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
164 $disk->{file
} = $volid;
165 $disk->{media
} = 'cdrom';
166 push @$vollist, $volid;
167 delete $disk->{format
}; # no longer needed
168 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
169 } elsif ($volid =~ $NEW_DISK_RE) {
170 my ($storeid, $size) = ($2 || $default_storage, $3);
171 die "no storage ID specified (and no default storage)\n" if !$storeid;
172 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
173 my $fmt = $disk->{format
} || $defformat;
175 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
178 if ($ds eq 'efidisk0') {
179 ($volid, $size) = PVE
::QemuServer
::create_efidisk
($storecfg, $storeid, $vmid, $fmt, $arch);
181 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
183 push @$vollist, $volid;
184 $disk->{file
} = $volid;
185 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
186 delete $disk->{format
}; # no longer needed
187 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
190 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
192 my $volid_is_new = 1;
195 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
196 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
201 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
203 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
205 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
207 die "volume $volid does not exists\n" if !$size;
209 $disk->{size
} = $size;
212 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
216 eval { PVE
::QemuServer
::foreach_drive
($settings, $code); };
218 # free allocated images on error
220 syslog
('err', "VM $vmid creating disks failed");
221 foreach my $volid (@$vollist) {
222 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
228 # modify vm config if everything went well
229 foreach my $ds (keys %$res) {
230 $conf->{$ds} = $res->{$ds};
247 my $memoryoptions = {
253 my $hwtypeoptions = {
265 my $generaloptions = {
272 'migrate_downtime' => 1,
273 'migrate_speed' => 1,
285 my $vmpoweroptions = {
292 'vmstatestorage' => 1,
295 my $cloudinitoptions = {
305 my $check_vm_modify_config_perm = sub {
306 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
308 return 1 if $authuser eq 'root@pam';
310 foreach my $opt (@$key_list) {
311 # some checks (e.g., disk, serial port, usb) need to be done somewhere
312 # else, as there the permission can be value dependend
313 next if PVE
::QemuServer
::is_valid_drivename
($opt);
314 next if $opt eq 'cdrom';
315 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
318 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
319 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
320 } elsif ($memoryoptions->{$opt}) {
321 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
322 } elsif ($hwtypeoptions->{$opt}) {
323 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
324 } elsif ($generaloptions->{$opt}) {
325 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
326 # special case for startup since it changes host behaviour
327 if ($opt eq 'startup') {
328 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
330 } elsif ($vmpoweroptions->{$opt}) {
331 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
332 } elsif ($diskoptions->{$opt}) {
333 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
334 } elsif ($cloudinitoptions->{$opt} || ($opt =~ m/^(?:net|ipconfig)\d+$/)) {
335 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
337 # catches hostpci\d+, args, lock, etc.
338 # new options will be checked here
339 die "only root can set '$opt' config\n";
346 __PACKAGE__-
>register_method({
350 description
=> "Virtual machine index (per node).",
352 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
356 protected
=> 1, # qemu pid files are only readable by root
358 additionalProperties
=> 0,
360 node
=> get_standard_option
('pve-node'),
364 description
=> "Determine the full status of active VMs.",
372 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
374 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
379 my $rpcenv = PVE
::RPCEnvironment
::get
();
380 my $authuser = $rpcenv->get_user();
382 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
385 foreach my $vmid (keys %$vmstatus) {
386 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
388 my $data = $vmstatus->{$vmid};
397 __PACKAGE__-
>register_method({
401 description
=> "Create or restore a virtual machine.",
403 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
404 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
405 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
406 user
=> 'all', # check inside
411 additionalProperties
=> 0,
412 properties
=> PVE
::QemuServer
::json_config_properties
(
414 node
=> get_standard_option
('pve-node'),
415 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
417 description
=> "The backup file.",
421 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
423 storage
=> get_standard_option
('pve-storage-id', {
424 description
=> "Default storage.",
426 completion
=> \
&PVE
::QemuServer
::complete_storage
,
431 description
=> "Allow to overwrite existing VM.",
432 requires
=> 'archive',
437 description
=> "Assign a unique random ethernet address.",
438 requires
=> 'archive',
442 type
=> 'string', format
=> 'pve-poolid',
443 description
=> "Add the VM to the specified pool.",
446 description
=> "Override I/O bandwidth limit (in KiB/s).",
450 default => 'restore limit from datacenter or storage config',
456 description
=> "Start VM after it was created successfully.",
466 my $rpcenv = PVE
::RPCEnvironment
::get
();
468 my $authuser = $rpcenv->get_user();
470 my $node = extract_param
($param, 'node');
472 my $vmid = extract_param
($param, 'vmid');
474 my $archive = extract_param
($param, 'archive');
475 my $is_restore = !!$archive;
477 my $storage = extract_param
($param, 'storage');
479 my $force = extract_param
($param, 'force');
481 my $unique = extract_param
($param, 'unique');
483 my $pool = extract_param
($param, 'pool');
485 my $bwlimit = extract_param
($param, 'bwlimit');
487 my $start_after_create = extract_param
($param, 'start');
489 my $filename = PVE
::QemuConfig-
>config_file($vmid);
491 my $storecfg = PVE
::Storage
::config
();
493 if (defined(my $ssh_keys = $param->{sshkeys
})) {
494 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
495 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
498 PVE
::Cluster
::check_cfs_quorum
();
500 if (defined($pool)) {
501 $rpcenv->check_pool_exist($pool);
504 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
505 if defined($storage);
507 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
509 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
511 } elsif ($archive && $force && (-f
$filename) &&
512 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
513 # OK: user has VM.Backup permissions, and want to restore an existing VM
519 &$resolve_cdrom_alias($param);
521 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
523 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
525 foreach my $opt (keys %$param) {
526 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
527 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
528 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
530 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
531 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
535 PVE
::QemuServer
::add_random_macs
($param);
537 my $keystr = join(' ', keys %$param);
538 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
540 if ($archive eq '-') {
541 die "pipe requires cli environment\n"
542 if $rpcenv->{type
} ne 'cli';
544 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
545 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
549 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
551 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
552 die "$emsg $@" if $@;
554 my $restorefn = sub {
555 my $conf = PVE
::QemuConfig-
>load_config($vmid);
557 PVE
::QemuConfig-
>check_protection($conf, $emsg);
559 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
562 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
568 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
569 # Convert restored VM to template if backup was VM template
570 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
571 warn "Convert to template.\n";
572 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
576 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
578 if ($start_after_create) {
579 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
584 # ensure no old replication state are exists
585 PVE
::ReplicationState
::delete_guest_states
($vmid);
587 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
591 # ensure no old replication state are exists
592 PVE
::ReplicationState
::delete_guest_states
($vmid);
600 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
604 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
606 if (!$conf->{bootdisk
}) {
607 my $firstdisk = PVE
::QemuServer
::resolve_first_disk
($conf);
608 $conf->{bootdisk
} = $firstdisk if $firstdisk;
611 # auto generate uuid if user did not specify smbios1 option
612 if (!$conf->{smbios1
}) {
613 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
616 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
617 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
620 PVE
::QemuConfig-
>write_config($vmid, $conf);
626 foreach my $volid (@$vollist) {
627 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
633 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
636 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
638 if ($start_after_create) {
639 print "Execute autostart\n";
640 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
645 my ($code, $worker_name);
647 $worker_name = 'qmrestore';
649 eval { $restorefn->() };
651 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
657 $worker_name = 'qmcreate';
659 eval { $createfn->() };
662 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
663 unlink($conffile) or die "failed to remove config file: $!\n";
671 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
674 __PACKAGE__-
>register_method({
679 description
=> "Directory index",
684 additionalProperties
=> 0,
686 node
=> get_standard_option
('pve-node'),
687 vmid
=> get_standard_option
('pve-vmid'),
695 subdir
=> { type
=> 'string' },
698 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
704 { subdir
=> 'config' },
705 { subdir
=> 'pending' },
706 { subdir
=> 'status' },
707 { subdir
=> 'unlink' },
708 { subdir
=> 'vncproxy' },
709 { subdir
=> 'termproxy' },
710 { subdir
=> 'migrate' },
711 { subdir
=> 'resize' },
712 { subdir
=> 'move' },
714 { subdir
=> 'rrddata' },
715 { subdir
=> 'monitor' },
716 { subdir
=> 'agent' },
717 { subdir
=> 'snapshot' },
718 { subdir
=> 'spiceproxy' },
719 { subdir
=> 'sendkey' },
720 { subdir
=> 'firewall' },
726 __PACKAGE__-
>register_method ({
727 subclass
=> "PVE::API2::Firewall::VM",
728 path
=> '{vmid}/firewall',
731 __PACKAGE__-
>register_method ({
732 subclass
=> "PVE::API2::Qemu::Agent",
733 path
=> '{vmid}/agent',
736 __PACKAGE__-
>register_method({
738 path
=> '{vmid}/rrd',
740 protected
=> 1, # fixme: can we avoid that?
742 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
744 description
=> "Read VM RRD statistics (returns PNG)",
746 additionalProperties
=> 0,
748 node
=> get_standard_option
('pve-node'),
749 vmid
=> get_standard_option
('pve-vmid'),
751 description
=> "Specify the time frame you are interested in.",
753 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
756 description
=> "The list of datasources you want to display.",
757 type
=> 'string', format
=> 'pve-configid-list',
760 description
=> "The RRD consolidation function",
762 enum
=> [ 'AVERAGE', 'MAX' ],
770 filename
=> { type
=> 'string' },
776 return PVE
::Cluster
::create_rrd_graph
(
777 "pve2-vm/$param->{vmid}", $param->{timeframe
},
778 $param->{ds
}, $param->{cf
});
782 __PACKAGE__-
>register_method({
784 path
=> '{vmid}/rrddata',
786 protected
=> 1, # fixme: can we avoid that?
788 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
790 description
=> "Read VM RRD statistics",
792 additionalProperties
=> 0,
794 node
=> get_standard_option
('pve-node'),
795 vmid
=> get_standard_option
('pve-vmid'),
797 description
=> "Specify the time frame you are interested in.",
799 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
802 description
=> "The RRD consolidation function",
804 enum
=> [ 'AVERAGE', 'MAX' ],
819 return PVE
::Cluster
::create_rrd_data
(
820 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
824 __PACKAGE__-
>register_method({
826 path
=> '{vmid}/config',
829 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
831 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
834 additionalProperties
=> 0,
836 node
=> get_standard_option
('pve-node'),
837 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
839 description
=> "Get current values (instead of pending values).",
844 snapshot
=> get_standard_option
('pve-snapshot-name', {
845 description
=> "Fetch config values from given snapshot.",
848 my ($cmd, $pname, $cur, $args) = @_;
849 PVE
::QemuConfig-
>snapshot_list($args->[0]);
855 description
=> "The current VM configuration.",
857 properties
=> PVE
::QemuServer
::json_config_properties
({
860 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
867 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
869 if (my $snapname = $param->{snapshot
}) {
870 my $snapshot = $conf->{snapshots
}->{$snapname};
871 die "snapshot '$snapname' does not exist\n" if !defined($snapshot);
873 $snapshot->{digest
} = $conf->{digest
}; # keep file digest for API
878 delete $conf->{snapshots
};
880 if (!$param->{current
}) {
881 foreach my $opt (keys %{$conf->{pending
}}) {
882 next if $opt eq 'delete';
883 my $value = $conf->{pending
}->{$opt};
884 next if ref($value); # just to be sure
885 $conf->{$opt} = $value;
887 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
888 foreach my $opt (keys %$pending_delete_hash) {
889 delete $conf->{$opt} if $conf->{$opt};
893 delete $conf->{pending
};
895 # hide cloudinit password
896 if ($conf->{cipassword
}) {
897 $conf->{cipassword
} = '**********';
903 __PACKAGE__-
>register_method({
904 name
=> 'vm_pending',
905 path
=> '{vmid}/pending',
908 description
=> "Get virtual machine configuration, including pending changes.",
910 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
913 additionalProperties
=> 0,
915 node
=> get_standard_option
('pve-node'),
916 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
925 description
=> "Configuration option name.",
929 description
=> "Current value.",
934 description
=> "Pending value.",
939 description
=> "Indicates a pending delete request if present and not 0. " .
940 "The value 2 indicates a force-delete request.",
952 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
954 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
958 foreach my $opt (keys %$conf) {
959 next if ref($conf->{$opt});
960 my $item = { key
=> $opt };
961 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
962 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
963 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
965 # hide cloudinit password
966 if ($opt eq 'cipassword') {
967 $item->{value
} = '**********' if defined($item->{value
});
968 # the trailing space so that the pending string is different
969 $item->{pending
} = '********** ' if defined($item->{pending
});
974 foreach my $opt (keys %{$conf->{pending
}}) {
975 next if $opt eq 'delete';
976 next if ref($conf->{pending
}->{$opt}); # just to be sure
977 next if defined($conf->{$opt});
978 my $item = { key
=> $opt };
979 $item->{pending
} = $conf->{pending
}->{$opt};
981 # hide cloudinit password
982 if ($opt eq 'cipassword') {
983 $item->{pending
} = '**********' if defined($item->{pending
});
988 while (my ($opt, $force) = each %$pending_delete_hash) {
989 next if $conf->{pending
}->{$opt}; # just to be sure
990 next if $conf->{$opt};
991 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
998 # POST/PUT {vmid}/config implementation
1000 # The original API used PUT (idempotent) an we assumed that all operations
1001 # are fast. But it turned out that almost any configuration change can
1002 # involve hot-plug actions, or disk alloc/free. Such actions can take long
1003 # time to complete and have side effects (not idempotent).
1005 # The new implementation uses POST and forks a worker process. We added
1006 # a new option 'background_delay'. If specified we wait up to
1007 # 'background_delay' second for the worker task to complete. It returns null
1008 # if the task is finished within that time, else we return the UPID.
1010 my $update_vm_api = sub {
1011 my ($param, $sync) = @_;
1013 my $rpcenv = PVE
::RPCEnvironment
::get
();
1015 my $authuser = $rpcenv->get_user();
1017 my $node = extract_param
($param, 'node');
1019 my $vmid = extract_param
($param, 'vmid');
1021 my $digest = extract_param
($param, 'digest');
1023 my $background_delay = extract_param
($param, 'background_delay');
1025 if (defined(my $cipassword = $param->{cipassword
})) {
1026 # Same logic as in cloud-init (but with the regex fixed...)
1027 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
1028 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1031 my @paramarr = (); # used for log message
1032 foreach my $key (sort keys %$param) {
1033 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1034 push @paramarr, "-$key", $value;
1037 my $skiplock = extract_param
($param, 'skiplock');
1038 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1039 if $skiplock && $authuser ne 'root@pam';
1041 my $delete_str = extract_param
($param, 'delete');
1043 my $revert_str = extract_param
($param, 'revert');
1045 my $force = extract_param
($param, 'force');
1047 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1048 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1049 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1052 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1054 my $storecfg = PVE
::Storage
::config
();
1056 my $defaults = PVE
::QemuServer
::load_defaults
();
1058 &$resolve_cdrom_alias($param);
1060 # now try to verify all parameters
1063 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1064 if (!PVE
::QemuServer
::option_exists
($opt)) {
1065 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1068 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1069 "-revert $opt' at the same time" })
1070 if defined($param->{$opt});
1072 $revert->{$opt} = 1;
1076 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1077 $opt = 'ide2' if $opt eq 'cdrom';
1079 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1080 "-delete $opt' at the same time" })
1081 if defined($param->{$opt});
1083 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1084 "-revert $opt' at the same time" })
1087 if (!PVE
::QemuServer
::option_exists
($opt)) {
1088 raise_param_exc
({ delete => "unknown option '$opt'" });
1094 my $repl_conf = PVE
::ReplicationConfig-
>new();
1095 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1096 my $check_replication = sub {
1098 return if !$is_replicated;
1099 my $volid = $drive->{file
};
1100 return if !$volid || !($drive->{replicate
}//1);
1101 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1102 my ($storeid, $format);
1103 if ($volid =~ $NEW_DISK_RE) {
1105 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1107 ($storeid, undef) = PVE
::Storage
::parse_volume_id
($volid, 1);
1108 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1110 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1111 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1112 return if $scfg->{shared
};
1113 die "cannot add non-replicatable volume to a replicated VM\n";
1116 foreach my $opt (keys %$param) {
1117 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1118 # cleanup drive path
1119 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1120 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1121 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1122 $check_replication->($drive);
1123 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1124 } elsif ($opt =~ m/^net(\d+)$/) {
1126 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1127 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1128 } elsif ($opt eq 'vmgenid') {
1129 if ($param->{$opt} eq '1') {
1130 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1132 } elsif ($opt eq 'hookscript') {
1133 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1134 raise_param_exc
({ $opt => $@ }) if $@;
1138 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1140 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1142 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1144 my $updatefn = sub {
1146 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1148 die "checksum missmatch (file change by other user?)\n"
1149 if $digest && $digest ne $conf->{digest
};
1151 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1153 foreach my $opt (keys %$revert) {
1154 if (defined($conf->{$opt})) {
1155 $param->{$opt} = $conf->{$opt};
1156 } elsif (defined($conf->{pending
}->{$opt})) {
1161 if ($param->{memory
} || defined($param->{balloon
})) {
1162 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1163 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1165 die "balloon value too large (must be smaller than assigned memory)\n"
1166 if $balloon && $balloon > $maxmem;
1169 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1173 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1175 # write updates to pending section
1177 my $modified = {}; # record what $option we modify
1179 foreach my $opt (@delete) {
1180 $modified->{$opt} = 1;
1181 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1182 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1183 warn "cannot delete '$opt' - not set in current configuration!\n";
1184 $modified->{$opt} = 0;
1188 if ($opt =~ m/^unused/) {
1189 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1190 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1191 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1192 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1193 delete $conf->{$opt};
1194 PVE
::QemuConfig-
>write_config($vmid, $conf);
1196 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1197 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1198 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1199 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1200 if defined($conf->{pending
}->{$opt});
1201 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1202 PVE
::QemuConfig-
>write_config($vmid, $conf);
1203 } elsif ($opt =~ m/^serial\d+$/) {
1204 if ($conf->{$opt} eq 'socket') {
1205 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1206 } elsif ($authuser ne 'root@pam') {
1207 die "only root can delete '$opt' config for real devices\n";
1209 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1210 PVE
::QemuConfig-
>write_config($vmid, $conf);
1211 } elsif ($opt =~ m/^usb\d+$/) {
1212 if ($conf->{$opt} =~ m/spice/) {
1213 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1214 } elsif ($authuser ne 'root@pam') {
1215 die "only root can delete '$opt' config for real devices\n";
1217 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1218 PVE
::QemuConfig-
>write_config($vmid, $conf);
1220 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1221 PVE
::QemuConfig-
>write_config($vmid, $conf);
1225 foreach my $opt (keys %$param) { # add/change
1226 $modified->{$opt} = 1;
1227 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1228 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1230 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
1232 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1233 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1234 # FIXME: cloudinit: CDROM or Disk?
1235 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1236 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1238 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1240 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1241 if defined($conf->{pending
}->{$opt});
1243 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1244 } elsif ($opt =~ m/^serial\d+/) {
1245 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1246 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1247 } elsif ($authuser ne 'root@pam') {
1248 die "only root can modify '$opt' config for real devices\n";
1250 $conf->{pending
}->{$opt} = $param->{$opt};
1251 } elsif ($opt =~ m/^usb\d+/) {
1252 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1253 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1254 } elsif ($authuser ne 'root@pam') {
1255 die "only root can modify '$opt' config for real devices\n";
1257 $conf->{pending
}->{$opt} = $param->{$opt};
1259 $conf->{pending
}->{$opt} = $param->{$opt};
1261 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1262 PVE
::QemuConfig-
>write_config($vmid, $conf);
1265 # remove pending changes when nothing changed
1266 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1267 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1268 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1270 return if !scalar(keys %{$conf->{pending
}});
1272 my $running = PVE
::QemuServer
::check_running
($vmid);
1274 # apply pending changes
1276 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1280 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1281 raise_param_exc
($errors) if scalar(keys %$errors);
1283 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1293 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1295 if ($background_delay) {
1297 # Note: It would be better to do that in the Event based HTTPServer
1298 # to avoid blocking call to sleep.
1300 my $end_time = time() + $background_delay;
1302 my $task = PVE
::Tools
::upid_decode
($upid);
1305 while (time() < $end_time) {
1306 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1308 sleep(1); # this gets interrupted when child process ends
1312 my $status = PVE
::Tools
::upid_read_status
($upid);
1313 return undef if $status eq 'OK';
1322 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1325 my $vm_config_perm_list = [
1330 'VM.Config.Network',
1332 'VM.Config.Options',
1335 __PACKAGE__-
>register_method({
1336 name
=> 'update_vm_async',
1337 path
=> '{vmid}/config',
1341 description
=> "Set virtual machine options (asynchrounous API).",
1343 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1346 additionalProperties
=> 0,
1347 properties
=> PVE
::QemuServer
::json_config_properties
(
1349 node
=> get_standard_option
('pve-node'),
1350 vmid
=> get_standard_option
('pve-vmid'),
1351 skiplock
=> get_standard_option
('skiplock'),
1353 type
=> 'string', format
=> 'pve-configid-list',
1354 description
=> "A list of settings you want to delete.",
1358 type
=> 'string', format
=> 'pve-configid-list',
1359 description
=> "Revert a pending change.",
1364 description
=> $opt_force_description,
1366 requires
=> 'delete',
1370 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1374 background_delay
=> {
1376 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1387 code
=> $update_vm_api,
1390 __PACKAGE__-
>register_method({
1391 name
=> 'update_vm',
1392 path
=> '{vmid}/config',
1396 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1398 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1401 additionalProperties
=> 0,
1402 properties
=> PVE
::QemuServer
::json_config_properties
(
1404 node
=> get_standard_option
('pve-node'),
1405 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1406 skiplock
=> get_standard_option
('skiplock'),
1408 type
=> 'string', format
=> 'pve-configid-list',
1409 description
=> "A list of settings you want to delete.",
1413 type
=> 'string', format
=> 'pve-configid-list',
1414 description
=> "Revert a pending change.",
1419 description
=> $opt_force_description,
1421 requires
=> 'delete',
1425 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1431 returns
=> { type
=> 'null' },
1434 &$update_vm_api($param, 1);
1440 __PACKAGE__-
>register_method({
1441 name
=> 'destroy_vm',
1446 description
=> "Destroy the vm (also delete all used/owned volumes).",
1448 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1451 additionalProperties
=> 0,
1453 node
=> get_standard_option
('pve-node'),
1454 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1455 skiplock
=> get_standard_option
('skiplock'),
1464 my $rpcenv = PVE
::RPCEnvironment
::get
();
1466 my $authuser = $rpcenv->get_user();
1468 my $vmid = $param->{vmid
};
1470 my $skiplock = $param->{skiplock
};
1471 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1472 if $skiplock && $authuser ne 'root@pam';
1475 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1477 my $storecfg = PVE
::Storage
::config
();
1479 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1481 die "unable to remove VM $vmid - used in HA resources\n"
1482 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1484 # do not allow destroy if there are replication jobs
1485 my $repl_conf = PVE
::ReplicationConfig-
>new();
1486 $repl_conf->check_for_existing_jobs($vmid);
1488 # early tests (repeat after locking)
1489 die "VM $vmid is running - destroy failed\n"
1490 if PVE
::QemuServer
::check_running
($vmid);
1495 syslog
('info', "destroy VM $vmid: $upid\n");
1497 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1499 PVE
::AccessControl
::remove_vm_access
($vmid);
1501 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1504 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1507 __PACKAGE__-
>register_method({
1509 path
=> '{vmid}/unlink',
1513 description
=> "Unlink/delete disk images.",
1515 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1518 additionalProperties
=> 0,
1520 node
=> get_standard_option
('pve-node'),
1521 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1523 type
=> 'string', format
=> 'pve-configid-list',
1524 description
=> "A list of disk IDs you want to delete.",
1528 description
=> $opt_force_description,
1533 returns
=> { type
=> 'null'},
1537 $param->{delete} = extract_param
($param, 'idlist');
1539 __PACKAGE__-
>update_vm($param);
1546 __PACKAGE__-
>register_method({
1548 path
=> '{vmid}/vncproxy',
1552 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1554 description
=> "Creates a TCP VNC proxy connections.",
1556 additionalProperties
=> 0,
1558 node
=> get_standard_option
('pve-node'),
1559 vmid
=> get_standard_option
('pve-vmid'),
1563 description
=> "starts websockify instead of vncproxy",
1568 additionalProperties
=> 0,
1570 user
=> { type
=> 'string' },
1571 ticket
=> { type
=> 'string' },
1572 cert
=> { type
=> 'string' },
1573 port
=> { type
=> 'integer' },
1574 upid
=> { type
=> 'string' },
1580 my $rpcenv = PVE
::RPCEnvironment
::get
();
1582 my $authuser = $rpcenv->get_user();
1584 my $vmid = $param->{vmid
};
1585 my $node = $param->{node
};
1586 my $websocket = $param->{websocket
};
1588 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1589 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1591 my $authpath = "/vms/$vmid";
1593 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1595 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1601 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1602 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1603 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1604 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1605 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1607 $family = PVE
::Tools
::get_host_address_family
($node);
1610 my $port = PVE
::Tools
::next_vnc_port
($family);
1617 syslog
('info', "starting vnc proxy $upid\n");
1623 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1625 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1626 '-timeout', $timeout, '-authpath', $authpath,
1627 '-perm', 'Sys.Console'];
1629 if ($param->{websocket
}) {
1630 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1631 push @$cmd, '-notls', '-listen', 'localhost';
1634 push @$cmd, '-c', @$remcmd, @$termcmd;
1636 PVE
::Tools
::run_command
($cmd);
1640 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1642 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1644 my $sock = IO
::Socket
::IP-
>new(
1649 GetAddrInfoFlags
=> 0,
1650 ) or die "failed to create socket: $!\n";
1651 # Inside the worker we shouldn't have any previous alarms
1652 # running anyway...:
1654 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1656 accept(my $cli, $sock) or die "connection failed: $!\n";
1659 if (PVE
::Tools
::run_command
($cmd,
1660 output
=> '>&'.fileno($cli),
1661 input
=> '<&'.fileno($cli),
1664 die "Failed to run vncproxy.\n";
1671 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1673 PVE
::Tools
::wait_for_vnc_port
($port);
1684 __PACKAGE__-
>register_method({
1685 name
=> 'termproxy',
1686 path
=> '{vmid}/termproxy',
1690 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1692 description
=> "Creates a TCP proxy connections.",
1694 additionalProperties
=> 0,
1696 node
=> get_standard_option
('pve-node'),
1697 vmid
=> get_standard_option
('pve-vmid'),
1701 enum
=> [qw(serial0 serial1 serial2 serial3)],
1702 description
=> "opens a serial terminal (defaults to display)",
1707 additionalProperties
=> 0,
1709 user
=> { type
=> 'string' },
1710 ticket
=> { type
=> 'string' },
1711 port
=> { type
=> 'integer' },
1712 upid
=> { type
=> 'string' },
1718 my $rpcenv = PVE
::RPCEnvironment
::get
();
1720 my $authuser = $rpcenv->get_user();
1722 my $vmid = $param->{vmid
};
1723 my $node = $param->{node
};
1724 my $serial = $param->{serial
};
1726 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1728 if (!defined($serial)) {
1729 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1730 $serial = $conf->{vga
};
1734 my $authpath = "/vms/$vmid";
1736 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1741 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1742 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1743 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1744 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, '-t');
1745 push @$remcmd, '--';
1747 $family = PVE
::Tools
::get_host_address_family
($node);
1750 my $port = PVE
::Tools
::next_vnc_port
($family);
1752 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1753 push @$termcmd, '-iface', $serial if $serial;
1758 syslog
('info', "starting qemu termproxy $upid\n");
1760 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1761 '--perm', 'VM.Console', '--'];
1762 push @$cmd, @$remcmd, @$termcmd;
1764 PVE
::Tools
::run_command
($cmd);
1767 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1769 PVE
::Tools
::wait_for_vnc_port
($port);
1779 __PACKAGE__-
>register_method({
1780 name
=> 'vncwebsocket',
1781 path
=> '{vmid}/vncwebsocket',
1784 description
=> "You also need to pass a valid ticket (vncticket).",
1785 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1787 description
=> "Opens a weksocket for VNC traffic.",
1789 additionalProperties
=> 0,
1791 node
=> get_standard_option
('pve-node'),
1792 vmid
=> get_standard_option
('pve-vmid'),
1794 description
=> "Ticket from previous call to vncproxy.",
1799 description
=> "Port number returned by previous vncproxy call.",
1809 port
=> { type
=> 'string' },
1815 my $rpcenv = PVE
::RPCEnvironment
::get
();
1817 my $authuser = $rpcenv->get_user();
1819 my $vmid = $param->{vmid
};
1820 my $node = $param->{node
};
1822 my $authpath = "/vms/$vmid";
1824 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1826 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1828 # Note: VNC ports are acessible from outside, so we do not gain any
1829 # security if we verify that $param->{port} belongs to VM $vmid. This
1830 # check is done by verifying the VNC ticket (inside VNC protocol).
1832 my $port = $param->{port
};
1834 return { port
=> $port };
1837 __PACKAGE__-
>register_method({
1838 name
=> 'spiceproxy',
1839 path
=> '{vmid}/spiceproxy',
1844 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1846 description
=> "Returns a SPICE configuration to connect to the VM.",
1848 additionalProperties
=> 0,
1850 node
=> get_standard_option
('pve-node'),
1851 vmid
=> get_standard_option
('pve-vmid'),
1852 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1855 returns
=> get_standard_option
('remote-viewer-config'),
1859 my $rpcenv = PVE
::RPCEnvironment
::get
();
1861 my $authuser = $rpcenv->get_user();
1863 my $vmid = $param->{vmid
};
1864 my $node = $param->{node
};
1865 my $proxy = $param->{proxy
};
1867 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1868 my $title = "VM $vmid";
1869 $title .= " - ". $conf->{name
} if $conf->{name
};
1871 my $port = PVE
::QemuServer
::spice_port
($vmid);
1873 my ($ticket, undef, $remote_viewer_config) =
1874 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1876 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1877 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1879 return $remote_viewer_config;
1882 __PACKAGE__-
>register_method({
1884 path
=> '{vmid}/status',
1887 description
=> "Directory index",
1892 additionalProperties
=> 0,
1894 node
=> get_standard_option
('pve-node'),
1895 vmid
=> get_standard_option
('pve-vmid'),
1903 subdir
=> { type
=> 'string' },
1906 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1912 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1915 { subdir
=> 'current' },
1916 { subdir
=> 'start' },
1917 { subdir
=> 'stop' },
1923 __PACKAGE__-
>register_method({
1924 name
=> 'vm_status',
1925 path
=> '{vmid}/status/current',
1928 protected
=> 1, # qemu pid files are only readable by root
1929 description
=> "Get virtual machine status.",
1931 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1934 additionalProperties
=> 0,
1936 node
=> get_standard_option
('pve-node'),
1937 vmid
=> get_standard_option
('pve-vmid'),
1943 %$PVE::QemuServer
::vmstatus_return_properties
,
1945 description
=> "HA manager service status.",
1949 description
=> "Qemu VGA configuration supports spice.",
1954 description
=> "Qemu GuestAgent enabled in config.",
1964 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1966 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1967 my $status = $vmstatus->{$param->{vmid
}};
1969 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1971 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1972 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1977 __PACKAGE__-
>register_method({
1979 path
=> '{vmid}/status/start',
1983 description
=> "Start virtual machine.",
1985 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1988 additionalProperties
=> 0,
1990 node
=> get_standard_option
('pve-node'),
1991 vmid
=> get_standard_option
('pve-vmid',
1992 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1993 skiplock
=> get_standard_option
('skiplock'),
1994 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1995 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1998 enum
=> ['secure', 'insecure'],
1999 description
=> "Migration traffic is encrypted using an SSH " .
2000 "tunnel by default. On secure, completely private networks " .
2001 "this can be disabled to increase performance.",
2004 migration_network
=> {
2005 type
=> 'string', format
=> 'CIDR',
2006 description
=> "CIDR of the (sub) network that is used for migration.",
2009 machine
=> get_standard_option
('pve-qm-machine'),
2011 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
2023 my $rpcenv = PVE
::RPCEnvironment
::get
();
2024 my $authuser = $rpcenv->get_user();
2026 my $node = extract_param
($param, 'node');
2027 my $vmid = extract_param
($param, 'vmid');
2029 my $machine = extract_param
($param, 'machine');
2031 my $get_root_param = sub {
2032 my $value = extract_param
($param, $_[0]);
2033 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2034 if $value && $authuser ne 'root@pam';
2038 my $stateuri = $get_root_param->('stateuri');
2039 my $skiplock = $get_root_param->('skiplock');
2040 my $migratedfrom = $get_root_param->('migratedfrom');
2041 my $migration_type = $get_root_param->('migration_type');
2042 my $migration_network = $get_root_param->('migration_network');
2043 my $targetstorage = $get_root_param->('targetstorage');
2045 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2046 if $targetstorage && !$migratedfrom;
2048 # read spice ticket from STDIN
2050 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2051 if (defined(my $line = <STDIN
>)) {
2053 $spice_ticket = $line;
2057 PVE
::Cluster
::check_cfs_quorum
();
2059 my $storecfg = PVE
::Storage
::config
();
2061 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2065 print "Requesting HA start for VM $vmid\n";
2067 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2068 PVE
::Tools
::run_command
($cmd);
2072 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2079 syslog
('info', "start VM $vmid: $upid\n");
2081 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2082 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
2086 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2090 __PACKAGE__-
>register_method({
2092 path
=> '{vmid}/status/stop',
2096 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2097 "is akin to pulling the power plug of a running computer and may damage the VM data",
2099 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2102 additionalProperties
=> 0,
2104 node
=> get_standard_option
('pve-node'),
2105 vmid
=> get_standard_option
('pve-vmid',
2106 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2107 skiplock
=> get_standard_option
('skiplock'),
2108 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2110 description
=> "Wait maximal timeout seconds.",
2116 description
=> "Do not deactivate storage volumes.",
2129 my $rpcenv = PVE
::RPCEnvironment
::get
();
2130 my $authuser = $rpcenv->get_user();
2132 my $node = extract_param
($param, 'node');
2133 my $vmid = extract_param
($param, 'vmid');
2135 my $skiplock = extract_param
($param, 'skiplock');
2136 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2137 if $skiplock && $authuser ne 'root@pam';
2139 my $keepActive = extract_param
($param, 'keepActive');
2140 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2141 if $keepActive && $authuser ne 'root@pam';
2143 my $migratedfrom = extract_param
($param, 'migratedfrom');
2144 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2145 if $migratedfrom && $authuser ne 'root@pam';
2148 my $storecfg = PVE
::Storage
::config
();
2150 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2155 print "Requesting HA stop for VM $vmid\n";
2157 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'stopped'];
2158 PVE
::Tools
::run_command
($cmd);
2162 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2168 syslog
('info', "stop VM $vmid: $upid\n");
2170 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2171 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2175 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2179 __PACKAGE__-
>register_method({
2181 path
=> '{vmid}/status/reset',
2185 description
=> "Reset virtual machine.",
2187 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2190 additionalProperties
=> 0,
2192 node
=> get_standard_option
('pve-node'),
2193 vmid
=> get_standard_option
('pve-vmid',
2194 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2195 skiplock
=> get_standard_option
('skiplock'),
2204 my $rpcenv = PVE
::RPCEnvironment
::get
();
2206 my $authuser = $rpcenv->get_user();
2208 my $node = extract_param
($param, 'node');
2210 my $vmid = extract_param
($param, 'vmid');
2212 my $skiplock = extract_param
($param, 'skiplock');
2213 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2214 if $skiplock && $authuser ne 'root@pam';
2216 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2221 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2226 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2229 __PACKAGE__-
>register_method({
2230 name
=> 'vm_shutdown',
2231 path
=> '{vmid}/status/shutdown',
2235 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2236 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2238 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2241 additionalProperties
=> 0,
2243 node
=> get_standard_option
('pve-node'),
2244 vmid
=> get_standard_option
('pve-vmid',
2245 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2246 skiplock
=> get_standard_option
('skiplock'),
2248 description
=> "Wait maximal timeout seconds.",
2254 description
=> "Make sure the VM stops.",
2260 description
=> "Do not deactivate storage volumes.",
2273 my $rpcenv = PVE
::RPCEnvironment
::get
();
2274 my $authuser = $rpcenv->get_user();
2276 my $node = extract_param
($param, 'node');
2277 my $vmid = extract_param
($param, 'vmid');
2279 my $skiplock = extract_param
($param, 'skiplock');
2280 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2281 if $skiplock && $authuser ne 'root@pam';
2283 my $keepActive = extract_param
($param, 'keepActive');
2284 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2285 if $keepActive && $authuser ne 'root@pam';
2287 my $storecfg = PVE
::Storage
::config
();
2291 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2292 # otherwise, we will infer a shutdown command, but run into the timeout,
2293 # then when the vm is resumed, it will instantly shutdown
2295 # checking the qmp status here to get feedback to the gui/cli/api
2296 # and the status query should not take too long
2297 my $qmpstatus = eval {
2298 PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2302 if (!$err && $qmpstatus->{status
} eq "paused") {
2303 if ($param->{forceStop
}) {
2304 warn "VM is paused - stop instead of shutdown\n";
2307 die "VM is paused - cannot shutdown\n";
2311 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2316 print "Requesting HA stop for VM $vmid\n";
2318 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'stopped'];
2319 PVE
::Tools
::run_command
($cmd);
2323 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2330 syslog
('info', "shutdown VM $vmid: $upid\n");
2332 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2333 $shutdown, $param->{forceStop
}, $keepActive);
2337 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2341 __PACKAGE__-
>register_method({
2342 name
=> 'vm_suspend',
2343 path
=> '{vmid}/status/suspend',
2347 description
=> "Suspend virtual machine.",
2349 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2352 additionalProperties
=> 0,
2354 node
=> get_standard_option
('pve-node'),
2355 vmid
=> get_standard_option
('pve-vmid',
2356 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2357 skiplock
=> get_standard_option
('skiplock'),
2362 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2364 statestorage
=> get_standard_option
('pve-storage-id', {
2365 description
=> "The storage for the VM state",
2366 requires
=> 'todisk',
2368 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2378 my $rpcenv = PVE
::RPCEnvironment
::get
();
2379 my $authuser = $rpcenv->get_user();
2381 my $node = extract_param
($param, 'node');
2382 my $vmid = extract_param
($param, 'vmid');
2384 my $todisk = extract_param
($param, 'todisk') // 0;
2386 my $statestorage = extract_param
($param, 'statestorage');
2388 my $skiplock = extract_param
($param, 'skiplock');
2389 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2390 if $skiplock && $authuser ne 'root@pam';
2392 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2394 die "Cannot suspend HA managed VM to disk\n"
2395 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2400 syslog
('info', "suspend VM $vmid: $upid\n");
2402 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2407 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2408 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2411 __PACKAGE__-
>register_method({
2412 name
=> 'vm_resume',
2413 path
=> '{vmid}/status/resume',
2417 description
=> "Resume virtual machine.",
2419 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2422 additionalProperties
=> 0,
2424 node
=> get_standard_option
('pve-node'),
2425 vmid
=> get_standard_option
('pve-vmid',
2426 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2427 skiplock
=> get_standard_option
('skiplock'),
2428 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2438 my $rpcenv = PVE
::RPCEnvironment
::get
();
2440 my $authuser = $rpcenv->get_user();
2442 my $node = extract_param
($param, 'node');
2444 my $vmid = extract_param
($param, 'vmid');
2446 my $skiplock = extract_param
($param, 'skiplock');
2447 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2448 if $skiplock && $authuser ne 'root@pam';
2450 my $nocheck = extract_param
($param, 'nocheck');
2452 my $to_disk_suspended;
2454 PVE
::QemuConfig-
>lock_config($vmid, sub {
2455 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2456 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2460 die "VM $vmid not running\n"
2461 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2466 syslog
('info', "resume VM $vmid: $upid\n");
2468 if (!$to_disk_suspended) {
2469 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2471 my $storecfg = PVE
::Storage
::config
();
2472 PVE
::QemuServer
::vm_start
($storecfg, $vmid, undef, $skiplock);
2478 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2481 __PACKAGE__-
>register_method({
2482 name
=> 'vm_sendkey',
2483 path
=> '{vmid}/sendkey',
2487 description
=> "Send key event to virtual machine.",
2489 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2492 additionalProperties
=> 0,
2494 node
=> get_standard_option
('pve-node'),
2495 vmid
=> get_standard_option
('pve-vmid',
2496 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2497 skiplock
=> get_standard_option
('skiplock'),
2499 description
=> "The key (qemu monitor encoding).",
2504 returns
=> { type
=> 'null'},
2508 my $rpcenv = PVE
::RPCEnvironment
::get
();
2510 my $authuser = $rpcenv->get_user();
2512 my $node = extract_param
($param, 'node');
2514 my $vmid = extract_param
($param, 'vmid');
2516 my $skiplock = extract_param
($param, 'skiplock');
2517 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2518 if $skiplock && $authuser ne 'root@pam';
2520 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2525 __PACKAGE__-
>register_method({
2526 name
=> 'vm_feature',
2527 path
=> '{vmid}/feature',
2531 description
=> "Check if feature for virtual machine is available.",
2533 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2536 additionalProperties
=> 0,
2538 node
=> get_standard_option
('pve-node'),
2539 vmid
=> get_standard_option
('pve-vmid'),
2541 description
=> "Feature to check.",
2543 enum
=> [ 'snapshot', 'clone', 'copy' ],
2545 snapname
=> get_standard_option
('pve-snapshot-name', {
2553 hasFeature
=> { type
=> 'boolean' },
2556 items
=> { type
=> 'string' },
2563 my $node = extract_param
($param, 'node');
2565 my $vmid = extract_param
($param, 'vmid');
2567 my $snapname = extract_param
($param, 'snapname');
2569 my $feature = extract_param
($param, 'feature');
2571 my $running = PVE
::QemuServer
::check_running
($vmid);
2573 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2576 my $snap = $conf->{snapshots
}->{$snapname};
2577 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2580 my $storecfg = PVE
::Storage
::config
();
2582 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2583 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2586 hasFeature
=> $hasFeature,
2587 nodes
=> [ keys %$nodelist ],
2591 __PACKAGE__-
>register_method({
2593 path
=> '{vmid}/clone',
2597 description
=> "Create a copy of virtual machine/template.",
2599 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2600 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2601 "'Datastore.AllocateSpace' on any used storage.",
2604 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2606 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2607 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2612 additionalProperties
=> 0,
2614 node
=> get_standard_option
('pve-node'),
2615 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2616 newid
=> get_standard_option
('pve-vmid', {
2617 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2618 description
=> 'VMID for the clone.' }),
2621 type
=> 'string', format
=> 'dns-name',
2622 description
=> "Set a name for the new VM.",
2627 description
=> "Description for the new VM.",
2631 type
=> 'string', format
=> 'pve-poolid',
2632 description
=> "Add the new VM to the specified pool.",
2634 snapname
=> get_standard_option
('pve-snapshot-name', {
2637 storage
=> get_standard_option
('pve-storage-id', {
2638 description
=> "Target storage for full clone.",
2642 description
=> "Target format for file storage. Only valid for full clone.",
2645 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2650 description
=> "Create a full copy of all disks. This is always done when " .
2651 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2653 target
=> get_standard_option
('pve-node', {
2654 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2658 description
=> "Override I/O bandwidth limit (in KiB/s).",
2662 default => 'clone limit from datacenter or storage config',
2672 my $rpcenv = PVE
::RPCEnvironment
::get
();
2674 my $authuser = $rpcenv->get_user();
2676 my $node = extract_param
($param, 'node');
2678 my $vmid = extract_param
($param, 'vmid');
2680 my $newid = extract_param
($param, 'newid');
2682 my $pool = extract_param
($param, 'pool');
2684 if (defined($pool)) {
2685 $rpcenv->check_pool_exist($pool);
2688 my $snapname = extract_param
($param, 'snapname');
2690 my $storage = extract_param
($param, 'storage');
2692 my $format = extract_param
($param, 'format');
2694 my $target = extract_param
($param, 'target');
2696 my $localnode = PVE
::INotify
::nodename
();
2698 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2700 PVE
::Cluster
::check_node_exists
($target) if $target;
2702 my $storecfg = PVE
::Storage
::config
();
2705 # check if storage is enabled on local node
2706 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2708 # check if storage is available on target node
2709 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2710 # clone only works if target storage is shared
2711 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2712 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2716 PVE
::Cluster
::check_cfs_quorum
();
2718 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2720 # exclusive lock if VM is running - else shared lock is enough;
2721 my $shared_lock = $running ?
0 : 1;
2725 # do all tests after lock
2726 # we also try to do all tests before we fork the worker
2728 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2730 PVE
::QemuConfig-
>check_lock($conf);
2732 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2734 die "unexpected state change\n" if $verify_running != $running;
2736 die "snapshot '$snapname' does not exist\n"
2737 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2739 my $full = extract_param
($param, 'full');
2740 if (!defined($full)) {
2741 $full = !PVE
::QemuConfig-
>is_template($conf);
2744 die "parameter 'storage' not allowed for linked clones\n"
2745 if defined($storage) && !$full;
2747 die "parameter 'format' not allowed for linked clones\n"
2748 if defined($format) && !$full;
2750 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2752 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2754 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2756 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2758 die "unable to create VM $newid: config file already exists\n"
2761 my $newconf = { lock => 'clone' };
2766 foreach my $opt (keys %$oldconf) {
2767 my $value = $oldconf->{$opt};
2769 # do not copy snapshot related info
2770 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2771 $opt eq 'vmstate' || $opt eq 'snapstate';
2773 # no need to copy unused images, because VMID(owner) changes anyways
2774 next if $opt =~ m/^unused\d+$/;
2776 # always change MAC! address
2777 if ($opt =~ m/^net(\d+)$/) {
2778 my $net = PVE
::QemuServer
::parse_net
($value);
2779 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2780 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2781 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2782 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2783 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2784 die "unable to parse drive options for '$opt'\n" if !$drive;
2785 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2786 $newconf->{$opt} = $value; # simply copy configuration
2788 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2789 die "Full clone feature is not supported for drive '$opt'\n"
2790 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2791 $fullclone->{$opt} = 1;
2793 # not full means clone instead of copy
2794 die "Linked clone feature is not supported for drive '$opt'\n"
2795 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2797 $drives->{$opt} = $drive;
2798 push @$vollist, $drive->{file
};
2801 # copy everything else
2802 $newconf->{$opt} = $value;
2806 # auto generate a new uuid
2807 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2808 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2809 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2811 # auto generate a new vmgenid if the option was set
2812 if ($newconf->{vmgenid
}) {
2813 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2816 delete $newconf->{template
};
2818 if ($param->{name
}) {
2819 $newconf->{name
} = $param->{name
};
2821 if ($oldconf->{name
}) {
2822 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2824 $newconf->{name
} = "Copy-of-VM-$vmid";
2828 if ($param->{description
}) {
2829 $newconf->{description
} = $param->{description
};
2832 # create empty/temp config - this fails if VM already exists on other node
2833 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2838 my $newvollist = [];
2845 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2847 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2849 my $bwlimit = extract_param
($param, 'bwlimit');
2851 my $total_jobs = scalar(keys %{$drives});
2854 foreach my $opt (keys %$drives) {
2855 my $drive = $drives->{$opt};
2856 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2858 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
2859 my $storage_list = [ $src_sid ];
2860 push @$storage_list, $storage if defined($storage);
2861 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
2863 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2864 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2865 $jobs, $skipcomplete, $oldconf->{agent
}, $clonelimit);
2867 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2869 PVE
::QemuConfig-
>write_config($newid, $newconf);
2873 delete $newconf->{lock};
2875 # do not write pending changes
2876 if (my @changes = keys %{$newconf->{pending
}}) {
2877 my $pending = join(',', @changes);
2878 warn "found pending changes for '$pending', discarding for clone\n";
2879 delete $newconf->{pending
};
2882 PVE
::QemuConfig-
>write_config($newid, $newconf);
2885 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2886 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2887 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2889 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2890 die "Failed to move config to node '$target' - rename failed: $!\n"
2891 if !rename($conffile, $newconffile);
2894 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2899 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2901 sleep 1; # some storage like rbd need to wait before release volume - really?
2903 foreach my $volid (@$newvollist) {
2904 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2907 die "clone failed: $err";
2913 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2915 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2918 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2919 # Aquire exclusive lock lock for $newid
2920 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2925 __PACKAGE__-
>register_method({
2926 name
=> 'move_vm_disk',
2927 path
=> '{vmid}/move_disk',
2931 description
=> "Move volume to different storage.",
2933 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2935 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2936 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2940 additionalProperties
=> 0,
2942 node
=> get_standard_option
('pve-node'),
2943 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2946 description
=> "The disk you want to move.",
2947 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2949 storage
=> get_standard_option
('pve-storage-id', {
2950 description
=> "Target storage.",
2951 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2955 description
=> "Target Format.",
2956 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2961 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2967 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2972 description
=> "Override I/O bandwidth limit (in KiB/s).",
2976 default => 'move limit from datacenter or storage config',
2982 description
=> "the task ID.",
2987 my $rpcenv = PVE
::RPCEnvironment
::get
();
2989 my $authuser = $rpcenv->get_user();
2991 my $node = extract_param
($param, 'node');
2993 my $vmid = extract_param
($param, 'vmid');
2995 my $digest = extract_param
($param, 'digest');
2997 my $disk = extract_param
($param, 'disk');
2999 my $storeid = extract_param
($param, 'storage');
3001 my $format = extract_param
($param, 'format');
3003 my $storecfg = PVE
::Storage
::config
();
3005 my $updatefn = sub {
3007 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3009 PVE
::QemuConfig-
>check_lock($conf);
3011 die "checksum missmatch (file change by other user?)\n"
3012 if $digest && $digest ne $conf->{digest
};
3014 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3016 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3018 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
3020 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3023 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3024 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3028 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
3029 (!$format || !$oldfmt || $oldfmt eq $format);
3031 # this only checks snapshots because $disk is passed!
3032 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3033 die "you can't move a disk with snapshots and delete the source\n"
3034 if $snapshotted && $param->{delete};
3036 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3038 my $running = PVE
::QemuServer
::check_running
($vmid);
3040 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3044 my $newvollist = [];
3050 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3052 warn "moving disk with snapshots, snapshots will not be moved!\n"
3055 my $bwlimit = extract_param
($param, 'bwlimit');
3056 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3058 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3059 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit);
3061 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
3063 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3065 # convert moved disk to base if part of template
3066 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3067 if PVE
::QemuConfig-
>is_template($conf);
3069 PVE
::QemuConfig-
>write_config($vmid, $conf);
3071 if ($running && PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
} && PVE
::QemuServer
::qga_check_running
($vmid)) {
3072 eval { PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-fstrim"); };
3076 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3077 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3084 foreach my $volid (@$newvollist) {
3085 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3088 die "storage migration failed: $err";
3091 if ($param->{delete}) {
3093 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3094 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3100 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3103 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3106 __PACKAGE__-
>register_method({
3107 name
=> 'migrate_vm',
3108 path
=> '{vmid}/migrate',
3112 description
=> "Migrate virtual machine. Creates a new migration task.",
3114 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3117 additionalProperties
=> 0,
3119 node
=> get_standard_option
('pve-node'),
3120 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3121 target
=> get_standard_option
('pve-node', {
3122 description
=> "Target node.",
3123 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3127 description
=> "Use online/live migration.",
3132 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3137 enum
=> ['secure', 'insecure'],
3138 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3141 migration_network
=> {
3142 type
=> 'string', format
=> 'CIDR',
3143 description
=> "CIDR of the (sub) network that is used for migration.",
3146 "with-local-disks" => {
3148 description
=> "Enable live storage migration for local disk",
3151 targetstorage
=> get_standard_option
('pve-storage-id', {
3152 description
=> "Default target storage.",
3154 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3157 description
=> "Override I/O bandwidth limit (in KiB/s).",
3161 default => 'migrate limit from datacenter or storage config',
3167 description
=> "the task ID.",
3172 my $rpcenv = PVE
::RPCEnvironment
::get
();
3173 my $authuser = $rpcenv->get_user();
3175 my $target = extract_param
($param, 'target');
3177 my $localnode = PVE
::INotify
::nodename
();
3178 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3180 PVE
::Cluster
::check_cfs_quorum
();
3182 PVE
::Cluster
::check_node_exists
($target);
3184 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3186 my $vmid = extract_param
($param, 'vmid');
3188 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3189 if !$param->{online
} && $param->{targetstorage
};
3191 raise_param_exc
({ force
=> "Only root may use this option." })
3192 if $param->{force
} && $authuser ne 'root@pam';
3194 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3195 if $param->{migration_type
} && $authuser ne 'root@pam';
3197 # allow root only until better network permissions are available
3198 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3199 if $param->{migration_network
} && $authuser ne 'root@pam';
3202 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3204 # try to detect errors early
3206 PVE
::QemuConfig-
>check_lock($conf);
3208 if (PVE
::QemuServer
::check_running
($vmid)) {
3209 die "cant migrate running VM without --online\n"
3210 if !$param->{online
};
3213 my $storecfg = PVE
::Storage
::config
();
3215 if( $param->{targetstorage
}) {
3216 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3218 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3221 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3226 print "Requesting HA migration for VM $vmid to node $target\n";
3228 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3229 PVE
::Tools
::run_command
($cmd);
3233 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3238 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3242 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3245 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3250 __PACKAGE__-
>register_method({
3252 path
=> '{vmid}/monitor',
3256 description
=> "Execute Qemu monitor commands.",
3258 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3259 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3262 additionalProperties
=> 0,
3264 node
=> get_standard_option
('pve-node'),
3265 vmid
=> get_standard_option
('pve-vmid'),
3268 description
=> "The monitor command.",
3272 returns
=> { type
=> 'string'},
3276 my $rpcenv = PVE
::RPCEnvironment
::get
();
3277 my $authuser = $rpcenv->get_user();
3280 my $command = shift;
3281 return $command =~ m/^\s*info(\s+|$)/
3282 || $command =~ m/^\s*help\s*$/;
3285 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3286 if !&$is_ro($param->{command
});
3288 my $vmid = $param->{vmid
};
3290 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3294 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3296 $res = "ERROR: $@" if $@;
3301 __PACKAGE__-
>register_method({
3302 name
=> 'resize_vm',
3303 path
=> '{vmid}/resize',
3307 description
=> "Extend volume size.",
3309 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3312 additionalProperties
=> 0,
3314 node
=> get_standard_option
('pve-node'),
3315 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3316 skiplock
=> get_standard_option
('skiplock'),
3319 description
=> "The disk you want to resize.",
3320 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3324 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3325 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.",
3329 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3335 returns
=> { type
=> 'null'},
3339 my $rpcenv = PVE
::RPCEnvironment
::get
();
3341 my $authuser = $rpcenv->get_user();
3343 my $node = extract_param
($param, 'node');
3345 my $vmid = extract_param
($param, 'vmid');
3347 my $digest = extract_param
($param, 'digest');
3349 my $disk = extract_param
($param, 'disk');
3351 my $sizestr = extract_param
($param, 'size');
3353 my $skiplock = extract_param
($param, 'skiplock');
3354 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3355 if $skiplock && $authuser ne 'root@pam';
3357 my $storecfg = PVE
::Storage
::config
();
3359 my $updatefn = sub {
3361 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3363 die "checksum missmatch (file change by other user?)\n"
3364 if $digest && $digest ne $conf->{digest
};
3365 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3367 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3369 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3371 my (undef, undef, undef, undef, undef, undef, $format) =
3372 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3374 die "can't resize volume: $disk if snapshot exists\n"
3375 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3377 my $volid = $drive->{file
};
3379 die "disk '$disk' has no associated volume\n" if !$volid;
3381 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3383 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3385 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3387 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3388 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3390 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3391 my ($ext, $newsize, $unit) = ($1, $2, $4);
3394 $newsize = $newsize * 1024;
3395 } elsif ($unit eq 'M') {
3396 $newsize = $newsize * 1024 * 1024;
3397 } elsif ($unit eq 'G') {
3398 $newsize = $newsize * 1024 * 1024 * 1024;
3399 } elsif ($unit eq 'T') {
3400 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3403 $newsize += $size if $ext;
3404 $newsize = int($newsize);
3406 die "shrinking disks is not supported\n" if $newsize < $size;
3408 return if $size == $newsize;
3410 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3412 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3414 $drive->{size
} = $newsize;
3415 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3417 PVE
::QemuConfig-
>write_config($vmid, $conf);
3420 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3424 __PACKAGE__-
>register_method({
3425 name
=> 'snapshot_list',
3426 path
=> '{vmid}/snapshot',
3428 description
=> "List all snapshots.",
3430 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3433 protected
=> 1, # qemu pid files are only readable by root
3435 additionalProperties
=> 0,
3437 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3438 node
=> get_standard_option
('pve-node'),
3447 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3451 description
=> "Snapshot includes RAM.",
3456 description
=> "Snapshot description.",
3460 description
=> "Snapshot creation time",
3462 renderer
=> 'timestamp',
3466 description
=> "Parent snapshot identifier.",
3472 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3477 my $vmid = $param->{vmid
};
3479 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3480 my $snaphash = $conf->{snapshots
} || {};
3484 foreach my $name (keys %$snaphash) {
3485 my $d = $snaphash->{$name};
3488 snaptime
=> $d->{snaptime
} || 0,
3489 vmstate
=> $d->{vmstate
} ?
1 : 0,
3490 description
=> $d->{description
} || '',
3492 $item->{parent
} = $d->{parent
} if $d->{parent
};
3493 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3497 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3500 digest
=> $conf->{digest
},
3501 running
=> $running,
3502 description
=> "You are here!",
3504 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3506 push @$res, $current;
3511 __PACKAGE__-
>register_method({
3513 path
=> '{vmid}/snapshot',
3517 description
=> "Snapshot a VM.",
3519 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3522 additionalProperties
=> 0,
3524 node
=> get_standard_option
('pve-node'),
3525 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3526 snapname
=> get_standard_option
('pve-snapshot-name'),
3530 description
=> "Save the vmstate",
3535 description
=> "A textual description or comment.",
3541 description
=> "the task ID.",
3546 my $rpcenv = PVE
::RPCEnvironment
::get
();
3548 my $authuser = $rpcenv->get_user();
3550 my $node = extract_param
($param, 'node');
3552 my $vmid = extract_param
($param, 'vmid');
3554 my $snapname = extract_param
($param, 'snapname');
3556 die "unable to use snapshot name 'current' (reserved name)\n"
3557 if $snapname eq 'current';
3560 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3561 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3562 $param->{description
});
3565 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3568 __PACKAGE__-
>register_method({
3569 name
=> 'snapshot_cmd_idx',
3570 path
=> '{vmid}/snapshot/{snapname}',
3577 additionalProperties
=> 0,
3579 vmid
=> get_standard_option
('pve-vmid'),
3580 node
=> get_standard_option
('pve-node'),
3581 snapname
=> get_standard_option
('pve-snapshot-name'),
3590 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3597 push @$res, { cmd
=> 'rollback' };
3598 push @$res, { cmd
=> 'config' };
3603 __PACKAGE__-
>register_method({
3604 name
=> 'update_snapshot_config',
3605 path
=> '{vmid}/snapshot/{snapname}/config',
3609 description
=> "Update snapshot metadata.",
3611 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3614 additionalProperties
=> 0,
3616 node
=> get_standard_option
('pve-node'),
3617 vmid
=> get_standard_option
('pve-vmid'),
3618 snapname
=> get_standard_option
('pve-snapshot-name'),
3622 description
=> "A textual description or comment.",
3626 returns
=> { type
=> 'null' },
3630 my $rpcenv = PVE
::RPCEnvironment
::get
();
3632 my $authuser = $rpcenv->get_user();
3634 my $vmid = extract_param
($param, 'vmid');
3636 my $snapname = extract_param
($param, 'snapname');
3638 return undef if !defined($param->{description
});
3640 my $updatefn = sub {
3642 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3644 PVE
::QemuConfig-
>check_lock($conf);
3646 my $snap = $conf->{snapshots
}->{$snapname};
3648 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3650 $snap->{description
} = $param->{description
} if defined($param->{description
});
3652 PVE
::QemuConfig-
>write_config($vmid, $conf);
3655 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3660 __PACKAGE__-
>register_method({
3661 name
=> 'get_snapshot_config',
3662 path
=> '{vmid}/snapshot/{snapname}/config',
3665 description
=> "Get snapshot configuration",
3667 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3670 additionalProperties
=> 0,
3672 node
=> get_standard_option
('pve-node'),
3673 vmid
=> get_standard_option
('pve-vmid'),
3674 snapname
=> get_standard_option
('pve-snapshot-name'),
3677 returns
=> { type
=> "object" },
3681 my $rpcenv = PVE
::RPCEnvironment
::get
();
3683 my $authuser = $rpcenv->get_user();
3685 my $vmid = extract_param
($param, 'vmid');
3687 my $snapname = extract_param
($param, 'snapname');
3689 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3691 my $snap = $conf->{snapshots
}->{$snapname};
3693 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3698 __PACKAGE__-
>register_method({
3700 path
=> '{vmid}/snapshot/{snapname}/rollback',
3704 description
=> "Rollback VM state to specified snapshot.",
3706 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3709 additionalProperties
=> 0,
3711 node
=> get_standard_option
('pve-node'),
3712 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3713 snapname
=> get_standard_option
('pve-snapshot-name'),
3718 description
=> "the task ID.",
3723 my $rpcenv = PVE
::RPCEnvironment
::get
();
3725 my $authuser = $rpcenv->get_user();
3727 my $node = extract_param
($param, 'node');
3729 my $vmid = extract_param
($param, 'vmid');
3731 my $snapname = extract_param
($param, 'snapname');
3734 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3735 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3739 # hold migration lock, this makes sure that nobody create replication snapshots
3740 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3743 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3746 __PACKAGE__-
>register_method({
3747 name
=> 'delsnapshot',
3748 path
=> '{vmid}/snapshot/{snapname}',
3752 description
=> "Delete a VM snapshot.",
3754 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3757 additionalProperties
=> 0,
3759 node
=> get_standard_option
('pve-node'),
3760 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3761 snapname
=> get_standard_option
('pve-snapshot-name'),
3765 description
=> "For removal from config file, even if removing disk snapshots fails.",
3771 description
=> "the task ID.",
3776 my $rpcenv = PVE
::RPCEnvironment
::get
();
3778 my $authuser = $rpcenv->get_user();
3780 my $node = extract_param
($param, 'node');
3782 my $vmid = extract_param
($param, 'vmid');
3784 my $snapname = extract_param
($param, 'snapname');
3787 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3788 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3791 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3794 __PACKAGE__-
>register_method({
3796 path
=> '{vmid}/template',
3800 description
=> "Create a Template.",
3802 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3803 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3806 additionalProperties
=> 0,
3808 node
=> get_standard_option
('pve-node'),
3809 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3813 description
=> "If you want to convert only 1 disk to base image.",
3814 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3819 returns
=> { type
=> 'null'},
3823 my $rpcenv = PVE
::RPCEnvironment
::get
();
3825 my $authuser = $rpcenv->get_user();
3827 my $node = extract_param
($param, 'node');
3829 my $vmid = extract_param
($param, 'vmid');
3831 my $disk = extract_param
($param, 'disk');
3833 my $updatefn = sub {
3835 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3837 PVE
::QemuConfig-
>check_lock($conf);
3839 die "unable to create template, because VM contains snapshots\n"
3840 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3842 die "you can't convert a template to a template\n"
3843 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3845 die "you can't convert a VM to template if VM is running\n"
3846 if PVE
::QemuServer
::check_running
($vmid);
3849 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3852 $conf->{template
} = 1;
3853 PVE
::QemuConfig-
>write_config($vmid, $conf);
3855 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3858 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);