1 package PVE
::API2
::Qemu
;
11 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
14 use PVE
::Tools
qw(extract_param);
15 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
17 use PVE
::JSONSchema
qw(get_standard_option);
19 use PVE
::ReplicationConfig
;
20 use PVE
::GuestHelpers
;
24 use PVE
::RPCEnvironment
;
25 use PVE
::AccessControl
;
29 use PVE
::API2
::Firewall
::VM
;
30 use PVE
::API2
::Qemu
::Agent
;
31 use PVE
::VZDump
::Plugin
;
32 use PVE
::DataCenterConfig
;
36 if (!$ENV{PVE_GENERATING_DOCS
}) {
37 require PVE
::HA
::Env
::PVE2
;
38 import PVE
::HA
::Env
::PVE2
;
39 require PVE
::HA
::Config
;
40 import PVE
::HA
::Config
;
44 use Data
::Dumper
; # fixme: remove
46 use base
qw(PVE::RESTHandler);
48 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.";
50 my $resolve_cdrom_alias = sub {
53 if (my $value = $param->{cdrom
}) {
54 $value .= ",media=cdrom" if $value !~ m/media=/;
55 $param->{ide2
} = $value;
56 delete $param->{cdrom
};
60 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
61 my $check_storage_access = sub {
62 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
64 PVE
::QemuServer
::foreach_drive
($settings, sub {
65 my ($ds, $drive) = @_;
67 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
69 my $volid = $drive->{file
};
70 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
72 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit' || $volname eq 'cloudinit')) {
74 } elsif ($isCDROM && ($volid eq 'cdrom')) {
75 $rpcenv->check($authuser, "/", ['Sys.Console']);
76 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
77 my ($storeid, $size) = ($2 || $default_storage, $3);
78 die "no storage ID specified (and no default storage)\n" if !$storeid;
79 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
80 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
81 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
82 if !$scfg->{content
}->{images
};
84 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
88 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
89 if defined($settings->{vmstatestorage
});
92 my $check_storage_access_clone = sub {
93 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
97 PVE
::QemuServer
::foreach_drive
($conf, sub {
98 my ($ds, $drive) = @_;
100 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
102 my $volid = $drive->{file
};
104 return if !$volid || $volid eq 'none';
107 if ($volid eq 'cdrom') {
108 $rpcenv->check($authuser, "/", ['Sys.Console']);
110 # we simply allow access
111 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
112 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
113 $sharedvm = 0 if !$scfg->{shared
};
117 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
118 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
119 $sharedvm = 0 if !$scfg->{shared
};
121 $sid = $storage if $storage;
122 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
126 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
127 if defined($conf->{vmstatestorage
});
132 # Note: $pool is only needed when creating a VM, because pool permissions
133 # are automatically inherited if VM already exists inside a pool.
134 my $create_disks = sub {
135 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
142 my ($ds, $disk) = @_;
144 my $volid = $disk->{file
};
145 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
147 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
148 delete $disk->{size
};
149 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
150 } elsif ($volname eq 'cloudinit') {
151 $storeid = $storeid // $default_storage;
152 die "no storage ID specified (and no default storage)\n" if !$storeid;
153 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
154 my $name = "vm-$vmid-cloudinit";
158 $fmt = $disk->{format
} // "qcow2";
161 $fmt = $disk->{format
} // "raw";
164 # Initial disk created with 4 MB and aligned to 4MB on regeneration
165 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
166 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
167 $disk->{file
} = $volid;
168 $disk->{media
} = 'cdrom';
169 push @$vollist, $volid;
170 delete $disk->{format
}; # no longer needed
171 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
172 } elsif ($volid =~ $NEW_DISK_RE) {
173 my ($storeid, $size) = ($2 || $default_storage, $3);
174 die "no storage ID specified (and no default storage)\n" if !$storeid;
175 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
176 my $fmt = $disk->{format
} || $defformat;
178 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
181 if ($ds eq 'efidisk0') {
182 ($volid, $size) = PVE
::QemuServer
::create_efidisk
($storecfg, $storeid, $vmid, $fmt, $arch);
184 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
186 push @$vollist, $volid;
187 $disk->{file
} = $volid;
188 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
189 delete $disk->{format
}; # no longer needed
190 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
193 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
195 my $volid_is_new = 1;
198 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
199 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
204 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
206 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
208 die "volume $volid does not exists\n" if !$size;
210 $disk->{size
} = $size;
213 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
217 eval { PVE
::QemuServer
::foreach_drive
($settings, $code); };
219 # free allocated images on error
221 syslog
('err', "VM $vmid creating disks failed");
222 foreach my $volid (@$vollist) {
223 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
229 # modify vm config if everything went well
230 foreach my $ds (keys %$res) {
231 $conf->{$ds} = $res->{$ds};
248 my $memoryoptions = {
254 my $hwtypeoptions = {
267 my $generaloptions = {
274 'migrate_downtime' => 1,
275 'migrate_speed' => 1,
287 my $vmpoweroptions = {
294 'vmstatestorage' => 1,
297 my $cloudinitoptions = {
307 my $check_vm_modify_config_perm = sub {
308 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
310 return 1 if $authuser eq 'root@pam';
312 foreach my $opt (@$key_list) {
313 # some checks (e.g., disk, serial port, usb) need to be done somewhere
314 # else, as there the permission can be value dependend
315 next if PVE
::QemuServer
::is_valid_drivename
($opt);
316 next if $opt eq 'cdrom';
317 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
320 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
321 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
322 } elsif ($memoryoptions->{$opt}) {
323 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
324 } elsif ($hwtypeoptions->{$opt}) {
325 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
326 } elsif ($generaloptions->{$opt}) {
327 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
328 # special case for startup since it changes host behaviour
329 if ($opt eq 'startup') {
330 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
332 } elsif ($vmpoweroptions->{$opt}) {
333 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
334 } elsif ($diskoptions->{$opt}) {
335 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
336 } elsif ($cloudinitoptions->{$opt} || ($opt =~ m/^(?:net|ipconfig)\d+$/)) {
337 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
339 # catches hostpci\d+, args, lock, etc.
340 # new options will be checked here
341 die "only root can set '$opt' config\n";
348 __PACKAGE__-
>register_method({
352 description
=> "Virtual machine index (per node).",
354 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
358 protected
=> 1, # qemu pid files are only readable by root
360 additionalProperties
=> 0,
362 node
=> get_standard_option
('pve-node'),
366 description
=> "Determine the full status of active VMs.",
374 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
376 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
381 my $rpcenv = PVE
::RPCEnvironment
::get
();
382 my $authuser = $rpcenv->get_user();
384 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
387 foreach my $vmid (keys %$vmstatus) {
388 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
390 my $data = $vmstatus->{$vmid};
399 __PACKAGE__-
>register_method({
403 description
=> "Create or restore a virtual machine.",
405 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
406 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
407 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
408 user
=> 'all', # check inside
413 additionalProperties
=> 0,
414 properties
=> PVE
::QemuServer
::json_config_properties
(
416 node
=> get_standard_option
('pve-node'),
417 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
419 description
=> "The backup file.",
423 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
425 storage
=> get_standard_option
('pve-storage-id', {
426 description
=> "Default storage.",
428 completion
=> \
&PVE
::QemuServer
::complete_storage
,
433 description
=> "Allow to overwrite existing VM.",
434 requires
=> 'archive',
439 description
=> "Assign a unique random ethernet address.",
440 requires
=> 'archive',
444 type
=> 'string', format
=> 'pve-poolid',
445 description
=> "Add the VM to the specified pool.",
448 description
=> "Override I/O bandwidth limit (in KiB/s).",
452 default => 'restore limit from datacenter or storage config',
458 description
=> "Start VM after it was created successfully.",
468 my $rpcenv = PVE
::RPCEnvironment
::get
();
469 my $authuser = $rpcenv->get_user();
471 my $node = extract_param
($param, 'node');
472 my $vmid = extract_param
($param, 'vmid');
474 my $archive = extract_param
($param, 'archive');
475 my $is_restore = !!$archive;
477 my $bwlimit = extract_param
($param, 'bwlimit');
478 my $force = extract_param
($param, 'force');
479 my $pool = extract_param
($param, 'pool');
480 my $start_after_create = extract_param
($param, 'start');
481 my $storage = extract_param
($param, 'storage');
482 my $unique = extract_param
($param, 'unique');
484 if (defined(my $ssh_keys = $param->{sshkeys
})) {
485 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
486 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
489 PVE
::Cluster
::check_cfs_quorum
();
491 my $filename = PVE
::QemuConfig-
>config_file($vmid);
492 my $storecfg = PVE
::Storage
::config
();
494 if (defined($pool)) {
495 $rpcenv->check_pool_exist($pool);
498 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
499 if defined($storage);
501 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
503 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
505 } elsif ($archive && $force && (-f
$filename) &&
506 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
507 # OK: user has VM.Backup permissions, and want to restore an existing VM
513 &$resolve_cdrom_alias($param);
515 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
517 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
519 foreach my $opt (keys %$param) {
520 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
521 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
522 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
524 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
525 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
529 PVE
::QemuServer
::add_random_macs
($param);
531 my $keystr = join(' ', keys %$param);
532 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
534 if ($archive eq '-') {
535 die "pipe requires cli environment\n"
536 if $rpcenv->{type
} ne 'cli';
538 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
539 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
543 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
545 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
546 die "$emsg $@" if $@;
548 my $restorefn = sub {
549 my $conf = PVE
::QemuConfig-
>load_config($vmid);
551 PVE
::QemuConfig-
>check_protection($conf, $emsg);
553 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
556 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
562 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
563 # Convert restored VM to template if backup was VM template
564 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
565 warn "Convert to template.\n";
566 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
570 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
572 if ($start_after_create) {
573 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
578 # ensure no old replication state are exists
579 PVE
::ReplicationState
::delete_guest_states
($vmid);
581 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
585 # ensure no old replication state are exists
586 PVE
::ReplicationState
::delete_guest_states
($vmid);
594 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
598 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
600 if (!$conf->{bootdisk
}) {
601 my $firstdisk = PVE
::QemuServer
::resolve_first_disk
($conf);
602 $conf->{bootdisk
} = $firstdisk if $firstdisk;
605 # auto generate uuid if user did not specify smbios1 option
606 if (!$conf->{smbios1
}) {
607 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
610 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
611 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
614 PVE
::QemuConfig-
>write_config($vmid, $conf);
620 foreach my $volid (@$vollist) {
621 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
627 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
630 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
632 if ($start_after_create) {
633 print "Execute autostart\n";
634 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
639 my ($code, $worker_name);
641 $worker_name = 'qmrestore';
643 eval { $restorefn->() };
645 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
651 $worker_name = 'qmcreate';
653 eval { $createfn->() };
656 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
657 unlink($conffile) or die "failed to remove config file: $!\n";
665 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
668 __PACKAGE__-
>register_method({
673 description
=> "Directory index",
678 additionalProperties
=> 0,
680 node
=> get_standard_option
('pve-node'),
681 vmid
=> get_standard_option
('pve-vmid'),
689 subdir
=> { type
=> 'string' },
692 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
698 { subdir
=> 'config' },
699 { subdir
=> 'pending' },
700 { subdir
=> 'status' },
701 { subdir
=> 'unlink' },
702 { subdir
=> 'vncproxy' },
703 { subdir
=> 'termproxy' },
704 { subdir
=> 'migrate' },
705 { subdir
=> 'resize' },
706 { subdir
=> 'move' },
708 { subdir
=> 'rrddata' },
709 { subdir
=> 'monitor' },
710 { subdir
=> 'agent' },
711 { subdir
=> 'snapshot' },
712 { subdir
=> 'spiceproxy' },
713 { subdir
=> 'sendkey' },
714 { subdir
=> 'firewall' },
720 __PACKAGE__-
>register_method ({
721 subclass
=> "PVE::API2::Firewall::VM",
722 path
=> '{vmid}/firewall',
725 __PACKAGE__-
>register_method ({
726 subclass
=> "PVE::API2::Qemu::Agent",
727 path
=> '{vmid}/agent',
730 __PACKAGE__-
>register_method({
732 path
=> '{vmid}/rrd',
734 protected
=> 1, # fixme: can we avoid that?
736 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
738 description
=> "Read VM RRD statistics (returns PNG)",
740 additionalProperties
=> 0,
742 node
=> get_standard_option
('pve-node'),
743 vmid
=> get_standard_option
('pve-vmid'),
745 description
=> "Specify the time frame you are interested in.",
747 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
750 description
=> "The list of datasources you want to display.",
751 type
=> 'string', format
=> 'pve-configid-list',
754 description
=> "The RRD consolidation function",
756 enum
=> [ 'AVERAGE', 'MAX' ],
764 filename
=> { type
=> 'string' },
770 return PVE
::RRD
::create_rrd_graph
(
771 "pve2-vm/$param->{vmid}", $param->{timeframe
},
772 $param->{ds
}, $param->{cf
});
776 __PACKAGE__-
>register_method({
778 path
=> '{vmid}/rrddata',
780 protected
=> 1, # fixme: can we avoid that?
782 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
784 description
=> "Read VM RRD statistics",
786 additionalProperties
=> 0,
788 node
=> get_standard_option
('pve-node'),
789 vmid
=> get_standard_option
('pve-vmid'),
791 description
=> "Specify the time frame you are interested in.",
793 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
796 description
=> "The RRD consolidation function",
798 enum
=> [ 'AVERAGE', 'MAX' ],
813 return PVE
::RRD
::create_rrd_data
(
814 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
818 __PACKAGE__-
>register_method({
820 path
=> '{vmid}/config',
823 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
825 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
828 additionalProperties
=> 0,
830 node
=> get_standard_option
('pve-node'),
831 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
833 description
=> "Get current values (instead of pending values).",
838 snapshot
=> get_standard_option
('pve-snapshot-name', {
839 description
=> "Fetch config values from given snapshot.",
842 my ($cmd, $pname, $cur, $args) = @_;
843 PVE
::QemuConfig-
>snapshot_list($args->[0]);
849 description
=> "The current VM configuration.",
851 properties
=> PVE
::QemuServer
::json_config_properties
({
854 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
861 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
862 current
=> "cannot use 'snapshot' parameter with 'current'"})
863 if ($param->{snapshot
} && $param->{current
});
866 if ($param->{snapshot
}) {
867 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
869 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
871 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
876 __PACKAGE__-
>register_method({
877 name
=> 'vm_pending',
878 path
=> '{vmid}/pending',
881 description
=> "Get virtual machine configuration, including pending changes.",
883 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
886 additionalProperties
=> 0,
888 node
=> get_standard_option
('pve-node'),
889 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
898 description
=> "Configuration option name.",
902 description
=> "Current value.",
907 description
=> "Pending value.",
912 description
=> "Indicates a pending delete request if present and not 0. " .
913 "The value 2 indicates a force-delete request.",
925 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
927 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
929 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
930 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
932 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
935 # POST/PUT {vmid}/config implementation
937 # The original API used PUT (idempotent) an we assumed that all operations
938 # are fast. But it turned out that almost any configuration change can
939 # involve hot-plug actions, or disk alloc/free. Such actions can take long
940 # time to complete and have side effects (not idempotent).
942 # The new implementation uses POST and forks a worker process. We added
943 # a new option 'background_delay'. If specified we wait up to
944 # 'background_delay' second for the worker task to complete. It returns null
945 # if the task is finished within that time, else we return the UPID.
947 my $update_vm_api = sub {
948 my ($param, $sync) = @_;
950 my $rpcenv = PVE
::RPCEnvironment
::get
();
952 my $authuser = $rpcenv->get_user();
954 my $node = extract_param
($param, 'node');
956 my $vmid = extract_param
($param, 'vmid');
958 my $digest = extract_param
($param, 'digest');
960 my $background_delay = extract_param
($param, 'background_delay');
962 if (defined(my $cipassword = $param->{cipassword
})) {
963 # Same logic as in cloud-init (but with the regex fixed...)
964 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
965 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
968 my @paramarr = (); # used for log message
969 foreach my $key (sort keys %$param) {
970 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
971 push @paramarr, "-$key", $value;
974 my $skiplock = extract_param
($param, 'skiplock');
975 raise_param_exc
({ skiplock
=> "Only root may use this option." })
976 if $skiplock && $authuser ne 'root@pam';
978 my $delete_str = extract_param
($param, 'delete');
980 my $revert_str = extract_param
($param, 'revert');
982 my $force = extract_param
($param, 'force');
984 if (defined(my $ssh_keys = $param->{sshkeys
})) {
985 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
986 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
989 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
991 my $storecfg = PVE
::Storage
::config
();
993 my $defaults = PVE
::QemuServer
::load_defaults
();
995 &$resolve_cdrom_alias($param);
997 # now try to verify all parameters
1000 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1001 if (!PVE
::QemuServer
::option_exists
($opt)) {
1002 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1005 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1006 "-revert $opt' at the same time" })
1007 if defined($param->{$opt});
1009 $revert->{$opt} = 1;
1013 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1014 $opt = 'ide2' if $opt eq 'cdrom';
1016 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1017 "-delete $opt' at the same time" })
1018 if defined($param->{$opt});
1020 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1021 "-revert $opt' at the same time" })
1024 if (!PVE
::QemuServer
::option_exists
($opt)) {
1025 raise_param_exc
({ delete => "unknown option '$opt'" });
1031 my $repl_conf = PVE
::ReplicationConfig-
>new();
1032 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1033 my $check_replication = sub {
1035 return if !$is_replicated;
1036 my $volid = $drive->{file
};
1037 return if !$volid || !($drive->{replicate
}//1);
1038 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1040 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1041 return if $volname eq 'cloudinit';
1044 if ($volid =~ $NEW_DISK_RE) {
1046 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1048 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1050 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1051 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1052 return if $scfg->{shared
};
1053 die "cannot add non-replicatable volume to a replicated VM\n";
1056 foreach my $opt (keys %$param) {
1057 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1058 # cleanup drive path
1059 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1060 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1061 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1062 $check_replication->($drive);
1063 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1064 } elsif ($opt =~ m/^net(\d+)$/) {
1066 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1067 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1068 } elsif ($opt eq 'vmgenid') {
1069 if ($param->{$opt} eq '1') {
1070 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1072 } elsif ($opt eq 'hookscript') {
1073 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1074 raise_param_exc
({ $opt => $@ }) if $@;
1078 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1080 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1082 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1084 my $updatefn = sub {
1086 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1088 die "checksum missmatch (file change by other user?)\n"
1089 if $digest && $digest ne $conf->{digest
};
1091 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1093 foreach my $opt (keys %$revert) {
1094 if (defined($conf->{$opt})) {
1095 $param->{$opt} = $conf->{$opt};
1096 } elsif (defined($conf->{pending
}->{$opt})) {
1101 if ($param->{memory
} || defined($param->{balloon
})) {
1102 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1103 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1105 die "balloon value too large (must be smaller than assigned memory)\n"
1106 if $balloon && $balloon > $maxmem;
1109 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1113 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1115 # write updates to pending section
1117 my $modified = {}; # record what $option we modify
1119 foreach my $opt (@delete) {
1120 $modified->{$opt} = 1;
1121 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1123 # value of what we want to delete, independent if pending or not
1124 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1125 if (!defined($val)) {
1126 warn "cannot delete '$opt' - not set in current configuration!\n";
1127 $modified->{$opt} = 0;
1130 my $is_pending_val = defined($conf->{pending
}->{$opt});
1131 delete $conf->{pending
}->{$opt};
1133 if ($opt =~ m/^unused/) {
1134 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1135 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1136 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1137 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1138 delete $conf->{$opt};
1139 PVE
::QemuConfig-
>write_config($vmid, $conf);
1141 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1142 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1143 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1144 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1146 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1147 PVE
::QemuConfig-
>write_config($vmid, $conf);
1148 } elsif ($opt =~ m/^serial\d+$/) {
1149 if ($val eq 'socket') {
1150 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1151 } elsif ($authuser ne 'root@pam') {
1152 die "only root can delete '$opt' config for real devices\n";
1154 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1155 PVE
::QemuConfig-
>write_config($vmid, $conf);
1156 } elsif ($opt =~ m/^usb\d+$/) {
1157 if ($val =~ m/spice/) {
1158 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1159 } elsif ($authuser ne 'root@pam') {
1160 die "only root can delete '$opt' config for real devices\n";
1162 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1163 PVE
::QemuConfig-
>write_config($vmid, $conf);
1165 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1166 PVE
::QemuConfig-
>write_config($vmid, $conf);
1170 foreach my $opt (keys %$param) { # add/change
1171 $modified->{$opt} = 1;
1172 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1173 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1175 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
1177 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1178 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1179 # FIXME: cloudinit: CDROM or Disk?
1180 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1181 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1183 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1185 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1186 if defined($conf->{pending
}->{$opt});
1188 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1189 } elsif ($opt =~ m/^serial\d+/) {
1190 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1191 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1192 } elsif ($authuser ne 'root@pam') {
1193 die "only root can modify '$opt' config for real devices\n";
1195 $conf->{pending
}->{$opt} = $param->{$opt};
1196 } elsif ($opt =~ m/^usb\d+/) {
1197 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1198 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1199 } elsif ($authuser ne 'root@pam') {
1200 die "only root can modify '$opt' config for real devices\n";
1202 $conf->{pending
}->{$opt} = $param->{$opt};
1204 $conf->{pending
}->{$opt} = $param->{$opt};
1206 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1207 PVE
::QemuConfig-
>write_config($vmid, $conf);
1210 # remove pending changes when nothing changed
1211 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1212 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1213 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1215 return if !scalar(keys %{$conf->{pending
}});
1217 my $running = PVE
::QemuServer
::check_running
($vmid);
1219 # apply pending changes
1221 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1225 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1226 raise_param_exc
($errors) if scalar(keys %$errors);
1228 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1238 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1240 if ($background_delay) {
1242 # Note: It would be better to do that in the Event based HTTPServer
1243 # to avoid blocking call to sleep.
1245 my $end_time = time() + $background_delay;
1247 my $task = PVE
::Tools
::upid_decode
($upid);
1250 while (time() < $end_time) {
1251 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1253 sleep(1); # this gets interrupted when child process ends
1257 my $status = PVE
::Tools
::upid_read_status
($upid);
1258 return undef if $status eq 'OK';
1267 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1270 my $vm_config_perm_list = [
1275 'VM.Config.Network',
1277 'VM.Config.Options',
1280 __PACKAGE__-
>register_method({
1281 name
=> 'update_vm_async',
1282 path
=> '{vmid}/config',
1286 description
=> "Set virtual machine options (asynchrounous API).",
1288 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1291 additionalProperties
=> 0,
1292 properties
=> PVE
::QemuServer
::json_config_properties
(
1294 node
=> get_standard_option
('pve-node'),
1295 vmid
=> get_standard_option
('pve-vmid'),
1296 skiplock
=> get_standard_option
('skiplock'),
1298 type
=> 'string', format
=> 'pve-configid-list',
1299 description
=> "A list of settings you want to delete.",
1303 type
=> 'string', format
=> 'pve-configid-list',
1304 description
=> "Revert a pending change.",
1309 description
=> $opt_force_description,
1311 requires
=> 'delete',
1315 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1319 background_delay
=> {
1321 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1332 code
=> $update_vm_api,
1335 __PACKAGE__-
>register_method({
1336 name
=> 'update_vm',
1337 path
=> '{vmid}/config',
1341 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1343 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1346 additionalProperties
=> 0,
1347 properties
=> PVE
::QemuServer
::json_config_properties
(
1349 node
=> get_standard_option
('pve-node'),
1350 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1351 skiplock
=> get_standard_option
('skiplock'),
1353 type
=> 'string', format
=> 'pve-configid-list',
1354 description
=> "A list of settings you want to delete.",
1358 type
=> 'string', format
=> 'pve-configid-list',
1359 description
=> "Revert a pending change.",
1364 description
=> $opt_force_description,
1366 requires
=> 'delete',
1370 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1376 returns
=> { type
=> 'null' },
1379 &$update_vm_api($param, 1);
1384 __PACKAGE__-
>register_method({
1385 name
=> 'destroy_vm',
1390 description
=> "Destroy the vm (also delete all used/owned volumes).",
1392 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1395 additionalProperties
=> 0,
1397 node
=> get_standard_option
('pve-node'),
1398 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1399 skiplock
=> get_standard_option
('skiplock'),
1402 description
=> "Remove vmid from backup cron jobs.",
1413 my $rpcenv = PVE
::RPCEnvironment
::get
();
1414 my $authuser = $rpcenv->get_user();
1415 my $vmid = $param->{vmid
};
1417 my $skiplock = $param->{skiplock
};
1418 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1419 if $skiplock && $authuser ne 'root@pam';
1422 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1423 my $storecfg = PVE
::Storage
::config
();
1424 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1425 die "unable to remove VM $vmid - used in HA resources\n"
1426 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1428 if (!$param->{purge
}) {
1429 # don't allow destroy if with replication jobs but no purge param
1430 my $repl_conf = PVE
::ReplicationConfig-
>new();
1431 $repl_conf->check_for_existing_jobs($vmid);
1434 # early tests (repeat after locking)
1435 die "VM $vmid is running - destroy failed\n"
1436 if PVE
::QemuServer
::check_running
($vmid);
1441 syslog
('info', "destroy VM $vmid: $upid\n");
1442 PVE
::QemuConfig-
>lock_config($vmid, sub {
1443 die "VM $vmid is running - destroy failed\n"
1444 if (PVE
::QemuServer
::check_running
($vmid));
1446 PVE
::QemuServer
::destroy_vm
($storecfg, $vmid, $skiplock, { lock => 'destroyed' });
1448 PVE
::AccessControl
::remove_vm_access
($vmid);
1449 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1450 if ($param->{purge
}) {
1451 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1452 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1455 # only now remove the zombie config, else we can have reuse race
1456 PVE
::QemuConfig-
>destroy_config($vmid);
1460 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1463 __PACKAGE__-
>register_method({
1465 path
=> '{vmid}/unlink',
1469 description
=> "Unlink/delete disk images.",
1471 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1474 additionalProperties
=> 0,
1476 node
=> get_standard_option
('pve-node'),
1477 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1479 type
=> 'string', format
=> 'pve-configid-list',
1480 description
=> "A list of disk IDs you want to delete.",
1484 description
=> $opt_force_description,
1489 returns
=> { type
=> 'null'},
1493 $param->{delete} = extract_param
($param, 'idlist');
1495 __PACKAGE__-
>update_vm($param);
1502 __PACKAGE__-
>register_method({
1504 path
=> '{vmid}/vncproxy',
1508 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1510 description
=> "Creates a TCP VNC proxy connections.",
1512 additionalProperties
=> 0,
1514 node
=> get_standard_option
('pve-node'),
1515 vmid
=> get_standard_option
('pve-vmid'),
1519 description
=> "starts websockify instead of vncproxy",
1524 additionalProperties
=> 0,
1526 user
=> { type
=> 'string' },
1527 ticket
=> { type
=> 'string' },
1528 cert
=> { type
=> 'string' },
1529 port
=> { type
=> 'integer' },
1530 upid
=> { type
=> 'string' },
1536 my $rpcenv = PVE
::RPCEnvironment
::get
();
1538 my $authuser = $rpcenv->get_user();
1540 my $vmid = $param->{vmid
};
1541 my $node = $param->{node
};
1542 my $websocket = $param->{websocket
};
1544 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1545 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1547 my $authpath = "/vms/$vmid";
1549 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1551 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1557 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1558 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1559 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1560 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1561 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1563 $family = PVE
::Tools
::get_host_address_family
($node);
1566 my $port = PVE
::Tools
::next_vnc_port
($family);
1573 syslog
('info', "starting vnc proxy $upid\n");
1579 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1581 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1582 '-timeout', $timeout, '-authpath', $authpath,
1583 '-perm', 'Sys.Console'];
1585 if ($param->{websocket
}) {
1586 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1587 push @$cmd, '-notls', '-listen', 'localhost';
1590 push @$cmd, '-c', @$remcmd, @$termcmd;
1592 PVE
::Tools
::run_command
($cmd);
1596 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1598 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1600 my $sock = IO
::Socket
::IP-
>new(
1605 GetAddrInfoFlags
=> 0,
1606 ) or die "failed to create socket: $!\n";
1607 # Inside the worker we shouldn't have any previous alarms
1608 # running anyway...:
1610 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1612 accept(my $cli, $sock) or die "connection failed: $!\n";
1615 if (PVE
::Tools
::run_command
($cmd,
1616 output
=> '>&'.fileno($cli),
1617 input
=> '<&'.fileno($cli),
1620 die "Failed to run vncproxy.\n";
1627 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1629 PVE
::Tools
::wait_for_vnc_port
($port);
1640 __PACKAGE__-
>register_method({
1641 name
=> 'termproxy',
1642 path
=> '{vmid}/termproxy',
1646 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1648 description
=> "Creates a TCP proxy connections.",
1650 additionalProperties
=> 0,
1652 node
=> get_standard_option
('pve-node'),
1653 vmid
=> get_standard_option
('pve-vmid'),
1657 enum
=> [qw(serial0 serial1 serial2 serial3)],
1658 description
=> "opens a serial terminal (defaults to display)",
1663 additionalProperties
=> 0,
1665 user
=> { type
=> 'string' },
1666 ticket
=> { type
=> 'string' },
1667 port
=> { type
=> 'integer' },
1668 upid
=> { type
=> 'string' },
1674 my $rpcenv = PVE
::RPCEnvironment
::get
();
1676 my $authuser = $rpcenv->get_user();
1678 my $vmid = $param->{vmid
};
1679 my $node = $param->{node
};
1680 my $serial = $param->{serial
};
1682 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1684 if (!defined($serial)) {
1685 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1686 $serial = $conf->{vga
};
1690 my $authpath = "/vms/$vmid";
1692 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1697 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1698 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1699 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1700 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1701 push @$remcmd, '--';
1703 $family = PVE
::Tools
::get_host_address_family
($node);
1706 my $port = PVE
::Tools
::next_vnc_port
($family);
1708 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1709 push @$termcmd, '-iface', $serial if $serial;
1714 syslog
('info', "starting qemu termproxy $upid\n");
1716 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1717 '--perm', 'VM.Console', '--'];
1718 push @$cmd, @$remcmd, @$termcmd;
1720 PVE
::Tools
::run_command
($cmd);
1723 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1725 PVE
::Tools
::wait_for_vnc_port
($port);
1735 __PACKAGE__-
>register_method({
1736 name
=> 'vncwebsocket',
1737 path
=> '{vmid}/vncwebsocket',
1740 description
=> "You also need to pass a valid ticket (vncticket).",
1741 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1743 description
=> "Opens a weksocket for VNC traffic.",
1745 additionalProperties
=> 0,
1747 node
=> get_standard_option
('pve-node'),
1748 vmid
=> get_standard_option
('pve-vmid'),
1750 description
=> "Ticket from previous call to vncproxy.",
1755 description
=> "Port number returned by previous vncproxy call.",
1765 port
=> { type
=> 'string' },
1771 my $rpcenv = PVE
::RPCEnvironment
::get
();
1773 my $authuser = $rpcenv->get_user();
1775 my $vmid = $param->{vmid
};
1776 my $node = $param->{node
};
1778 my $authpath = "/vms/$vmid";
1780 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1782 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1784 # Note: VNC ports are acessible from outside, so we do not gain any
1785 # security if we verify that $param->{port} belongs to VM $vmid. This
1786 # check is done by verifying the VNC ticket (inside VNC protocol).
1788 my $port = $param->{port
};
1790 return { port
=> $port };
1793 __PACKAGE__-
>register_method({
1794 name
=> 'spiceproxy',
1795 path
=> '{vmid}/spiceproxy',
1800 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1802 description
=> "Returns a SPICE configuration to connect to the VM.",
1804 additionalProperties
=> 0,
1806 node
=> get_standard_option
('pve-node'),
1807 vmid
=> get_standard_option
('pve-vmid'),
1808 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1811 returns
=> get_standard_option
('remote-viewer-config'),
1815 my $rpcenv = PVE
::RPCEnvironment
::get
();
1817 my $authuser = $rpcenv->get_user();
1819 my $vmid = $param->{vmid
};
1820 my $node = $param->{node
};
1821 my $proxy = $param->{proxy
};
1823 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1824 my $title = "VM $vmid";
1825 $title .= " - ". $conf->{name
} if $conf->{name
};
1827 my $port = PVE
::QemuServer
::spice_port
($vmid);
1829 my ($ticket, undef, $remote_viewer_config) =
1830 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1832 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1833 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1835 return $remote_viewer_config;
1838 __PACKAGE__-
>register_method({
1840 path
=> '{vmid}/status',
1843 description
=> "Directory index",
1848 additionalProperties
=> 0,
1850 node
=> get_standard_option
('pve-node'),
1851 vmid
=> get_standard_option
('pve-vmid'),
1859 subdir
=> { type
=> 'string' },
1862 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1868 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1871 { subdir
=> 'current' },
1872 { subdir
=> 'start' },
1873 { subdir
=> 'stop' },
1874 { subdir
=> 'reset' },
1875 { subdir
=> 'shutdown' },
1876 { subdir
=> 'suspend' },
1877 { subdir
=> 'reboot' },
1883 __PACKAGE__-
>register_method({
1884 name
=> 'vm_status',
1885 path
=> '{vmid}/status/current',
1888 protected
=> 1, # qemu pid files are only readable by root
1889 description
=> "Get virtual machine status.",
1891 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1894 additionalProperties
=> 0,
1896 node
=> get_standard_option
('pve-node'),
1897 vmid
=> get_standard_option
('pve-vmid'),
1903 %$PVE::QemuServer
::vmstatus_return_properties
,
1905 description
=> "HA manager service status.",
1909 description
=> "Qemu VGA configuration supports spice.",
1914 description
=> "Qemu GuestAgent enabled in config.",
1924 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1926 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1927 my $status = $vmstatus->{$param->{vmid
}};
1929 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1931 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1932 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1937 __PACKAGE__-
>register_method({
1939 path
=> '{vmid}/status/start',
1943 description
=> "Start virtual machine.",
1945 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1948 additionalProperties
=> 0,
1950 node
=> get_standard_option
('pve-node'),
1951 vmid
=> get_standard_option
('pve-vmid',
1952 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1953 skiplock
=> get_standard_option
('skiplock'),
1954 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1955 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1958 enum
=> ['secure', 'insecure'],
1959 description
=> "Migration traffic is encrypted using an SSH " .
1960 "tunnel by default. On secure, completely private networks " .
1961 "this can be disabled to increase performance.",
1964 migration_network
=> {
1965 type
=> 'string', format
=> 'CIDR',
1966 description
=> "CIDR of the (sub) network that is used for migration.",
1969 machine
=> get_standard_option
('pve-qm-machine'),
1971 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1983 my $rpcenv = PVE
::RPCEnvironment
::get
();
1984 my $authuser = $rpcenv->get_user();
1986 my $node = extract_param
($param, 'node');
1987 my $vmid = extract_param
($param, 'vmid');
1989 my $machine = extract_param
($param, 'machine');
1991 my $get_root_param = sub {
1992 my $value = extract_param
($param, $_[0]);
1993 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
1994 if $value && $authuser ne 'root@pam';
1998 my $stateuri = $get_root_param->('stateuri');
1999 my $skiplock = $get_root_param->('skiplock');
2000 my $migratedfrom = $get_root_param->('migratedfrom');
2001 my $migration_type = $get_root_param->('migration_type');
2002 my $migration_network = $get_root_param->('migration_network');
2003 my $targetstorage = $get_root_param->('targetstorage');
2005 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2006 if $targetstorage && !$migratedfrom;
2008 # read spice ticket from STDIN
2010 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2011 if (defined(my $line = <STDIN
>)) {
2013 $spice_ticket = $line;
2017 PVE
::Cluster
::check_cfs_quorum
();
2019 my $storecfg = PVE
::Storage
::config
();
2021 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2025 print "Requesting HA start for VM $vmid\n";
2027 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2028 PVE
::Tools
::run_command
($cmd);
2032 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2039 syslog
('info', "start VM $vmid: $upid\n");
2041 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2042 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
2046 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2050 __PACKAGE__-
>register_method({
2052 path
=> '{vmid}/status/stop',
2056 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2057 "is akin to pulling the power plug of a running computer and may damage the VM data",
2059 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2062 additionalProperties
=> 0,
2064 node
=> get_standard_option
('pve-node'),
2065 vmid
=> get_standard_option
('pve-vmid',
2066 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2067 skiplock
=> get_standard_option
('skiplock'),
2068 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2070 description
=> "Wait maximal timeout seconds.",
2076 description
=> "Do not deactivate storage volumes.",
2089 my $rpcenv = PVE
::RPCEnvironment
::get
();
2090 my $authuser = $rpcenv->get_user();
2092 my $node = extract_param
($param, 'node');
2093 my $vmid = extract_param
($param, 'vmid');
2095 my $skiplock = extract_param
($param, 'skiplock');
2096 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2097 if $skiplock && $authuser ne 'root@pam';
2099 my $keepActive = extract_param
($param, 'keepActive');
2100 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2101 if $keepActive && $authuser ne 'root@pam';
2103 my $migratedfrom = extract_param
($param, 'migratedfrom');
2104 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2105 if $migratedfrom && $authuser ne 'root@pam';
2108 my $storecfg = PVE
::Storage
::config
();
2110 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2115 print "Requesting HA stop for VM $vmid\n";
2117 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2118 PVE
::Tools
::run_command
($cmd);
2122 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2128 syslog
('info', "stop VM $vmid: $upid\n");
2130 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2131 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2135 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2139 __PACKAGE__-
>register_method({
2141 path
=> '{vmid}/status/reset',
2145 description
=> "Reset virtual machine.",
2147 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2150 additionalProperties
=> 0,
2152 node
=> get_standard_option
('pve-node'),
2153 vmid
=> get_standard_option
('pve-vmid',
2154 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2155 skiplock
=> get_standard_option
('skiplock'),
2164 my $rpcenv = PVE
::RPCEnvironment
::get
();
2166 my $authuser = $rpcenv->get_user();
2168 my $node = extract_param
($param, 'node');
2170 my $vmid = extract_param
($param, 'vmid');
2172 my $skiplock = extract_param
($param, 'skiplock');
2173 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2174 if $skiplock && $authuser ne 'root@pam';
2176 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2181 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2186 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2189 __PACKAGE__-
>register_method({
2190 name
=> 'vm_shutdown',
2191 path
=> '{vmid}/status/shutdown',
2195 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2196 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2198 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2201 additionalProperties
=> 0,
2203 node
=> get_standard_option
('pve-node'),
2204 vmid
=> get_standard_option
('pve-vmid',
2205 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2206 skiplock
=> get_standard_option
('skiplock'),
2208 description
=> "Wait maximal timeout seconds.",
2214 description
=> "Make sure the VM stops.",
2220 description
=> "Do not deactivate storage volumes.",
2233 my $rpcenv = PVE
::RPCEnvironment
::get
();
2234 my $authuser = $rpcenv->get_user();
2236 my $node = extract_param
($param, 'node');
2237 my $vmid = extract_param
($param, 'vmid');
2239 my $skiplock = extract_param
($param, 'skiplock');
2240 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2241 if $skiplock && $authuser ne 'root@pam';
2243 my $keepActive = extract_param
($param, 'keepActive');
2244 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2245 if $keepActive && $authuser ne 'root@pam';
2247 my $storecfg = PVE
::Storage
::config
();
2251 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2252 # otherwise, we will infer a shutdown command, but run into the timeout,
2253 # then when the vm is resumed, it will instantly shutdown
2255 # checking the qmp status here to get feedback to the gui/cli/api
2256 # and the status query should not take too long
2257 my $qmpstatus = eval {
2258 PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2262 if (!$err && $qmpstatus->{status
} eq "paused") {
2263 if ($param->{forceStop
}) {
2264 warn "VM is paused - stop instead of shutdown\n";
2267 die "VM is paused - cannot shutdown\n";
2271 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2273 my $timeout = $param->{timeout
} // 60;
2277 print "Requesting HA stop for VM $vmid\n";
2279 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2280 PVE
::Tools
::run_command
($cmd);
2284 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2291 syslog
('info', "shutdown VM $vmid: $upid\n");
2293 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2294 $shutdown, $param->{forceStop
}, $keepActive);
2298 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2302 __PACKAGE__-
>register_method({
2303 name
=> 'vm_reboot',
2304 path
=> '{vmid}/status/reboot',
2308 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2310 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2313 additionalProperties
=> 0,
2315 node
=> get_standard_option
('pve-node'),
2316 vmid
=> get_standard_option
('pve-vmid',
2317 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2319 description
=> "Wait maximal timeout seconds for the shutdown.",
2332 my $rpcenv = PVE
::RPCEnvironment
::get
();
2333 my $authuser = $rpcenv->get_user();
2335 my $node = extract_param
($param, 'node');
2336 my $vmid = extract_param
($param, 'vmid');
2338 my $qmpstatus = eval {
2339 PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2343 if (!$err && $qmpstatus->{status
} eq "paused") {
2344 die "VM is paused - cannot shutdown\n";
2347 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2352 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2353 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2357 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2360 __PACKAGE__-
>register_method({
2361 name
=> 'vm_suspend',
2362 path
=> '{vmid}/status/suspend',
2366 description
=> "Suspend virtual machine.",
2368 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2371 additionalProperties
=> 0,
2373 node
=> get_standard_option
('pve-node'),
2374 vmid
=> get_standard_option
('pve-vmid',
2375 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2376 skiplock
=> get_standard_option
('skiplock'),
2381 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2383 statestorage
=> get_standard_option
('pve-storage-id', {
2384 description
=> "The storage for the VM state",
2385 requires
=> 'todisk',
2387 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2397 my $rpcenv = PVE
::RPCEnvironment
::get
();
2398 my $authuser = $rpcenv->get_user();
2400 my $node = extract_param
($param, 'node');
2401 my $vmid = extract_param
($param, 'vmid');
2403 my $todisk = extract_param
($param, 'todisk') // 0;
2405 my $statestorage = extract_param
($param, 'statestorage');
2407 my $skiplock = extract_param
($param, 'skiplock');
2408 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2409 if $skiplock && $authuser ne 'root@pam';
2411 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2413 die "Cannot suspend HA managed VM to disk\n"
2414 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2419 syslog
('info', "suspend VM $vmid: $upid\n");
2421 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2426 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2427 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2430 __PACKAGE__-
>register_method({
2431 name
=> 'vm_resume',
2432 path
=> '{vmid}/status/resume',
2436 description
=> "Resume virtual machine.",
2438 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2441 additionalProperties
=> 0,
2443 node
=> get_standard_option
('pve-node'),
2444 vmid
=> get_standard_option
('pve-vmid',
2445 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2446 skiplock
=> get_standard_option
('skiplock'),
2447 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2457 my $rpcenv = PVE
::RPCEnvironment
::get
();
2459 my $authuser = $rpcenv->get_user();
2461 my $node = extract_param
($param, 'node');
2463 my $vmid = extract_param
($param, 'vmid');
2465 my $skiplock = extract_param
($param, 'skiplock');
2466 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2467 if $skiplock && $authuser ne 'root@pam';
2469 my $nocheck = extract_param
($param, 'nocheck');
2471 my $to_disk_suspended;
2473 PVE
::QemuConfig-
>lock_config($vmid, sub {
2474 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2475 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2479 die "VM $vmid not running\n"
2480 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2485 syslog
('info', "resume VM $vmid: $upid\n");
2487 if (!$to_disk_suspended) {
2488 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2490 my $storecfg = PVE
::Storage
::config
();
2491 PVE
::QemuServer
::vm_start
($storecfg, $vmid, undef, $skiplock);
2497 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2500 __PACKAGE__-
>register_method({
2501 name
=> 'vm_sendkey',
2502 path
=> '{vmid}/sendkey',
2506 description
=> "Send key event to virtual machine.",
2508 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2511 additionalProperties
=> 0,
2513 node
=> get_standard_option
('pve-node'),
2514 vmid
=> get_standard_option
('pve-vmid',
2515 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2516 skiplock
=> get_standard_option
('skiplock'),
2518 description
=> "The key (qemu monitor encoding).",
2523 returns
=> { type
=> 'null'},
2527 my $rpcenv = PVE
::RPCEnvironment
::get
();
2529 my $authuser = $rpcenv->get_user();
2531 my $node = extract_param
($param, 'node');
2533 my $vmid = extract_param
($param, 'vmid');
2535 my $skiplock = extract_param
($param, 'skiplock');
2536 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2537 if $skiplock && $authuser ne 'root@pam';
2539 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2544 __PACKAGE__-
>register_method({
2545 name
=> 'vm_feature',
2546 path
=> '{vmid}/feature',
2550 description
=> "Check if feature for virtual machine is available.",
2552 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2555 additionalProperties
=> 0,
2557 node
=> get_standard_option
('pve-node'),
2558 vmid
=> get_standard_option
('pve-vmid'),
2560 description
=> "Feature to check.",
2562 enum
=> [ 'snapshot', 'clone', 'copy' ],
2564 snapname
=> get_standard_option
('pve-snapshot-name', {
2572 hasFeature
=> { type
=> 'boolean' },
2575 items
=> { type
=> 'string' },
2582 my $node = extract_param
($param, 'node');
2584 my $vmid = extract_param
($param, 'vmid');
2586 my $snapname = extract_param
($param, 'snapname');
2588 my $feature = extract_param
($param, 'feature');
2590 my $running = PVE
::QemuServer
::check_running
($vmid);
2592 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2595 my $snap = $conf->{snapshots
}->{$snapname};
2596 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2599 my $storecfg = PVE
::Storage
::config
();
2601 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2602 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2605 hasFeature
=> $hasFeature,
2606 nodes
=> [ keys %$nodelist ],
2610 __PACKAGE__-
>register_method({
2612 path
=> '{vmid}/clone',
2616 description
=> "Create a copy of virtual machine/template.",
2618 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2619 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2620 "'Datastore.AllocateSpace' on any used storage.",
2623 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2625 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2626 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2631 additionalProperties
=> 0,
2633 node
=> get_standard_option
('pve-node'),
2634 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2635 newid
=> get_standard_option
('pve-vmid', {
2636 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2637 description
=> 'VMID for the clone.' }),
2640 type
=> 'string', format
=> 'dns-name',
2641 description
=> "Set a name for the new VM.",
2646 description
=> "Description for the new VM.",
2650 type
=> 'string', format
=> 'pve-poolid',
2651 description
=> "Add the new VM to the specified pool.",
2653 snapname
=> get_standard_option
('pve-snapshot-name', {
2656 storage
=> get_standard_option
('pve-storage-id', {
2657 description
=> "Target storage for full clone.",
2661 description
=> "Target format for file storage. Only valid for full clone.",
2664 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2669 description
=> "Create a full copy of all disks. This is always done when " .
2670 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2672 target
=> get_standard_option
('pve-node', {
2673 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2677 description
=> "Override I/O bandwidth limit (in KiB/s).",
2681 default => 'clone limit from datacenter or storage config',
2691 my $rpcenv = PVE
::RPCEnvironment
::get
();
2693 my $authuser = $rpcenv->get_user();
2695 my $node = extract_param
($param, 'node');
2697 my $vmid = extract_param
($param, 'vmid');
2699 my $newid = extract_param
($param, 'newid');
2701 my $pool = extract_param
($param, 'pool');
2703 if (defined($pool)) {
2704 $rpcenv->check_pool_exist($pool);
2707 my $snapname = extract_param
($param, 'snapname');
2709 my $storage = extract_param
($param, 'storage');
2711 my $format = extract_param
($param, 'format');
2713 my $target = extract_param
($param, 'target');
2715 my $localnode = PVE
::INotify
::nodename
();
2717 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2719 PVE
::Cluster
::check_node_exists
($target) if $target;
2721 my $storecfg = PVE
::Storage
::config
();
2724 # check if storage is enabled on local node
2725 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2727 # check if storage is available on target node
2728 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2729 # clone only works if target storage is shared
2730 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2731 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2735 PVE
::Cluster
::check_cfs_quorum
();
2737 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2739 # exclusive lock if VM is running - else shared lock is enough;
2740 my $shared_lock = $running ?
0 : 1;
2744 # do all tests after lock
2745 # we also try to do all tests before we fork the worker
2747 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2749 PVE
::QemuConfig-
>check_lock($conf);
2751 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2753 die "unexpected state change\n" if $verify_running != $running;
2755 die "snapshot '$snapname' does not exist\n"
2756 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2758 my $full = extract_param
($param, 'full');
2759 if (!defined($full)) {
2760 $full = !PVE
::QemuConfig-
>is_template($conf);
2763 die "parameter 'storage' not allowed for linked clones\n"
2764 if defined($storage) && !$full;
2766 die "parameter 'format' not allowed for linked clones\n"
2767 if defined($format) && !$full;
2769 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2771 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2773 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2775 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2777 die "unable to create VM $newid: config file already exists\n"
2780 my $newconf = { lock => 'clone' };
2785 foreach my $opt (keys %$oldconf) {
2786 my $value = $oldconf->{$opt};
2788 # do not copy snapshot related info
2789 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2790 $opt eq 'vmstate' || $opt eq 'snapstate';
2792 # no need to copy unused images, because VMID(owner) changes anyways
2793 next if $opt =~ m/^unused\d+$/;
2795 # always change MAC! address
2796 if ($opt =~ m/^net(\d+)$/) {
2797 my $net = PVE
::QemuServer
::parse_net
($value);
2798 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2799 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2800 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2801 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2802 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2803 die "unable to parse drive options for '$opt'\n" if !$drive;
2804 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2805 $newconf->{$opt} = $value; # simply copy configuration
2808 die "Full clone feature is not supported for drive '$opt'\n"
2809 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2810 $fullclone->{$opt} = 1;
2812 # not full means clone instead of copy
2813 die "Linked clone feature is not supported for drive '$opt'\n"
2814 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2816 $drives->{$opt} = $drive;
2817 push @$vollist, $drive->{file
};
2820 # copy everything else
2821 $newconf->{$opt} = $value;
2825 # auto generate a new uuid
2826 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2827 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2828 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2830 # auto generate a new vmgenid if the option was set
2831 if ($newconf->{vmgenid
}) {
2832 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2835 delete $newconf->{template
};
2837 if ($param->{name
}) {
2838 $newconf->{name
} = $param->{name
};
2840 if ($oldconf->{name
}) {
2841 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2843 $newconf->{name
} = "Copy-of-VM-$vmid";
2847 if ($param->{description
}) {
2848 $newconf->{description
} = $param->{description
};
2851 # create empty/temp config - this fails if VM already exists on other node
2852 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2857 my $newvollist = [];
2864 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2866 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2868 my $bwlimit = extract_param
($param, 'bwlimit');
2870 my $total_jobs = scalar(keys %{$drives});
2873 foreach my $opt (keys %$drives) {
2874 my $drive = $drives->{$opt};
2875 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2877 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
2878 my $storage_list = [ $src_sid ];
2879 push @$storage_list, $storage if defined($storage);
2880 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
2882 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2883 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2884 $jobs, $skipcomplete, $oldconf->{agent
}, $clonelimit);
2886 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2888 PVE
::QemuConfig-
>write_config($newid, $newconf);
2892 delete $newconf->{lock};
2894 # do not write pending changes
2895 if (my @changes = keys %{$newconf->{pending
}}) {
2896 my $pending = join(',', @changes);
2897 warn "found pending changes for '$pending', discarding for clone\n";
2898 delete $newconf->{pending
};
2901 PVE
::QemuConfig-
>write_config($newid, $newconf);
2904 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2905 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2906 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2908 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2909 die "Failed to move config to node '$target' - rename failed: $!\n"
2910 if !rename($conffile, $newconffile);
2913 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2918 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2920 sleep 1; # some storage like rbd need to wait before release volume - really?
2922 foreach my $volid (@$newvollist) {
2923 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2926 die "clone failed: $err";
2932 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2934 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2937 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2938 # Aquire exclusive lock lock for $newid
2939 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2944 __PACKAGE__-
>register_method({
2945 name
=> 'move_vm_disk',
2946 path
=> '{vmid}/move_disk',
2950 description
=> "Move volume to different storage.",
2952 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2954 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2955 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2959 additionalProperties
=> 0,
2961 node
=> get_standard_option
('pve-node'),
2962 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2965 description
=> "The disk you want to move.",
2966 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2968 storage
=> get_standard_option
('pve-storage-id', {
2969 description
=> "Target storage.",
2970 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2974 description
=> "Target Format.",
2975 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2980 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2986 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2991 description
=> "Override I/O bandwidth limit (in KiB/s).",
2995 default => 'move limit from datacenter or storage config',
3001 description
=> "the task ID.",
3006 my $rpcenv = PVE
::RPCEnvironment
::get
();
3008 my $authuser = $rpcenv->get_user();
3010 my $node = extract_param
($param, 'node');
3012 my $vmid = extract_param
($param, 'vmid');
3014 my $digest = extract_param
($param, 'digest');
3016 my $disk = extract_param
($param, 'disk');
3018 my $storeid = extract_param
($param, 'storage');
3020 my $format = extract_param
($param, 'format');
3022 my $storecfg = PVE
::Storage
::config
();
3024 my $updatefn = sub {
3026 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3028 PVE
::QemuConfig-
>check_lock($conf);
3030 die "checksum missmatch (file change by other user?)\n"
3031 if $digest && $digest ne $conf->{digest
};
3033 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3035 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3037 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
3039 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3042 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3043 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3047 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
3048 (!$format || !$oldfmt || $oldfmt eq $format);
3050 # this only checks snapshots because $disk is passed!
3051 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3052 die "you can't move a disk with snapshots and delete the source\n"
3053 if $snapshotted && $param->{delete};
3055 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3057 my $running = PVE
::QemuServer
::check_running
($vmid);
3059 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3063 my $newvollist = [];
3069 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3071 warn "moving disk with snapshots, snapshots will not be moved!\n"
3074 my $bwlimit = extract_param
($param, 'bwlimit');
3075 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3077 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3078 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit);
3080 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
3082 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3084 # convert moved disk to base if part of template
3085 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3086 if PVE
::QemuConfig-
>is_template($conf);
3088 PVE
::QemuConfig-
>write_config($vmid, $conf);
3090 if ($running && PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
} && PVE
::QemuServer
::qga_check_running
($vmid)) {
3091 eval { PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-fstrim"); };
3095 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3096 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3103 foreach my $volid (@$newvollist) {
3104 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3107 die "storage migration failed: $err";
3110 if ($param->{delete}) {
3112 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3113 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3119 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3122 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3125 my $check_vm_disks_local = sub {
3126 my ($storecfg, $vmconf, $vmid) = @_;
3128 my $local_disks = {};
3130 # add some more information to the disks e.g. cdrom
3131 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3132 my ($volid, $attr) = @_;
3134 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3136 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3137 return if $scfg->{shared
};
3139 # The shared attr here is just a special case where the vdisk
3140 # is marked as shared manually
3141 return if $attr->{shared
};
3142 return if $attr->{cdrom
} and $volid eq "none";
3144 if (exists $local_disks->{$volid}) {
3145 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3147 $local_disks->{$volid} = $attr;
3148 # ensure volid is present in case it's needed
3149 $local_disks->{$volid}->{volid
} = $volid;
3153 return $local_disks;
3156 __PACKAGE__-
>register_method({
3157 name
=> 'migrate_vm_precondition',
3158 path
=> '{vmid}/migrate',
3162 description
=> "Get preconditions for migration.",
3164 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3167 additionalProperties
=> 0,
3169 node
=> get_standard_option
('pve-node'),
3170 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3171 target
=> get_standard_option
('pve-node', {
3172 description
=> "Target node.",
3173 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3181 running
=> { type
=> 'boolean' },
3185 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3187 not_allowed_nodes
=> {
3190 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3194 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3196 local_resources
=> {
3198 description
=> "List local resources e.g. pci, usb"
3205 my $rpcenv = PVE
::RPCEnvironment
::get
();
3207 my $authuser = $rpcenv->get_user();
3209 PVE
::Cluster
::check_cfs_quorum
();
3213 my $vmid = extract_param
($param, 'vmid');
3214 my $target = extract_param
($param, 'target');
3215 my $localnode = PVE
::INotify
::nodename
();
3219 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3220 my $storecfg = PVE
::Storage
::config
();
3223 # try to detect errors early
3224 PVE
::QemuConfig-
>check_lock($vmconf);
3226 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3228 # if vm is not running, return target nodes where local storage is available
3229 # for offline migration
3230 if (!$res->{running
}) {
3231 $res->{allowed_nodes
} = [];
3232 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3233 delete $checked_nodes->{$localnode};
3235 foreach my $node (keys %$checked_nodes) {
3236 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3237 push @{$res->{allowed_nodes
}}, $node;
3241 $res->{not_allowed_nodes
} = $checked_nodes;
3245 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3246 $res->{local_disks
} = [ values %$local_disks ];;
3248 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3250 $res->{local_resources
} = $local_resources;
3257 __PACKAGE__-
>register_method({
3258 name
=> 'migrate_vm',
3259 path
=> '{vmid}/migrate',
3263 description
=> "Migrate virtual machine. Creates a new migration task.",
3265 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3268 additionalProperties
=> 0,
3270 node
=> get_standard_option
('pve-node'),
3271 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3272 target
=> get_standard_option
('pve-node', {
3273 description
=> "Target node.",
3274 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3278 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3283 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3288 enum
=> ['secure', 'insecure'],
3289 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3292 migration_network
=> {
3293 type
=> 'string', format
=> 'CIDR',
3294 description
=> "CIDR of the (sub) network that is used for migration.",
3297 "with-local-disks" => {
3299 description
=> "Enable live storage migration for local disk",
3302 targetstorage
=> get_standard_option
('pve-storage-id', {
3303 description
=> "Default target storage.",
3305 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3308 description
=> "Override I/O bandwidth limit (in KiB/s).",
3312 default => 'migrate limit from datacenter or storage config',
3318 description
=> "the task ID.",
3323 my $rpcenv = PVE
::RPCEnvironment
::get
();
3324 my $authuser = $rpcenv->get_user();
3326 my $target = extract_param
($param, 'target');
3328 my $localnode = PVE
::INotify
::nodename
();
3329 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3331 PVE
::Cluster
::check_cfs_quorum
();
3333 PVE
::Cluster
::check_node_exists
($target);
3335 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3337 my $vmid = extract_param
($param, 'vmid');
3339 raise_param_exc
({ force
=> "Only root may use this option." })
3340 if $param->{force
} && $authuser ne 'root@pam';
3342 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3343 if $param->{migration_type
} && $authuser ne 'root@pam';
3345 # allow root only until better network permissions are available
3346 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3347 if $param->{migration_network
} && $authuser ne 'root@pam';
3350 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3352 # try to detect errors early
3354 PVE
::QemuConfig-
>check_lock($conf);
3356 if (PVE
::QemuServer
::check_running
($vmid)) {
3357 die "can't migrate running VM without --online\n" if !$param->{online
};
3359 warn "VM isn't running. Doing offline migration instead\n." if $param->{online
};
3360 $param->{online
} = 0;
3363 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3364 if !$param->{online
} && $param->{targetstorage
};
3366 my $storecfg = PVE
::Storage
::config
();
3368 if( $param->{targetstorage
}) {
3369 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3371 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3374 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3379 print "Requesting HA migration for VM $vmid to node $target\n";
3381 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3382 PVE
::Tools
::run_command
($cmd);
3386 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3391 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3395 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3398 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3403 __PACKAGE__-
>register_method({
3405 path
=> '{vmid}/monitor',
3409 description
=> "Execute Qemu monitor commands.",
3411 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3412 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3415 additionalProperties
=> 0,
3417 node
=> get_standard_option
('pve-node'),
3418 vmid
=> get_standard_option
('pve-vmid'),
3421 description
=> "The monitor command.",
3425 returns
=> { type
=> 'string'},
3429 my $rpcenv = PVE
::RPCEnvironment
::get
();
3430 my $authuser = $rpcenv->get_user();
3433 my $command = shift;
3434 return $command =~ m/^\s*info(\s+|$)/
3435 || $command =~ m/^\s*help\s*$/;
3438 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3439 if !&$is_ro($param->{command
});
3441 my $vmid = $param->{vmid
};
3443 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3447 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3449 $res = "ERROR: $@" if $@;
3454 __PACKAGE__-
>register_method({
3455 name
=> 'resize_vm',
3456 path
=> '{vmid}/resize',
3460 description
=> "Extend volume size.",
3462 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3465 additionalProperties
=> 0,
3467 node
=> get_standard_option
('pve-node'),
3468 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3469 skiplock
=> get_standard_option
('skiplock'),
3472 description
=> "The disk you want to resize.",
3473 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3477 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3478 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.",
3482 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3488 returns
=> { type
=> 'null'},
3492 my $rpcenv = PVE
::RPCEnvironment
::get
();
3494 my $authuser = $rpcenv->get_user();
3496 my $node = extract_param
($param, 'node');
3498 my $vmid = extract_param
($param, 'vmid');
3500 my $digest = extract_param
($param, 'digest');
3502 my $disk = extract_param
($param, 'disk');
3504 my $sizestr = extract_param
($param, 'size');
3506 my $skiplock = extract_param
($param, 'skiplock');
3507 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3508 if $skiplock && $authuser ne 'root@pam';
3510 my $storecfg = PVE
::Storage
::config
();
3512 my $updatefn = sub {
3514 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3516 die "checksum missmatch (file change by other user?)\n"
3517 if $digest && $digest ne $conf->{digest
};
3518 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3520 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3522 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3524 my (undef, undef, undef, undef, undef, undef, $format) =
3525 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3527 die "can't resize volume: $disk if snapshot exists\n"
3528 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3530 my $volid = $drive->{file
};
3532 die "disk '$disk' has no associated volume\n" if !$volid;
3534 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3536 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3538 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3540 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3541 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3543 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3545 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3546 my ($ext, $newsize, $unit) = ($1, $2, $4);
3549 $newsize = $newsize * 1024;
3550 } elsif ($unit eq 'M') {
3551 $newsize = $newsize * 1024 * 1024;
3552 } elsif ($unit eq 'G') {
3553 $newsize = $newsize * 1024 * 1024 * 1024;
3554 } elsif ($unit eq 'T') {
3555 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3558 $newsize += $size if $ext;
3559 $newsize = int($newsize);
3561 die "shrinking disks is not supported\n" if $newsize < $size;
3563 return if $size == $newsize;
3565 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3567 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3569 $drive->{size
} = $newsize;
3570 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3572 PVE
::QemuConfig-
>write_config($vmid, $conf);
3575 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3579 __PACKAGE__-
>register_method({
3580 name
=> 'snapshot_list',
3581 path
=> '{vmid}/snapshot',
3583 description
=> "List all snapshots.",
3585 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3588 protected
=> 1, # qemu pid files are only readable by root
3590 additionalProperties
=> 0,
3592 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3593 node
=> get_standard_option
('pve-node'),
3602 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3606 description
=> "Snapshot includes RAM.",
3611 description
=> "Snapshot description.",
3615 description
=> "Snapshot creation time",
3617 renderer
=> 'timestamp',
3621 description
=> "Parent snapshot identifier.",
3627 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3632 my $vmid = $param->{vmid
};
3634 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3635 my $snaphash = $conf->{snapshots
} || {};
3639 foreach my $name (keys %$snaphash) {
3640 my $d = $snaphash->{$name};
3643 snaptime
=> $d->{snaptime
} || 0,
3644 vmstate
=> $d->{vmstate
} ?
1 : 0,
3645 description
=> $d->{description
} || '',
3647 $item->{parent
} = $d->{parent
} if $d->{parent
};
3648 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3652 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3655 digest
=> $conf->{digest
},
3656 running
=> $running,
3657 description
=> "You are here!",
3659 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3661 push @$res, $current;
3666 __PACKAGE__-
>register_method({
3668 path
=> '{vmid}/snapshot',
3672 description
=> "Snapshot a VM.",
3674 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3677 additionalProperties
=> 0,
3679 node
=> get_standard_option
('pve-node'),
3680 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3681 snapname
=> get_standard_option
('pve-snapshot-name'),
3685 description
=> "Save the vmstate",
3690 description
=> "A textual description or comment.",
3696 description
=> "the task ID.",
3701 my $rpcenv = PVE
::RPCEnvironment
::get
();
3703 my $authuser = $rpcenv->get_user();
3705 my $node = extract_param
($param, 'node');
3707 my $vmid = extract_param
($param, 'vmid');
3709 my $snapname = extract_param
($param, 'snapname');
3711 die "unable to use snapshot name 'current' (reserved name)\n"
3712 if $snapname eq 'current';
3715 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3716 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3717 $param->{description
});
3720 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3723 __PACKAGE__-
>register_method({
3724 name
=> 'snapshot_cmd_idx',
3725 path
=> '{vmid}/snapshot/{snapname}',
3732 additionalProperties
=> 0,
3734 vmid
=> get_standard_option
('pve-vmid'),
3735 node
=> get_standard_option
('pve-node'),
3736 snapname
=> get_standard_option
('pve-snapshot-name'),
3745 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3752 push @$res, { cmd
=> 'rollback' };
3753 push @$res, { cmd
=> 'config' };
3758 __PACKAGE__-
>register_method({
3759 name
=> 'update_snapshot_config',
3760 path
=> '{vmid}/snapshot/{snapname}/config',
3764 description
=> "Update snapshot metadata.",
3766 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3769 additionalProperties
=> 0,
3771 node
=> get_standard_option
('pve-node'),
3772 vmid
=> get_standard_option
('pve-vmid'),
3773 snapname
=> get_standard_option
('pve-snapshot-name'),
3777 description
=> "A textual description or comment.",
3781 returns
=> { type
=> 'null' },
3785 my $rpcenv = PVE
::RPCEnvironment
::get
();
3787 my $authuser = $rpcenv->get_user();
3789 my $vmid = extract_param
($param, 'vmid');
3791 my $snapname = extract_param
($param, 'snapname');
3793 return undef if !defined($param->{description
});
3795 my $updatefn = sub {
3797 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3799 PVE
::QemuConfig-
>check_lock($conf);
3801 my $snap = $conf->{snapshots
}->{$snapname};
3803 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3805 $snap->{description
} = $param->{description
} if defined($param->{description
});
3807 PVE
::QemuConfig-
>write_config($vmid, $conf);
3810 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3815 __PACKAGE__-
>register_method({
3816 name
=> 'get_snapshot_config',
3817 path
=> '{vmid}/snapshot/{snapname}/config',
3820 description
=> "Get snapshot configuration",
3822 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3825 additionalProperties
=> 0,
3827 node
=> get_standard_option
('pve-node'),
3828 vmid
=> get_standard_option
('pve-vmid'),
3829 snapname
=> get_standard_option
('pve-snapshot-name'),
3832 returns
=> { type
=> "object" },
3836 my $rpcenv = PVE
::RPCEnvironment
::get
();
3838 my $authuser = $rpcenv->get_user();
3840 my $vmid = extract_param
($param, 'vmid');
3842 my $snapname = extract_param
($param, 'snapname');
3844 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3846 my $snap = $conf->{snapshots
}->{$snapname};
3848 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3853 __PACKAGE__-
>register_method({
3855 path
=> '{vmid}/snapshot/{snapname}/rollback',
3859 description
=> "Rollback VM state to specified snapshot.",
3861 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3864 additionalProperties
=> 0,
3866 node
=> get_standard_option
('pve-node'),
3867 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3868 snapname
=> get_standard_option
('pve-snapshot-name'),
3873 description
=> "the task ID.",
3878 my $rpcenv = PVE
::RPCEnvironment
::get
();
3880 my $authuser = $rpcenv->get_user();
3882 my $node = extract_param
($param, 'node');
3884 my $vmid = extract_param
($param, 'vmid');
3886 my $snapname = extract_param
($param, 'snapname');
3889 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3890 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3894 # hold migration lock, this makes sure that nobody create replication snapshots
3895 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3898 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3901 __PACKAGE__-
>register_method({
3902 name
=> 'delsnapshot',
3903 path
=> '{vmid}/snapshot/{snapname}',
3907 description
=> "Delete a VM snapshot.",
3909 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3912 additionalProperties
=> 0,
3914 node
=> get_standard_option
('pve-node'),
3915 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3916 snapname
=> get_standard_option
('pve-snapshot-name'),
3920 description
=> "For removal from config file, even if removing disk snapshots fails.",
3926 description
=> "the task ID.",
3931 my $rpcenv = PVE
::RPCEnvironment
::get
();
3933 my $authuser = $rpcenv->get_user();
3935 my $node = extract_param
($param, 'node');
3937 my $vmid = extract_param
($param, 'vmid');
3939 my $snapname = extract_param
($param, 'snapname');
3942 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3943 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3946 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3949 __PACKAGE__-
>register_method({
3951 path
=> '{vmid}/template',
3955 description
=> "Create a Template.",
3957 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3958 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3961 additionalProperties
=> 0,
3963 node
=> get_standard_option
('pve-node'),
3964 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3968 description
=> "If you want to convert only 1 disk to base image.",
3969 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3974 returns
=> { type
=> 'null'},
3978 my $rpcenv = PVE
::RPCEnvironment
::get
();
3980 my $authuser = $rpcenv->get_user();
3982 my $node = extract_param
($param, 'node');
3984 my $vmid = extract_param
($param, 'vmid');
3986 my $disk = extract_param
($param, 'disk');
3988 my $updatefn = sub {
3990 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3992 PVE
::QemuConfig-
>check_lock($conf);
3994 die "unable to create template, because VM contains snapshots\n"
3995 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3997 die "you can't convert a template to a template\n"
3998 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4000 die "you can't convert a VM to template if VM is running\n"
4001 if PVE
::QemuServer
::check_running
($vmid);
4004 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4007 $conf->{template
} = 1;
4008 PVE
::QemuConfig-
>write_config($vmid, $conf);
4010 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4013 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4017 __PACKAGE__-
>register_method({
4018 name
=> 'cloudinit_generated_config_dump',
4019 path
=> '{vmid}/cloudinit/dump',
4022 description
=> "Get automatically generated cloudinit config.",
4024 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4027 additionalProperties
=> 0,
4029 node
=> get_standard_option
('pve-node'),
4030 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4032 description
=> 'Config type.',
4034 enum
=> ['user', 'network', 'meta'],
4044 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4046 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});