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
();
466 my $authuser = $rpcenv->get_user();
468 my $node = extract_param
($param, 'node');
469 my $vmid = extract_param
($param, 'vmid');
471 my $archive = extract_param
($param, 'archive');
472 my $is_restore = !!$archive;
474 my $bwlimit = extract_param
($param, 'bwlimit');
475 my $force = extract_param
($param, 'force');
476 my $pool = extract_param
($param, 'pool');
477 my $start_after_create = extract_param
($param, 'start');
478 my $storage = extract_param
($param, 'storage');
479 my $unique = extract_param
($param, 'unique');
481 if (defined(my $ssh_keys = $param->{sshkeys
})) {
482 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
483 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
486 PVE
::Cluster
::check_cfs_quorum
();
488 my $filename = PVE
::QemuConfig-
>config_file($vmid);
489 my $storecfg = PVE
::Storage
::config
();
491 if (defined($pool)) {
492 $rpcenv->check_pool_exist($pool);
495 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
496 if defined($storage);
498 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
500 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
502 } elsif ($archive && $force && (-f
$filename) &&
503 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
504 # OK: user has VM.Backup permissions, and want to restore an existing VM
510 &$resolve_cdrom_alias($param);
512 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
514 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
516 foreach my $opt (keys %$param) {
517 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
518 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
519 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
521 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
522 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
526 PVE
::QemuServer
::add_random_macs
($param);
528 my $keystr = join(' ', keys %$param);
529 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
531 if ($archive eq '-') {
532 die "pipe requires cli environment\n"
533 if $rpcenv->{type
} ne 'cli';
535 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
536 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
540 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
542 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
543 die "$emsg $@" if $@;
545 my $restorefn = sub {
546 my $conf = PVE
::QemuConfig-
>load_config($vmid);
548 PVE
::QemuConfig-
>check_protection($conf, $emsg);
550 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
553 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
559 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
560 # Convert restored VM to template if backup was VM template
561 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
562 warn "Convert to template.\n";
563 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
567 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
569 if ($start_after_create) {
570 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
575 # ensure no old replication state are exists
576 PVE
::ReplicationState
::delete_guest_states
($vmid);
578 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
582 # ensure no old replication state are exists
583 PVE
::ReplicationState
::delete_guest_states
($vmid);
591 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
595 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
597 if (!$conf->{bootdisk
}) {
598 my $firstdisk = PVE
::QemuServer
::resolve_first_disk
($conf);
599 $conf->{bootdisk
} = $firstdisk if $firstdisk;
602 # auto generate uuid if user did not specify smbios1 option
603 if (!$conf->{smbios1
}) {
604 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
607 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
608 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
611 PVE
::QemuConfig-
>write_config($vmid, $conf);
617 foreach my $volid (@$vollist) {
618 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
624 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
627 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
629 if ($start_after_create) {
630 print "Execute autostart\n";
631 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
636 my ($code, $worker_name);
638 $worker_name = 'qmrestore';
640 eval { $restorefn->() };
642 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
648 $worker_name = 'qmcreate';
650 eval { $createfn->() };
653 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
654 unlink($conffile) or die "failed to remove config file: $!\n";
662 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
665 __PACKAGE__-
>register_method({
670 description
=> "Directory index",
675 additionalProperties
=> 0,
677 node
=> get_standard_option
('pve-node'),
678 vmid
=> get_standard_option
('pve-vmid'),
686 subdir
=> { type
=> 'string' },
689 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
695 { subdir
=> 'config' },
696 { subdir
=> 'pending' },
697 { subdir
=> 'status' },
698 { subdir
=> 'unlink' },
699 { subdir
=> 'vncproxy' },
700 { subdir
=> 'termproxy' },
701 { subdir
=> 'migrate' },
702 { subdir
=> 'resize' },
703 { subdir
=> 'move' },
705 { subdir
=> 'rrddata' },
706 { subdir
=> 'monitor' },
707 { subdir
=> 'agent' },
708 { subdir
=> 'snapshot' },
709 { subdir
=> 'spiceproxy' },
710 { subdir
=> 'sendkey' },
711 { subdir
=> 'firewall' },
717 __PACKAGE__-
>register_method ({
718 subclass
=> "PVE::API2::Firewall::VM",
719 path
=> '{vmid}/firewall',
722 __PACKAGE__-
>register_method ({
723 subclass
=> "PVE::API2::Qemu::Agent",
724 path
=> '{vmid}/agent',
727 __PACKAGE__-
>register_method({
729 path
=> '{vmid}/rrd',
731 protected
=> 1, # fixme: can we avoid that?
733 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
735 description
=> "Read VM RRD statistics (returns PNG)",
737 additionalProperties
=> 0,
739 node
=> get_standard_option
('pve-node'),
740 vmid
=> get_standard_option
('pve-vmid'),
742 description
=> "Specify the time frame you are interested in.",
744 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
747 description
=> "The list of datasources you want to display.",
748 type
=> 'string', format
=> 'pve-configid-list',
751 description
=> "The RRD consolidation function",
753 enum
=> [ 'AVERAGE', 'MAX' ],
761 filename
=> { type
=> 'string' },
767 return PVE
::Cluster
::create_rrd_graph
(
768 "pve2-vm/$param->{vmid}", $param->{timeframe
},
769 $param->{ds
}, $param->{cf
});
773 __PACKAGE__-
>register_method({
775 path
=> '{vmid}/rrddata',
777 protected
=> 1, # fixme: can we avoid that?
779 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
781 description
=> "Read VM RRD statistics",
783 additionalProperties
=> 0,
785 node
=> get_standard_option
('pve-node'),
786 vmid
=> get_standard_option
('pve-vmid'),
788 description
=> "Specify the time frame you are interested in.",
790 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
793 description
=> "The RRD consolidation function",
795 enum
=> [ 'AVERAGE', 'MAX' ],
810 return PVE
::Cluster
::create_rrd_data
(
811 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
815 __PACKAGE__-
>register_method({
817 path
=> '{vmid}/config',
820 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
822 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
825 additionalProperties
=> 0,
827 node
=> get_standard_option
('pve-node'),
828 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
830 description
=> "Get current values (instead of pending values).",
835 snapshot
=> get_standard_option
('pve-snapshot-name', {
836 description
=> "Fetch config values from given snapshot.",
839 my ($cmd, $pname, $cur, $args) = @_;
840 PVE
::QemuConfig-
>snapshot_list($args->[0]);
846 description
=> "The current VM configuration.",
848 properties
=> PVE
::QemuServer
::json_config_properties
({
851 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
858 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
859 current
=> "cannot use 'snapshot' parameter with 'current'"})
860 if ($param->{snapshot
} && $param->{current
});
863 if ($param->{snapshot
}) {
864 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
866 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
868 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
873 __PACKAGE__-
>register_method({
874 name
=> 'vm_pending',
875 path
=> '{vmid}/pending',
878 description
=> "Get virtual machine configuration, including pending changes.",
880 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
883 additionalProperties
=> 0,
885 node
=> get_standard_option
('pve-node'),
886 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
895 description
=> "Configuration option name.",
899 description
=> "Current value.",
904 description
=> "Pending value.",
909 description
=> "Indicates a pending delete request if present and not 0. " .
910 "The value 2 indicates a force-delete request.",
922 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
924 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
926 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
927 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
929 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
932 # POST/PUT {vmid}/config implementation
934 # The original API used PUT (idempotent) an we assumed that all operations
935 # are fast. But it turned out that almost any configuration change can
936 # involve hot-plug actions, or disk alloc/free. Such actions can take long
937 # time to complete and have side effects (not idempotent).
939 # The new implementation uses POST and forks a worker process. We added
940 # a new option 'background_delay'. If specified we wait up to
941 # 'background_delay' second for the worker task to complete. It returns null
942 # if the task is finished within that time, else we return the UPID.
944 my $update_vm_api = sub {
945 my ($param, $sync) = @_;
947 my $rpcenv = PVE
::RPCEnvironment
::get
();
949 my $authuser = $rpcenv->get_user();
951 my $node = extract_param
($param, 'node');
953 my $vmid = extract_param
($param, 'vmid');
955 my $digest = extract_param
($param, 'digest');
957 my $background_delay = extract_param
($param, 'background_delay');
959 if (defined(my $cipassword = $param->{cipassword
})) {
960 # Same logic as in cloud-init (but with the regex fixed...)
961 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
962 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
965 my @paramarr = (); # used for log message
966 foreach my $key (sort keys %$param) {
967 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
968 push @paramarr, "-$key", $value;
971 my $skiplock = extract_param
($param, 'skiplock');
972 raise_param_exc
({ skiplock
=> "Only root may use this option." })
973 if $skiplock && $authuser ne 'root@pam';
975 my $delete_str = extract_param
($param, 'delete');
977 my $revert_str = extract_param
($param, 'revert');
979 my $force = extract_param
($param, 'force');
981 if (defined(my $ssh_keys = $param->{sshkeys
})) {
982 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
983 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
986 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
988 my $storecfg = PVE
::Storage
::config
();
990 my $defaults = PVE
::QemuServer
::load_defaults
();
992 &$resolve_cdrom_alias($param);
994 # now try to verify all parameters
997 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
998 if (!PVE
::QemuServer
::option_exists
($opt)) {
999 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1002 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1003 "-revert $opt' at the same time" })
1004 if defined($param->{$opt});
1006 $revert->{$opt} = 1;
1010 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1011 $opt = 'ide2' if $opt eq 'cdrom';
1013 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1014 "-delete $opt' at the same time" })
1015 if defined($param->{$opt});
1017 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1018 "-revert $opt' at the same time" })
1021 if (!PVE
::QemuServer
::option_exists
($opt)) {
1022 raise_param_exc
({ delete => "unknown option '$opt'" });
1028 my $repl_conf = PVE
::ReplicationConfig-
>new();
1029 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1030 my $check_replication = sub {
1032 return if !$is_replicated;
1033 my $volid = $drive->{file
};
1034 return if !$volid || !($drive->{replicate
}//1);
1035 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1037 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1038 return if $volname eq 'cloudinit';
1041 if ($volid =~ $NEW_DISK_RE) {
1043 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1045 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1047 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1048 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1049 return if $scfg->{shared
};
1050 die "cannot add non-replicatable volume to a replicated VM\n";
1053 foreach my $opt (keys %$param) {
1054 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1055 # cleanup drive path
1056 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1057 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1058 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1059 $check_replication->($drive);
1060 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1061 } elsif ($opt =~ m/^net(\d+)$/) {
1063 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1064 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1065 } elsif ($opt eq 'vmgenid') {
1066 if ($param->{$opt} eq '1') {
1067 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1069 } elsif ($opt eq 'hookscript') {
1070 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1071 raise_param_exc
({ $opt => $@ }) if $@;
1075 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1077 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1079 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1081 my $updatefn = sub {
1083 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1085 die "checksum missmatch (file change by other user?)\n"
1086 if $digest && $digest ne $conf->{digest
};
1088 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1090 foreach my $opt (keys %$revert) {
1091 if (defined($conf->{$opt})) {
1092 $param->{$opt} = $conf->{$opt};
1093 } elsif (defined($conf->{pending
}->{$opt})) {
1098 if ($param->{memory
} || defined($param->{balloon
})) {
1099 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1100 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1102 die "balloon value too large (must be smaller than assigned memory)\n"
1103 if $balloon && $balloon > $maxmem;
1106 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1110 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1112 # write updates to pending section
1114 my $modified = {}; # record what $option we modify
1116 foreach my $opt (@delete) {
1117 $modified->{$opt} = 1;
1118 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1120 # value of what we want to delete, independent if pending or not
1121 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1122 if (!defined($val)) {
1123 warn "cannot delete '$opt' - not set in current configuration!\n";
1124 $modified->{$opt} = 0;
1127 my $is_pending_val = defined($conf->{pending
}->{$opt});
1128 delete $conf->{pending
}->{$opt};
1130 if ($opt =~ m/^unused/) {
1131 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1132 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1133 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1134 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1135 delete $conf->{$opt};
1136 PVE
::QemuConfig-
>write_config($vmid, $conf);
1138 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1139 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1140 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1141 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1143 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1144 PVE
::QemuConfig-
>write_config($vmid, $conf);
1145 } elsif ($opt =~ m/^serial\d+$/) {
1146 if ($val eq 'socket') {
1147 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1148 } elsif ($authuser ne 'root@pam') {
1149 die "only root can delete '$opt' config for real devices\n";
1151 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1152 PVE
::QemuConfig-
>write_config($vmid, $conf);
1153 } elsif ($opt =~ m/^usb\d+$/) {
1154 if ($val =~ m/spice/) {
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);
1162 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1163 PVE
::QemuConfig-
>write_config($vmid, $conf);
1167 foreach my $opt (keys %$param) { # add/change
1168 $modified->{$opt} = 1;
1169 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1170 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1172 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
1174 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1175 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1176 # FIXME: cloudinit: CDROM or Disk?
1177 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1178 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1180 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1182 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1183 if defined($conf->{pending
}->{$opt});
1185 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1186 } elsif ($opt =~ m/^serial\d+/) {
1187 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1188 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1189 } elsif ($authuser ne 'root@pam') {
1190 die "only root can modify '$opt' config for real devices\n";
1192 $conf->{pending
}->{$opt} = $param->{$opt};
1193 } elsif ($opt =~ m/^usb\d+/) {
1194 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1195 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1196 } elsif ($authuser ne 'root@pam') {
1197 die "only root can modify '$opt' config for real devices\n";
1199 $conf->{pending
}->{$opt} = $param->{$opt};
1201 $conf->{pending
}->{$opt} = $param->{$opt};
1203 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1204 PVE
::QemuConfig-
>write_config($vmid, $conf);
1207 # remove pending changes when nothing changed
1208 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1209 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1210 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1212 return if !scalar(keys %{$conf->{pending
}});
1214 my $running = PVE
::QemuServer
::check_running
($vmid);
1216 # apply pending changes
1218 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1222 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1223 raise_param_exc
($errors) if scalar(keys %$errors);
1225 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1235 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1237 if ($background_delay) {
1239 # Note: It would be better to do that in the Event based HTTPServer
1240 # to avoid blocking call to sleep.
1242 my $end_time = time() + $background_delay;
1244 my $task = PVE
::Tools
::upid_decode
($upid);
1247 while (time() < $end_time) {
1248 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1250 sleep(1); # this gets interrupted when child process ends
1254 my $status = PVE
::Tools
::upid_read_status
($upid);
1255 return undef if $status eq 'OK';
1264 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1267 my $vm_config_perm_list = [
1272 'VM.Config.Network',
1274 'VM.Config.Options',
1277 __PACKAGE__-
>register_method({
1278 name
=> 'update_vm_async',
1279 path
=> '{vmid}/config',
1283 description
=> "Set virtual machine options (asynchrounous API).",
1285 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1288 additionalProperties
=> 0,
1289 properties
=> PVE
::QemuServer
::json_config_properties
(
1291 node
=> get_standard_option
('pve-node'),
1292 vmid
=> get_standard_option
('pve-vmid'),
1293 skiplock
=> get_standard_option
('skiplock'),
1295 type
=> 'string', format
=> 'pve-configid-list',
1296 description
=> "A list of settings you want to delete.",
1300 type
=> 'string', format
=> 'pve-configid-list',
1301 description
=> "Revert a pending change.",
1306 description
=> $opt_force_description,
1308 requires
=> 'delete',
1312 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1316 background_delay
=> {
1318 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1329 code
=> $update_vm_api,
1332 __PACKAGE__-
>register_method({
1333 name
=> 'update_vm',
1334 path
=> '{vmid}/config',
1338 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1340 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1343 additionalProperties
=> 0,
1344 properties
=> PVE
::QemuServer
::json_config_properties
(
1346 node
=> get_standard_option
('pve-node'),
1347 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1348 skiplock
=> get_standard_option
('skiplock'),
1350 type
=> 'string', format
=> 'pve-configid-list',
1351 description
=> "A list of settings you want to delete.",
1355 type
=> 'string', format
=> 'pve-configid-list',
1356 description
=> "Revert a pending change.",
1361 description
=> $opt_force_description,
1363 requires
=> 'delete',
1367 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1373 returns
=> { type
=> 'null' },
1376 &$update_vm_api($param, 1);
1381 __PACKAGE__-
>register_method({
1382 name
=> 'destroy_vm',
1387 description
=> "Destroy the vm (also delete all used/owned volumes).",
1389 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1392 additionalProperties
=> 0,
1394 node
=> get_standard_option
('pve-node'),
1395 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1396 skiplock
=> get_standard_option
('skiplock'),
1399 description
=> "Remove vmid from backup cron jobs.",
1410 my $rpcenv = PVE
::RPCEnvironment
::get
();
1411 my $authuser = $rpcenv->get_user();
1412 my $vmid = $param->{vmid
};
1414 my $skiplock = $param->{skiplock
};
1415 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1416 if $skiplock && $authuser ne 'root@pam';
1419 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1420 my $storecfg = PVE
::Storage
::config
();
1421 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1422 die "unable to remove VM $vmid - used in HA resources\n"
1423 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1425 if (!$param->{purge
}) {
1426 # don't allow destroy if with replication jobs but no purge param
1427 my $repl_conf = PVE
::ReplicationConfig-
>new();
1428 $repl_conf->check_for_existing_jobs($vmid);
1431 # early tests (repeat after locking)
1432 die "VM $vmid is running - destroy failed\n"
1433 if PVE
::QemuServer
::check_running
($vmid);
1438 syslog
('info', "destroy VM $vmid: $upid\n");
1439 PVE
::QemuConfig-
>lock_config($vmid, sub {
1440 die "VM $vmid is running - destroy failed\n"
1441 if (PVE
::QemuServer
::check_running
($vmid));
1443 PVE
::QemuServer
::destroy_vm
($storecfg, $vmid, $skiplock, { lock => 'destroyed' });
1445 PVE
::AccessControl
::remove_vm_access
($vmid);
1446 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1447 if ($param->{purge
}) {
1448 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1449 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1452 # only now remove the zombie config, else we can have reuse race
1453 PVE
::QemuConfig-
>destroy_config($vmid);
1457 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1460 __PACKAGE__-
>register_method({
1462 path
=> '{vmid}/unlink',
1466 description
=> "Unlink/delete disk images.",
1468 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1471 additionalProperties
=> 0,
1473 node
=> get_standard_option
('pve-node'),
1474 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1476 type
=> 'string', format
=> 'pve-configid-list',
1477 description
=> "A list of disk IDs you want to delete.",
1481 description
=> $opt_force_description,
1486 returns
=> { type
=> 'null'},
1490 $param->{delete} = extract_param
($param, 'idlist');
1492 __PACKAGE__-
>update_vm($param);
1499 __PACKAGE__-
>register_method({
1501 path
=> '{vmid}/vncproxy',
1505 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1507 description
=> "Creates a TCP VNC proxy connections.",
1509 additionalProperties
=> 0,
1511 node
=> get_standard_option
('pve-node'),
1512 vmid
=> get_standard_option
('pve-vmid'),
1516 description
=> "starts websockify instead of vncproxy",
1521 additionalProperties
=> 0,
1523 user
=> { type
=> 'string' },
1524 ticket
=> { type
=> 'string' },
1525 cert
=> { type
=> 'string' },
1526 port
=> { type
=> 'integer' },
1527 upid
=> { type
=> 'string' },
1533 my $rpcenv = PVE
::RPCEnvironment
::get
();
1535 my $authuser = $rpcenv->get_user();
1537 my $vmid = $param->{vmid
};
1538 my $node = $param->{node
};
1539 my $websocket = $param->{websocket
};
1541 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1542 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1544 my $authpath = "/vms/$vmid";
1546 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1548 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1554 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1555 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1556 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1557 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1558 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1560 $family = PVE
::Tools
::get_host_address_family
($node);
1563 my $port = PVE
::Tools
::next_vnc_port
($family);
1570 syslog
('info', "starting vnc proxy $upid\n");
1576 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1578 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1579 '-timeout', $timeout, '-authpath', $authpath,
1580 '-perm', 'Sys.Console'];
1582 if ($param->{websocket
}) {
1583 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1584 push @$cmd, '-notls', '-listen', 'localhost';
1587 push @$cmd, '-c', @$remcmd, @$termcmd;
1589 PVE
::Tools
::run_command
($cmd);
1593 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1595 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1597 my $sock = IO
::Socket
::IP-
>new(
1602 GetAddrInfoFlags
=> 0,
1603 ) or die "failed to create socket: $!\n";
1604 # Inside the worker we shouldn't have any previous alarms
1605 # running anyway...:
1607 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1609 accept(my $cli, $sock) or die "connection failed: $!\n";
1612 if (PVE
::Tools
::run_command
($cmd,
1613 output
=> '>&'.fileno($cli),
1614 input
=> '<&'.fileno($cli),
1617 die "Failed to run vncproxy.\n";
1624 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1626 PVE
::Tools
::wait_for_vnc_port
($port);
1637 __PACKAGE__-
>register_method({
1638 name
=> 'termproxy',
1639 path
=> '{vmid}/termproxy',
1643 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1645 description
=> "Creates a TCP proxy connections.",
1647 additionalProperties
=> 0,
1649 node
=> get_standard_option
('pve-node'),
1650 vmid
=> get_standard_option
('pve-vmid'),
1654 enum
=> [qw(serial0 serial1 serial2 serial3)],
1655 description
=> "opens a serial terminal (defaults to display)",
1660 additionalProperties
=> 0,
1662 user
=> { type
=> 'string' },
1663 ticket
=> { type
=> 'string' },
1664 port
=> { type
=> 'integer' },
1665 upid
=> { type
=> 'string' },
1671 my $rpcenv = PVE
::RPCEnvironment
::get
();
1673 my $authuser = $rpcenv->get_user();
1675 my $vmid = $param->{vmid
};
1676 my $node = $param->{node
};
1677 my $serial = $param->{serial
};
1679 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1681 if (!defined($serial)) {
1682 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1683 $serial = $conf->{vga
};
1687 my $authpath = "/vms/$vmid";
1689 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1694 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1695 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1696 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1697 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, '-t');
1698 push @$remcmd, '--';
1700 $family = PVE
::Tools
::get_host_address_family
($node);
1703 my $port = PVE
::Tools
::next_vnc_port
($family);
1705 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1706 push @$termcmd, '-iface', $serial if $serial;
1711 syslog
('info', "starting qemu termproxy $upid\n");
1713 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1714 '--perm', 'VM.Console', '--'];
1715 push @$cmd, @$remcmd, @$termcmd;
1717 PVE
::Tools
::run_command
($cmd);
1720 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1722 PVE
::Tools
::wait_for_vnc_port
($port);
1732 __PACKAGE__-
>register_method({
1733 name
=> 'vncwebsocket',
1734 path
=> '{vmid}/vncwebsocket',
1737 description
=> "You also need to pass a valid ticket (vncticket).",
1738 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1740 description
=> "Opens a weksocket for VNC traffic.",
1742 additionalProperties
=> 0,
1744 node
=> get_standard_option
('pve-node'),
1745 vmid
=> get_standard_option
('pve-vmid'),
1747 description
=> "Ticket from previous call to vncproxy.",
1752 description
=> "Port number returned by previous vncproxy call.",
1762 port
=> { type
=> 'string' },
1768 my $rpcenv = PVE
::RPCEnvironment
::get
();
1770 my $authuser = $rpcenv->get_user();
1772 my $vmid = $param->{vmid
};
1773 my $node = $param->{node
};
1775 my $authpath = "/vms/$vmid";
1777 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1779 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1781 # Note: VNC ports are acessible from outside, so we do not gain any
1782 # security if we verify that $param->{port} belongs to VM $vmid. This
1783 # check is done by verifying the VNC ticket (inside VNC protocol).
1785 my $port = $param->{port
};
1787 return { port
=> $port };
1790 __PACKAGE__-
>register_method({
1791 name
=> 'spiceproxy',
1792 path
=> '{vmid}/spiceproxy',
1797 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1799 description
=> "Returns a SPICE configuration to connect to the VM.",
1801 additionalProperties
=> 0,
1803 node
=> get_standard_option
('pve-node'),
1804 vmid
=> get_standard_option
('pve-vmid'),
1805 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1808 returns
=> get_standard_option
('remote-viewer-config'),
1812 my $rpcenv = PVE
::RPCEnvironment
::get
();
1814 my $authuser = $rpcenv->get_user();
1816 my $vmid = $param->{vmid
};
1817 my $node = $param->{node
};
1818 my $proxy = $param->{proxy
};
1820 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1821 my $title = "VM $vmid";
1822 $title .= " - ". $conf->{name
} if $conf->{name
};
1824 my $port = PVE
::QemuServer
::spice_port
($vmid);
1826 my ($ticket, undef, $remote_viewer_config) =
1827 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1829 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1830 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1832 return $remote_viewer_config;
1835 __PACKAGE__-
>register_method({
1837 path
=> '{vmid}/status',
1840 description
=> "Directory index",
1845 additionalProperties
=> 0,
1847 node
=> get_standard_option
('pve-node'),
1848 vmid
=> get_standard_option
('pve-vmid'),
1856 subdir
=> { type
=> 'string' },
1859 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1865 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1868 { subdir
=> 'current' },
1869 { subdir
=> 'start' },
1870 { subdir
=> 'stop' },
1871 { subdir
=> 'reset' },
1872 { subdir
=> 'shutdown' },
1873 { subdir
=> 'suspend' },
1874 { subdir
=> 'reboot' },
1880 __PACKAGE__-
>register_method({
1881 name
=> 'vm_status',
1882 path
=> '{vmid}/status/current',
1885 protected
=> 1, # qemu pid files are only readable by root
1886 description
=> "Get virtual machine status.",
1888 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1891 additionalProperties
=> 0,
1893 node
=> get_standard_option
('pve-node'),
1894 vmid
=> get_standard_option
('pve-vmid'),
1900 %$PVE::QemuServer
::vmstatus_return_properties
,
1902 description
=> "HA manager service status.",
1906 description
=> "Qemu VGA configuration supports spice.",
1911 description
=> "Qemu GuestAgent enabled in config.",
1921 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1923 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1924 my $status = $vmstatus->{$param->{vmid
}};
1926 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1928 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1929 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1934 __PACKAGE__-
>register_method({
1936 path
=> '{vmid}/status/start',
1940 description
=> "Start virtual machine.",
1942 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1945 additionalProperties
=> 0,
1947 node
=> get_standard_option
('pve-node'),
1948 vmid
=> get_standard_option
('pve-vmid',
1949 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1950 skiplock
=> get_standard_option
('skiplock'),
1951 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1952 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1955 enum
=> ['secure', 'insecure'],
1956 description
=> "Migration traffic is encrypted using an SSH " .
1957 "tunnel by default. On secure, completely private networks " .
1958 "this can be disabled to increase performance.",
1961 migration_network
=> {
1962 type
=> 'string', format
=> 'CIDR',
1963 description
=> "CIDR of the (sub) network that is used for migration.",
1966 machine
=> get_standard_option
('pve-qm-machine'),
1968 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1980 my $rpcenv = PVE
::RPCEnvironment
::get
();
1981 my $authuser = $rpcenv->get_user();
1983 my $node = extract_param
($param, 'node');
1984 my $vmid = extract_param
($param, 'vmid');
1986 my $machine = extract_param
($param, 'machine');
1988 my $get_root_param = sub {
1989 my $value = extract_param
($param, $_[0]);
1990 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
1991 if $value && $authuser ne 'root@pam';
1995 my $stateuri = $get_root_param->('stateuri');
1996 my $skiplock = $get_root_param->('skiplock');
1997 my $migratedfrom = $get_root_param->('migratedfrom');
1998 my $migration_type = $get_root_param->('migration_type');
1999 my $migration_network = $get_root_param->('migration_network');
2000 my $targetstorage = $get_root_param->('targetstorage');
2002 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2003 if $targetstorage && !$migratedfrom;
2005 # read spice ticket from STDIN
2007 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2008 if (defined(my $line = <STDIN
>)) {
2010 $spice_ticket = $line;
2014 PVE
::Cluster
::check_cfs_quorum
();
2016 my $storecfg = PVE
::Storage
::config
();
2018 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2022 print "Requesting HA start for VM $vmid\n";
2024 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2025 PVE
::Tools
::run_command
($cmd);
2029 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2036 syslog
('info', "start VM $vmid: $upid\n");
2038 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2039 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
2043 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2047 __PACKAGE__-
>register_method({
2049 path
=> '{vmid}/status/stop',
2053 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2054 "is akin to pulling the power plug of a running computer and may damage the VM data",
2056 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2059 additionalProperties
=> 0,
2061 node
=> get_standard_option
('pve-node'),
2062 vmid
=> get_standard_option
('pve-vmid',
2063 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2064 skiplock
=> get_standard_option
('skiplock'),
2065 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2067 description
=> "Wait maximal timeout seconds.",
2073 description
=> "Do not deactivate storage volumes.",
2086 my $rpcenv = PVE
::RPCEnvironment
::get
();
2087 my $authuser = $rpcenv->get_user();
2089 my $node = extract_param
($param, 'node');
2090 my $vmid = extract_param
($param, 'vmid');
2092 my $skiplock = extract_param
($param, 'skiplock');
2093 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2094 if $skiplock && $authuser ne 'root@pam';
2096 my $keepActive = extract_param
($param, 'keepActive');
2097 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2098 if $keepActive && $authuser ne 'root@pam';
2100 my $migratedfrom = extract_param
($param, 'migratedfrom');
2101 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2102 if $migratedfrom && $authuser ne 'root@pam';
2105 my $storecfg = PVE
::Storage
::config
();
2107 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2112 print "Requesting HA stop for VM $vmid\n";
2114 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'stopped'];
2115 PVE
::Tools
::run_command
($cmd);
2119 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2125 syslog
('info', "stop VM $vmid: $upid\n");
2127 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2128 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2132 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2136 __PACKAGE__-
>register_method({
2138 path
=> '{vmid}/status/reset',
2142 description
=> "Reset virtual machine.",
2144 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2147 additionalProperties
=> 0,
2149 node
=> get_standard_option
('pve-node'),
2150 vmid
=> get_standard_option
('pve-vmid',
2151 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2152 skiplock
=> get_standard_option
('skiplock'),
2161 my $rpcenv = PVE
::RPCEnvironment
::get
();
2163 my $authuser = $rpcenv->get_user();
2165 my $node = extract_param
($param, 'node');
2167 my $vmid = extract_param
($param, 'vmid');
2169 my $skiplock = extract_param
($param, 'skiplock');
2170 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2171 if $skiplock && $authuser ne 'root@pam';
2173 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2178 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2183 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2186 __PACKAGE__-
>register_method({
2187 name
=> 'vm_shutdown',
2188 path
=> '{vmid}/status/shutdown',
2192 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2193 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2195 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2198 additionalProperties
=> 0,
2200 node
=> get_standard_option
('pve-node'),
2201 vmid
=> get_standard_option
('pve-vmid',
2202 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2203 skiplock
=> get_standard_option
('skiplock'),
2205 description
=> "Wait maximal timeout seconds.",
2211 description
=> "Make sure the VM stops.",
2217 description
=> "Do not deactivate storage volumes.",
2230 my $rpcenv = PVE
::RPCEnvironment
::get
();
2231 my $authuser = $rpcenv->get_user();
2233 my $node = extract_param
($param, 'node');
2234 my $vmid = extract_param
($param, 'vmid');
2236 my $skiplock = extract_param
($param, 'skiplock');
2237 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2238 if $skiplock && $authuser ne 'root@pam';
2240 my $keepActive = extract_param
($param, 'keepActive');
2241 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2242 if $keepActive && $authuser ne 'root@pam';
2244 my $storecfg = PVE
::Storage
::config
();
2248 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2249 # otherwise, we will infer a shutdown command, but run into the timeout,
2250 # then when the vm is resumed, it will instantly shutdown
2252 # checking the qmp status here to get feedback to the gui/cli/api
2253 # and the status query should not take too long
2254 my $qmpstatus = eval {
2255 PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2259 if (!$err && $qmpstatus->{status
} eq "paused") {
2260 if ($param->{forceStop
}) {
2261 warn "VM is paused - stop instead of shutdown\n";
2264 die "VM is paused - cannot shutdown\n";
2268 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2273 print "Requesting HA stop for VM $vmid\n";
2275 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'stopped'];
2276 PVE
::Tools
::run_command
($cmd);
2280 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2287 syslog
('info', "shutdown VM $vmid: $upid\n");
2289 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2290 $shutdown, $param->{forceStop
}, $keepActive);
2294 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2298 __PACKAGE__-
>register_method({
2299 name
=> 'vm_reboot',
2300 path
=> '{vmid}/status/reboot',
2304 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2306 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2309 additionalProperties
=> 0,
2311 node
=> get_standard_option
('pve-node'),
2312 vmid
=> get_standard_option
('pve-vmid',
2313 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2315 description
=> "Wait maximal timeout seconds for the shutdown.",
2328 my $rpcenv = PVE
::RPCEnvironment
::get
();
2329 my $authuser = $rpcenv->get_user();
2331 my $node = extract_param
($param, 'node');
2332 my $vmid = extract_param
($param, 'vmid');
2334 my $qmpstatus = eval {
2335 PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2339 if (!$err && $qmpstatus->{status
} eq "paused") {
2340 die "VM is paused - cannot shutdown\n";
2343 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2348 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2349 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2353 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2356 __PACKAGE__-
>register_method({
2357 name
=> 'vm_suspend',
2358 path
=> '{vmid}/status/suspend',
2362 description
=> "Suspend virtual machine.",
2364 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2367 additionalProperties
=> 0,
2369 node
=> get_standard_option
('pve-node'),
2370 vmid
=> get_standard_option
('pve-vmid',
2371 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2372 skiplock
=> get_standard_option
('skiplock'),
2377 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2379 statestorage
=> get_standard_option
('pve-storage-id', {
2380 description
=> "The storage for the VM state",
2381 requires
=> 'todisk',
2383 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2393 my $rpcenv = PVE
::RPCEnvironment
::get
();
2394 my $authuser = $rpcenv->get_user();
2396 my $node = extract_param
($param, 'node');
2397 my $vmid = extract_param
($param, 'vmid');
2399 my $todisk = extract_param
($param, 'todisk') // 0;
2401 my $statestorage = extract_param
($param, 'statestorage');
2403 my $skiplock = extract_param
($param, 'skiplock');
2404 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2405 if $skiplock && $authuser ne 'root@pam';
2407 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2409 die "Cannot suspend HA managed VM to disk\n"
2410 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2415 syslog
('info', "suspend VM $vmid: $upid\n");
2417 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2422 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2423 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2426 __PACKAGE__-
>register_method({
2427 name
=> 'vm_resume',
2428 path
=> '{vmid}/status/resume',
2432 description
=> "Resume virtual machine.",
2434 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2437 additionalProperties
=> 0,
2439 node
=> get_standard_option
('pve-node'),
2440 vmid
=> get_standard_option
('pve-vmid',
2441 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2442 skiplock
=> get_standard_option
('skiplock'),
2443 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2453 my $rpcenv = PVE
::RPCEnvironment
::get
();
2455 my $authuser = $rpcenv->get_user();
2457 my $node = extract_param
($param, 'node');
2459 my $vmid = extract_param
($param, 'vmid');
2461 my $skiplock = extract_param
($param, 'skiplock');
2462 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2463 if $skiplock && $authuser ne 'root@pam';
2465 my $nocheck = extract_param
($param, 'nocheck');
2467 my $to_disk_suspended;
2469 PVE
::QemuConfig-
>lock_config($vmid, sub {
2470 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2471 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2475 die "VM $vmid not running\n"
2476 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2481 syslog
('info', "resume VM $vmid: $upid\n");
2483 if (!$to_disk_suspended) {
2484 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2486 my $storecfg = PVE
::Storage
::config
();
2487 PVE
::QemuServer
::vm_start
($storecfg, $vmid, undef, $skiplock);
2493 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2496 __PACKAGE__-
>register_method({
2497 name
=> 'vm_sendkey',
2498 path
=> '{vmid}/sendkey',
2502 description
=> "Send key event to virtual machine.",
2504 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2507 additionalProperties
=> 0,
2509 node
=> get_standard_option
('pve-node'),
2510 vmid
=> get_standard_option
('pve-vmid',
2511 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2512 skiplock
=> get_standard_option
('skiplock'),
2514 description
=> "The key (qemu monitor encoding).",
2519 returns
=> { type
=> 'null'},
2523 my $rpcenv = PVE
::RPCEnvironment
::get
();
2525 my $authuser = $rpcenv->get_user();
2527 my $node = extract_param
($param, 'node');
2529 my $vmid = extract_param
($param, 'vmid');
2531 my $skiplock = extract_param
($param, 'skiplock');
2532 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2533 if $skiplock && $authuser ne 'root@pam';
2535 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2540 __PACKAGE__-
>register_method({
2541 name
=> 'vm_feature',
2542 path
=> '{vmid}/feature',
2546 description
=> "Check if feature for virtual machine is available.",
2548 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2551 additionalProperties
=> 0,
2553 node
=> get_standard_option
('pve-node'),
2554 vmid
=> get_standard_option
('pve-vmid'),
2556 description
=> "Feature to check.",
2558 enum
=> [ 'snapshot', 'clone', 'copy' ],
2560 snapname
=> get_standard_option
('pve-snapshot-name', {
2568 hasFeature
=> { type
=> 'boolean' },
2571 items
=> { type
=> 'string' },
2578 my $node = extract_param
($param, 'node');
2580 my $vmid = extract_param
($param, 'vmid');
2582 my $snapname = extract_param
($param, 'snapname');
2584 my $feature = extract_param
($param, 'feature');
2586 my $running = PVE
::QemuServer
::check_running
($vmid);
2588 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2591 my $snap = $conf->{snapshots
}->{$snapname};
2592 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2595 my $storecfg = PVE
::Storage
::config
();
2597 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2598 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2601 hasFeature
=> $hasFeature,
2602 nodes
=> [ keys %$nodelist ],
2606 __PACKAGE__-
>register_method({
2608 path
=> '{vmid}/clone',
2612 description
=> "Create a copy of virtual machine/template.",
2614 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2615 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2616 "'Datastore.AllocateSpace' on any used storage.",
2619 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2621 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2622 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2627 additionalProperties
=> 0,
2629 node
=> get_standard_option
('pve-node'),
2630 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2631 newid
=> get_standard_option
('pve-vmid', {
2632 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2633 description
=> 'VMID for the clone.' }),
2636 type
=> 'string', format
=> 'dns-name',
2637 description
=> "Set a name for the new VM.",
2642 description
=> "Description for the new VM.",
2646 type
=> 'string', format
=> 'pve-poolid',
2647 description
=> "Add the new VM to the specified pool.",
2649 snapname
=> get_standard_option
('pve-snapshot-name', {
2652 storage
=> get_standard_option
('pve-storage-id', {
2653 description
=> "Target storage for full clone.",
2657 description
=> "Target format for file storage. Only valid for full clone.",
2660 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2665 description
=> "Create a full copy of all disks. This is always done when " .
2666 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2668 target
=> get_standard_option
('pve-node', {
2669 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2673 description
=> "Override I/O bandwidth limit (in KiB/s).",
2677 default => 'clone limit from datacenter or storage config',
2687 my $rpcenv = PVE
::RPCEnvironment
::get
();
2689 my $authuser = $rpcenv->get_user();
2691 my $node = extract_param
($param, 'node');
2693 my $vmid = extract_param
($param, 'vmid');
2695 my $newid = extract_param
($param, 'newid');
2697 my $pool = extract_param
($param, 'pool');
2699 if (defined($pool)) {
2700 $rpcenv->check_pool_exist($pool);
2703 my $snapname = extract_param
($param, 'snapname');
2705 my $storage = extract_param
($param, 'storage');
2707 my $format = extract_param
($param, 'format');
2709 my $target = extract_param
($param, 'target');
2711 my $localnode = PVE
::INotify
::nodename
();
2713 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2715 PVE
::Cluster
::check_node_exists
($target) if $target;
2717 my $storecfg = PVE
::Storage
::config
();
2720 # check if storage is enabled on local node
2721 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2723 # check if storage is available on target node
2724 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2725 # clone only works if target storage is shared
2726 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2727 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2731 PVE
::Cluster
::check_cfs_quorum
();
2733 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2735 # exclusive lock if VM is running - else shared lock is enough;
2736 my $shared_lock = $running ?
0 : 1;
2740 # do all tests after lock
2741 # we also try to do all tests before we fork the worker
2743 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2745 PVE
::QemuConfig-
>check_lock($conf);
2747 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2749 die "unexpected state change\n" if $verify_running != $running;
2751 die "snapshot '$snapname' does not exist\n"
2752 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2754 my $full = extract_param
($param, 'full');
2755 if (!defined($full)) {
2756 $full = !PVE
::QemuConfig-
>is_template($conf);
2759 die "parameter 'storage' not allowed for linked clones\n"
2760 if defined($storage) && !$full;
2762 die "parameter 'format' not allowed for linked clones\n"
2763 if defined($format) && !$full;
2765 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2767 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2769 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2771 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2773 die "unable to create VM $newid: config file already exists\n"
2776 my $newconf = { lock => 'clone' };
2781 foreach my $opt (keys %$oldconf) {
2782 my $value = $oldconf->{$opt};
2784 # do not copy snapshot related info
2785 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2786 $opt eq 'vmstate' || $opt eq 'snapstate';
2788 # no need to copy unused images, because VMID(owner) changes anyways
2789 next if $opt =~ m/^unused\d+$/;
2791 # always change MAC! address
2792 if ($opt =~ m/^net(\d+)$/) {
2793 my $net = PVE
::QemuServer
::parse_net
($value);
2794 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2795 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2796 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2797 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2798 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2799 die "unable to parse drive options for '$opt'\n" if !$drive;
2800 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2801 $newconf->{$opt} = $value; # simply copy configuration
2804 die "Full clone feature is not supported for drive '$opt'\n"
2805 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2806 $fullclone->{$opt} = 1;
2808 # not full means clone instead of copy
2809 die "Linked clone feature is not supported for drive '$opt'\n"
2810 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2812 $drives->{$opt} = $drive;
2813 push @$vollist, $drive->{file
};
2816 # copy everything else
2817 $newconf->{$opt} = $value;
2821 # auto generate a new uuid
2822 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2823 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2824 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2826 # auto generate a new vmgenid if the option was set
2827 if ($newconf->{vmgenid
}) {
2828 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2831 delete $newconf->{template
};
2833 if ($param->{name
}) {
2834 $newconf->{name
} = $param->{name
};
2836 if ($oldconf->{name
}) {
2837 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2839 $newconf->{name
} = "Copy-of-VM-$vmid";
2843 if ($param->{description
}) {
2844 $newconf->{description
} = $param->{description
};
2847 # create empty/temp config - this fails if VM already exists on other node
2848 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2853 my $newvollist = [];
2860 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2862 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2864 my $bwlimit = extract_param
($param, 'bwlimit');
2866 my $total_jobs = scalar(keys %{$drives});
2869 foreach my $opt (keys %$drives) {
2870 my $drive = $drives->{$opt};
2871 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2873 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
2874 my $storage_list = [ $src_sid ];
2875 push @$storage_list, $storage if defined($storage);
2876 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
2878 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2879 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2880 $jobs, $skipcomplete, $oldconf->{agent
}, $clonelimit);
2882 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2884 PVE
::QemuConfig-
>write_config($newid, $newconf);
2888 delete $newconf->{lock};
2890 # do not write pending changes
2891 if (my @changes = keys %{$newconf->{pending
}}) {
2892 my $pending = join(',', @changes);
2893 warn "found pending changes for '$pending', discarding for clone\n";
2894 delete $newconf->{pending
};
2897 PVE
::QemuConfig-
>write_config($newid, $newconf);
2900 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2901 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2902 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2904 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2905 die "Failed to move config to node '$target' - rename failed: $!\n"
2906 if !rename($conffile, $newconffile);
2909 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2914 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2916 sleep 1; # some storage like rbd need to wait before release volume - really?
2918 foreach my $volid (@$newvollist) {
2919 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2922 die "clone failed: $err";
2928 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2930 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2933 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2934 # Aquire exclusive lock lock for $newid
2935 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2940 __PACKAGE__-
>register_method({
2941 name
=> 'move_vm_disk',
2942 path
=> '{vmid}/move_disk',
2946 description
=> "Move volume to different storage.",
2948 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2950 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2951 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2955 additionalProperties
=> 0,
2957 node
=> get_standard_option
('pve-node'),
2958 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2961 description
=> "The disk you want to move.",
2962 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2964 storage
=> get_standard_option
('pve-storage-id', {
2965 description
=> "Target storage.",
2966 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2970 description
=> "Target Format.",
2971 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2976 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2982 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2987 description
=> "Override I/O bandwidth limit (in KiB/s).",
2991 default => 'move limit from datacenter or storage config',
2997 description
=> "the task ID.",
3002 my $rpcenv = PVE
::RPCEnvironment
::get
();
3004 my $authuser = $rpcenv->get_user();
3006 my $node = extract_param
($param, 'node');
3008 my $vmid = extract_param
($param, 'vmid');
3010 my $digest = extract_param
($param, 'digest');
3012 my $disk = extract_param
($param, 'disk');
3014 my $storeid = extract_param
($param, 'storage');
3016 my $format = extract_param
($param, 'format');
3018 my $storecfg = PVE
::Storage
::config
();
3020 my $updatefn = sub {
3022 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3024 PVE
::QemuConfig-
>check_lock($conf);
3026 die "checksum missmatch (file change by other user?)\n"
3027 if $digest && $digest ne $conf->{digest
};
3029 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3031 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3033 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
3035 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3038 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3039 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3043 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
3044 (!$format || !$oldfmt || $oldfmt eq $format);
3046 # this only checks snapshots because $disk is passed!
3047 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3048 die "you can't move a disk with snapshots and delete the source\n"
3049 if $snapshotted && $param->{delete};
3051 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3053 my $running = PVE
::QemuServer
::check_running
($vmid);
3055 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3059 my $newvollist = [];
3065 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3067 warn "moving disk with snapshots, snapshots will not be moved!\n"
3070 my $bwlimit = extract_param
($param, 'bwlimit');
3071 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3073 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3074 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit);
3076 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
3078 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3080 # convert moved disk to base if part of template
3081 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3082 if PVE
::QemuConfig-
>is_template($conf);
3084 PVE
::QemuConfig-
>write_config($vmid, $conf);
3086 if ($running && PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
} && PVE
::QemuServer
::qga_check_running
($vmid)) {
3087 eval { PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-fstrim"); };
3091 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3092 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3099 foreach my $volid (@$newvollist) {
3100 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3103 die "storage migration failed: $err";
3106 if ($param->{delete}) {
3108 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3109 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3115 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3118 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3121 my $check_vm_disks_local = sub {
3122 my ($storecfg, $vmconf, $vmid) = @_;
3124 my $local_disks = {};
3126 # add some more information to the disks e.g. cdrom
3127 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3128 my ($volid, $attr) = @_;
3130 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3132 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3133 return if $scfg->{shared
};
3135 # The shared attr here is just a special case where the vdisk
3136 # is marked as shared manually
3137 return if $attr->{shared
};
3138 return if $attr->{cdrom
} and $volid eq "none";
3140 if (exists $local_disks->{$volid}) {
3141 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3143 $local_disks->{$volid} = $attr;
3144 # ensure volid is present in case it's needed
3145 $local_disks->{$volid}->{volid
} = $volid;
3149 return $local_disks;
3152 __PACKAGE__-
>register_method({
3153 name
=> 'migrate_vm_precondition',
3154 path
=> '{vmid}/migrate',
3158 description
=> "Get preconditions for migration.",
3160 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3163 additionalProperties
=> 0,
3165 node
=> get_standard_option
('pve-node'),
3166 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3167 target
=> get_standard_option
('pve-node', {
3168 description
=> "Target node.",
3169 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3177 running
=> { type
=> 'boolean' },
3181 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3183 not_allowed_nodes
=> {
3186 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3190 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3192 local_resources
=> {
3194 description
=> "List local resources e.g. pci, usb"
3201 my $rpcenv = PVE
::RPCEnvironment
::get
();
3203 my $authuser = $rpcenv->get_user();
3205 PVE
::Cluster
::check_cfs_quorum
();
3209 my $vmid = extract_param
($param, 'vmid');
3210 my $target = extract_param
($param, 'target');
3211 my $localnode = PVE
::INotify
::nodename
();
3215 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3216 my $storecfg = PVE
::Storage
::config
();
3219 # try to detect errors early
3220 PVE
::QemuConfig-
>check_lock($vmconf);
3222 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3224 # if vm is not running, return target nodes where local storage is available
3225 # for offline migration
3226 if (!$res->{running
}) {
3227 $res->{allowed_nodes
} = [];
3228 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3229 delete $checked_nodes->{$localnode};
3231 foreach my $node (keys %$checked_nodes) {
3232 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3233 push @{$res->{allowed_nodes
}}, $node;
3237 $res->{not_allowed_nodes
} = $checked_nodes;
3241 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3242 $res->{local_disks
} = [ values %$local_disks ];;
3244 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3246 $res->{local_resources
} = $local_resources;
3253 __PACKAGE__-
>register_method({
3254 name
=> 'migrate_vm',
3255 path
=> '{vmid}/migrate',
3259 description
=> "Migrate virtual machine. Creates a new migration task.",
3261 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3264 additionalProperties
=> 0,
3266 node
=> get_standard_option
('pve-node'),
3267 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3268 target
=> get_standard_option
('pve-node', {
3269 description
=> "Target node.",
3270 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3274 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3279 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3284 enum
=> ['secure', 'insecure'],
3285 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3288 migration_network
=> {
3289 type
=> 'string', format
=> 'CIDR',
3290 description
=> "CIDR of the (sub) network that is used for migration.",
3293 "with-local-disks" => {
3295 description
=> "Enable live storage migration for local disk",
3298 targetstorage
=> get_standard_option
('pve-storage-id', {
3299 description
=> "Default target storage.",
3301 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3304 description
=> "Override I/O bandwidth limit (in KiB/s).",
3308 default => 'migrate limit from datacenter or storage config',
3314 description
=> "the task ID.",
3319 my $rpcenv = PVE
::RPCEnvironment
::get
();
3320 my $authuser = $rpcenv->get_user();
3322 my $target = extract_param
($param, 'target');
3324 my $localnode = PVE
::INotify
::nodename
();
3325 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3327 PVE
::Cluster
::check_cfs_quorum
();
3329 PVE
::Cluster
::check_node_exists
($target);
3331 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3333 my $vmid = extract_param
($param, 'vmid');
3335 raise_param_exc
({ force
=> "Only root may use this option." })
3336 if $param->{force
} && $authuser ne 'root@pam';
3338 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3339 if $param->{migration_type
} && $authuser ne 'root@pam';
3341 # allow root only until better network permissions are available
3342 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3343 if $param->{migration_network
} && $authuser ne 'root@pam';
3346 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3348 # try to detect errors early
3350 PVE
::QemuConfig-
>check_lock($conf);
3352 if (PVE
::QemuServer
::check_running
($vmid)) {
3353 die "can't migrate running VM without --online\n" if !$param->{online
};
3355 warn "VM isn't running. Doing offline migration instead\n." if $param->{online
};
3356 $param->{online
} = 0;
3359 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3360 if !$param->{online
} && $param->{targetstorage
};
3362 my $storecfg = PVE
::Storage
::config
();
3364 if( $param->{targetstorage
}) {
3365 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3367 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3370 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3375 print "Requesting HA migration for VM $vmid to node $target\n";
3377 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3378 PVE
::Tools
::run_command
($cmd);
3382 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3387 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3391 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3394 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3399 __PACKAGE__-
>register_method({
3401 path
=> '{vmid}/monitor',
3405 description
=> "Execute Qemu monitor commands.",
3407 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3408 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3411 additionalProperties
=> 0,
3413 node
=> get_standard_option
('pve-node'),
3414 vmid
=> get_standard_option
('pve-vmid'),
3417 description
=> "The monitor command.",
3421 returns
=> { type
=> 'string'},
3425 my $rpcenv = PVE
::RPCEnvironment
::get
();
3426 my $authuser = $rpcenv->get_user();
3429 my $command = shift;
3430 return $command =~ m/^\s*info(\s+|$)/
3431 || $command =~ m/^\s*help\s*$/;
3434 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3435 if !&$is_ro($param->{command
});
3437 my $vmid = $param->{vmid
};
3439 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3443 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3445 $res = "ERROR: $@" if $@;
3450 __PACKAGE__-
>register_method({
3451 name
=> 'resize_vm',
3452 path
=> '{vmid}/resize',
3456 description
=> "Extend volume size.",
3458 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3461 additionalProperties
=> 0,
3463 node
=> get_standard_option
('pve-node'),
3464 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3465 skiplock
=> get_standard_option
('skiplock'),
3468 description
=> "The disk you want to resize.",
3469 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3473 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3474 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.",
3478 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3484 returns
=> { type
=> 'null'},
3488 my $rpcenv = PVE
::RPCEnvironment
::get
();
3490 my $authuser = $rpcenv->get_user();
3492 my $node = extract_param
($param, 'node');
3494 my $vmid = extract_param
($param, 'vmid');
3496 my $digest = extract_param
($param, 'digest');
3498 my $disk = extract_param
($param, 'disk');
3500 my $sizestr = extract_param
($param, 'size');
3502 my $skiplock = extract_param
($param, 'skiplock');
3503 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3504 if $skiplock && $authuser ne 'root@pam';
3506 my $storecfg = PVE
::Storage
::config
();
3508 my $updatefn = sub {
3510 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3512 die "checksum missmatch (file change by other user?)\n"
3513 if $digest && $digest ne $conf->{digest
};
3514 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3516 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3518 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3520 my (undef, undef, undef, undef, undef, undef, $format) =
3521 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3523 die "can't resize volume: $disk if snapshot exists\n"
3524 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3526 my $volid = $drive->{file
};
3528 die "disk '$disk' has no associated volume\n" if !$volid;
3530 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3532 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3534 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3536 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3537 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3539 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3541 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3542 my ($ext, $newsize, $unit) = ($1, $2, $4);
3545 $newsize = $newsize * 1024;
3546 } elsif ($unit eq 'M') {
3547 $newsize = $newsize * 1024 * 1024;
3548 } elsif ($unit eq 'G') {
3549 $newsize = $newsize * 1024 * 1024 * 1024;
3550 } elsif ($unit eq 'T') {
3551 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3554 $newsize += $size if $ext;
3555 $newsize = int($newsize);
3557 die "shrinking disks is not supported\n" if $newsize < $size;
3559 return if $size == $newsize;
3561 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3563 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3565 $drive->{size
} = $newsize;
3566 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3568 PVE
::QemuConfig-
>write_config($vmid, $conf);
3571 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3575 __PACKAGE__-
>register_method({
3576 name
=> 'snapshot_list',
3577 path
=> '{vmid}/snapshot',
3579 description
=> "List all snapshots.",
3581 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3584 protected
=> 1, # qemu pid files are only readable by root
3586 additionalProperties
=> 0,
3588 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3589 node
=> get_standard_option
('pve-node'),
3598 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3602 description
=> "Snapshot includes RAM.",
3607 description
=> "Snapshot description.",
3611 description
=> "Snapshot creation time",
3613 renderer
=> 'timestamp',
3617 description
=> "Parent snapshot identifier.",
3623 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3628 my $vmid = $param->{vmid
};
3630 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3631 my $snaphash = $conf->{snapshots
} || {};
3635 foreach my $name (keys %$snaphash) {
3636 my $d = $snaphash->{$name};
3639 snaptime
=> $d->{snaptime
} || 0,
3640 vmstate
=> $d->{vmstate
} ?
1 : 0,
3641 description
=> $d->{description
} || '',
3643 $item->{parent
} = $d->{parent
} if $d->{parent
};
3644 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3648 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3651 digest
=> $conf->{digest
},
3652 running
=> $running,
3653 description
=> "You are here!",
3655 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3657 push @$res, $current;
3662 __PACKAGE__-
>register_method({
3664 path
=> '{vmid}/snapshot',
3668 description
=> "Snapshot a VM.",
3670 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3673 additionalProperties
=> 0,
3675 node
=> get_standard_option
('pve-node'),
3676 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3677 snapname
=> get_standard_option
('pve-snapshot-name'),
3681 description
=> "Save the vmstate",
3686 description
=> "A textual description or comment.",
3692 description
=> "the task ID.",
3697 my $rpcenv = PVE
::RPCEnvironment
::get
();
3699 my $authuser = $rpcenv->get_user();
3701 my $node = extract_param
($param, 'node');
3703 my $vmid = extract_param
($param, 'vmid');
3705 my $snapname = extract_param
($param, 'snapname');
3707 die "unable to use snapshot name 'current' (reserved name)\n"
3708 if $snapname eq 'current';
3711 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3712 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3713 $param->{description
});
3716 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3719 __PACKAGE__-
>register_method({
3720 name
=> 'snapshot_cmd_idx',
3721 path
=> '{vmid}/snapshot/{snapname}',
3728 additionalProperties
=> 0,
3730 vmid
=> get_standard_option
('pve-vmid'),
3731 node
=> get_standard_option
('pve-node'),
3732 snapname
=> get_standard_option
('pve-snapshot-name'),
3741 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3748 push @$res, { cmd
=> 'rollback' };
3749 push @$res, { cmd
=> 'config' };
3754 __PACKAGE__-
>register_method({
3755 name
=> 'update_snapshot_config',
3756 path
=> '{vmid}/snapshot/{snapname}/config',
3760 description
=> "Update snapshot metadata.",
3762 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3765 additionalProperties
=> 0,
3767 node
=> get_standard_option
('pve-node'),
3768 vmid
=> get_standard_option
('pve-vmid'),
3769 snapname
=> get_standard_option
('pve-snapshot-name'),
3773 description
=> "A textual description or comment.",
3777 returns
=> { type
=> 'null' },
3781 my $rpcenv = PVE
::RPCEnvironment
::get
();
3783 my $authuser = $rpcenv->get_user();
3785 my $vmid = extract_param
($param, 'vmid');
3787 my $snapname = extract_param
($param, 'snapname');
3789 return undef if !defined($param->{description
});
3791 my $updatefn = sub {
3793 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3795 PVE
::QemuConfig-
>check_lock($conf);
3797 my $snap = $conf->{snapshots
}->{$snapname};
3799 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3801 $snap->{description
} = $param->{description
} if defined($param->{description
});
3803 PVE
::QemuConfig-
>write_config($vmid, $conf);
3806 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3811 __PACKAGE__-
>register_method({
3812 name
=> 'get_snapshot_config',
3813 path
=> '{vmid}/snapshot/{snapname}/config',
3816 description
=> "Get snapshot configuration",
3818 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3821 additionalProperties
=> 0,
3823 node
=> get_standard_option
('pve-node'),
3824 vmid
=> get_standard_option
('pve-vmid'),
3825 snapname
=> get_standard_option
('pve-snapshot-name'),
3828 returns
=> { type
=> "object" },
3832 my $rpcenv = PVE
::RPCEnvironment
::get
();
3834 my $authuser = $rpcenv->get_user();
3836 my $vmid = extract_param
($param, 'vmid');
3838 my $snapname = extract_param
($param, 'snapname');
3840 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3842 my $snap = $conf->{snapshots
}->{$snapname};
3844 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3849 __PACKAGE__-
>register_method({
3851 path
=> '{vmid}/snapshot/{snapname}/rollback',
3855 description
=> "Rollback VM state to specified snapshot.",
3857 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3860 additionalProperties
=> 0,
3862 node
=> get_standard_option
('pve-node'),
3863 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3864 snapname
=> get_standard_option
('pve-snapshot-name'),
3869 description
=> "the task ID.",
3874 my $rpcenv = PVE
::RPCEnvironment
::get
();
3876 my $authuser = $rpcenv->get_user();
3878 my $node = extract_param
($param, 'node');
3880 my $vmid = extract_param
($param, 'vmid');
3882 my $snapname = extract_param
($param, 'snapname');
3885 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3886 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3890 # hold migration lock, this makes sure that nobody create replication snapshots
3891 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3894 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3897 __PACKAGE__-
>register_method({
3898 name
=> 'delsnapshot',
3899 path
=> '{vmid}/snapshot/{snapname}',
3903 description
=> "Delete a VM snapshot.",
3905 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3908 additionalProperties
=> 0,
3910 node
=> get_standard_option
('pve-node'),
3911 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3912 snapname
=> get_standard_option
('pve-snapshot-name'),
3916 description
=> "For removal from config file, even if removing disk snapshots fails.",
3922 description
=> "the task ID.",
3927 my $rpcenv = PVE
::RPCEnvironment
::get
();
3929 my $authuser = $rpcenv->get_user();
3931 my $node = extract_param
($param, 'node');
3933 my $vmid = extract_param
($param, 'vmid');
3935 my $snapname = extract_param
($param, 'snapname');
3938 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3939 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3942 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3945 __PACKAGE__-
>register_method({
3947 path
=> '{vmid}/template',
3951 description
=> "Create a Template.",
3953 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3954 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3957 additionalProperties
=> 0,
3959 node
=> get_standard_option
('pve-node'),
3960 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3964 description
=> "If you want to convert only 1 disk to base image.",
3965 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3970 returns
=> { type
=> 'null'},
3974 my $rpcenv = PVE
::RPCEnvironment
::get
();
3976 my $authuser = $rpcenv->get_user();
3978 my $node = extract_param
($param, 'node');
3980 my $vmid = extract_param
($param, 'vmid');
3982 my $disk = extract_param
($param, 'disk');
3984 my $updatefn = sub {
3986 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3988 PVE
::QemuConfig-
>check_lock($conf);
3990 die "unable to create template, because VM contains snapshots\n"
3991 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3993 die "you can't convert a template to a template\n"
3994 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3996 die "you can't convert a VM to template if VM is running\n"
3997 if PVE
::QemuServer
::check_running
($vmid);
4000 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4003 $conf->{template
} = 1;
4004 PVE
::QemuConfig-
>write_config($vmid, $conf);
4006 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4009 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4013 __PACKAGE__-
>register_method({
4014 name
=> 'cloudinit_generated_config_dump',
4015 path
=> '{vmid}/cloudinit/dump',
4018 description
=> "Get automatically generated cloudinit config.",
4020 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4023 additionalProperties
=> 0,
4025 node
=> get_standard_option
('pve-node'),
4026 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4028 description
=> 'Config type.',
4030 enum
=> ['user', 'network', 'meta'],
4040 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4042 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});