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 = 4 * 1024;
163 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size);
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);
560 die "$emsg vm is a template\n" if PVE
::QemuConfig-
>is_template($conf);
563 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
567 bwlimit
=> $bwlimit, });
569 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
571 if ($start_after_create) {
572 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
577 # ensure no old replication state are exists
578 PVE
::ReplicationState
::delete_guest_states
($vmid);
580 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
584 # ensure no old replication state are exists
585 PVE
::ReplicationState
::delete_guest_states
($vmid);
593 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
597 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
599 if (!$conf->{bootdisk
}) {
600 my $firstdisk = PVE
::QemuServer
::resolve_first_disk
($conf);
601 $conf->{bootdisk
} = $firstdisk if $firstdisk;
604 # auto generate uuid if user did not specify smbios1 option
605 if (!$conf->{smbios1
}) {
606 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
609 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
610 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
613 PVE
::QemuConfig-
>write_config($vmid, $conf);
619 foreach my $volid (@$vollist) {
620 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
626 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
629 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
631 if ($start_after_create) {
632 print "Execute autostart\n";
633 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
638 my ($code, $worker_name);
640 $worker_name = 'qmrestore';
642 eval { $restorefn->() };
644 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
650 $worker_name = 'qmcreate';
652 eval { $createfn->() };
655 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
656 unlink($conffile) or die "failed to remove config file: $!\n";
664 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
667 __PACKAGE__-
>register_method({
672 description
=> "Directory index",
677 additionalProperties
=> 0,
679 node
=> get_standard_option
('pve-node'),
680 vmid
=> get_standard_option
('pve-vmid'),
688 subdir
=> { type
=> 'string' },
691 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
697 { subdir
=> 'config' },
698 { subdir
=> 'pending' },
699 { subdir
=> 'status' },
700 { subdir
=> 'unlink' },
701 { subdir
=> 'vncproxy' },
702 { subdir
=> 'termproxy' },
703 { subdir
=> 'migrate' },
704 { subdir
=> 'resize' },
705 { subdir
=> 'move' },
707 { subdir
=> 'rrddata' },
708 { subdir
=> 'monitor' },
709 { subdir
=> 'agent' },
710 { subdir
=> 'snapshot' },
711 { subdir
=> 'spiceproxy' },
712 { subdir
=> 'sendkey' },
713 { subdir
=> 'firewall' },
719 __PACKAGE__-
>register_method ({
720 subclass
=> "PVE::API2::Firewall::VM",
721 path
=> '{vmid}/firewall',
724 __PACKAGE__-
>register_method ({
725 subclass
=> "PVE::API2::Qemu::Agent",
726 path
=> '{vmid}/agent',
729 __PACKAGE__-
>register_method({
731 path
=> '{vmid}/rrd',
733 protected
=> 1, # fixme: can we avoid that?
735 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
737 description
=> "Read VM RRD statistics (returns PNG)",
739 additionalProperties
=> 0,
741 node
=> get_standard_option
('pve-node'),
742 vmid
=> get_standard_option
('pve-vmid'),
744 description
=> "Specify the time frame you are interested in.",
746 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
749 description
=> "The list of datasources you want to display.",
750 type
=> 'string', format
=> 'pve-configid-list',
753 description
=> "The RRD consolidation function",
755 enum
=> [ 'AVERAGE', 'MAX' ],
763 filename
=> { type
=> 'string' },
769 return PVE
::Cluster
::create_rrd_graph
(
770 "pve2-vm/$param->{vmid}", $param->{timeframe
},
771 $param->{ds
}, $param->{cf
});
775 __PACKAGE__-
>register_method({
777 path
=> '{vmid}/rrddata',
779 protected
=> 1, # fixme: can we avoid that?
781 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
783 description
=> "Read VM RRD statistics",
785 additionalProperties
=> 0,
787 node
=> get_standard_option
('pve-node'),
788 vmid
=> get_standard_option
('pve-vmid'),
790 description
=> "Specify the time frame you are interested in.",
792 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
795 description
=> "The RRD consolidation function",
797 enum
=> [ 'AVERAGE', 'MAX' ],
812 return PVE
::Cluster
::create_rrd_data
(
813 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
817 __PACKAGE__-
>register_method({
819 path
=> '{vmid}/config',
822 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
824 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
827 additionalProperties
=> 0,
829 node
=> get_standard_option
('pve-node'),
830 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
832 description
=> "Get current values (instead of pending values).",
837 snapshot
=> get_standard_option
('pve-snapshot-name', {
838 description
=> "Fetch config values from given snapshot.",
841 my ($cmd, $pname, $cur, $args) = @_;
842 PVE
::QemuConfig-
>snapshot_list($args->[0]);
848 description
=> "The current VM configuration.",
850 properties
=> PVE
::QemuServer
::json_config_properties
({
853 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
860 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
862 if (my $snapname = $param->{snapshot
}) {
863 my $snapshot = $conf->{snapshots
}->{$snapname};
864 die "snapshot '$snapname' does not exist\n" if !defined($snapshot);
866 $snapshot->{digest
} = $conf->{digest
}; # keep file digest for API
871 delete $conf->{snapshots
};
873 if (!$param->{current
}) {
874 foreach my $opt (keys %{$conf->{pending
}}) {
875 next if $opt eq 'delete';
876 my $value = $conf->{pending
}->{$opt};
877 next if ref($value); # just to be sure
878 $conf->{$opt} = $value;
880 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
881 foreach my $opt (keys %$pending_delete_hash) {
882 delete $conf->{$opt} if $conf->{$opt};
886 delete $conf->{pending
};
888 # hide cloudinit password
889 if ($conf->{cipassword
}) {
890 $conf->{cipassword
} = '**********';
896 __PACKAGE__-
>register_method({
897 name
=> 'vm_pending',
898 path
=> '{vmid}/pending',
901 description
=> "Get virtual machine configuration, including pending changes.",
903 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
906 additionalProperties
=> 0,
908 node
=> get_standard_option
('pve-node'),
909 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
918 description
=> "Configuration option name.",
922 description
=> "Current value.",
927 description
=> "Pending value.",
932 description
=> "Indicates a pending delete request if present and not 0. " .
933 "The value 2 indicates a force-delete request.",
945 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
947 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
951 foreach my $opt (keys %$conf) {
952 next if ref($conf->{$opt});
953 my $item = { key
=> $opt };
954 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
955 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
956 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
958 # hide cloudinit password
959 if ($opt eq 'cipassword') {
960 $item->{value
} = '**********' if defined($item->{value
});
961 # the trailing space so that the pending string is different
962 $item->{pending
} = '********** ' if defined($item->{pending
});
967 foreach my $opt (keys %{$conf->{pending
}}) {
968 next if $opt eq 'delete';
969 next if ref($conf->{pending
}->{$opt}); # just to be sure
970 next if defined($conf->{$opt});
971 my $item = { key
=> $opt };
972 $item->{pending
} = $conf->{pending
}->{$opt};
974 # hide cloudinit password
975 if ($opt eq 'cipassword') {
976 $item->{pending
} = '**********' if defined($item->{pending
});
981 while (my ($opt, $force) = each %$pending_delete_hash) {
982 next if $conf->{pending
}->{$opt}; # just to be sure
983 next if $conf->{$opt};
984 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
991 # POST/PUT {vmid}/config implementation
993 # The original API used PUT (idempotent) an we assumed that all operations
994 # are fast. But it turned out that almost any configuration change can
995 # involve hot-plug actions, or disk alloc/free. Such actions can take long
996 # time to complete and have side effects (not idempotent).
998 # The new implementation uses POST and forks a worker process. We added
999 # a new option 'background_delay'. If specified we wait up to
1000 # 'background_delay' second for the worker task to complete. It returns null
1001 # if the task is finished within that time, else we return the UPID.
1003 my $update_vm_api = sub {
1004 my ($param, $sync) = @_;
1006 my $rpcenv = PVE
::RPCEnvironment
::get
();
1008 my $authuser = $rpcenv->get_user();
1010 my $node = extract_param
($param, 'node');
1012 my $vmid = extract_param
($param, 'vmid');
1014 my $digest = extract_param
($param, 'digest');
1016 my $background_delay = extract_param
($param, 'background_delay');
1018 if (defined(my $cipassword = $param->{cipassword
})) {
1019 # Same logic as in cloud-init (but with the regex fixed...)
1020 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
1021 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1024 my @paramarr = (); # used for log message
1025 foreach my $key (sort keys %$param) {
1026 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1027 push @paramarr, "-$key", $value;
1030 my $skiplock = extract_param
($param, 'skiplock');
1031 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1032 if $skiplock && $authuser ne 'root@pam';
1034 my $delete_str = extract_param
($param, 'delete');
1036 my $revert_str = extract_param
($param, 'revert');
1038 my $force = extract_param
($param, 'force');
1040 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1041 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1042 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1045 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1047 my $storecfg = PVE
::Storage
::config
();
1049 my $defaults = PVE
::QemuServer
::load_defaults
();
1051 &$resolve_cdrom_alias($param);
1053 # now try to verify all parameters
1056 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1057 if (!PVE
::QemuServer
::option_exists
($opt)) {
1058 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1061 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1062 "-revert $opt' at the same time" })
1063 if defined($param->{$opt});
1065 $revert->{$opt} = 1;
1069 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1070 $opt = 'ide2' if $opt eq 'cdrom';
1072 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1073 "-delete $opt' at the same time" })
1074 if defined($param->{$opt});
1076 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1077 "-revert $opt' at the same time" })
1080 if (!PVE
::QemuServer
::option_exists
($opt)) {
1081 raise_param_exc
({ delete => "unknown option '$opt'" });
1087 my $repl_conf = PVE
::ReplicationConfig-
>new();
1088 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1089 my $check_replication = sub {
1091 return if !$is_replicated;
1092 my $volid = $drive->{file
};
1093 return if !$volid || !($drive->{replicate
}//1);
1094 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1095 my ($storeid, $format);
1096 if ($volid =~ $NEW_DISK_RE) {
1098 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1100 ($storeid, undef) = PVE
::Storage
::parse_volume_id
($volid, 1);
1101 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1103 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1104 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1105 return if $scfg->{shared
};
1106 die "cannot add non-replicatable volume to a replicated VM\n";
1109 foreach my $opt (keys %$param) {
1110 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1111 # cleanup drive path
1112 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1113 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1114 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1115 $check_replication->($drive);
1116 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1117 } elsif ($opt =~ m/^net(\d+)$/) {
1119 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1120 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1121 } elsif ($opt eq 'vmgenid') {
1122 if ($param->{$opt} eq '1') {
1123 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1125 } elsif ($opt eq 'hookscript') {
1126 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1127 raise_param_exc
({ $opt => $@ }) if $@;
1131 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1133 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1135 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1137 my $updatefn = sub {
1139 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1141 die "checksum missmatch (file change by other user?)\n"
1142 if $digest && $digest ne $conf->{digest
};
1144 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1146 foreach my $opt (keys %$revert) {
1147 if (defined($conf->{$opt})) {
1148 $param->{$opt} = $conf->{$opt};
1149 } elsif (defined($conf->{pending
}->{$opt})) {
1154 if ($param->{memory
} || defined($param->{balloon
})) {
1155 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1156 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1158 die "balloon value too large (must be smaller than assigned memory)\n"
1159 if $balloon && $balloon > $maxmem;
1162 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1166 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1168 # write updates to pending section
1170 my $modified = {}; # record what $option we modify
1172 foreach my $opt (@delete) {
1173 $modified->{$opt} = 1;
1174 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1175 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1176 warn "cannot delete '$opt' - not set in current configuration!\n";
1177 $modified->{$opt} = 0;
1181 if ($opt =~ m/^unused/) {
1182 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1183 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1184 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1185 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1186 delete $conf->{$opt};
1187 PVE
::QemuConfig-
>write_config($vmid, $conf);
1189 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1190 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1191 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1192 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1193 if defined($conf->{pending
}->{$opt});
1194 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1195 PVE
::QemuConfig-
>write_config($vmid, $conf);
1196 } elsif ($opt =~ m/^serial\d+$/) {
1197 if ($conf->{$opt} eq 'socket') {
1198 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1199 } elsif ($authuser ne 'root@pam') {
1200 die "only root can delete '$opt' config for real devices\n";
1202 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1203 PVE
::QemuConfig-
>write_config($vmid, $conf);
1204 } elsif ($opt =~ m/^usb\d+$/) {
1205 if ($conf->{$opt} =~ m/spice/) {
1206 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1207 } elsif ($authuser ne 'root@pam') {
1208 die "only root can delete '$opt' config for real devices\n";
1210 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1211 PVE
::QemuConfig-
>write_config($vmid, $conf);
1213 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1214 PVE
::QemuConfig-
>write_config($vmid, $conf);
1218 foreach my $opt (keys %$param) { # add/change
1219 $modified->{$opt} = 1;
1220 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1221 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1223 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
1225 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1226 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1227 # FIXME: cloudinit: CDROM or Disk?
1228 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1229 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1231 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1233 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1234 if defined($conf->{pending
}->{$opt});
1236 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1237 } elsif ($opt =~ m/^serial\d+/) {
1238 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1239 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1240 } elsif ($authuser ne 'root@pam') {
1241 die "only root can modify '$opt' config for real devices\n";
1243 $conf->{pending
}->{$opt} = $param->{$opt};
1244 } elsif ($opt =~ m/^usb\d+/) {
1245 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
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};
1252 $conf->{pending
}->{$opt} = $param->{$opt};
1254 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1255 PVE
::QemuConfig-
>write_config($vmid, $conf);
1258 # remove pending changes when nothing changed
1259 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1260 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1261 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1263 return if !scalar(keys %{$conf->{pending
}});
1265 my $running = PVE
::QemuServer
::check_running
($vmid);
1267 # apply pending changes
1269 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1273 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1274 raise_param_exc
($errors) if scalar(keys %$errors);
1276 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1286 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1288 if ($background_delay) {
1290 # Note: It would be better to do that in the Event based HTTPServer
1291 # to avoid blocking call to sleep.
1293 my $end_time = time() + $background_delay;
1295 my $task = PVE
::Tools
::upid_decode
($upid);
1298 while (time() < $end_time) {
1299 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1301 sleep(1); # this gets interrupted when child process ends
1305 my $status = PVE
::Tools
::upid_read_status
($upid);
1306 return undef if $status eq 'OK';
1315 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1318 my $vm_config_perm_list = [
1323 'VM.Config.Network',
1325 'VM.Config.Options',
1328 __PACKAGE__-
>register_method({
1329 name
=> 'update_vm_async',
1330 path
=> '{vmid}/config',
1334 description
=> "Set virtual machine options (asynchrounous API).",
1336 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1339 additionalProperties
=> 0,
1340 properties
=> PVE
::QemuServer
::json_config_properties
(
1342 node
=> get_standard_option
('pve-node'),
1343 vmid
=> get_standard_option
('pve-vmid'),
1344 skiplock
=> get_standard_option
('skiplock'),
1346 type
=> 'string', format
=> 'pve-configid-list',
1347 description
=> "A list of settings you want to delete.",
1351 type
=> 'string', format
=> 'pve-configid-list',
1352 description
=> "Revert a pending change.",
1357 description
=> $opt_force_description,
1359 requires
=> 'delete',
1363 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1367 background_delay
=> {
1369 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1380 code
=> $update_vm_api,
1383 __PACKAGE__-
>register_method({
1384 name
=> 'update_vm',
1385 path
=> '{vmid}/config',
1389 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1391 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1394 additionalProperties
=> 0,
1395 properties
=> PVE
::QemuServer
::json_config_properties
(
1397 node
=> get_standard_option
('pve-node'),
1398 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1399 skiplock
=> get_standard_option
('skiplock'),
1401 type
=> 'string', format
=> 'pve-configid-list',
1402 description
=> "A list of settings you want to delete.",
1406 type
=> 'string', format
=> 'pve-configid-list',
1407 description
=> "Revert a pending change.",
1412 description
=> $opt_force_description,
1414 requires
=> 'delete',
1418 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1424 returns
=> { type
=> 'null' },
1427 &$update_vm_api($param, 1);
1433 __PACKAGE__-
>register_method({
1434 name
=> 'destroy_vm',
1439 description
=> "Destroy the vm (also delete all used/owned volumes).",
1441 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1444 additionalProperties
=> 0,
1446 node
=> get_standard_option
('pve-node'),
1447 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1448 skiplock
=> get_standard_option
('skiplock'),
1457 my $rpcenv = PVE
::RPCEnvironment
::get
();
1459 my $authuser = $rpcenv->get_user();
1461 my $vmid = $param->{vmid
};
1463 my $skiplock = $param->{skiplock
};
1464 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1465 if $skiplock && $authuser ne 'root@pam';
1468 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1470 my $storecfg = PVE
::Storage
::config
();
1472 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1474 die "unable to remove VM $vmid - used in HA resources\n"
1475 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1477 # do not allow destroy if there are replication jobs
1478 my $repl_conf = PVE
::ReplicationConfig-
>new();
1479 $repl_conf->check_for_existing_jobs($vmid);
1481 # early tests (repeat after locking)
1482 die "VM $vmid is running - destroy failed\n"
1483 if PVE
::QemuServer
::check_running
($vmid);
1488 syslog
('info', "destroy VM $vmid: $upid\n");
1490 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1492 PVE
::AccessControl
::remove_vm_access
($vmid);
1494 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1497 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1500 __PACKAGE__-
>register_method({
1502 path
=> '{vmid}/unlink',
1506 description
=> "Unlink/delete disk images.",
1508 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1511 additionalProperties
=> 0,
1513 node
=> get_standard_option
('pve-node'),
1514 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1516 type
=> 'string', format
=> 'pve-configid-list',
1517 description
=> "A list of disk IDs you want to delete.",
1521 description
=> $opt_force_description,
1526 returns
=> { type
=> 'null'},
1530 $param->{delete} = extract_param
($param, 'idlist');
1532 __PACKAGE__-
>update_vm($param);
1539 __PACKAGE__-
>register_method({
1541 path
=> '{vmid}/vncproxy',
1545 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1547 description
=> "Creates a TCP VNC proxy connections.",
1549 additionalProperties
=> 0,
1551 node
=> get_standard_option
('pve-node'),
1552 vmid
=> get_standard_option
('pve-vmid'),
1556 description
=> "starts websockify instead of vncproxy",
1561 additionalProperties
=> 0,
1563 user
=> { type
=> 'string' },
1564 ticket
=> { type
=> 'string' },
1565 cert
=> { type
=> 'string' },
1566 port
=> { type
=> 'integer' },
1567 upid
=> { type
=> 'string' },
1573 my $rpcenv = PVE
::RPCEnvironment
::get
();
1575 my $authuser = $rpcenv->get_user();
1577 my $vmid = $param->{vmid
};
1578 my $node = $param->{node
};
1579 my $websocket = $param->{websocket
};
1581 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1582 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1584 my $authpath = "/vms/$vmid";
1586 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1588 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1594 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1595 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1596 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1597 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1598 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1600 $family = PVE
::Tools
::get_host_address_family
($node);
1603 my $port = PVE
::Tools
::next_vnc_port
($family);
1610 syslog
('info', "starting vnc proxy $upid\n");
1616 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1618 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1619 '-timeout', $timeout, '-authpath', $authpath,
1620 '-perm', 'Sys.Console'];
1622 if ($param->{websocket
}) {
1623 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1624 push @$cmd, '-notls', '-listen', 'localhost';
1627 push @$cmd, '-c', @$remcmd, @$termcmd;
1629 PVE
::Tools
::run_command
($cmd);
1633 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1635 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1637 my $sock = IO
::Socket
::IP-
>new(
1642 GetAddrInfoFlags
=> 0,
1643 ) or die "failed to create socket: $!\n";
1644 # Inside the worker we shouldn't have any previous alarms
1645 # running anyway...:
1647 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1649 accept(my $cli, $sock) or die "connection failed: $!\n";
1652 if (PVE
::Tools
::run_command
($cmd,
1653 output
=> '>&'.fileno($cli),
1654 input
=> '<&'.fileno($cli),
1657 die "Failed to run vncproxy.\n";
1664 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1666 PVE
::Tools
::wait_for_vnc_port
($port);
1677 __PACKAGE__-
>register_method({
1678 name
=> 'termproxy',
1679 path
=> '{vmid}/termproxy',
1683 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1685 description
=> "Creates a TCP proxy connections.",
1687 additionalProperties
=> 0,
1689 node
=> get_standard_option
('pve-node'),
1690 vmid
=> get_standard_option
('pve-vmid'),
1694 enum
=> [qw(serial0 serial1 serial2 serial3)],
1695 description
=> "opens a serial terminal (defaults to display)",
1700 additionalProperties
=> 0,
1702 user
=> { type
=> 'string' },
1703 ticket
=> { type
=> 'string' },
1704 port
=> { type
=> 'integer' },
1705 upid
=> { type
=> 'string' },
1711 my $rpcenv = PVE
::RPCEnvironment
::get
();
1713 my $authuser = $rpcenv->get_user();
1715 my $vmid = $param->{vmid
};
1716 my $node = $param->{node
};
1717 my $serial = $param->{serial
};
1719 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1721 if (!defined($serial)) {
1722 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1723 $serial = $conf->{vga
};
1727 my $authpath = "/vms/$vmid";
1729 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1734 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1735 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1736 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1737 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, '-t');
1738 push @$remcmd, '--';
1740 $family = PVE
::Tools
::get_host_address_family
($node);
1743 my $port = PVE
::Tools
::next_vnc_port
($family);
1745 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1746 push @$termcmd, '-iface', $serial if $serial;
1751 syslog
('info', "starting qemu termproxy $upid\n");
1753 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1754 '--perm', 'VM.Console', '--'];
1755 push @$cmd, @$remcmd, @$termcmd;
1757 PVE
::Tools
::run_command
($cmd);
1760 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1762 PVE
::Tools
::wait_for_vnc_port
($port);
1772 __PACKAGE__-
>register_method({
1773 name
=> 'vncwebsocket',
1774 path
=> '{vmid}/vncwebsocket',
1777 description
=> "You also need to pass a valid ticket (vncticket).",
1778 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1780 description
=> "Opens a weksocket for VNC traffic.",
1782 additionalProperties
=> 0,
1784 node
=> get_standard_option
('pve-node'),
1785 vmid
=> get_standard_option
('pve-vmid'),
1787 description
=> "Ticket from previous call to vncproxy.",
1792 description
=> "Port number returned by previous vncproxy call.",
1802 port
=> { type
=> 'string' },
1808 my $rpcenv = PVE
::RPCEnvironment
::get
();
1810 my $authuser = $rpcenv->get_user();
1812 my $vmid = $param->{vmid
};
1813 my $node = $param->{node
};
1815 my $authpath = "/vms/$vmid";
1817 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1819 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1821 # Note: VNC ports are acessible from outside, so we do not gain any
1822 # security if we verify that $param->{port} belongs to VM $vmid. This
1823 # check is done by verifying the VNC ticket (inside VNC protocol).
1825 my $port = $param->{port
};
1827 return { port
=> $port };
1830 __PACKAGE__-
>register_method({
1831 name
=> 'spiceproxy',
1832 path
=> '{vmid}/spiceproxy',
1837 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1839 description
=> "Returns a SPICE configuration to connect to the VM.",
1841 additionalProperties
=> 0,
1843 node
=> get_standard_option
('pve-node'),
1844 vmid
=> get_standard_option
('pve-vmid'),
1845 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1848 returns
=> get_standard_option
('remote-viewer-config'),
1852 my $rpcenv = PVE
::RPCEnvironment
::get
();
1854 my $authuser = $rpcenv->get_user();
1856 my $vmid = $param->{vmid
};
1857 my $node = $param->{node
};
1858 my $proxy = $param->{proxy
};
1860 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1861 my $title = "VM $vmid";
1862 $title .= " - ". $conf->{name
} if $conf->{name
};
1864 my $port = PVE
::QemuServer
::spice_port
($vmid);
1866 my ($ticket, undef, $remote_viewer_config) =
1867 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1869 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1870 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1872 return $remote_viewer_config;
1875 __PACKAGE__-
>register_method({
1877 path
=> '{vmid}/status',
1880 description
=> "Directory index",
1885 additionalProperties
=> 0,
1887 node
=> get_standard_option
('pve-node'),
1888 vmid
=> get_standard_option
('pve-vmid'),
1896 subdir
=> { type
=> 'string' },
1899 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1905 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1908 { subdir
=> 'current' },
1909 { subdir
=> 'start' },
1910 { subdir
=> 'stop' },
1916 __PACKAGE__-
>register_method({
1917 name
=> 'vm_status',
1918 path
=> '{vmid}/status/current',
1921 protected
=> 1, # qemu pid files are only readable by root
1922 description
=> "Get virtual machine status.",
1924 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1927 additionalProperties
=> 0,
1929 node
=> get_standard_option
('pve-node'),
1930 vmid
=> get_standard_option
('pve-vmid'),
1936 %$PVE::QemuServer
::vmstatus_return_properties
,
1938 description
=> "HA manager service status.",
1942 description
=> "Qemu VGA configuration supports spice.",
1947 description
=> "Qemu GuestAgent enabled in config.",
1957 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1959 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1960 my $status = $vmstatus->{$param->{vmid
}};
1962 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1964 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1965 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1970 __PACKAGE__-
>register_method({
1972 path
=> '{vmid}/status/start',
1976 description
=> "Start virtual machine.",
1978 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1981 additionalProperties
=> 0,
1983 node
=> get_standard_option
('pve-node'),
1984 vmid
=> get_standard_option
('pve-vmid',
1985 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1986 skiplock
=> get_standard_option
('skiplock'),
1987 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1988 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1991 enum
=> ['secure', 'insecure'],
1992 description
=> "Migration traffic is encrypted using an SSH " .
1993 "tunnel by default. On secure, completely private networks " .
1994 "this can be disabled to increase performance.",
1997 migration_network
=> {
1998 type
=> 'string', format
=> 'CIDR',
1999 description
=> "CIDR of the (sub) network that is used for migration.",
2002 machine
=> get_standard_option
('pve-qm-machine'),
2004 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
2016 my $rpcenv = PVE
::RPCEnvironment
::get
();
2018 my $authuser = $rpcenv->get_user();
2020 my $node = extract_param
($param, 'node');
2022 my $vmid = extract_param
($param, 'vmid');
2024 my $machine = extract_param
($param, 'machine');
2026 my $stateuri = extract_param
($param, 'stateuri');
2027 raise_param_exc
({ stateuri
=> "Only root may use this option." })
2028 if $stateuri && $authuser ne 'root@pam';
2030 my $skiplock = extract_param
($param, 'skiplock');
2031 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2032 if $skiplock && $authuser ne 'root@pam';
2034 my $migratedfrom = extract_param
($param, 'migratedfrom');
2035 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2036 if $migratedfrom && $authuser ne 'root@pam';
2038 my $migration_type = extract_param
($param, 'migration_type');
2039 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2040 if $migration_type && $authuser ne 'root@pam';
2042 my $migration_network = extract_param
($param, 'migration_network');
2043 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2044 if $migration_network && $authuser ne 'root@pam';
2046 my $targetstorage = extract_param
($param, 'targetstorage');
2047 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
2048 if $targetstorage && $authuser ne 'root@pam';
2050 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2051 if $targetstorage && !$migratedfrom;
2053 # read spice ticket from STDIN
2055 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2056 if (defined(my $line = <STDIN
>)) {
2058 $spice_ticket = $line;
2062 PVE
::Cluster
::check_cfs_quorum
();
2064 my $storecfg = PVE
::Storage
::config
();
2066 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
2067 $rpcenv->{type
} ne 'ha') {
2072 my $service = "vm:$vmid";
2074 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
2076 print "Requesting HA start for VM $vmid\n";
2078 PVE
::Tools
::run_command
($cmd);
2083 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2090 syslog
('info', "start VM $vmid: $upid\n");
2092 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2093 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
2098 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2102 __PACKAGE__-
>register_method({
2104 path
=> '{vmid}/status/stop',
2108 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2109 "is akin to pulling the power plug of a running computer and may damage the VM data",
2111 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2114 additionalProperties
=> 0,
2116 node
=> get_standard_option
('pve-node'),
2117 vmid
=> get_standard_option
('pve-vmid',
2118 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2119 skiplock
=> get_standard_option
('skiplock'),
2120 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2122 description
=> "Wait maximal timeout seconds.",
2128 description
=> "Do not deactivate storage volumes.",
2141 my $rpcenv = PVE
::RPCEnvironment
::get
();
2143 my $authuser = $rpcenv->get_user();
2145 my $node = extract_param
($param, 'node');
2147 my $vmid = extract_param
($param, 'vmid');
2149 my $skiplock = extract_param
($param, 'skiplock');
2150 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2151 if $skiplock && $authuser ne 'root@pam';
2153 my $keepActive = extract_param
($param, 'keepActive');
2154 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2155 if $keepActive && $authuser ne 'root@pam';
2157 my $migratedfrom = extract_param
($param, 'migratedfrom');
2158 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2159 if $migratedfrom && $authuser ne 'root@pam';
2162 my $storecfg = PVE
::Storage
::config
();
2164 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2169 my $service = "vm:$vmid";
2171 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2173 print "Requesting HA stop for VM $vmid\n";
2175 PVE
::Tools
::run_command
($cmd);
2180 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2186 syslog
('info', "stop VM $vmid: $upid\n");
2188 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2189 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2194 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2198 __PACKAGE__-
>register_method({
2200 path
=> '{vmid}/status/reset',
2204 description
=> "Reset virtual machine.",
2206 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2209 additionalProperties
=> 0,
2211 node
=> get_standard_option
('pve-node'),
2212 vmid
=> get_standard_option
('pve-vmid',
2213 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2214 skiplock
=> get_standard_option
('skiplock'),
2223 my $rpcenv = PVE
::RPCEnvironment
::get
();
2225 my $authuser = $rpcenv->get_user();
2227 my $node = extract_param
($param, 'node');
2229 my $vmid = extract_param
($param, 'vmid');
2231 my $skiplock = extract_param
($param, 'skiplock');
2232 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2233 if $skiplock && $authuser ne 'root@pam';
2235 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2240 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2245 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2248 __PACKAGE__-
>register_method({
2249 name
=> 'vm_shutdown',
2250 path
=> '{vmid}/status/shutdown',
2254 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2255 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2257 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2260 additionalProperties
=> 0,
2262 node
=> get_standard_option
('pve-node'),
2263 vmid
=> get_standard_option
('pve-vmid',
2264 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2265 skiplock
=> get_standard_option
('skiplock'),
2267 description
=> "Wait maximal timeout seconds.",
2273 description
=> "Make sure the VM stops.",
2279 description
=> "Do not deactivate storage volumes.",
2292 my $rpcenv = PVE
::RPCEnvironment
::get
();
2294 my $authuser = $rpcenv->get_user();
2296 my $node = extract_param
($param, 'node');
2298 my $vmid = extract_param
($param, 'vmid');
2300 my $skiplock = extract_param
($param, 'skiplock');
2301 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2302 if $skiplock && $authuser ne 'root@pam';
2304 my $keepActive = extract_param
($param, 'keepActive');
2305 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2306 if $keepActive && $authuser ne 'root@pam';
2308 my $storecfg = PVE
::Storage
::config
();
2312 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2313 # otherwise, we will infer a shutdown command, but run into the timeout,
2314 # then when the vm is resumed, it will instantly shutdown
2316 # checking the qmp status here to get feedback to the gui/cli/api
2317 # and the status query should not take too long
2320 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2324 if (!$err && $qmpstatus->{status
} eq "paused") {
2325 if ($param->{forceStop
}) {
2326 warn "VM is paused - stop instead of shutdown\n";
2329 die "VM is paused - cannot shutdown\n";
2333 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2334 ($rpcenv->{type
} ne 'ha')) {
2339 my $service = "vm:$vmid";
2341 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2343 print "Requesting HA stop for VM $vmid\n";
2345 PVE
::Tools
::run_command
($cmd);
2350 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2357 syslog
('info', "shutdown VM $vmid: $upid\n");
2359 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2360 $shutdown, $param->{forceStop
}, $keepActive);
2365 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2369 __PACKAGE__-
>register_method({
2370 name
=> 'vm_suspend',
2371 path
=> '{vmid}/status/suspend',
2375 description
=> "Suspend virtual machine.",
2377 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2380 additionalProperties
=> 0,
2382 node
=> get_standard_option
('pve-node'),
2383 vmid
=> get_standard_option
('pve-vmid',
2384 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2385 skiplock
=> get_standard_option
('skiplock'),
2390 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2392 statestorage
=> get_standard_option
('pve-storage-id', {
2393 description
=> "The storage for the VM state",
2394 requires
=> 'todisk',
2396 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2406 my $rpcenv = PVE
::RPCEnvironment
::get
();
2408 my $authuser = $rpcenv->get_user();
2410 my $node = extract_param
($param, 'node');
2412 my $vmid = extract_param
($param, 'vmid');
2414 my $todisk = extract_param
($param, 'todisk') // 0;
2416 my $statestorage = extract_param
($param, 'statestorage');
2418 my $skiplock = extract_param
($param, 'skiplock');
2419 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2420 if $skiplock && $authuser ne 'root@pam';
2422 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2424 die "Cannot suspend HA managed VM to disk\n"
2425 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2427 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2432 syslog
('info', "suspend VM $vmid: $upid\n");
2434 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2439 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2442 __PACKAGE__-
>register_method({
2443 name
=> 'vm_resume',
2444 path
=> '{vmid}/status/resume',
2448 description
=> "Resume virtual machine.",
2450 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2453 additionalProperties
=> 0,
2455 node
=> get_standard_option
('pve-node'),
2456 vmid
=> get_standard_option
('pve-vmid',
2457 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2458 skiplock
=> get_standard_option
('skiplock'),
2459 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2469 my $rpcenv = PVE
::RPCEnvironment
::get
();
2471 my $authuser = $rpcenv->get_user();
2473 my $node = extract_param
($param, 'node');
2475 my $vmid = extract_param
($param, 'vmid');
2477 my $skiplock = extract_param
($param, 'skiplock');
2478 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2479 if $skiplock && $authuser ne 'root@pam';
2481 my $nocheck = extract_param
($param, 'nocheck');
2483 my $to_disk_suspended;
2485 PVE
::QemuConfig-
>lock_config($vmid, sub {
2486 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2487 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2491 die "VM $vmid not running\n"
2492 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2497 syslog
('info', "resume VM $vmid: $upid\n");
2499 if (!$to_disk_suspended) {
2500 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2502 my $storecfg = PVE
::Storage
::config
();
2503 PVE
::QemuServer
::vm_start
($storecfg, $vmid, undef, $skiplock);
2509 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2512 __PACKAGE__-
>register_method({
2513 name
=> 'vm_sendkey',
2514 path
=> '{vmid}/sendkey',
2518 description
=> "Send key event to virtual machine.",
2520 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2523 additionalProperties
=> 0,
2525 node
=> get_standard_option
('pve-node'),
2526 vmid
=> get_standard_option
('pve-vmid',
2527 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2528 skiplock
=> get_standard_option
('skiplock'),
2530 description
=> "The key (qemu monitor encoding).",
2535 returns
=> { type
=> 'null'},
2539 my $rpcenv = PVE
::RPCEnvironment
::get
();
2541 my $authuser = $rpcenv->get_user();
2543 my $node = extract_param
($param, 'node');
2545 my $vmid = extract_param
($param, 'vmid');
2547 my $skiplock = extract_param
($param, 'skiplock');
2548 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2549 if $skiplock && $authuser ne 'root@pam';
2551 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2556 __PACKAGE__-
>register_method({
2557 name
=> 'vm_feature',
2558 path
=> '{vmid}/feature',
2562 description
=> "Check if feature for virtual machine is available.",
2564 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2567 additionalProperties
=> 0,
2569 node
=> get_standard_option
('pve-node'),
2570 vmid
=> get_standard_option
('pve-vmid'),
2572 description
=> "Feature to check.",
2574 enum
=> [ 'snapshot', 'clone', 'copy' ],
2576 snapname
=> get_standard_option
('pve-snapshot-name', {
2584 hasFeature
=> { type
=> 'boolean' },
2587 items
=> { type
=> 'string' },
2594 my $node = extract_param
($param, 'node');
2596 my $vmid = extract_param
($param, 'vmid');
2598 my $snapname = extract_param
($param, 'snapname');
2600 my $feature = extract_param
($param, 'feature');
2602 my $running = PVE
::QemuServer
::check_running
($vmid);
2604 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2607 my $snap = $conf->{snapshots
}->{$snapname};
2608 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2611 my $storecfg = PVE
::Storage
::config
();
2613 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2614 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2617 hasFeature
=> $hasFeature,
2618 nodes
=> [ keys %$nodelist ],
2622 __PACKAGE__-
>register_method({
2624 path
=> '{vmid}/clone',
2628 description
=> "Create a copy of virtual machine/template.",
2630 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2631 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2632 "'Datastore.AllocateSpace' on any used storage.",
2635 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2637 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2638 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2643 additionalProperties
=> 0,
2645 node
=> get_standard_option
('pve-node'),
2646 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2647 newid
=> get_standard_option
('pve-vmid', {
2648 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2649 description
=> 'VMID for the clone.' }),
2652 type
=> 'string', format
=> 'dns-name',
2653 description
=> "Set a name for the new VM.",
2658 description
=> "Description for the new VM.",
2662 type
=> 'string', format
=> 'pve-poolid',
2663 description
=> "Add the new VM to the specified pool.",
2665 snapname
=> get_standard_option
('pve-snapshot-name', {
2668 storage
=> get_standard_option
('pve-storage-id', {
2669 description
=> "Target storage for full clone.",
2673 description
=> "Target format for file storage. Only valid for full clone.",
2676 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2681 description
=> "Create a full copy of all disks. This is always done when " .
2682 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2684 target
=> get_standard_option
('pve-node', {
2685 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2689 description
=> "Override I/O bandwidth limit (in KiB/s).",
2693 default => 'clone limit from datacenter or storage config',
2703 my $rpcenv = PVE
::RPCEnvironment
::get
();
2705 my $authuser = $rpcenv->get_user();
2707 my $node = extract_param
($param, 'node');
2709 my $vmid = extract_param
($param, 'vmid');
2711 my $newid = extract_param
($param, 'newid');
2713 my $pool = extract_param
($param, 'pool');
2715 if (defined($pool)) {
2716 $rpcenv->check_pool_exist($pool);
2719 my $snapname = extract_param
($param, 'snapname');
2721 my $storage = extract_param
($param, 'storage');
2723 my $format = extract_param
($param, 'format');
2725 my $target = extract_param
($param, 'target');
2727 my $localnode = PVE
::INotify
::nodename
();
2729 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2731 PVE
::Cluster
::check_node_exists
($target) if $target;
2733 my $storecfg = PVE
::Storage
::config
();
2736 # check if storage is enabled on local node
2737 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2739 # check if storage is available on target node
2740 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2741 # clone only works if target storage is shared
2742 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2743 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2747 PVE
::Cluster
::check_cfs_quorum
();
2749 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2751 # exclusive lock if VM is running - else shared lock is enough;
2752 my $shared_lock = $running ?
0 : 1;
2756 # do all tests after lock
2757 # we also try to do all tests before we fork the worker
2759 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2761 PVE
::QemuConfig-
>check_lock($conf);
2763 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2765 die "unexpected state change\n" if $verify_running != $running;
2767 die "snapshot '$snapname' does not exist\n"
2768 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2770 my $full = extract_param
($param, 'full');
2771 if (!defined($full)) {
2772 $full = !PVE
::QemuConfig-
>is_template($conf);
2775 die "parameter 'storage' not allowed for linked clones\n"
2776 if defined($storage) && !$full;
2778 die "parameter 'format' not allowed for linked clones\n"
2779 if defined($format) && !$full;
2781 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2783 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2785 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2787 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2789 die "unable to create VM $newid: config file already exists\n"
2792 my $newconf = { lock => 'clone' };
2797 foreach my $opt (keys %$oldconf) {
2798 my $value = $oldconf->{$opt};
2800 # do not copy snapshot related info
2801 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2802 $opt eq 'vmstate' || $opt eq 'snapstate';
2804 # no need to copy unused images, because VMID(owner) changes anyways
2805 next if $opt =~ m/^unused\d+$/;
2807 # always change MAC! address
2808 if ($opt =~ m/^net(\d+)$/) {
2809 my $net = PVE
::QemuServer
::parse_net
($value);
2810 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2811 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2812 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2813 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2814 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2815 die "unable to parse drive options for '$opt'\n" if !$drive;
2816 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2817 $newconf->{$opt} = $value; # simply copy configuration
2819 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2820 die "Full clone feature is not supported for drive '$opt'\n"
2821 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2822 $fullclone->{$opt} = 1;
2824 # not full means clone instead of copy
2825 die "Linked clone feature is not supported for drive '$opt'\n"
2826 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2828 $drives->{$opt} = $drive;
2829 push @$vollist, $drive->{file
};
2832 # copy everything else
2833 $newconf->{$opt} = $value;
2837 # auto generate a new uuid
2838 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2839 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2840 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2842 # auto generate a new vmgenid if the option was set
2843 if ($newconf->{vmgenid
}) {
2844 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2847 delete $newconf->{template
};
2849 if ($param->{name
}) {
2850 $newconf->{name
} = $param->{name
};
2852 if ($oldconf->{name
}) {
2853 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2855 $newconf->{name
} = "Copy-of-VM-$vmid";
2859 if ($param->{description
}) {
2860 $newconf->{description
} = $param->{description
};
2863 # create empty/temp config - this fails if VM already exists on other node
2864 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2869 my $newvollist = [];
2876 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2878 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2880 my $bwlimit = extract_param
($param, 'bwlimit');
2882 my $total_jobs = scalar(keys %{$drives});
2885 foreach my $opt (keys %$drives) {
2886 my $drive = $drives->{$opt};
2887 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2889 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
2890 my $storage_list = [ $src_sid ];
2891 push @$storage_list, $storage if defined($storage);
2892 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
2894 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2895 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2896 $jobs, $skipcomplete, $oldconf->{agent
}, $clonelimit);
2898 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2900 PVE
::QemuConfig-
>write_config($newid, $newconf);
2904 delete $newconf->{lock};
2906 # do not write pending changes
2907 if (my @changes = keys %{$newconf->{pending
}}) {
2908 my $pending = join(',', @changes);
2909 warn "found pending changes for '$pending', discarding for clone\n";
2910 delete $newconf->{pending
};
2913 PVE
::QemuConfig-
>write_config($newid, $newconf);
2916 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2917 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2918 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2920 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2921 die "Failed to move config to node '$target' - rename failed: $!\n"
2922 if !rename($conffile, $newconffile);
2925 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2930 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2932 sleep 1; # some storage like rbd need to wait before release volume - really?
2934 foreach my $volid (@$newvollist) {
2935 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2938 die "clone failed: $err";
2944 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2946 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2949 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2950 # Aquire exclusive lock lock for $newid
2951 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2956 __PACKAGE__-
>register_method({
2957 name
=> 'move_vm_disk',
2958 path
=> '{vmid}/move_disk',
2962 description
=> "Move volume to different storage.",
2964 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2966 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2967 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2971 additionalProperties
=> 0,
2973 node
=> get_standard_option
('pve-node'),
2974 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2977 description
=> "The disk you want to move.",
2978 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2980 storage
=> get_standard_option
('pve-storage-id', {
2981 description
=> "Target storage.",
2982 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2986 description
=> "Target Format.",
2987 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2992 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2998 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3003 description
=> "Override I/O bandwidth limit (in KiB/s).",
3007 default => 'move limit from datacenter or storage config',
3013 description
=> "the task ID.",
3018 my $rpcenv = PVE
::RPCEnvironment
::get
();
3020 my $authuser = $rpcenv->get_user();
3022 my $node = extract_param
($param, 'node');
3024 my $vmid = extract_param
($param, 'vmid');
3026 my $digest = extract_param
($param, 'digest');
3028 my $disk = extract_param
($param, 'disk');
3030 my $storeid = extract_param
($param, 'storage');
3032 my $format = extract_param
($param, 'format');
3034 my $storecfg = PVE
::Storage
::config
();
3036 my $updatefn = sub {
3038 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3040 PVE
::QemuConfig-
>check_lock($conf);
3042 die "checksum missmatch (file change by other user?)\n"
3043 if $digest && $digest ne $conf->{digest
};
3045 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3047 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3049 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
3051 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3054 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3055 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3059 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
3060 (!$format || !$oldfmt || $oldfmt eq $format);
3062 # this only checks snapshots because $disk is passed!
3063 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3064 die "you can't move a disk with snapshots and delete the source\n"
3065 if $snapshotted && $param->{delete};
3067 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3069 my $running = PVE
::QemuServer
::check_running
($vmid);
3071 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3075 my $newvollist = [];
3081 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3083 warn "moving disk with snapshots, snapshots will not be moved!\n"
3086 my $bwlimit = extract_param
($param, 'bwlimit');
3087 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3089 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3090 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit);
3092 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
3094 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3096 # convert moved disk to base if part of template
3097 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3098 if PVE
::QemuConfig-
>is_template($conf);
3100 PVE
::QemuConfig-
>write_config($vmid, $conf);
3102 if ($running && PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
} && PVE
::QemuServer
::qga_check_running
($vmid)) {
3103 eval { PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-fstrim"); };
3107 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3108 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3115 foreach my $volid (@$newvollist) {
3116 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3119 die "storage migration failed: $err";
3122 if ($param->{delete}) {
3124 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3125 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3131 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3134 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3137 __PACKAGE__-
>register_method({
3138 name
=> 'migrate_vm',
3139 path
=> '{vmid}/migrate',
3143 description
=> "Migrate virtual machine. Creates a new migration task.",
3145 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3148 additionalProperties
=> 0,
3150 node
=> get_standard_option
('pve-node'),
3151 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3152 target
=> get_standard_option
('pve-node', {
3153 description
=> "Target node.",
3154 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3158 description
=> "Use online/live migration.",
3163 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3168 enum
=> ['secure', 'insecure'],
3169 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3172 migration_network
=> {
3173 type
=> 'string', format
=> 'CIDR',
3174 description
=> "CIDR of the (sub) network that is used for migration.",
3177 "with-local-disks" => {
3179 description
=> "Enable live storage migration for local disk",
3182 targetstorage
=> get_standard_option
('pve-storage-id', {
3183 description
=> "Default target storage.",
3185 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3188 description
=> "Override I/O bandwidth limit (in KiB/s).",
3192 default => 'migrate limit from datacenter or storage config',
3198 description
=> "the task ID.",
3203 my $rpcenv = PVE
::RPCEnvironment
::get
();
3205 my $authuser = $rpcenv->get_user();
3207 my $target = extract_param
($param, 'target');
3209 my $localnode = PVE
::INotify
::nodename
();
3210 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3212 PVE
::Cluster
::check_cfs_quorum
();
3214 PVE
::Cluster
::check_node_exists
($target);
3216 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3218 my $vmid = extract_param
($param, 'vmid');
3220 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3221 if !$param->{online
} && $param->{targetstorage
};
3223 raise_param_exc
({ force
=> "Only root may use this option." })
3224 if $param->{force
} && $authuser ne 'root@pam';
3226 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3227 if $param->{migration_type
} && $authuser ne 'root@pam';
3229 # allow root only until better network permissions are available
3230 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3231 if $param->{migration_network
} && $authuser ne 'root@pam';
3234 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3236 # try to detect errors early
3238 PVE
::QemuConfig-
>check_lock($conf);
3240 if (PVE
::QemuServer
::check_running
($vmid)) {
3241 die "cant migrate running VM without --online\n"
3242 if !$param->{online
};
3245 my $storecfg = PVE
::Storage
::config
();
3247 if( $param->{targetstorage
}) {
3248 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3250 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3253 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3258 my $service = "vm:$vmid";
3260 my $cmd = ['ha-manager', 'migrate', $service, $target];
3262 print "Requesting HA migration for VM $vmid to node $target\n";
3264 PVE
::Tools
::run_command
($cmd);
3269 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3274 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3278 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3281 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3286 __PACKAGE__-
>register_method({
3288 path
=> '{vmid}/monitor',
3292 description
=> "Execute Qemu monitor commands.",
3294 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3295 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3298 additionalProperties
=> 0,
3300 node
=> get_standard_option
('pve-node'),
3301 vmid
=> get_standard_option
('pve-vmid'),
3304 description
=> "The monitor command.",
3308 returns
=> { type
=> 'string'},
3312 my $rpcenv = PVE
::RPCEnvironment
::get
();
3313 my $authuser = $rpcenv->get_user();
3316 my $command = shift;
3317 return $command =~ m/^\s*info(\s+|$)/
3318 || $command =~ m/^\s*help\s*$/;
3321 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3322 if !&$is_ro($param->{command
});
3324 my $vmid = $param->{vmid
};
3326 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3330 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3332 $res = "ERROR: $@" if $@;
3337 __PACKAGE__-
>register_method({
3338 name
=> 'resize_vm',
3339 path
=> '{vmid}/resize',
3343 description
=> "Extend volume size.",
3345 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3348 additionalProperties
=> 0,
3350 node
=> get_standard_option
('pve-node'),
3351 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3352 skiplock
=> get_standard_option
('skiplock'),
3355 description
=> "The disk you want to resize.",
3356 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3360 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3361 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.",
3365 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3371 returns
=> { type
=> 'null'},
3375 my $rpcenv = PVE
::RPCEnvironment
::get
();
3377 my $authuser = $rpcenv->get_user();
3379 my $node = extract_param
($param, 'node');
3381 my $vmid = extract_param
($param, 'vmid');
3383 my $digest = extract_param
($param, 'digest');
3385 my $disk = extract_param
($param, 'disk');
3387 my $sizestr = extract_param
($param, 'size');
3389 my $skiplock = extract_param
($param, 'skiplock');
3390 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3391 if $skiplock && $authuser ne 'root@pam';
3393 my $storecfg = PVE
::Storage
::config
();
3395 my $updatefn = sub {
3397 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3399 die "checksum missmatch (file change by other user?)\n"
3400 if $digest && $digest ne $conf->{digest
};
3401 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3403 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3405 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3407 my (undef, undef, undef, undef, undef, undef, $format) =
3408 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3410 die "can't resize volume: $disk if snapshot exists\n"
3411 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3413 my $volid = $drive->{file
};
3415 die "disk '$disk' has no associated volume\n" if !$volid;
3417 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3419 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3421 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3423 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3424 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3426 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3427 my ($ext, $newsize, $unit) = ($1, $2, $4);
3430 $newsize = $newsize * 1024;
3431 } elsif ($unit eq 'M') {
3432 $newsize = $newsize * 1024 * 1024;
3433 } elsif ($unit eq 'G') {
3434 $newsize = $newsize * 1024 * 1024 * 1024;
3435 } elsif ($unit eq 'T') {
3436 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3439 $newsize += $size if $ext;
3440 $newsize = int($newsize);
3442 die "shrinking disks is not supported\n" if $newsize < $size;
3444 return if $size == $newsize;
3446 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3448 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3450 $drive->{size
} = $newsize;
3451 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3453 PVE
::QemuConfig-
>write_config($vmid, $conf);
3456 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3460 __PACKAGE__-
>register_method({
3461 name
=> 'snapshot_list',
3462 path
=> '{vmid}/snapshot',
3464 description
=> "List all snapshots.",
3466 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3469 protected
=> 1, # qemu pid files are only readable by root
3471 additionalProperties
=> 0,
3473 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3474 node
=> get_standard_option
('pve-node'),
3483 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3487 description
=> "Snapshot includes RAM.",
3492 description
=> "Snapshot description.",
3496 description
=> "Snapshot creation time",
3498 renderer
=> 'timestamp',
3502 description
=> "Parent snapshot identifier.",
3508 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3513 my $vmid = $param->{vmid
};
3515 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3516 my $snaphash = $conf->{snapshots
} || {};
3520 foreach my $name (keys %$snaphash) {
3521 my $d = $snaphash->{$name};
3524 snaptime
=> $d->{snaptime
} || 0,
3525 vmstate
=> $d->{vmstate
} ?
1 : 0,
3526 description
=> $d->{description
} || '',
3528 $item->{parent
} = $d->{parent
} if $d->{parent
};
3529 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3533 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3536 digest
=> $conf->{digest
},
3537 running
=> $running,
3538 description
=> "You are here!",
3540 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3542 push @$res, $current;
3547 __PACKAGE__-
>register_method({
3549 path
=> '{vmid}/snapshot',
3553 description
=> "Snapshot a VM.",
3555 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3558 additionalProperties
=> 0,
3560 node
=> get_standard_option
('pve-node'),
3561 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3562 snapname
=> get_standard_option
('pve-snapshot-name'),
3566 description
=> "Save the vmstate",
3571 description
=> "A textual description or comment.",
3577 description
=> "the task ID.",
3582 my $rpcenv = PVE
::RPCEnvironment
::get
();
3584 my $authuser = $rpcenv->get_user();
3586 my $node = extract_param
($param, 'node');
3588 my $vmid = extract_param
($param, 'vmid');
3590 my $snapname = extract_param
($param, 'snapname');
3592 die "unable to use snapshot name 'current' (reserved name)\n"
3593 if $snapname eq 'current';
3596 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3597 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3598 $param->{description
});
3601 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3604 __PACKAGE__-
>register_method({
3605 name
=> 'snapshot_cmd_idx',
3606 path
=> '{vmid}/snapshot/{snapname}',
3613 additionalProperties
=> 0,
3615 vmid
=> get_standard_option
('pve-vmid'),
3616 node
=> get_standard_option
('pve-node'),
3617 snapname
=> get_standard_option
('pve-snapshot-name'),
3626 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3633 push @$res, { cmd
=> 'rollback' };
3634 push @$res, { cmd
=> 'config' };
3639 __PACKAGE__-
>register_method({
3640 name
=> 'update_snapshot_config',
3641 path
=> '{vmid}/snapshot/{snapname}/config',
3645 description
=> "Update snapshot metadata.",
3647 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3650 additionalProperties
=> 0,
3652 node
=> get_standard_option
('pve-node'),
3653 vmid
=> get_standard_option
('pve-vmid'),
3654 snapname
=> get_standard_option
('pve-snapshot-name'),
3658 description
=> "A textual description or comment.",
3662 returns
=> { type
=> 'null' },
3666 my $rpcenv = PVE
::RPCEnvironment
::get
();
3668 my $authuser = $rpcenv->get_user();
3670 my $vmid = extract_param
($param, 'vmid');
3672 my $snapname = extract_param
($param, 'snapname');
3674 return undef if !defined($param->{description
});
3676 my $updatefn = sub {
3678 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3680 PVE
::QemuConfig-
>check_lock($conf);
3682 my $snap = $conf->{snapshots
}->{$snapname};
3684 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3686 $snap->{description
} = $param->{description
} if defined($param->{description
});
3688 PVE
::QemuConfig-
>write_config($vmid, $conf);
3691 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3696 __PACKAGE__-
>register_method({
3697 name
=> 'get_snapshot_config',
3698 path
=> '{vmid}/snapshot/{snapname}/config',
3701 description
=> "Get snapshot configuration",
3703 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3706 additionalProperties
=> 0,
3708 node
=> get_standard_option
('pve-node'),
3709 vmid
=> get_standard_option
('pve-vmid'),
3710 snapname
=> get_standard_option
('pve-snapshot-name'),
3713 returns
=> { type
=> "object" },
3717 my $rpcenv = PVE
::RPCEnvironment
::get
();
3719 my $authuser = $rpcenv->get_user();
3721 my $vmid = extract_param
($param, 'vmid');
3723 my $snapname = extract_param
($param, 'snapname');
3725 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3727 my $snap = $conf->{snapshots
}->{$snapname};
3729 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3734 __PACKAGE__-
>register_method({
3736 path
=> '{vmid}/snapshot/{snapname}/rollback',
3740 description
=> "Rollback VM state to specified snapshot.",
3742 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3745 additionalProperties
=> 0,
3747 node
=> get_standard_option
('pve-node'),
3748 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3749 snapname
=> get_standard_option
('pve-snapshot-name'),
3754 description
=> "the task ID.",
3759 my $rpcenv = PVE
::RPCEnvironment
::get
();
3761 my $authuser = $rpcenv->get_user();
3763 my $node = extract_param
($param, 'node');
3765 my $vmid = extract_param
($param, 'vmid');
3767 my $snapname = extract_param
($param, 'snapname');
3770 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3771 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3775 # hold migration lock, this makes sure that nobody create replication snapshots
3776 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3779 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3782 __PACKAGE__-
>register_method({
3783 name
=> 'delsnapshot',
3784 path
=> '{vmid}/snapshot/{snapname}',
3788 description
=> "Delete a VM snapshot.",
3790 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3793 additionalProperties
=> 0,
3795 node
=> get_standard_option
('pve-node'),
3796 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3797 snapname
=> get_standard_option
('pve-snapshot-name'),
3801 description
=> "For removal from config file, even if removing disk snapshots fails.",
3807 description
=> "the task ID.",
3812 my $rpcenv = PVE
::RPCEnvironment
::get
();
3814 my $authuser = $rpcenv->get_user();
3816 my $node = extract_param
($param, 'node');
3818 my $vmid = extract_param
($param, 'vmid');
3820 my $snapname = extract_param
($param, 'snapname');
3823 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3824 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3827 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3830 __PACKAGE__-
>register_method({
3832 path
=> '{vmid}/template',
3836 description
=> "Create a Template.",
3838 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3839 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3842 additionalProperties
=> 0,
3844 node
=> get_standard_option
('pve-node'),
3845 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3849 description
=> "If you want to convert only 1 disk to base image.",
3850 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3855 returns
=> { type
=> 'null'},
3859 my $rpcenv = PVE
::RPCEnvironment
::get
();
3861 my $authuser = $rpcenv->get_user();
3863 my $node = extract_param
($param, 'node');
3865 my $vmid = extract_param
($param, 'vmid');
3867 my $disk = extract_param
($param, 'disk');
3869 my $updatefn = sub {
3871 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3873 PVE
::QemuConfig-
>check_lock($conf);
3875 die "unable to create template, because VM contains snapshots\n"
3876 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3878 die "you can't convert a template to a template\n"
3879 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3881 die "you can't convert a VM to template if VM is running\n"
3882 if PVE
::QemuServer
::check_running
($vmid);
3885 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3888 $conf->{template
} = 1;
3889 PVE
::QemuConfig-
>write_config($vmid, $conf);
3891 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3894 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);