1 package PVE
::API2
::Qemu
;
11 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
13 use PVE
::Tools
qw(extract_param);
14 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
16 use PVE
::JSONSchema
qw(get_standard_option);
18 use PVE
::ReplicationConfig
;
19 use PVE
::GuestHelpers
;
23 use PVE
::RPCEnvironment
;
24 use PVE
::AccessControl
;
28 use PVE
::API2
::Firewall
::VM
;
29 use PVE
::API2
::Qemu
::Agent
;
30 use PVE
::VZDump
::Plugin
;
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
};
67 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
69 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit' || $volname eq 'cloudinit')) {
71 } elsif ($isCDROM && ($volid eq 'cdrom')) {
72 $rpcenv->check($authuser, "/", ['Sys.Console']);
73 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
74 my ($storeid, $size) = ($2 || $default_storage, $3);
75 die "no storage ID specified (and no default storage)\n" if !$storeid;
76 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
77 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
78 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
79 if !$scfg->{content
}->{images
};
81 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
85 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
86 if defined($settings->{vmstatestorage
});
89 my $check_storage_access_clone = sub {
90 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
94 PVE
::QemuServer
::foreach_drive
($conf, sub {
95 my ($ds, $drive) = @_;
97 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
99 my $volid = $drive->{file
};
101 return if !$volid || $volid eq 'none';
104 if ($volid eq 'cdrom') {
105 $rpcenv->check($authuser, "/", ['Sys.Console']);
107 # we simply allow access
108 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
109 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
110 $sharedvm = 0 if !$scfg->{shared
};
114 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
115 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
116 $sharedvm = 0 if !$scfg->{shared
};
118 $sid = $storage if $storage;
119 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
123 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
124 if defined($conf->{vmstatestorage
});
129 # Note: $pool is only needed when creating a VM, because pool permissions
130 # are automatically inherited if VM already exists inside a pool.
131 my $create_disks = sub {
132 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
139 my ($ds, $disk) = @_;
141 my $volid = $disk->{file
};
142 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
144 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
145 delete $disk->{size
};
146 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
147 } elsif ($volname eq 'cloudinit') {
148 $storeid = $storeid // $default_storage;
149 die "no storage ID specified (and no default storage)\n" if !$storeid;
150 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
151 my $name = "vm-$vmid-cloudinit";
155 $fmt = $disk->{format
} // "qcow2";
158 $fmt = $disk->{format
} // "raw";
161 # Initial disk created with 4 MB and aligned to 4MB on regeneration
162 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
163 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
164 $disk->{file
} = $volid;
165 $disk->{media
} = 'cdrom';
166 push @$vollist, $volid;
167 delete $disk->{format
}; # no longer needed
168 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
169 } elsif ($volid =~ $NEW_DISK_RE) {
170 my ($storeid, $size) = ($2 || $default_storage, $3);
171 die "no storage ID specified (and no default storage)\n" if !$storeid;
172 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
173 my $fmt = $disk->{format
} || $defformat;
175 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
178 if ($ds eq 'efidisk0') {
179 ($volid, $size) = PVE
::QemuServer
::create_efidisk
($storecfg, $storeid, $vmid, $fmt, $arch);
181 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
183 push @$vollist, $volid;
184 $disk->{file
} = $volid;
185 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
186 delete $disk->{format
}; # no longer needed
187 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
190 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
192 my $volid_is_new = 1;
195 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
196 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
201 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
203 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
205 die "volume $volid does not exists\n" if !$size;
207 $disk->{size
} = $size;
210 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
214 eval { PVE
::QemuServer
::foreach_drive
($settings, $code); };
216 # free allocated images on error
218 syslog
('err', "VM $vmid creating disks failed");
219 foreach my $volid (@$vollist) {
220 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
226 # modify vm config if everything went well
227 foreach my $ds (keys %$res) {
228 $conf->{$ds} = $res->{$ds};
245 my $memoryoptions = {
251 my $hwtypeoptions = {
264 my $generaloptions = {
271 'migrate_downtime' => 1,
272 'migrate_speed' => 1,
284 my $vmpoweroptions = {
291 'vmstatestorage' => 1,
294 my $cloudinitoptions = {
304 my $check_vm_modify_config_perm = sub {
305 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
307 return 1 if $authuser eq 'root@pam';
309 foreach my $opt (@$key_list) {
310 # some checks (e.g., disk, serial port, usb) need to be done somewhere
311 # else, as there the permission can be value dependend
312 next if PVE
::QemuServer
::is_valid_drivename
($opt);
313 next if $opt eq 'cdrom';
314 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
317 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
318 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
319 } elsif ($memoryoptions->{$opt}) {
320 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
321 } elsif ($hwtypeoptions->{$opt}) {
322 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
323 } elsif ($generaloptions->{$opt}) {
324 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
325 # special case for startup since it changes host behaviour
326 if ($opt eq 'startup') {
327 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
329 } elsif ($vmpoweroptions->{$opt}) {
330 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
331 } elsif ($diskoptions->{$opt}) {
332 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
333 } elsif ($cloudinitoptions->{$opt} || ($opt =~ m/^(?:net|ipconfig)\d+$/)) {
334 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
336 # catches hostpci\d+, args, lock, etc.
337 # new options will be checked here
338 die "only root can set '$opt' config\n";
345 __PACKAGE__-
>register_method({
349 description
=> "Virtual machine index (per node).",
351 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
355 protected
=> 1, # qemu pid files are only readable by root
357 additionalProperties
=> 0,
359 node
=> get_standard_option
('pve-node'),
363 description
=> "Determine the full status of active VMs.",
371 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
373 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
378 my $rpcenv = PVE
::RPCEnvironment
::get
();
379 my $authuser = $rpcenv->get_user();
381 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
384 foreach my $vmid (keys %$vmstatus) {
385 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
387 my $data = $vmstatus->{$vmid};
396 __PACKAGE__-
>register_method({
400 description
=> "Create or restore a virtual machine.",
402 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
403 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
404 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
405 user
=> 'all', # check inside
410 additionalProperties
=> 0,
411 properties
=> PVE
::QemuServer
::json_config_properties
(
413 node
=> get_standard_option
('pve-node'),
414 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
416 description
=> "The backup file.",
420 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
422 storage
=> get_standard_option
('pve-storage-id', {
423 description
=> "Default storage.",
425 completion
=> \
&PVE
::QemuServer
::complete_storage
,
430 description
=> "Allow to overwrite existing VM.",
431 requires
=> 'archive',
436 description
=> "Assign a unique random ethernet address.",
437 requires
=> 'archive',
441 type
=> 'string', format
=> 'pve-poolid',
442 description
=> "Add the VM to the specified pool.",
445 description
=> "Override I/O bandwidth limit (in KiB/s).",
449 default => 'restore limit from datacenter or storage config',
455 description
=> "Start VM after it was created successfully.",
465 my $rpcenv = PVE
::RPCEnvironment
::get
();
467 my $authuser = $rpcenv->get_user();
469 my $node = extract_param
($param, 'node');
471 my $vmid = extract_param
($param, 'vmid');
473 my $archive = extract_param
($param, 'archive');
474 my $is_restore = !!$archive;
476 my $storage = extract_param
($param, 'storage');
478 my $force = extract_param
($param, 'force');
480 my $unique = extract_param
($param, 'unique');
482 my $pool = extract_param
($param, 'pool');
484 my $bwlimit = extract_param
($param, 'bwlimit');
486 my $start_after_create = extract_param
($param, 'start');
488 my $filename = PVE
::QemuConfig-
>config_file($vmid);
490 my $storecfg = PVE
::Storage
::config
();
492 if (defined(my $ssh_keys = $param->{sshkeys
})) {
493 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
494 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
497 PVE
::Cluster
::check_cfs_quorum
();
499 if (defined($pool)) {
500 $rpcenv->check_pool_exist($pool);
503 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
504 if defined($storage);
506 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
508 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
510 } elsif ($archive && $force && (-f
$filename) &&
511 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
512 # OK: user has VM.Backup permissions, and want to restore an existing VM
518 &$resolve_cdrom_alias($param);
520 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
522 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
524 foreach my $opt (keys %$param) {
525 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
526 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
527 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
529 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
530 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
534 PVE
::QemuServer
::add_random_macs
($param);
536 my $keystr = join(' ', keys %$param);
537 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
539 if ($archive eq '-') {
540 die "pipe requires cli environment\n"
541 if $rpcenv->{type
} ne 'cli';
543 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
544 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
548 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
550 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
551 die "$emsg $@" if $@;
553 my $restorefn = sub {
554 my $conf = PVE
::QemuConfig-
>load_config($vmid);
556 PVE
::QemuConfig-
>check_protection($conf, $emsg);
558 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
561 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
567 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
568 # Convert restored VM to template if backup was VM template
569 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
570 warn "Convert to template.\n";
571 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
575 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
577 if ($start_after_create) {
578 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
583 # ensure no old replication state are exists
584 PVE
::ReplicationState
::delete_guest_states
($vmid);
586 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
590 # ensure no old replication state are exists
591 PVE
::ReplicationState
::delete_guest_states
($vmid);
599 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
603 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
605 if (!$conf->{bootdisk
}) {
606 my $firstdisk = PVE
::QemuServer
::resolve_first_disk
($conf);
607 $conf->{bootdisk
} = $firstdisk if $firstdisk;
610 # auto generate uuid if user did not specify smbios1 option
611 if (!$conf->{smbios1
}) {
612 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
615 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
616 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
619 PVE
::QemuConfig-
>write_config($vmid, $conf);
625 foreach my $volid (@$vollist) {
626 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
632 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
635 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
637 if ($start_after_create) {
638 print "Execute autostart\n";
639 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
644 my ($code, $worker_name);
646 $worker_name = 'qmrestore';
648 eval { $restorefn->() };
650 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
656 $worker_name = 'qmcreate';
658 eval { $createfn->() };
661 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
662 unlink($conffile) or die "failed to remove config file: $!\n";
670 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
673 __PACKAGE__-
>register_method({
678 description
=> "Directory index",
683 additionalProperties
=> 0,
685 node
=> get_standard_option
('pve-node'),
686 vmid
=> get_standard_option
('pve-vmid'),
694 subdir
=> { type
=> 'string' },
697 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
703 { subdir
=> 'config' },
704 { subdir
=> 'pending' },
705 { subdir
=> 'status' },
706 { subdir
=> 'unlink' },
707 { subdir
=> 'vncproxy' },
708 { subdir
=> 'termproxy' },
709 { subdir
=> 'migrate' },
710 { subdir
=> 'resize' },
711 { subdir
=> 'move' },
713 { subdir
=> 'rrddata' },
714 { subdir
=> 'monitor' },
715 { subdir
=> 'agent' },
716 { subdir
=> 'snapshot' },
717 { subdir
=> 'spiceproxy' },
718 { subdir
=> 'sendkey' },
719 { subdir
=> 'firewall' },
725 __PACKAGE__-
>register_method ({
726 subclass
=> "PVE::API2::Firewall::VM",
727 path
=> '{vmid}/firewall',
730 __PACKAGE__-
>register_method ({
731 subclass
=> "PVE::API2::Qemu::Agent",
732 path
=> '{vmid}/agent',
735 __PACKAGE__-
>register_method({
737 path
=> '{vmid}/rrd',
739 protected
=> 1, # fixme: can we avoid that?
741 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
743 description
=> "Read VM RRD statistics (returns PNG)",
745 additionalProperties
=> 0,
747 node
=> get_standard_option
('pve-node'),
748 vmid
=> get_standard_option
('pve-vmid'),
750 description
=> "Specify the time frame you are interested in.",
752 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
755 description
=> "The list of datasources you want to display.",
756 type
=> 'string', format
=> 'pve-configid-list',
759 description
=> "The RRD consolidation function",
761 enum
=> [ 'AVERAGE', 'MAX' ],
769 filename
=> { type
=> 'string' },
775 return PVE
::Cluster
::create_rrd_graph
(
776 "pve2-vm/$param->{vmid}", $param->{timeframe
},
777 $param->{ds
}, $param->{cf
});
781 __PACKAGE__-
>register_method({
783 path
=> '{vmid}/rrddata',
785 protected
=> 1, # fixme: can we avoid that?
787 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
789 description
=> "Read VM RRD statistics",
791 additionalProperties
=> 0,
793 node
=> get_standard_option
('pve-node'),
794 vmid
=> get_standard_option
('pve-vmid'),
796 description
=> "Specify the time frame you are interested in.",
798 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
801 description
=> "The RRD consolidation function",
803 enum
=> [ 'AVERAGE', 'MAX' ],
818 return PVE
::Cluster
::create_rrd_data
(
819 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
823 __PACKAGE__-
>register_method({
825 path
=> '{vmid}/config',
828 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
830 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
833 additionalProperties
=> 0,
835 node
=> get_standard_option
('pve-node'),
836 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
838 description
=> "Get current values (instead of pending values).",
843 snapshot
=> get_standard_option
('pve-snapshot-name', {
844 description
=> "Fetch config values from given snapshot.",
847 my ($cmd, $pname, $cur, $args) = @_;
848 PVE
::QemuConfig-
>snapshot_list($args->[0]);
854 description
=> "The current VM configuration.",
856 properties
=> PVE
::QemuServer
::json_config_properties
({
859 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
866 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
867 current
=> "cannot use 'snapshot' parameter with 'current'"})
868 if ($param->{snapshot
} && $param->{current
});
871 if ($param->{snapshot
}) {
872 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
874 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
876 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
881 __PACKAGE__-
>register_method({
882 name
=> 'vm_pending',
883 path
=> '{vmid}/pending',
886 description
=> "Get virtual machine configuration, including pending changes.",
888 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
891 additionalProperties
=> 0,
893 node
=> get_standard_option
('pve-node'),
894 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
903 description
=> "Configuration option name.",
907 description
=> "Current value.",
912 description
=> "Pending value.",
917 description
=> "Indicates a pending delete request if present and not 0. " .
918 "The value 2 indicates a force-delete request.",
930 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
932 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
934 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
935 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
937 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
940 # POST/PUT {vmid}/config implementation
942 # The original API used PUT (idempotent) an we assumed that all operations
943 # are fast. But it turned out that almost any configuration change can
944 # involve hot-plug actions, or disk alloc/free. Such actions can take long
945 # time to complete and have side effects (not idempotent).
947 # The new implementation uses POST and forks a worker process. We added
948 # a new option 'background_delay'. If specified we wait up to
949 # 'background_delay' second for the worker task to complete. It returns null
950 # if the task is finished within that time, else we return the UPID.
952 my $update_vm_api = sub {
953 my ($param, $sync) = @_;
955 my $rpcenv = PVE
::RPCEnvironment
::get
();
957 my $authuser = $rpcenv->get_user();
959 my $node = extract_param
($param, 'node');
961 my $vmid = extract_param
($param, 'vmid');
963 my $digest = extract_param
($param, 'digest');
965 my $background_delay = extract_param
($param, 'background_delay');
967 if (defined(my $cipassword = $param->{cipassword
})) {
968 # Same logic as in cloud-init (but with the regex fixed...)
969 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
970 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
973 my @paramarr = (); # used for log message
974 foreach my $key (sort keys %$param) {
975 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
976 push @paramarr, "-$key", $value;
979 my $skiplock = extract_param
($param, 'skiplock');
980 raise_param_exc
({ skiplock
=> "Only root may use this option." })
981 if $skiplock && $authuser ne 'root@pam';
983 my $delete_str = extract_param
($param, 'delete');
985 my $revert_str = extract_param
($param, 'revert');
987 my $force = extract_param
($param, 'force');
989 if (defined(my $ssh_keys = $param->{sshkeys
})) {
990 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
991 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
994 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
996 my $storecfg = PVE
::Storage
::config
();
998 my $defaults = PVE
::QemuServer
::load_defaults
();
1000 &$resolve_cdrom_alias($param);
1002 # now try to verify all parameters
1005 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1006 if (!PVE
::QemuServer
::option_exists
($opt)) {
1007 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1010 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1011 "-revert $opt' at the same time" })
1012 if defined($param->{$opt});
1014 $revert->{$opt} = 1;
1018 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1019 $opt = 'ide2' if $opt eq 'cdrom';
1021 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1022 "-delete $opt' at the same time" })
1023 if defined($param->{$opt});
1025 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1026 "-revert $opt' at the same time" })
1029 if (!PVE
::QemuServer
::option_exists
($opt)) {
1030 raise_param_exc
({ delete => "unknown option '$opt'" });
1036 my $repl_conf = PVE
::ReplicationConfig-
>new();
1037 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1038 my $check_replication = sub {
1040 return if !$is_replicated;
1041 my $volid = $drive->{file
};
1042 return if !$volid || !($drive->{replicate
}//1);
1043 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1045 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1046 return if $volname eq 'cloudinit';
1049 if ($volid =~ $NEW_DISK_RE) {
1051 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1053 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1055 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1056 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1057 return if $scfg->{shared
};
1058 die "cannot add non-replicatable volume to a replicated VM\n";
1061 foreach my $opt (keys %$param) {
1062 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1063 # cleanup drive path
1064 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1065 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1066 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1067 $check_replication->($drive);
1068 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1069 } elsif ($opt =~ m/^net(\d+)$/) {
1071 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1072 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1073 } elsif ($opt eq 'vmgenid') {
1074 if ($param->{$opt} eq '1') {
1075 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1077 } elsif ($opt eq 'hookscript') {
1078 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1079 raise_param_exc
({ $opt => $@ }) if $@;
1083 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1085 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1087 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1089 my $updatefn = sub {
1091 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1093 die "checksum missmatch (file change by other user?)\n"
1094 if $digest && $digest ne $conf->{digest
};
1096 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1098 foreach my $opt (keys %$revert) {
1099 if (defined($conf->{$opt})) {
1100 $param->{$opt} = $conf->{$opt};
1101 } elsif (defined($conf->{pending
}->{$opt})) {
1106 if ($param->{memory
} || defined($param->{balloon
})) {
1107 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1108 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1110 die "balloon value too large (must be smaller than assigned memory)\n"
1111 if $balloon && $balloon > $maxmem;
1114 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1118 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1120 # write updates to pending section
1122 my $modified = {}; # record what $option we modify
1124 foreach my $opt (@delete) {
1125 $modified->{$opt} = 1;
1126 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1128 # value of what we want to delete, independent if pending or not
1129 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1130 if (!defined($val)) {
1131 warn "cannot delete '$opt' - not set in current configuration!\n";
1132 $modified->{$opt} = 0;
1135 my $is_pending_val = defined($conf->{pending
}->{$opt});
1136 delete $conf->{pending
}->{$opt};
1138 if ($opt =~ m/^unused/) {
1139 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1140 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1141 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1142 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1143 delete $conf->{$opt};
1144 PVE
::QemuConfig-
>write_config($vmid, $conf);
1146 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1147 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1148 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1149 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1151 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1152 PVE
::QemuConfig-
>write_config($vmid, $conf);
1153 } elsif ($opt =~ m/^serial\d+$/) {
1154 if ($val eq 'socket') {
1155 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1156 } elsif ($authuser ne 'root@pam') {
1157 die "only root can delete '$opt' config for real devices\n";
1159 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1160 PVE
::QemuConfig-
>write_config($vmid, $conf);
1161 } elsif ($opt =~ m/^usb\d+$/) {
1162 if ($val =~ m/spice/) {
1163 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1164 } elsif ($authuser ne 'root@pam') {
1165 die "only root can delete '$opt' config for real devices\n";
1167 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1168 PVE
::QemuConfig-
>write_config($vmid, $conf);
1170 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1171 PVE
::QemuConfig-
>write_config($vmid, $conf);
1175 foreach my $opt (keys %$param) { # add/change
1176 $modified->{$opt} = 1;
1177 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1178 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1180 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
1182 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1183 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1184 # FIXME: cloudinit: CDROM or Disk?
1185 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1186 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1188 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1190 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1191 if defined($conf->{pending
}->{$opt});
1193 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1194 } elsif ($opt =~ m/^serial\d+/) {
1195 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1196 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1197 } elsif ($authuser ne 'root@pam') {
1198 die "only root can modify '$opt' config for real devices\n";
1200 $conf->{pending
}->{$opt} = $param->{$opt};
1201 } elsif ($opt =~ m/^usb\d+/) {
1202 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1203 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1204 } elsif ($authuser ne 'root@pam') {
1205 die "only root can modify '$opt' config for real devices\n";
1207 $conf->{pending
}->{$opt} = $param->{$opt};
1209 $conf->{pending
}->{$opt} = $param->{$opt};
1211 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1212 PVE
::QemuConfig-
>write_config($vmid, $conf);
1215 # remove pending changes when nothing changed
1216 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1217 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1218 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1220 return if !scalar(keys %{$conf->{pending
}});
1222 my $running = PVE
::QemuServer
::check_running
($vmid);
1224 # apply pending changes
1226 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1230 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1231 raise_param_exc
($errors) if scalar(keys %$errors);
1233 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1243 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1245 if ($background_delay) {
1247 # Note: It would be better to do that in the Event based HTTPServer
1248 # to avoid blocking call to sleep.
1250 my $end_time = time() + $background_delay;
1252 my $task = PVE
::Tools
::upid_decode
($upid);
1255 while (time() < $end_time) {
1256 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1258 sleep(1); # this gets interrupted when child process ends
1262 my $status = PVE
::Tools
::upid_read_status
($upid);
1263 return undef if $status eq 'OK';
1272 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1275 my $vm_config_perm_list = [
1280 'VM.Config.Network',
1282 'VM.Config.Options',
1285 __PACKAGE__-
>register_method({
1286 name
=> 'update_vm_async',
1287 path
=> '{vmid}/config',
1291 description
=> "Set virtual machine options (asynchrounous API).",
1293 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1296 additionalProperties
=> 0,
1297 properties
=> PVE
::QemuServer
::json_config_properties
(
1299 node
=> get_standard_option
('pve-node'),
1300 vmid
=> get_standard_option
('pve-vmid'),
1301 skiplock
=> get_standard_option
('skiplock'),
1303 type
=> 'string', format
=> 'pve-configid-list',
1304 description
=> "A list of settings you want to delete.",
1308 type
=> 'string', format
=> 'pve-configid-list',
1309 description
=> "Revert a pending change.",
1314 description
=> $opt_force_description,
1316 requires
=> 'delete',
1320 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1324 background_delay
=> {
1326 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1337 code
=> $update_vm_api,
1340 __PACKAGE__-
>register_method({
1341 name
=> 'update_vm',
1342 path
=> '{vmid}/config',
1346 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1348 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1351 additionalProperties
=> 0,
1352 properties
=> PVE
::QemuServer
::json_config_properties
(
1354 node
=> get_standard_option
('pve-node'),
1355 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1356 skiplock
=> get_standard_option
('skiplock'),
1358 type
=> 'string', format
=> 'pve-configid-list',
1359 description
=> "A list of settings you want to delete.",
1363 type
=> 'string', format
=> 'pve-configid-list',
1364 description
=> "Revert a pending change.",
1369 description
=> $opt_force_description,
1371 requires
=> 'delete',
1375 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1381 returns
=> { type
=> 'null' },
1384 &$update_vm_api($param, 1);
1389 __PACKAGE__-
>register_method({
1390 name
=> 'destroy_vm',
1395 description
=> "Destroy the vm (also delete all used/owned volumes).",
1397 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1400 additionalProperties
=> 0,
1402 node
=> get_standard_option
('pve-node'),
1403 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1404 skiplock
=> get_standard_option
('skiplock'),
1407 description
=> "Remove vmid from backup cron jobs.",
1418 my $rpcenv = PVE
::RPCEnvironment
::get
();
1419 my $authuser = $rpcenv->get_user();
1420 my $vmid = $param->{vmid
};
1422 my $skiplock = $param->{skiplock
};
1423 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1424 if $skiplock && $authuser ne 'root@pam';
1427 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1428 my $storecfg = PVE
::Storage
::config
();
1429 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1430 die "unable to remove VM $vmid - used in HA resources\n"
1431 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1433 if (!$param->{purge
}) {
1434 # don't allow destroy if with replication jobs but no purge param
1435 my $repl_conf = PVE
::ReplicationConfig-
>new();
1436 $repl_conf->check_for_existing_jobs($vmid);
1439 # early tests (repeat after locking)
1440 die "VM $vmid is running - destroy failed\n"
1441 if PVE
::QemuServer
::check_running
($vmid);
1446 syslog
('info', "destroy VM $vmid: $upid\n");
1447 PVE
::QemuConfig-
>lock_config($vmid, sub {
1448 die "VM $vmid is running - destroy failed\n"
1449 if (PVE
::QemuServer
::check_running
($vmid));
1451 PVE
::QemuServer
::destroy_vm
($storecfg, $vmid, 1, $skiplock);
1453 PVE
::AccessControl
::remove_vm_access
($vmid);
1454 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1455 if ($param->{purge
}) {
1456 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1457 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1460 # only now remove the zombie config, else we can have reuse race
1461 PVE
::QemuConfig-
>destroy_config($vmid);
1465 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1468 __PACKAGE__-
>register_method({
1470 path
=> '{vmid}/unlink',
1474 description
=> "Unlink/delete disk images.",
1476 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1479 additionalProperties
=> 0,
1481 node
=> get_standard_option
('pve-node'),
1482 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1484 type
=> 'string', format
=> 'pve-configid-list',
1485 description
=> "A list of disk IDs you want to delete.",
1489 description
=> $opt_force_description,
1494 returns
=> { type
=> 'null'},
1498 $param->{delete} = extract_param
($param, 'idlist');
1500 __PACKAGE__-
>update_vm($param);
1507 __PACKAGE__-
>register_method({
1509 path
=> '{vmid}/vncproxy',
1513 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1515 description
=> "Creates a TCP VNC proxy connections.",
1517 additionalProperties
=> 0,
1519 node
=> get_standard_option
('pve-node'),
1520 vmid
=> get_standard_option
('pve-vmid'),
1524 description
=> "starts websockify instead of vncproxy",
1529 additionalProperties
=> 0,
1531 user
=> { type
=> 'string' },
1532 ticket
=> { type
=> 'string' },
1533 cert
=> { type
=> 'string' },
1534 port
=> { type
=> 'integer' },
1535 upid
=> { type
=> 'string' },
1541 my $rpcenv = PVE
::RPCEnvironment
::get
();
1543 my $authuser = $rpcenv->get_user();
1545 my $vmid = $param->{vmid
};
1546 my $node = $param->{node
};
1547 my $websocket = $param->{websocket
};
1549 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1550 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1552 my $authpath = "/vms/$vmid";
1554 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1556 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1562 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1563 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1564 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1565 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1566 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1568 $family = PVE
::Tools
::get_host_address_family
($node);
1571 my $port = PVE
::Tools
::next_vnc_port
($family);
1578 syslog
('info', "starting vnc proxy $upid\n");
1584 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1586 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1587 '-timeout', $timeout, '-authpath', $authpath,
1588 '-perm', 'Sys.Console'];
1590 if ($param->{websocket
}) {
1591 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1592 push @$cmd, '-notls', '-listen', 'localhost';
1595 push @$cmd, '-c', @$remcmd, @$termcmd;
1597 PVE
::Tools
::run_command
($cmd);
1601 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1603 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1605 my $sock = IO
::Socket
::IP-
>new(
1610 GetAddrInfoFlags
=> 0,
1611 ) or die "failed to create socket: $!\n";
1612 # Inside the worker we shouldn't have any previous alarms
1613 # running anyway...:
1615 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1617 accept(my $cli, $sock) or die "connection failed: $!\n";
1620 if (PVE
::Tools
::run_command
($cmd,
1621 output
=> '>&'.fileno($cli),
1622 input
=> '<&'.fileno($cli),
1625 die "Failed to run vncproxy.\n";
1632 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1634 PVE
::Tools
::wait_for_vnc_port
($port);
1645 __PACKAGE__-
>register_method({
1646 name
=> 'termproxy',
1647 path
=> '{vmid}/termproxy',
1651 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1653 description
=> "Creates a TCP proxy connections.",
1655 additionalProperties
=> 0,
1657 node
=> get_standard_option
('pve-node'),
1658 vmid
=> get_standard_option
('pve-vmid'),
1662 enum
=> [qw(serial0 serial1 serial2 serial3)],
1663 description
=> "opens a serial terminal (defaults to display)",
1668 additionalProperties
=> 0,
1670 user
=> { type
=> 'string' },
1671 ticket
=> { type
=> 'string' },
1672 port
=> { type
=> 'integer' },
1673 upid
=> { type
=> 'string' },
1679 my $rpcenv = PVE
::RPCEnvironment
::get
();
1681 my $authuser = $rpcenv->get_user();
1683 my $vmid = $param->{vmid
};
1684 my $node = $param->{node
};
1685 my $serial = $param->{serial
};
1687 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1689 if (!defined($serial)) {
1690 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1691 $serial = $conf->{vga
};
1695 my $authpath = "/vms/$vmid";
1697 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1702 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1703 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1704 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1705 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, '-t');
1706 push @$remcmd, '--';
1708 $family = PVE
::Tools
::get_host_address_family
($node);
1711 my $port = PVE
::Tools
::next_vnc_port
($family);
1713 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1714 push @$termcmd, '-iface', $serial if $serial;
1719 syslog
('info', "starting qemu termproxy $upid\n");
1721 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1722 '--perm', 'VM.Console', '--'];
1723 push @$cmd, @$remcmd, @$termcmd;
1725 PVE
::Tools
::run_command
($cmd);
1728 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1730 PVE
::Tools
::wait_for_vnc_port
($port);
1740 __PACKAGE__-
>register_method({
1741 name
=> 'vncwebsocket',
1742 path
=> '{vmid}/vncwebsocket',
1745 description
=> "You also need to pass a valid ticket (vncticket).",
1746 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1748 description
=> "Opens a weksocket for VNC traffic.",
1750 additionalProperties
=> 0,
1752 node
=> get_standard_option
('pve-node'),
1753 vmid
=> get_standard_option
('pve-vmid'),
1755 description
=> "Ticket from previous call to vncproxy.",
1760 description
=> "Port number returned by previous vncproxy call.",
1770 port
=> { type
=> 'string' },
1776 my $rpcenv = PVE
::RPCEnvironment
::get
();
1778 my $authuser = $rpcenv->get_user();
1780 my $vmid = $param->{vmid
};
1781 my $node = $param->{node
};
1783 my $authpath = "/vms/$vmid";
1785 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1787 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1789 # Note: VNC ports are acessible from outside, so we do not gain any
1790 # security if we verify that $param->{port} belongs to VM $vmid. This
1791 # check is done by verifying the VNC ticket (inside VNC protocol).
1793 my $port = $param->{port
};
1795 return { port
=> $port };
1798 __PACKAGE__-
>register_method({
1799 name
=> 'spiceproxy',
1800 path
=> '{vmid}/spiceproxy',
1805 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1807 description
=> "Returns a SPICE configuration to connect to the VM.",
1809 additionalProperties
=> 0,
1811 node
=> get_standard_option
('pve-node'),
1812 vmid
=> get_standard_option
('pve-vmid'),
1813 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1816 returns
=> get_standard_option
('remote-viewer-config'),
1820 my $rpcenv = PVE
::RPCEnvironment
::get
();
1822 my $authuser = $rpcenv->get_user();
1824 my $vmid = $param->{vmid
};
1825 my $node = $param->{node
};
1826 my $proxy = $param->{proxy
};
1828 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1829 my $title = "VM $vmid";
1830 $title .= " - ". $conf->{name
} if $conf->{name
};
1832 my $port = PVE
::QemuServer
::spice_port
($vmid);
1834 my ($ticket, undef, $remote_viewer_config) =
1835 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1837 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1838 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1840 return $remote_viewer_config;
1843 __PACKAGE__-
>register_method({
1845 path
=> '{vmid}/status',
1848 description
=> "Directory index",
1853 additionalProperties
=> 0,
1855 node
=> get_standard_option
('pve-node'),
1856 vmid
=> get_standard_option
('pve-vmid'),
1864 subdir
=> { type
=> 'string' },
1867 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1873 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1876 { subdir
=> 'current' },
1877 { subdir
=> 'start' },
1878 { subdir
=> 'stop' },
1879 { subdir
=> 'reset' },
1880 { subdir
=> 'shutdown' },
1881 { subdir
=> 'suspend' },
1882 { subdir
=> 'reboot' },
1888 __PACKAGE__-
>register_method({
1889 name
=> 'vm_status',
1890 path
=> '{vmid}/status/current',
1893 protected
=> 1, # qemu pid files are only readable by root
1894 description
=> "Get virtual machine status.",
1896 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1899 additionalProperties
=> 0,
1901 node
=> get_standard_option
('pve-node'),
1902 vmid
=> get_standard_option
('pve-vmid'),
1908 %$PVE::QemuServer
::vmstatus_return_properties
,
1910 description
=> "HA manager service status.",
1914 description
=> "Qemu VGA configuration supports spice.",
1919 description
=> "Qemu GuestAgent enabled in config.",
1929 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1931 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1932 my $status = $vmstatus->{$param->{vmid
}};
1934 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1936 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1937 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1942 __PACKAGE__-
>register_method({
1944 path
=> '{vmid}/status/start',
1948 description
=> "Start virtual machine.",
1950 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1953 additionalProperties
=> 0,
1955 node
=> get_standard_option
('pve-node'),
1956 vmid
=> get_standard_option
('pve-vmid',
1957 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1958 skiplock
=> get_standard_option
('skiplock'),
1959 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1960 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1963 enum
=> ['secure', 'insecure'],
1964 description
=> "Migration traffic is encrypted using an SSH " .
1965 "tunnel by default. On secure, completely private networks " .
1966 "this can be disabled to increase performance.",
1969 migration_network
=> {
1970 type
=> 'string', format
=> 'CIDR',
1971 description
=> "CIDR of the (sub) network that is used for migration.",
1974 machine
=> get_standard_option
('pve-qm-machine'),
1976 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1988 my $rpcenv = PVE
::RPCEnvironment
::get
();
1989 my $authuser = $rpcenv->get_user();
1991 my $node = extract_param
($param, 'node');
1992 my $vmid = extract_param
($param, 'vmid');
1994 my $machine = extract_param
($param, 'machine');
1996 my $get_root_param = sub {
1997 my $value = extract_param
($param, $_[0]);
1998 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
1999 if $value && $authuser ne 'root@pam';
2003 my $stateuri = $get_root_param->('stateuri');
2004 my $skiplock = $get_root_param->('skiplock');
2005 my $migratedfrom = $get_root_param->('migratedfrom');
2006 my $migration_type = $get_root_param->('migration_type');
2007 my $migration_network = $get_root_param->('migration_network');
2008 my $targetstorage = $get_root_param->('targetstorage');
2010 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2011 if $targetstorage && !$migratedfrom;
2013 # read spice ticket from STDIN
2015 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2016 if (defined(my $line = <STDIN
>)) {
2018 $spice_ticket = $line;
2022 PVE
::Cluster
::check_cfs_quorum
();
2024 my $storecfg = PVE
::Storage
::config
();
2026 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2030 print "Requesting HA start for VM $vmid\n";
2032 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2033 PVE
::Tools
::run_command
($cmd);
2037 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2044 syslog
('info', "start VM $vmid: $upid\n");
2046 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2047 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
2051 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2055 __PACKAGE__-
>register_method({
2057 path
=> '{vmid}/status/stop',
2061 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2062 "is akin to pulling the power plug of a running computer and may damage the VM data",
2064 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2067 additionalProperties
=> 0,
2069 node
=> get_standard_option
('pve-node'),
2070 vmid
=> get_standard_option
('pve-vmid',
2071 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2072 skiplock
=> get_standard_option
('skiplock'),
2073 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2075 description
=> "Wait maximal timeout seconds.",
2081 description
=> "Do not deactivate storage volumes.",
2094 my $rpcenv = PVE
::RPCEnvironment
::get
();
2095 my $authuser = $rpcenv->get_user();
2097 my $node = extract_param
($param, 'node');
2098 my $vmid = extract_param
($param, 'vmid');
2100 my $skiplock = extract_param
($param, 'skiplock');
2101 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2102 if $skiplock && $authuser ne 'root@pam';
2104 my $keepActive = extract_param
($param, 'keepActive');
2105 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2106 if $keepActive && $authuser ne 'root@pam';
2108 my $migratedfrom = extract_param
($param, 'migratedfrom');
2109 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2110 if $migratedfrom && $authuser ne 'root@pam';
2113 my $storecfg = PVE
::Storage
::config
();
2115 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2120 print "Requesting HA stop for VM $vmid\n";
2122 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'stopped'];
2123 PVE
::Tools
::run_command
($cmd);
2127 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2133 syslog
('info', "stop VM $vmid: $upid\n");
2135 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2136 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2140 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2144 __PACKAGE__-
>register_method({
2146 path
=> '{vmid}/status/reset',
2150 description
=> "Reset virtual machine.",
2152 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2155 additionalProperties
=> 0,
2157 node
=> get_standard_option
('pve-node'),
2158 vmid
=> get_standard_option
('pve-vmid',
2159 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2160 skiplock
=> get_standard_option
('skiplock'),
2169 my $rpcenv = PVE
::RPCEnvironment
::get
();
2171 my $authuser = $rpcenv->get_user();
2173 my $node = extract_param
($param, 'node');
2175 my $vmid = extract_param
($param, 'vmid');
2177 my $skiplock = extract_param
($param, 'skiplock');
2178 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2179 if $skiplock && $authuser ne 'root@pam';
2181 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2186 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2191 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2194 __PACKAGE__-
>register_method({
2195 name
=> 'vm_shutdown',
2196 path
=> '{vmid}/status/shutdown',
2200 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2201 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2203 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2206 additionalProperties
=> 0,
2208 node
=> get_standard_option
('pve-node'),
2209 vmid
=> get_standard_option
('pve-vmid',
2210 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2211 skiplock
=> get_standard_option
('skiplock'),
2213 description
=> "Wait maximal timeout seconds.",
2219 description
=> "Make sure the VM stops.",
2225 description
=> "Do not deactivate storage volumes.",
2238 my $rpcenv = PVE
::RPCEnvironment
::get
();
2239 my $authuser = $rpcenv->get_user();
2241 my $node = extract_param
($param, 'node');
2242 my $vmid = extract_param
($param, 'vmid');
2244 my $skiplock = extract_param
($param, 'skiplock');
2245 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2246 if $skiplock && $authuser ne 'root@pam';
2248 my $keepActive = extract_param
($param, 'keepActive');
2249 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2250 if $keepActive && $authuser ne 'root@pam';
2252 my $storecfg = PVE
::Storage
::config
();
2256 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2257 # otherwise, we will infer a shutdown command, but run into the timeout,
2258 # then when the vm is resumed, it will instantly shutdown
2260 # checking the qmp status here to get feedback to the gui/cli/api
2261 # and the status query should not take too long
2262 my $qmpstatus = eval {
2263 PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2267 if (!$err && $qmpstatus->{status
} eq "paused") {
2268 if ($param->{forceStop
}) {
2269 warn "VM is paused - stop instead of shutdown\n";
2272 die "VM is paused - cannot shutdown\n";
2276 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2281 print "Requesting HA stop for VM $vmid\n";
2283 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'stopped'];
2284 PVE
::Tools
::run_command
($cmd);
2288 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2295 syslog
('info', "shutdown VM $vmid: $upid\n");
2297 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2298 $shutdown, $param->{forceStop
}, $keepActive);
2302 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2306 __PACKAGE__-
>register_method({
2307 name
=> 'vm_reboot',
2308 path
=> '{vmid}/status/reboot',
2312 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2314 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2317 additionalProperties
=> 0,
2319 node
=> get_standard_option
('pve-node'),
2320 vmid
=> get_standard_option
('pve-vmid',
2321 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2323 description
=> "Wait maximal timeout seconds for the shutdown.",
2336 my $rpcenv = PVE
::RPCEnvironment
::get
();
2337 my $authuser = $rpcenv->get_user();
2339 my $node = extract_param
($param, 'node');
2340 my $vmid = extract_param
($param, 'vmid');
2342 my $qmpstatus = eval {
2343 PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2347 if (!$err && $qmpstatus->{status
} eq "paused") {
2348 die "VM is paused - cannot shutdown\n";
2351 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2356 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2357 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2361 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2364 __PACKAGE__-
>register_method({
2365 name
=> 'vm_suspend',
2366 path
=> '{vmid}/status/suspend',
2370 description
=> "Suspend virtual machine.",
2372 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2375 additionalProperties
=> 0,
2377 node
=> get_standard_option
('pve-node'),
2378 vmid
=> get_standard_option
('pve-vmid',
2379 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2380 skiplock
=> get_standard_option
('skiplock'),
2385 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2387 statestorage
=> get_standard_option
('pve-storage-id', {
2388 description
=> "The storage for the VM state",
2389 requires
=> 'todisk',
2391 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2401 my $rpcenv = PVE
::RPCEnvironment
::get
();
2402 my $authuser = $rpcenv->get_user();
2404 my $node = extract_param
($param, 'node');
2405 my $vmid = extract_param
($param, 'vmid');
2407 my $todisk = extract_param
($param, 'todisk') // 0;
2409 my $statestorage = extract_param
($param, 'statestorage');
2411 my $skiplock = extract_param
($param, 'skiplock');
2412 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2413 if $skiplock && $authuser ne 'root@pam';
2415 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2417 die "Cannot suspend HA managed VM to disk\n"
2418 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2423 syslog
('info', "suspend VM $vmid: $upid\n");
2425 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2430 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2431 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2434 __PACKAGE__-
>register_method({
2435 name
=> 'vm_resume',
2436 path
=> '{vmid}/status/resume',
2440 description
=> "Resume virtual machine.",
2442 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2445 additionalProperties
=> 0,
2447 node
=> get_standard_option
('pve-node'),
2448 vmid
=> get_standard_option
('pve-vmid',
2449 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2450 skiplock
=> get_standard_option
('skiplock'),
2451 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2461 my $rpcenv = PVE
::RPCEnvironment
::get
();
2463 my $authuser = $rpcenv->get_user();
2465 my $node = extract_param
($param, 'node');
2467 my $vmid = extract_param
($param, 'vmid');
2469 my $skiplock = extract_param
($param, 'skiplock');
2470 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2471 if $skiplock && $authuser ne 'root@pam';
2473 my $nocheck = extract_param
($param, 'nocheck');
2475 my $to_disk_suspended;
2477 PVE
::QemuConfig-
>lock_config($vmid, sub {
2478 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2479 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2483 die "VM $vmid not running\n"
2484 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2489 syslog
('info', "resume VM $vmid: $upid\n");
2491 if (!$to_disk_suspended) {
2492 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2494 my $storecfg = PVE
::Storage
::config
();
2495 PVE
::QemuServer
::vm_start
($storecfg, $vmid, undef, $skiplock);
2501 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2504 __PACKAGE__-
>register_method({
2505 name
=> 'vm_sendkey',
2506 path
=> '{vmid}/sendkey',
2510 description
=> "Send key event to virtual machine.",
2512 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2515 additionalProperties
=> 0,
2517 node
=> get_standard_option
('pve-node'),
2518 vmid
=> get_standard_option
('pve-vmid',
2519 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2520 skiplock
=> get_standard_option
('skiplock'),
2522 description
=> "The key (qemu monitor encoding).",
2527 returns
=> { type
=> 'null'},
2531 my $rpcenv = PVE
::RPCEnvironment
::get
();
2533 my $authuser = $rpcenv->get_user();
2535 my $node = extract_param
($param, 'node');
2537 my $vmid = extract_param
($param, 'vmid');
2539 my $skiplock = extract_param
($param, 'skiplock');
2540 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2541 if $skiplock && $authuser ne 'root@pam';
2543 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2548 __PACKAGE__-
>register_method({
2549 name
=> 'vm_feature',
2550 path
=> '{vmid}/feature',
2554 description
=> "Check if feature for virtual machine is available.",
2556 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2559 additionalProperties
=> 0,
2561 node
=> get_standard_option
('pve-node'),
2562 vmid
=> get_standard_option
('pve-vmid'),
2564 description
=> "Feature to check.",
2566 enum
=> [ 'snapshot', 'clone', 'copy' ],
2568 snapname
=> get_standard_option
('pve-snapshot-name', {
2576 hasFeature
=> { type
=> 'boolean' },
2579 items
=> { type
=> 'string' },
2586 my $node = extract_param
($param, 'node');
2588 my $vmid = extract_param
($param, 'vmid');
2590 my $snapname = extract_param
($param, 'snapname');
2592 my $feature = extract_param
($param, 'feature');
2594 my $running = PVE
::QemuServer
::check_running
($vmid);
2596 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2599 my $snap = $conf->{snapshots
}->{$snapname};
2600 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2603 my $storecfg = PVE
::Storage
::config
();
2605 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2606 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2609 hasFeature
=> $hasFeature,
2610 nodes
=> [ keys %$nodelist ],
2614 __PACKAGE__-
>register_method({
2616 path
=> '{vmid}/clone',
2620 description
=> "Create a copy of virtual machine/template.",
2622 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2623 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2624 "'Datastore.AllocateSpace' on any used storage.",
2627 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2629 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2630 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2635 additionalProperties
=> 0,
2637 node
=> get_standard_option
('pve-node'),
2638 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2639 newid
=> get_standard_option
('pve-vmid', {
2640 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2641 description
=> 'VMID for the clone.' }),
2644 type
=> 'string', format
=> 'dns-name',
2645 description
=> "Set a name for the new VM.",
2650 description
=> "Description for the new VM.",
2654 type
=> 'string', format
=> 'pve-poolid',
2655 description
=> "Add the new VM to the specified pool.",
2657 snapname
=> get_standard_option
('pve-snapshot-name', {
2660 storage
=> get_standard_option
('pve-storage-id', {
2661 description
=> "Target storage for full clone.",
2665 description
=> "Target format for file storage. Only valid for full clone.",
2668 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2673 description
=> "Create a full copy of all disks. This is always done when " .
2674 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2676 target
=> get_standard_option
('pve-node', {
2677 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2681 description
=> "Override I/O bandwidth limit (in KiB/s).",
2685 default => 'clone limit from datacenter or storage config',
2695 my $rpcenv = PVE
::RPCEnvironment
::get
();
2697 my $authuser = $rpcenv->get_user();
2699 my $node = extract_param
($param, 'node');
2701 my $vmid = extract_param
($param, 'vmid');
2703 my $newid = extract_param
($param, 'newid');
2705 my $pool = extract_param
($param, 'pool');
2707 if (defined($pool)) {
2708 $rpcenv->check_pool_exist($pool);
2711 my $snapname = extract_param
($param, 'snapname');
2713 my $storage = extract_param
($param, 'storage');
2715 my $format = extract_param
($param, 'format');
2717 my $target = extract_param
($param, 'target');
2719 my $localnode = PVE
::INotify
::nodename
();
2721 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2723 PVE
::Cluster
::check_node_exists
($target) if $target;
2725 my $storecfg = PVE
::Storage
::config
();
2728 # check if storage is enabled on local node
2729 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2731 # check if storage is available on target node
2732 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2733 # clone only works if target storage is shared
2734 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2735 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2739 PVE
::Cluster
::check_cfs_quorum
();
2741 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2743 # exclusive lock if VM is running - else shared lock is enough;
2744 my $shared_lock = $running ?
0 : 1;
2748 # do all tests after lock
2749 # we also try to do all tests before we fork the worker
2751 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2753 PVE
::QemuConfig-
>check_lock($conf);
2755 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2757 die "unexpected state change\n" if $verify_running != $running;
2759 die "snapshot '$snapname' does not exist\n"
2760 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2762 my $full = extract_param
($param, 'full');
2763 if (!defined($full)) {
2764 $full = !PVE
::QemuConfig-
>is_template($conf);
2767 die "parameter 'storage' not allowed for linked clones\n"
2768 if defined($storage) && !$full;
2770 die "parameter 'format' not allowed for linked clones\n"
2771 if defined($format) && !$full;
2773 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2775 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2777 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2779 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2781 die "unable to create VM $newid: config file already exists\n"
2784 my $newconf = { lock => 'clone' };
2789 foreach my $opt (keys %$oldconf) {
2790 my $value = $oldconf->{$opt};
2792 # do not copy snapshot related info
2793 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2794 $opt eq 'vmstate' || $opt eq 'snapstate';
2796 # no need to copy unused images, because VMID(owner) changes anyways
2797 next if $opt =~ m/^unused\d+$/;
2799 # always change MAC! address
2800 if ($opt =~ m/^net(\d+)$/) {
2801 my $net = PVE
::QemuServer
::parse_net
($value);
2802 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2803 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2804 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2805 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2806 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2807 die "unable to parse drive options for '$opt'\n" if !$drive;
2808 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2809 $newconf->{$opt} = $value; # simply copy configuration
2812 die "Full clone feature is not supported for drive '$opt'\n"
2813 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2814 $fullclone->{$opt} = 1;
2816 # not full means clone instead of copy
2817 die "Linked clone feature is not supported for drive '$opt'\n"
2818 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2820 $drives->{$opt} = $drive;
2821 push @$vollist, $drive->{file
};
2824 # copy everything else
2825 $newconf->{$opt} = $value;
2829 # auto generate a new uuid
2830 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2831 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2832 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2834 # auto generate a new vmgenid if the option was set
2835 if ($newconf->{vmgenid
}) {
2836 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2839 delete $newconf->{template
};
2841 if ($param->{name
}) {
2842 $newconf->{name
} = $param->{name
};
2844 if ($oldconf->{name
}) {
2845 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2847 $newconf->{name
} = "Copy-of-VM-$vmid";
2851 if ($param->{description
}) {
2852 $newconf->{description
} = $param->{description
};
2855 # create empty/temp config - this fails if VM already exists on other node
2856 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2861 my $newvollist = [];
2868 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2870 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2872 my $bwlimit = extract_param
($param, 'bwlimit');
2874 my $total_jobs = scalar(keys %{$drives});
2877 foreach my $opt (keys %$drives) {
2878 my $drive = $drives->{$opt};
2879 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2881 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
2882 my $storage_list = [ $src_sid ];
2883 push @$storage_list, $storage if defined($storage);
2884 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
2886 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2887 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2888 $jobs, $skipcomplete, $oldconf->{agent
}, $clonelimit);
2890 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2892 PVE
::QemuConfig-
>write_config($newid, $newconf);
2896 delete $newconf->{lock};
2898 # do not write pending changes
2899 if (my @changes = keys %{$newconf->{pending
}}) {
2900 my $pending = join(',', @changes);
2901 warn "found pending changes for '$pending', discarding for clone\n";
2902 delete $newconf->{pending
};
2905 PVE
::QemuConfig-
>write_config($newid, $newconf);
2908 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2909 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2910 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2912 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2913 die "Failed to move config to node '$target' - rename failed: $!\n"
2914 if !rename($conffile, $newconffile);
2917 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2922 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2924 sleep 1; # some storage like rbd need to wait before release volume - really?
2926 foreach my $volid (@$newvollist) {
2927 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2930 die "clone failed: $err";
2936 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2938 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2941 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2942 # Aquire exclusive lock lock for $newid
2943 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2948 __PACKAGE__-
>register_method({
2949 name
=> 'move_vm_disk',
2950 path
=> '{vmid}/move_disk',
2954 description
=> "Move volume to different storage.",
2956 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2958 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2959 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2963 additionalProperties
=> 0,
2965 node
=> get_standard_option
('pve-node'),
2966 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2969 description
=> "The disk you want to move.",
2970 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2972 storage
=> get_standard_option
('pve-storage-id', {
2973 description
=> "Target storage.",
2974 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2978 description
=> "Target Format.",
2979 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2984 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2990 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2995 description
=> "Override I/O bandwidth limit (in KiB/s).",
2999 default => 'move limit from datacenter or storage config',
3005 description
=> "the task ID.",
3010 my $rpcenv = PVE
::RPCEnvironment
::get
();
3012 my $authuser = $rpcenv->get_user();
3014 my $node = extract_param
($param, 'node');
3016 my $vmid = extract_param
($param, 'vmid');
3018 my $digest = extract_param
($param, 'digest');
3020 my $disk = extract_param
($param, 'disk');
3022 my $storeid = extract_param
($param, 'storage');
3024 my $format = extract_param
($param, 'format');
3026 my $storecfg = PVE
::Storage
::config
();
3028 my $updatefn = sub {
3030 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3032 PVE
::QemuConfig-
>check_lock($conf);
3034 die "checksum missmatch (file change by other user?)\n"
3035 if $digest && $digest ne $conf->{digest
};
3037 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3039 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3041 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
3043 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3046 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3047 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3051 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
3052 (!$format || !$oldfmt || $oldfmt eq $format);
3054 # this only checks snapshots because $disk is passed!
3055 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3056 die "you can't move a disk with snapshots and delete the source\n"
3057 if $snapshotted && $param->{delete};
3059 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3061 my $running = PVE
::QemuServer
::check_running
($vmid);
3063 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3067 my $newvollist = [];
3073 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3075 warn "moving disk with snapshots, snapshots will not be moved!\n"
3078 my $bwlimit = extract_param
($param, 'bwlimit');
3079 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3081 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3082 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit);
3084 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
3086 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3088 # convert moved disk to base if part of template
3089 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3090 if PVE
::QemuConfig-
>is_template($conf);
3092 PVE
::QemuConfig-
>write_config($vmid, $conf);
3094 if ($running && PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
} && PVE
::QemuServer
::qga_check_running
($vmid)) {
3095 eval { PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-fstrim"); };
3099 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3100 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3107 foreach my $volid (@$newvollist) {
3108 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3111 die "storage migration failed: $err";
3114 if ($param->{delete}) {
3116 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3117 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3123 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3126 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3129 my $check_vm_disks_local = sub {
3130 my ($storecfg, $vmconf, $vmid) = @_;
3132 my $local_disks = {};
3134 # add some more information to the disks e.g. cdrom
3135 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3136 my ($volid, $attr) = @_;
3138 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3140 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3141 return if $scfg->{shared
};
3143 # The shared attr here is just a special case where the vdisk
3144 # is marked as shared manually
3145 return if $attr->{shared
};
3146 return if $attr->{cdrom
} and $volid eq "none";
3148 if (exists $local_disks->{$volid}) {
3149 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3151 $local_disks->{$volid} = $attr;
3152 # ensure volid is present in case it's needed
3153 $local_disks->{$volid}->{volid
} = $volid;
3157 return $local_disks;
3160 __PACKAGE__-
>register_method({
3161 name
=> 'migrate_vm_precondition',
3162 path
=> '{vmid}/migrate',
3166 description
=> "Get preconditions for migration.",
3168 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3171 additionalProperties
=> 0,
3173 node
=> get_standard_option
('pve-node'),
3174 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3175 target
=> get_standard_option
('pve-node', {
3176 description
=> "Target node.",
3177 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3185 running
=> { type
=> 'boolean' },
3189 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3191 not_allowed_nodes
=> {
3194 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3198 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3200 local_resources
=> {
3202 description
=> "List local resources e.g. pci, usb"
3209 my $rpcenv = PVE
::RPCEnvironment
::get
();
3211 my $authuser = $rpcenv->get_user();
3213 PVE
::Cluster
::check_cfs_quorum
();
3217 my $vmid = extract_param
($param, 'vmid');
3218 my $target = extract_param
($param, 'target');
3219 my $localnode = PVE
::INotify
::nodename
();
3223 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3224 my $storecfg = PVE
::Storage
::config
();
3227 # try to detect errors early
3228 PVE
::QemuConfig-
>check_lock($vmconf);
3230 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3232 # if vm is not running, return target nodes where local storage is available
3233 # for offline migration
3234 if (!$res->{running
}) {
3235 $res->{allowed_nodes
} = [];
3236 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3237 delete $checked_nodes->{$localnode};
3239 foreach my $node (keys %$checked_nodes) {
3240 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3241 push @{$res->{allowed_nodes
}}, $node;
3245 $res->{not_allowed_nodes
} = $checked_nodes;
3249 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3250 $res->{local_disks
} = [ values %$local_disks ];;
3252 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3254 $res->{local_resources
} = $local_resources;
3261 __PACKAGE__-
>register_method({
3262 name
=> 'migrate_vm',
3263 path
=> '{vmid}/migrate',
3267 description
=> "Migrate virtual machine. Creates a new migration task.",
3269 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3272 additionalProperties
=> 0,
3274 node
=> get_standard_option
('pve-node'),
3275 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3276 target
=> get_standard_option
('pve-node', {
3277 description
=> "Target node.",
3278 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3282 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3287 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3292 enum
=> ['secure', 'insecure'],
3293 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3296 migration_network
=> {
3297 type
=> 'string', format
=> 'CIDR',
3298 description
=> "CIDR of the (sub) network that is used for migration.",
3301 "with-local-disks" => {
3303 description
=> "Enable live storage migration for local disk",
3306 targetstorage
=> get_standard_option
('pve-storage-id', {
3307 description
=> "Default target storage.",
3309 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3312 description
=> "Override I/O bandwidth limit (in KiB/s).",
3316 default => 'migrate limit from datacenter or storage config',
3322 description
=> "the task ID.",
3327 my $rpcenv = PVE
::RPCEnvironment
::get
();
3328 my $authuser = $rpcenv->get_user();
3330 my $target = extract_param
($param, 'target');
3332 my $localnode = PVE
::INotify
::nodename
();
3333 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3335 PVE
::Cluster
::check_cfs_quorum
();
3337 PVE
::Cluster
::check_node_exists
($target);
3339 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3341 my $vmid = extract_param
($param, 'vmid');
3343 raise_param_exc
({ force
=> "Only root may use this option." })
3344 if $param->{force
} && $authuser ne 'root@pam';
3346 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3347 if $param->{migration_type
} && $authuser ne 'root@pam';
3349 # allow root only until better network permissions are available
3350 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3351 if $param->{migration_network
} && $authuser ne 'root@pam';
3354 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3356 # try to detect errors early
3358 PVE
::QemuConfig-
>check_lock($conf);
3360 if (PVE
::QemuServer
::check_running
($vmid)) {
3361 die "can't migrate running VM without --online\n" if !$param->{online
};
3363 warn "VM isn't running. Doing offline migration instead\n." if $param->{online
};
3364 $param->{online
} = 0;
3367 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3368 if !$param->{online
} && $param->{targetstorage
};
3370 my $storecfg = PVE
::Storage
::config
();
3372 if( $param->{targetstorage
}) {
3373 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3375 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3378 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3383 print "Requesting HA migration for VM $vmid to node $target\n";
3385 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3386 PVE
::Tools
::run_command
($cmd);
3390 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3395 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3399 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3402 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3407 __PACKAGE__-
>register_method({
3409 path
=> '{vmid}/monitor',
3413 description
=> "Execute Qemu monitor commands.",
3415 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3416 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3419 additionalProperties
=> 0,
3421 node
=> get_standard_option
('pve-node'),
3422 vmid
=> get_standard_option
('pve-vmid'),
3425 description
=> "The monitor command.",
3429 returns
=> { type
=> 'string'},
3433 my $rpcenv = PVE
::RPCEnvironment
::get
();
3434 my $authuser = $rpcenv->get_user();
3437 my $command = shift;
3438 return $command =~ m/^\s*info(\s+|$)/
3439 || $command =~ m/^\s*help\s*$/;
3442 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3443 if !&$is_ro($param->{command
});
3445 my $vmid = $param->{vmid
};
3447 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3451 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3453 $res = "ERROR: $@" if $@;
3458 __PACKAGE__-
>register_method({
3459 name
=> 'resize_vm',
3460 path
=> '{vmid}/resize',
3464 description
=> "Extend volume size.",
3466 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3469 additionalProperties
=> 0,
3471 node
=> get_standard_option
('pve-node'),
3472 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3473 skiplock
=> get_standard_option
('skiplock'),
3476 description
=> "The disk you want to resize.",
3477 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3481 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3482 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.",
3486 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3492 returns
=> { type
=> 'null'},
3496 my $rpcenv = PVE
::RPCEnvironment
::get
();
3498 my $authuser = $rpcenv->get_user();
3500 my $node = extract_param
($param, 'node');
3502 my $vmid = extract_param
($param, 'vmid');
3504 my $digest = extract_param
($param, 'digest');
3506 my $disk = extract_param
($param, 'disk');
3508 my $sizestr = extract_param
($param, 'size');
3510 my $skiplock = extract_param
($param, 'skiplock');
3511 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3512 if $skiplock && $authuser ne 'root@pam';
3514 my $storecfg = PVE
::Storage
::config
();
3516 my $updatefn = sub {
3518 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3520 die "checksum missmatch (file change by other user?)\n"
3521 if $digest && $digest ne $conf->{digest
};
3522 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3524 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3526 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3528 my (undef, undef, undef, undef, undef, undef, $format) =
3529 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3531 die "can't resize volume: $disk if snapshot exists\n"
3532 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3534 my $volid = $drive->{file
};
3536 die "disk '$disk' has no associated volume\n" if !$volid;
3538 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3540 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3542 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3544 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3545 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3547 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3549 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3550 my ($ext, $newsize, $unit) = ($1, $2, $4);
3553 $newsize = $newsize * 1024;
3554 } elsif ($unit eq 'M') {
3555 $newsize = $newsize * 1024 * 1024;
3556 } elsif ($unit eq 'G') {
3557 $newsize = $newsize * 1024 * 1024 * 1024;
3558 } elsif ($unit eq 'T') {
3559 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3562 $newsize += $size if $ext;
3563 $newsize = int($newsize);
3565 die "shrinking disks is not supported\n" if $newsize < $size;
3567 return if $size == $newsize;
3569 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3571 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3573 $drive->{size
} = $newsize;
3574 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3576 PVE
::QemuConfig-
>write_config($vmid, $conf);
3579 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3583 __PACKAGE__-
>register_method({
3584 name
=> 'snapshot_list',
3585 path
=> '{vmid}/snapshot',
3587 description
=> "List all snapshots.",
3589 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3592 protected
=> 1, # qemu pid files are only readable by root
3594 additionalProperties
=> 0,
3596 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3597 node
=> get_standard_option
('pve-node'),
3606 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3610 description
=> "Snapshot includes RAM.",
3615 description
=> "Snapshot description.",
3619 description
=> "Snapshot creation time",
3621 renderer
=> 'timestamp',
3625 description
=> "Parent snapshot identifier.",
3631 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3636 my $vmid = $param->{vmid
};
3638 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3639 my $snaphash = $conf->{snapshots
} || {};
3643 foreach my $name (keys %$snaphash) {
3644 my $d = $snaphash->{$name};
3647 snaptime
=> $d->{snaptime
} || 0,
3648 vmstate
=> $d->{vmstate
} ?
1 : 0,
3649 description
=> $d->{description
} || '',
3651 $item->{parent
} = $d->{parent
} if $d->{parent
};
3652 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3656 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3659 digest
=> $conf->{digest
},
3660 running
=> $running,
3661 description
=> "You are here!",
3663 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3665 push @$res, $current;
3670 __PACKAGE__-
>register_method({
3672 path
=> '{vmid}/snapshot',
3676 description
=> "Snapshot a VM.",
3678 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3681 additionalProperties
=> 0,
3683 node
=> get_standard_option
('pve-node'),
3684 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3685 snapname
=> get_standard_option
('pve-snapshot-name'),
3689 description
=> "Save the vmstate",
3694 description
=> "A textual description or comment.",
3700 description
=> "the task ID.",
3705 my $rpcenv = PVE
::RPCEnvironment
::get
();
3707 my $authuser = $rpcenv->get_user();
3709 my $node = extract_param
($param, 'node');
3711 my $vmid = extract_param
($param, 'vmid');
3713 my $snapname = extract_param
($param, 'snapname');
3715 die "unable to use snapshot name 'current' (reserved name)\n"
3716 if $snapname eq 'current';
3719 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3720 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3721 $param->{description
});
3724 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3727 __PACKAGE__-
>register_method({
3728 name
=> 'snapshot_cmd_idx',
3729 path
=> '{vmid}/snapshot/{snapname}',
3736 additionalProperties
=> 0,
3738 vmid
=> get_standard_option
('pve-vmid'),
3739 node
=> get_standard_option
('pve-node'),
3740 snapname
=> get_standard_option
('pve-snapshot-name'),
3749 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3756 push @$res, { cmd
=> 'rollback' };
3757 push @$res, { cmd
=> 'config' };
3762 __PACKAGE__-
>register_method({
3763 name
=> 'update_snapshot_config',
3764 path
=> '{vmid}/snapshot/{snapname}/config',
3768 description
=> "Update snapshot metadata.",
3770 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3773 additionalProperties
=> 0,
3775 node
=> get_standard_option
('pve-node'),
3776 vmid
=> get_standard_option
('pve-vmid'),
3777 snapname
=> get_standard_option
('pve-snapshot-name'),
3781 description
=> "A textual description or comment.",
3785 returns
=> { type
=> 'null' },
3789 my $rpcenv = PVE
::RPCEnvironment
::get
();
3791 my $authuser = $rpcenv->get_user();
3793 my $vmid = extract_param
($param, 'vmid');
3795 my $snapname = extract_param
($param, 'snapname');
3797 return undef if !defined($param->{description
});
3799 my $updatefn = sub {
3801 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3803 PVE
::QemuConfig-
>check_lock($conf);
3805 my $snap = $conf->{snapshots
}->{$snapname};
3807 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3809 $snap->{description
} = $param->{description
} if defined($param->{description
});
3811 PVE
::QemuConfig-
>write_config($vmid, $conf);
3814 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3819 __PACKAGE__-
>register_method({
3820 name
=> 'get_snapshot_config',
3821 path
=> '{vmid}/snapshot/{snapname}/config',
3824 description
=> "Get snapshot configuration",
3826 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3829 additionalProperties
=> 0,
3831 node
=> get_standard_option
('pve-node'),
3832 vmid
=> get_standard_option
('pve-vmid'),
3833 snapname
=> get_standard_option
('pve-snapshot-name'),
3836 returns
=> { type
=> "object" },
3840 my $rpcenv = PVE
::RPCEnvironment
::get
();
3842 my $authuser = $rpcenv->get_user();
3844 my $vmid = extract_param
($param, 'vmid');
3846 my $snapname = extract_param
($param, 'snapname');
3848 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3850 my $snap = $conf->{snapshots
}->{$snapname};
3852 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3857 __PACKAGE__-
>register_method({
3859 path
=> '{vmid}/snapshot/{snapname}/rollback',
3863 description
=> "Rollback VM state to specified snapshot.",
3865 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3868 additionalProperties
=> 0,
3870 node
=> get_standard_option
('pve-node'),
3871 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3872 snapname
=> get_standard_option
('pve-snapshot-name'),
3877 description
=> "the task ID.",
3882 my $rpcenv = PVE
::RPCEnvironment
::get
();
3884 my $authuser = $rpcenv->get_user();
3886 my $node = extract_param
($param, 'node');
3888 my $vmid = extract_param
($param, 'vmid');
3890 my $snapname = extract_param
($param, 'snapname');
3893 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3894 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3898 # hold migration lock, this makes sure that nobody create replication snapshots
3899 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3902 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3905 __PACKAGE__-
>register_method({
3906 name
=> 'delsnapshot',
3907 path
=> '{vmid}/snapshot/{snapname}',
3911 description
=> "Delete a VM snapshot.",
3913 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3916 additionalProperties
=> 0,
3918 node
=> get_standard_option
('pve-node'),
3919 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3920 snapname
=> get_standard_option
('pve-snapshot-name'),
3924 description
=> "For removal from config file, even if removing disk snapshots fails.",
3930 description
=> "the task ID.",
3935 my $rpcenv = PVE
::RPCEnvironment
::get
();
3937 my $authuser = $rpcenv->get_user();
3939 my $node = extract_param
($param, 'node');
3941 my $vmid = extract_param
($param, 'vmid');
3943 my $snapname = extract_param
($param, 'snapname');
3946 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3947 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3950 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3953 __PACKAGE__-
>register_method({
3955 path
=> '{vmid}/template',
3959 description
=> "Create a Template.",
3961 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3962 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3965 additionalProperties
=> 0,
3967 node
=> get_standard_option
('pve-node'),
3968 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3972 description
=> "If you want to convert only 1 disk to base image.",
3973 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3978 returns
=> { type
=> 'null'},
3982 my $rpcenv = PVE
::RPCEnvironment
::get
();
3984 my $authuser = $rpcenv->get_user();
3986 my $node = extract_param
($param, 'node');
3988 my $vmid = extract_param
($param, 'vmid');
3990 my $disk = extract_param
($param, 'disk');
3992 my $updatefn = sub {
3994 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3996 PVE
::QemuConfig-
>check_lock($conf);
3998 die "unable to create template, because VM contains snapshots\n"
3999 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4001 die "you can't convert a template to a template\n"
4002 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4004 die "you can't convert a VM to template if VM is running\n"
4005 if PVE
::QemuServer
::check_running
($vmid);
4008 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4011 $conf->{template
} = 1;
4012 PVE
::QemuConfig-
>write_config($vmid, $conf);
4014 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4017 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4021 __PACKAGE__-
>register_method({
4022 name
=> 'cloudinit_generated_config_dump',
4023 path
=> '{vmid}/cloudinit/dump',
4026 description
=> "Get automatically generated cloudinit config.",
4028 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4031 additionalProperties
=> 0,
4033 node
=> get_standard_option
('pve-node'),
4034 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4036 description
=> 'Config type.',
4038 enum
=> ['user', 'network', 'meta'],
4048 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4050 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});