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
;
23 use PVE
::QemuServer
::Drive
;
24 use PVE
::QemuServer
::Monitor
qw(mon_cmd);
26 use PVE
::RPCEnvironment
;
27 use PVE
::AccessControl
;
31 use PVE
::API2
::Firewall
::VM
;
32 use PVE
::API2
::Qemu
::Agent
;
33 use PVE
::VZDump
::Plugin
;
34 use PVE
::DataCenterConfig
;
38 if (!$ENV{PVE_GENERATING_DOCS
}) {
39 require PVE
::HA
::Env
::PVE2
;
40 import PVE
::HA
::Env
::PVE2
;
41 require PVE
::HA
::Config
;
42 import PVE
::HA
::Config
;
46 use Data
::Dumper
; # fixme: remove
48 use base
qw(PVE::RESTHandler);
50 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.";
52 my $resolve_cdrom_alias = sub {
55 if (my $value = $param->{cdrom
}) {
56 $value .= ",media=cdrom" if $value !~ m/media=/;
57 $param->{ide2
} = $value;
58 delete $param->{cdrom
};
62 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
63 my $check_storage_access = sub {
64 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
66 PVE
::QemuServer
::foreach_drive
($settings, sub {
67 my ($ds, $drive) = @_;
69 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
71 my $volid = $drive->{file
};
72 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
74 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit' || (defined($volname) && $volname eq 'cloudinit'))) {
76 } elsif ($isCDROM && ($volid eq 'cdrom')) {
77 $rpcenv->check($authuser, "/", ['Sys.Console']);
78 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
79 my ($storeid, $size) = ($2 || $default_storage, $3);
80 die "no storage ID specified (and no default storage)\n" if !$storeid;
81 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
82 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
83 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
84 if !$scfg->{content
}->{images
};
86 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
90 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
91 if defined($settings->{vmstatestorage
});
94 my $check_storage_access_clone = sub {
95 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
99 PVE
::QemuServer
::foreach_drive
($conf, sub {
100 my ($ds, $drive) = @_;
102 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
104 my $volid = $drive->{file
};
106 return if !$volid || $volid eq 'none';
109 if ($volid eq 'cdrom') {
110 $rpcenv->check($authuser, "/", ['Sys.Console']);
112 # we simply allow access
113 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
114 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
115 $sharedvm = 0 if !$scfg->{shared
};
119 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
120 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
121 $sharedvm = 0 if !$scfg->{shared
};
123 $sid = $storage if $storage;
124 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
128 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
129 if defined($conf->{vmstatestorage
});
134 # Note: $pool is only needed when creating a VM, because pool permissions
135 # are automatically inherited if VM already exists inside a pool.
136 my $create_disks = sub {
137 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
144 my ($ds, $disk) = @_;
146 my $volid = $disk->{file
};
147 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
149 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
150 delete $disk->{size
};
151 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
152 } elsif (defined($volname) && $volname eq 'cloudinit') {
153 $storeid = $storeid // $default_storage;
154 die "no storage ID specified (and no default storage)\n" if !$storeid;
155 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
156 my $name = "vm-$vmid-cloudinit";
160 $fmt = $disk->{format
} // "qcow2";
163 $fmt = $disk->{format
} // "raw";
166 # Initial disk created with 4 MB and aligned to 4MB on regeneration
167 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
168 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
169 $disk->{file
} = $volid;
170 $disk->{media
} = 'cdrom';
171 push @$vollist, $volid;
172 delete $disk->{format
}; # no longer needed
173 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
174 } elsif ($volid =~ $NEW_DISK_RE) {
175 my ($storeid, $size) = ($2 || $default_storage, $3);
176 die "no storage ID specified (and no default storage)\n" if !$storeid;
177 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
178 my $fmt = $disk->{format
} || $defformat;
180 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
183 if ($ds eq 'efidisk0') {
184 ($volid, $size) = PVE
::QemuServer
::create_efidisk
($storecfg, $storeid, $vmid, $fmt, $arch);
186 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
188 push @$vollist, $volid;
189 $disk->{file
} = $volid;
190 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
191 delete $disk->{format
}; # no longer needed
192 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
195 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
197 my $volid_is_new = 1;
200 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
201 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
206 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
208 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
210 die "volume $volid does not exist\n" if !$size;
212 $disk->{size
} = $size;
215 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
219 eval { PVE
::QemuServer
::foreach_drive
($settings, $code); };
221 # free allocated images on error
223 syslog
('err', "VM $vmid creating disks failed");
224 foreach my $volid (@$vollist) {
225 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
231 # modify vm config if everything went well
232 foreach my $ds (keys %$res) {
233 $conf->{$ds} = $res->{$ds};
250 my $memoryoptions = {
256 my $hwtypeoptions = {
269 my $generaloptions = {
276 'migrate_downtime' => 1,
277 'migrate_speed' => 1,
290 my $vmpoweroptions = {
297 'vmstatestorage' => 1,
300 my $cloudinitoptions = {
310 my $check_vm_modify_config_perm = sub {
311 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
313 return 1 if $authuser eq 'root@pam';
315 foreach my $opt (@$key_list) {
316 # some checks (e.g., disk, serial port, usb) need to be done somewhere
317 # else, as there the permission can be value dependend
318 next if PVE
::QemuServer
::is_valid_drivename
($opt);
319 next if $opt eq 'cdrom';
320 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
323 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
324 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
325 } elsif ($memoryoptions->{$opt}) {
326 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
327 } elsif ($hwtypeoptions->{$opt}) {
328 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
329 } elsif ($generaloptions->{$opt}) {
330 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
331 # special case for startup since it changes host behaviour
332 if ($opt eq 'startup') {
333 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
335 } elsif ($vmpoweroptions->{$opt}) {
336 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
337 } elsif ($diskoptions->{$opt}) {
338 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
339 } elsif ($cloudinitoptions->{$opt} || ($opt =~ m/^(?:net|ipconfig)\d+$/)) {
340 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
341 } elsif ($opt eq 'vmstate') {
342 # the user needs Disk and PowerMgmt privileges to change the vmstate
343 # also needs privileges on the storage, that will be checked later
344 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt' ]);
346 # catches hostpci\d+, args, lock, etc.
347 # new options will be checked here
348 die "only root can set '$opt' config\n";
355 __PACKAGE__-
>register_method({
359 description
=> "Virtual machine index (per node).",
361 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
365 protected
=> 1, # qemu pid files are only readable by root
367 additionalProperties
=> 0,
369 node
=> get_standard_option
('pve-node'),
373 description
=> "Determine the full status of active VMs.",
381 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
383 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
388 my $rpcenv = PVE
::RPCEnvironment
::get
();
389 my $authuser = $rpcenv->get_user();
391 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
394 foreach my $vmid (keys %$vmstatus) {
395 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
397 my $data = $vmstatus->{$vmid};
404 my $parse_restore_archive = sub {
405 my ($storecfg, $archive) = @_;
407 my ($archive_storeid, $archive_volname) = PVE
::Storage
::parse_volume_id
($archive);
409 if (defined($archive_storeid)) {
410 my $scfg = PVE
::Storage
::storage_config
($storecfg, $archive_storeid);
411 if ($scfg->{type
} eq 'pbs') {
418 my $path = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
426 __PACKAGE__-
>register_method({
430 description
=> "Create or restore a virtual machine.",
432 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
433 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
434 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
435 user
=> 'all', # check inside
440 additionalProperties
=> 0,
441 properties
=> PVE
::QemuServer
::json_config_properties
(
443 node
=> get_standard_option
('pve-node'),
444 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
446 description
=> "The backup archive. Either the file system path to a .tar or .vma file (use '-' to pipe data from stdin) or a proxmox storage backup volume identifier.",
450 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
452 storage
=> get_standard_option
('pve-storage-id', {
453 description
=> "Default storage.",
455 completion
=> \
&PVE
::QemuServer
::complete_storage
,
460 description
=> "Allow to overwrite existing VM.",
461 requires
=> 'archive',
466 description
=> "Assign a unique random ethernet address.",
467 requires
=> 'archive',
471 type
=> 'string', format
=> 'pve-poolid',
472 description
=> "Add the VM to the specified pool.",
475 description
=> "Override I/O bandwidth limit (in KiB/s).",
479 default => 'restore limit from datacenter or storage config',
485 description
=> "Start VM after it was created successfully.",
495 my $rpcenv = PVE
::RPCEnvironment
::get
();
496 my $authuser = $rpcenv->get_user();
498 my $node = extract_param
($param, 'node');
499 my $vmid = extract_param
($param, 'vmid');
501 my $archive = extract_param
($param, 'archive');
502 my $is_restore = !!$archive;
504 my $bwlimit = extract_param
($param, 'bwlimit');
505 my $force = extract_param
($param, 'force');
506 my $pool = extract_param
($param, 'pool');
507 my $start_after_create = extract_param
($param, 'start');
508 my $storage = extract_param
($param, 'storage');
509 my $unique = extract_param
($param, 'unique');
511 if (defined(my $ssh_keys = $param->{sshkeys
})) {
512 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
513 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
516 PVE
::Cluster
::check_cfs_quorum
();
518 my $filename = PVE
::QemuConfig-
>config_file($vmid);
519 my $storecfg = PVE
::Storage
::config
();
521 if (defined($pool)) {
522 $rpcenv->check_pool_exist($pool);
525 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
526 if defined($storage);
528 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
530 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
532 } elsif ($archive && $force && (-f
$filename) &&
533 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
534 # OK: user has VM.Backup permissions, and want to restore an existing VM
540 &$resolve_cdrom_alias($param);
542 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
544 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
546 foreach my $opt (keys %$param) {
547 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
548 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
549 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
551 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
552 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
556 PVE
::QemuServer
::add_random_macs
($param);
558 my $keystr = join(' ', keys %$param);
559 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
561 if ($archive eq '-') {
562 die "pipe requires cli environment\n"
563 if $rpcenv->{type
} ne 'cli';
564 $archive = { type
=> 'pipe' };
566 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
568 $archive = $parse_restore_archive->($storecfg, $archive);
572 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
574 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
575 die "$emsg $@" if $@;
577 my $restorefn = sub {
578 my $conf = PVE
::QemuConfig-
>load_config($vmid);
580 PVE
::QemuConfig-
>check_protection($conf, $emsg);
582 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
585 my $restore_options = {
591 if ($archive->{type
} eq 'file' || $archive->{type
} eq 'pipe') {
592 PVE
::QemuServer
::restore_file_archive
($archive->{path
} // '-', $vmid, $authuser, $restore_options);
593 } elsif ($archive->{type
} eq 'pbs') {
594 PVE
::QemuServer
::restore_proxmox_backup_archive
($archive->{volid
}, $vmid, $authuser, $restore_options);
596 die "unknown backup archive type\n";
598 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
599 # Convert restored VM to template if backup was VM template
600 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
601 warn "Convert to template.\n";
602 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
606 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
609 # ensure no old replication state are exists
610 PVE
::ReplicationState
::delete_guest_states
($vmid);
612 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
614 if ($start_after_create) {
615 print "Execute autostart\n";
616 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
622 # ensure no old replication state are exists
623 PVE
::ReplicationState
::delete_guest_states
($vmid);
627 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
631 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
633 if (!$conf->{bootdisk
}) {
634 my $firstdisk = PVE
::QemuServer
::Drive
::resolve_first_disk
($conf);
635 $conf->{bootdisk
} = $firstdisk if $firstdisk;
638 # auto generate uuid if user did not specify smbios1 option
639 if (!$conf->{smbios1
}) {
640 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
643 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
644 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
647 PVE
::QemuConfig-
>write_config($vmid, $conf);
653 foreach my $volid (@$vollist) {
654 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
660 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
663 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
665 if ($start_after_create) {
666 print "Execute autostart\n";
667 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
672 my ($code, $worker_name);
674 $worker_name = 'qmrestore';
676 eval { $restorefn->() };
678 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
684 $worker_name = 'qmcreate';
686 eval { $createfn->() };
689 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
690 unlink($conffile) or die "failed to remove config file: $!\n";
698 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
701 __PACKAGE__-
>register_method({
706 description
=> "Directory index",
711 additionalProperties
=> 0,
713 node
=> get_standard_option
('pve-node'),
714 vmid
=> get_standard_option
('pve-vmid'),
722 subdir
=> { type
=> 'string' },
725 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
731 { subdir
=> 'config' },
732 { subdir
=> 'pending' },
733 { subdir
=> 'status' },
734 { subdir
=> 'unlink' },
735 { subdir
=> 'vncproxy' },
736 { subdir
=> 'termproxy' },
737 { subdir
=> 'migrate' },
738 { subdir
=> 'resize' },
739 { subdir
=> 'move' },
741 { subdir
=> 'rrddata' },
742 { subdir
=> 'monitor' },
743 { subdir
=> 'agent' },
744 { subdir
=> 'snapshot' },
745 { subdir
=> 'spiceproxy' },
746 { subdir
=> 'sendkey' },
747 { subdir
=> 'firewall' },
753 __PACKAGE__-
>register_method ({
754 subclass
=> "PVE::API2::Firewall::VM",
755 path
=> '{vmid}/firewall',
758 __PACKAGE__-
>register_method ({
759 subclass
=> "PVE::API2::Qemu::Agent",
760 path
=> '{vmid}/agent',
763 __PACKAGE__-
>register_method({
765 path
=> '{vmid}/rrd',
767 protected
=> 1, # fixme: can we avoid that?
769 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
771 description
=> "Read VM RRD statistics (returns PNG)",
773 additionalProperties
=> 0,
775 node
=> get_standard_option
('pve-node'),
776 vmid
=> get_standard_option
('pve-vmid'),
778 description
=> "Specify the time frame you are interested in.",
780 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
783 description
=> "The list of datasources you want to display.",
784 type
=> 'string', format
=> 'pve-configid-list',
787 description
=> "The RRD consolidation function",
789 enum
=> [ 'AVERAGE', 'MAX' ],
797 filename
=> { type
=> 'string' },
803 return PVE
::RRD
::create_rrd_graph
(
804 "pve2-vm/$param->{vmid}", $param->{timeframe
},
805 $param->{ds
}, $param->{cf
});
809 __PACKAGE__-
>register_method({
811 path
=> '{vmid}/rrddata',
813 protected
=> 1, # fixme: can we avoid that?
815 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
817 description
=> "Read VM RRD statistics",
819 additionalProperties
=> 0,
821 node
=> get_standard_option
('pve-node'),
822 vmid
=> get_standard_option
('pve-vmid'),
824 description
=> "Specify the time frame you are interested in.",
826 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
829 description
=> "The RRD consolidation function",
831 enum
=> [ 'AVERAGE', 'MAX' ],
846 return PVE
::RRD
::create_rrd_data
(
847 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
851 __PACKAGE__-
>register_method({
853 path
=> '{vmid}/config',
856 description
=> "Get the virtual machine configuration with pending configuration " .
857 "changes applied. Set the 'current' parameter to get the current configuration instead.",
859 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
862 additionalProperties
=> 0,
864 node
=> get_standard_option
('pve-node'),
865 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
867 description
=> "Get current values (instead of pending values).",
872 snapshot
=> get_standard_option
('pve-snapshot-name', {
873 description
=> "Fetch config values from given snapshot.",
876 my ($cmd, $pname, $cur, $args) = @_;
877 PVE
::QemuConfig-
>snapshot_list($args->[0]);
883 description
=> "The VM configuration.",
885 properties
=> PVE
::QemuServer
::json_config_properties
({
888 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
895 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
896 current
=> "cannot use 'snapshot' parameter with 'current'"})
897 if ($param->{snapshot
} && $param->{current
});
900 if ($param->{snapshot
}) {
901 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
903 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
905 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
910 __PACKAGE__-
>register_method({
911 name
=> 'vm_pending',
912 path
=> '{vmid}/pending',
915 description
=> "Get the virtual machine configuration with both current and pending values.",
917 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
920 additionalProperties
=> 0,
922 node
=> get_standard_option
('pve-node'),
923 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
932 description
=> "Configuration option name.",
936 description
=> "Current value.",
941 description
=> "Pending value.",
946 description
=> "Indicates a pending delete request if present and not 0. " .
947 "The value 2 indicates a force-delete request.",
959 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
961 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
963 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
964 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
966 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
969 # POST/PUT {vmid}/config implementation
971 # The original API used PUT (idempotent) an we assumed that all operations
972 # are fast. But it turned out that almost any configuration change can
973 # involve hot-plug actions, or disk alloc/free. Such actions can take long
974 # time to complete and have side effects (not idempotent).
976 # The new implementation uses POST and forks a worker process. We added
977 # a new option 'background_delay'. If specified we wait up to
978 # 'background_delay' second for the worker task to complete. It returns null
979 # if the task is finished within that time, else we return the UPID.
981 my $update_vm_api = sub {
982 my ($param, $sync) = @_;
984 my $rpcenv = PVE
::RPCEnvironment
::get
();
986 my $authuser = $rpcenv->get_user();
988 my $node = extract_param
($param, 'node');
990 my $vmid = extract_param
($param, 'vmid');
992 my $digest = extract_param
($param, 'digest');
994 my $background_delay = extract_param
($param, 'background_delay');
996 if (defined(my $cipassword = $param->{cipassword
})) {
997 # Same logic as in cloud-init (but with the regex fixed...)
998 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
999 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1002 my @paramarr = (); # used for log message
1003 foreach my $key (sort keys %$param) {
1004 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1005 push @paramarr, "-$key", $value;
1008 my $skiplock = extract_param
($param, 'skiplock');
1009 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1010 if $skiplock && $authuser ne 'root@pam';
1012 my $delete_str = extract_param
($param, 'delete');
1014 my $revert_str = extract_param
($param, 'revert');
1016 my $force = extract_param
($param, 'force');
1018 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1019 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1020 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1023 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1025 my $storecfg = PVE
::Storage
::config
();
1027 my $defaults = PVE
::QemuServer
::load_defaults
();
1029 &$resolve_cdrom_alias($param);
1031 # now try to verify all parameters
1034 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1035 if (!PVE
::QemuServer
::option_exists
($opt)) {
1036 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1039 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1040 "-revert $opt' at the same time" })
1041 if defined($param->{$opt});
1043 $revert->{$opt} = 1;
1047 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1048 $opt = 'ide2' if $opt eq 'cdrom';
1050 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1051 "-delete $opt' at the same time" })
1052 if defined($param->{$opt});
1054 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1055 "-revert $opt' at the same time" })
1058 if (!PVE
::QemuServer
::option_exists
($opt)) {
1059 raise_param_exc
({ delete => "unknown option '$opt'" });
1065 my $repl_conf = PVE
::ReplicationConfig-
>new();
1066 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1067 my $check_replication = sub {
1069 return if !$is_replicated;
1070 my $volid = $drive->{file
};
1071 return if !$volid || !($drive->{replicate
}//1);
1072 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1074 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1075 return if $volname eq 'cloudinit';
1078 if ($volid =~ $NEW_DISK_RE) {
1080 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1082 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1084 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1085 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1086 return if $scfg->{shared
};
1087 die "cannot add non-replicatable volume to a replicated VM\n";
1090 foreach my $opt (keys %$param) {
1091 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1092 # cleanup drive path
1093 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1094 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1095 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1096 $check_replication->($drive);
1097 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
1098 } elsif ($opt =~ m/^net(\d+)$/) {
1100 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1101 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1102 } elsif ($opt eq 'vmgenid') {
1103 if ($param->{$opt} eq '1') {
1104 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1106 } elsif ($opt eq 'hookscript') {
1107 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1108 raise_param_exc
({ $opt => $@ }) if $@;
1112 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1114 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1116 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1118 my $updatefn = sub {
1120 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1122 die "checksum missmatch (file change by other user?)\n"
1123 if $digest && $digest ne $conf->{digest
};
1125 # FIXME: 'suspended' lock should probabyl be a state or "weak" lock?!
1126 if (scalar(@delete) && grep { $_ eq 'vmstate'} @delete) {
1127 if (defined($conf->{lock}) && $conf->{lock} eq 'suspended') {
1128 delete $conf->{lock}; # for check lock check, not written out
1129 push @delete, 'lock'; # this is the real deal to write it out
1131 push @delete, 'runningmachine' if $conf->{runningmachine
};
1134 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1136 foreach my $opt (keys %$revert) {
1137 if (defined($conf->{$opt})) {
1138 $param->{$opt} = $conf->{$opt};
1139 } elsif (defined($conf->{pending
}->{$opt})) {
1144 if ($param->{memory
} || defined($param->{balloon
})) {
1145 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1146 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1148 die "balloon value too large (must be smaller than assigned memory)\n"
1149 if $balloon && $balloon > $maxmem;
1152 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1156 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1158 # write updates to pending section
1160 my $modified = {}; # record what $option we modify
1162 foreach my $opt (@delete) {
1163 $modified->{$opt} = 1;
1164 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1166 # value of what we want to delete, independent if pending or not
1167 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1168 if (!defined($val)) {
1169 warn "cannot delete '$opt' - not set in current configuration!\n";
1170 $modified->{$opt} = 0;
1173 my $is_pending_val = defined($conf->{pending
}->{$opt});
1174 delete $conf->{pending
}->{$opt};
1176 if ($opt =~ m/^unused/) {
1177 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1178 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1179 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1180 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1181 delete $conf->{$opt};
1182 PVE
::QemuConfig-
>write_config($vmid, $conf);
1184 } elsif ($opt eq 'vmstate') {
1185 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1186 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1187 delete $conf->{$opt};
1188 PVE
::QemuConfig-
>write_config($vmid, $conf);
1190 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1191 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1192 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1193 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1195 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1196 PVE
::QemuConfig-
>write_config($vmid, $conf);
1197 } elsif ($opt =~ m/^serial\d+$/) {
1198 if ($val eq 'socket') {
1199 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1200 } elsif ($authuser ne 'root@pam') {
1201 die "only root can delete '$opt' config for real devices\n";
1203 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1204 PVE
::QemuConfig-
>write_config($vmid, $conf);
1205 } elsif ($opt =~ m/^usb\d+$/) {
1206 if ($val =~ m/spice/) {
1207 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1208 } elsif ($authuser ne 'root@pam') {
1209 die "only root can delete '$opt' config for real devices\n";
1211 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1212 PVE
::QemuConfig-
>write_config($vmid, $conf);
1214 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1215 PVE
::QemuConfig-
>write_config($vmid, $conf);
1219 foreach my $opt (keys %$param) { # add/change
1220 $modified->{$opt} = 1;
1221 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1222 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1224 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1226 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1227 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1228 # FIXME: cloudinit: CDROM or Disk?
1229 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1230 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1232 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1234 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1235 if defined($conf->{pending
}->{$opt});
1237 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1238 } elsif ($opt =~ m/^serial\d+/) {
1239 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1240 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1241 } elsif ($authuser ne 'root@pam') {
1242 die "only root can modify '$opt' config for real devices\n";
1244 $conf->{pending
}->{$opt} = $param->{$opt};
1245 } elsif ($opt =~ m/^usb\d+/) {
1246 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1247 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1248 } elsif ($authuser ne 'root@pam') {
1249 die "only root can modify '$opt' config for real devices\n";
1251 $conf->{pending
}->{$opt} = $param->{$opt};
1253 $conf->{pending
}->{$opt} = $param->{$opt};
1255 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1256 PVE
::QemuConfig-
>write_config($vmid, $conf);
1259 # remove pending changes when nothing changed
1260 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1261 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1262 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1264 return if !scalar(keys %{$conf->{pending
}});
1266 my $running = PVE
::QemuServer
::check_running
($vmid);
1268 # apply pending changes
1270 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1274 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1276 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running, $errors);
1278 raise_param_exc
($errors) if scalar(keys %$errors);
1287 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1289 if ($background_delay) {
1291 # Note: It would be better to do that in the Event based HTTPServer
1292 # to avoid blocking call to sleep.
1294 my $end_time = time() + $background_delay;
1296 my $task = PVE
::Tools
::upid_decode
($upid);
1299 while (time() < $end_time) {
1300 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1302 sleep(1); # this gets interrupted when child process ends
1306 my $status = PVE
::Tools
::upid_read_status
($upid);
1307 return undef if $status eq 'OK';
1316 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1319 my $vm_config_perm_list = [
1324 'VM.Config.Network',
1326 'VM.Config.Options',
1329 __PACKAGE__-
>register_method({
1330 name
=> 'update_vm_async',
1331 path
=> '{vmid}/config',
1335 description
=> "Set virtual machine options (asynchrounous API).",
1337 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1340 additionalProperties
=> 0,
1341 properties
=> PVE
::QemuServer
::json_config_properties
(
1343 node
=> get_standard_option
('pve-node'),
1344 vmid
=> get_standard_option
('pve-vmid'),
1345 skiplock
=> get_standard_option
('skiplock'),
1347 type
=> 'string', format
=> 'pve-configid-list',
1348 description
=> "A list of settings you want to delete.",
1352 type
=> 'string', format
=> 'pve-configid-list',
1353 description
=> "Revert a pending change.",
1358 description
=> $opt_force_description,
1360 requires
=> 'delete',
1364 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1368 background_delay
=> {
1370 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1381 code
=> $update_vm_api,
1384 __PACKAGE__-
>register_method({
1385 name
=> 'update_vm',
1386 path
=> '{vmid}/config',
1390 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1392 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1395 additionalProperties
=> 0,
1396 properties
=> PVE
::QemuServer
::json_config_properties
(
1398 node
=> get_standard_option
('pve-node'),
1399 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1400 skiplock
=> get_standard_option
('skiplock'),
1402 type
=> 'string', format
=> 'pve-configid-list',
1403 description
=> "A list of settings you want to delete.",
1407 type
=> 'string', format
=> 'pve-configid-list',
1408 description
=> "Revert a pending change.",
1413 description
=> $opt_force_description,
1415 requires
=> 'delete',
1419 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1425 returns
=> { type
=> 'null' },
1428 &$update_vm_api($param, 1);
1433 __PACKAGE__-
>register_method({
1434 name
=> 'destroy_vm',
1439 description
=> "Destroy the vm (also delete all used/owned volumes).",
1441 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1444 additionalProperties
=> 0,
1446 node
=> get_standard_option
('pve-node'),
1447 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1448 skiplock
=> get_standard_option
('skiplock'),
1451 description
=> "Remove vmid from backup cron jobs.",
1462 my $rpcenv = PVE
::RPCEnvironment
::get
();
1463 my $authuser = $rpcenv->get_user();
1464 my $vmid = $param->{vmid
};
1466 my $skiplock = $param->{skiplock
};
1467 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1468 if $skiplock && $authuser ne 'root@pam';
1471 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1472 my $storecfg = PVE
::Storage
::config
();
1473 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1474 die "unable to remove VM $vmid - used in HA resources\n"
1475 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1477 if (!$param->{purge
}) {
1478 # don't allow destroy if with replication jobs but no purge param
1479 my $repl_conf = PVE
::ReplicationConfig-
>new();
1480 $repl_conf->check_for_existing_jobs($vmid);
1483 # early tests (repeat after locking)
1484 die "VM $vmid is running - destroy failed\n"
1485 if PVE
::QemuServer
::check_running
($vmid);
1490 syslog
('info', "destroy VM $vmid: $upid\n");
1491 PVE
::QemuConfig-
>lock_config($vmid, sub {
1492 die "VM $vmid is running - destroy failed\n"
1493 if (PVE
::QemuServer
::check_running
($vmid));
1495 PVE
::QemuServer
::destroy_vm
($storecfg, $vmid, $skiplock, { lock => 'destroyed' });
1497 PVE
::AccessControl
::remove_vm_access
($vmid);
1498 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1499 if ($param->{purge
}) {
1500 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1501 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1504 # only now remove the zombie config, else we can have reuse race
1505 PVE
::QemuConfig-
>destroy_config($vmid);
1509 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1512 __PACKAGE__-
>register_method({
1514 path
=> '{vmid}/unlink',
1518 description
=> "Unlink/delete disk images.",
1520 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1523 additionalProperties
=> 0,
1525 node
=> get_standard_option
('pve-node'),
1526 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1528 type
=> 'string', format
=> 'pve-configid-list',
1529 description
=> "A list of disk IDs you want to delete.",
1533 description
=> $opt_force_description,
1538 returns
=> { type
=> 'null'},
1542 $param->{delete} = extract_param
($param, 'idlist');
1544 __PACKAGE__-
>update_vm($param);
1551 __PACKAGE__-
>register_method({
1553 path
=> '{vmid}/vncproxy',
1557 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1559 description
=> "Creates a TCP VNC proxy connections.",
1561 additionalProperties
=> 0,
1563 node
=> get_standard_option
('pve-node'),
1564 vmid
=> get_standard_option
('pve-vmid'),
1568 description
=> "starts websockify instead of vncproxy",
1573 additionalProperties
=> 0,
1575 user
=> { type
=> 'string' },
1576 ticket
=> { type
=> 'string' },
1577 cert
=> { type
=> 'string' },
1578 port
=> { type
=> 'integer' },
1579 upid
=> { type
=> 'string' },
1585 my $rpcenv = PVE
::RPCEnvironment
::get
();
1587 my $authuser = $rpcenv->get_user();
1589 my $vmid = $param->{vmid
};
1590 my $node = $param->{node
};
1591 my $websocket = $param->{websocket
};
1593 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1594 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1596 my $authpath = "/vms/$vmid";
1598 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1600 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1606 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1607 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1608 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1609 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1610 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1612 $family = PVE
::Tools
::get_host_address_family
($node);
1615 my $port = PVE
::Tools
::next_vnc_port
($family);
1622 syslog
('info', "starting vnc proxy $upid\n");
1628 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1630 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1631 '-timeout', $timeout, '-authpath', $authpath,
1632 '-perm', 'Sys.Console'];
1634 if ($param->{websocket
}) {
1635 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1636 push @$cmd, '-notls', '-listen', 'localhost';
1639 push @$cmd, '-c', @$remcmd, @$termcmd;
1641 PVE
::Tools
::run_command
($cmd);
1645 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1647 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1649 my $sock = IO
::Socket
::IP-
>new(
1654 GetAddrInfoFlags
=> 0,
1655 ) or die "failed to create socket: $!\n";
1656 # Inside the worker we shouldn't have any previous alarms
1657 # running anyway...:
1659 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1661 accept(my $cli, $sock) or die "connection failed: $!\n";
1664 if (PVE
::Tools
::run_command
($cmd,
1665 output
=> '>&'.fileno($cli),
1666 input
=> '<&'.fileno($cli),
1669 die "Failed to run vncproxy.\n";
1676 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1678 PVE
::Tools
::wait_for_vnc_port
($port);
1689 __PACKAGE__-
>register_method({
1690 name
=> 'termproxy',
1691 path
=> '{vmid}/termproxy',
1695 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1697 description
=> "Creates a TCP proxy connections.",
1699 additionalProperties
=> 0,
1701 node
=> get_standard_option
('pve-node'),
1702 vmid
=> get_standard_option
('pve-vmid'),
1706 enum
=> [qw(serial0 serial1 serial2 serial3)],
1707 description
=> "opens a serial terminal (defaults to display)",
1712 additionalProperties
=> 0,
1714 user
=> { type
=> 'string' },
1715 ticket
=> { type
=> 'string' },
1716 port
=> { type
=> 'integer' },
1717 upid
=> { type
=> 'string' },
1723 my $rpcenv = PVE
::RPCEnvironment
::get
();
1725 my $authuser = $rpcenv->get_user();
1727 my $vmid = $param->{vmid
};
1728 my $node = $param->{node
};
1729 my $serial = $param->{serial
};
1731 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1733 if (!defined($serial)) {
1734 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1735 $serial = $conf->{vga
};
1739 my $authpath = "/vms/$vmid";
1741 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1746 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1747 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1748 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1749 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1750 push @$remcmd, '--';
1752 $family = PVE
::Tools
::get_host_address_family
($node);
1755 my $port = PVE
::Tools
::next_vnc_port
($family);
1757 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1758 push @$termcmd, '-iface', $serial if $serial;
1763 syslog
('info', "starting qemu termproxy $upid\n");
1765 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1766 '--perm', 'VM.Console', '--'];
1767 push @$cmd, @$remcmd, @$termcmd;
1769 PVE
::Tools
::run_command
($cmd);
1772 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1774 PVE
::Tools
::wait_for_vnc_port
($port);
1784 __PACKAGE__-
>register_method({
1785 name
=> 'vncwebsocket',
1786 path
=> '{vmid}/vncwebsocket',
1789 description
=> "You also need to pass a valid ticket (vncticket).",
1790 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1792 description
=> "Opens a weksocket for VNC traffic.",
1794 additionalProperties
=> 0,
1796 node
=> get_standard_option
('pve-node'),
1797 vmid
=> get_standard_option
('pve-vmid'),
1799 description
=> "Ticket from previous call to vncproxy.",
1804 description
=> "Port number returned by previous vncproxy call.",
1814 port
=> { type
=> 'string' },
1820 my $rpcenv = PVE
::RPCEnvironment
::get
();
1822 my $authuser = $rpcenv->get_user();
1824 my $vmid = $param->{vmid
};
1825 my $node = $param->{node
};
1827 my $authpath = "/vms/$vmid";
1829 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1831 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1833 # Note: VNC ports are acessible from outside, so we do not gain any
1834 # security if we verify that $param->{port} belongs to VM $vmid. This
1835 # check is done by verifying the VNC ticket (inside VNC protocol).
1837 my $port = $param->{port
};
1839 return { port
=> $port };
1842 __PACKAGE__-
>register_method({
1843 name
=> 'spiceproxy',
1844 path
=> '{vmid}/spiceproxy',
1849 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1851 description
=> "Returns a SPICE configuration to connect to the VM.",
1853 additionalProperties
=> 0,
1855 node
=> get_standard_option
('pve-node'),
1856 vmid
=> get_standard_option
('pve-vmid'),
1857 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1860 returns
=> get_standard_option
('remote-viewer-config'),
1864 my $rpcenv = PVE
::RPCEnvironment
::get
();
1866 my $authuser = $rpcenv->get_user();
1868 my $vmid = $param->{vmid
};
1869 my $node = $param->{node
};
1870 my $proxy = $param->{proxy
};
1872 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1873 my $title = "VM $vmid";
1874 $title .= " - ". $conf->{name
} if $conf->{name
};
1876 my $port = PVE
::QemuServer
::spice_port
($vmid);
1878 my ($ticket, undef, $remote_viewer_config) =
1879 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1881 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1882 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1884 return $remote_viewer_config;
1887 __PACKAGE__-
>register_method({
1889 path
=> '{vmid}/status',
1892 description
=> "Directory index",
1897 additionalProperties
=> 0,
1899 node
=> get_standard_option
('pve-node'),
1900 vmid
=> get_standard_option
('pve-vmid'),
1908 subdir
=> { type
=> 'string' },
1911 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1917 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1920 { subdir
=> 'current' },
1921 { subdir
=> 'start' },
1922 { subdir
=> 'stop' },
1923 { subdir
=> 'reset' },
1924 { subdir
=> 'shutdown' },
1925 { subdir
=> 'suspend' },
1926 { subdir
=> 'reboot' },
1932 __PACKAGE__-
>register_method({
1933 name
=> 'vm_status',
1934 path
=> '{vmid}/status/current',
1937 protected
=> 1, # qemu pid files are only readable by root
1938 description
=> "Get virtual machine status.",
1940 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1943 additionalProperties
=> 0,
1945 node
=> get_standard_option
('pve-node'),
1946 vmid
=> get_standard_option
('pve-vmid'),
1952 %$PVE::QemuServer
::vmstatus_return_properties
,
1954 description
=> "HA manager service status.",
1958 description
=> "Qemu VGA configuration supports spice.",
1963 description
=> "Qemu GuestAgent enabled in config.",
1973 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1975 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1976 my $status = $vmstatus->{$param->{vmid
}};
1978 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1980 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1981 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1986 __PACKAGE__-
>register_method({
1988 path
=> '{vmid}/status/start',
1992 description
=> "Start virtual machine.",
1994 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1997 additionalProperties
=> 0,
1999 node
=> get_standard_option
('pve-node'),
2000 vmid
=> get_standard_option
('pve-vmid',
2001 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2002 skiplock
=> get_standard_option
('skiplock'),
2003 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2004 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2007 enum
=> ['secure', 'insecure'],
2008 description
=> "Migration traffic is encrypted using an SSH " .
2009 "tunnel by default. On secure, completely private networks " .
2010 "this can be disabled to increase performance.",
2013 migration_network
=> {
2014 type
=> 'string', format
=> 'CIDR',
2015 description
=> "CIDR of the (sub) network that is used for migration.",
2018 machine
=> get_standard_option
('pve-qemu-machine'),
2020 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
2025 description
=> "Wait maximal timeout seconds.",
2028 default => 'max(30, vm memory in GiB)',
2039 my $rpcenv = PVE
::RPCEnvironment
::get
();
2040 my $authuser = $rpcenv->get_user();
2042 my $node = extract_param
($param, 'node');
2043 my $vmid = extract_param
($param, 'vmid');
2044 my $timeout = extract_param
($param, 'timeout');
2046 my $machine = extract_param
($param, 'machine');
2048 my $get_root_param = sub {
2049 my $value = extract_param
($param, $_[0]);
2050 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2051 if $value && $authuser ne 'root@pam';
2055 my $stateuri = $get_root_param->('stateuri');
2056 my $skiplock = $get_root_param->('skiplock');
2057 my $migratedfrom = $get_root_param->('migratedfrom');
2058 my $migration_type = $get_root_param->('migration_type');
2059 my $migration_network = $get_root_param->('migration_network');
2060 my $targetstorage = $get_root_param->('targetstorage');
2062 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2063 if $targetstorage && !$migratedfrom;
2065 # read spice ticket from STDIN
2067 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2068 if (defined(my $line = <STDIN
>)) {
2070 $spice_ticket = $line;
2074 PVE
::Cluster
::check_cfs_quorum
();
2076 my $storecfg = PVE
::Storage
::config
();
2078 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2082 print "Requesting HA start for VM $vmid\n";
2084 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2085 PVE
::Tools
::run_command
($cmd);
2089 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2096 syslog
('info', "start VM $vmid: $upid\n");
2098 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef, $machine,
2099 $spice_ticket, $migration_network, $migration_type, $targetstorage, $timeout);
2103 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2107 __PACKAGE__-
>register_method({
2109 path
=> '{vmid}/status/stop',
2113 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2114 "is akin to pulling the power plug of a running computer and may damage the VM data",
2116 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2119 additionalProperties
=> 0,
2121 node
=> get_standard_option
('pve-node'),
2122 vmid
=> get_standard_option
('pve-vmid',
2123 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2124 skiplock
=> get_standard_option
('skiplock'),
2125 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2127 description
=> "Wait maximal timeout seconds.",
2133 description
=> "Do not deactivate storage volumes.",
2146 my $rpcenv = PVE
::RPCEnvironment
::get
();
2147 my $authuser = $rpcenv->get_user();
2149 my $node = extract_param
($param, 'node');
2150 my $vmid = extract_param
($param, 'vmid');
2152 my $skiplock = extract_param
($param, 'skiplock');
2153 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2154 if $skiplock && $authuser ne 'root@pam';
2156 my $keepActive = extract_param
($param, 'keepActive');
2157 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2158 if $keepActive && $authuser ne 'root@pam';
2160 my $migratedfrom = extract_param
($param, 'migratedfrom');
2161 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2162 if $migratedfrom && $authuser ne 'root@pam';
2165 my $storecfg = PVE
::Storage
::config
();
2167 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2172 print "Requesting HA stop for VM $vmid\n";
2174 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2175 PVE
::Tools
::run_command
($cmd);
2179 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2185 syslog
('info', "stop VM $vmid: $upid\n");
2187 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2188 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2192 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2196 __PACKAGE__-
>register_method({
2198 path
=> '{vmid}/status/reset',
2202 description
=> "Reset virtual machine.",
2204 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2207 additionalProperties
=> 0,
2209 node
=> get_standard_option
('pve-node'),
2210 vmid
=> get_standard_option
('pve-vmid',
2211 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2212 skiplock
=> get_standard_option
('skiplock'),
2221 my $rpcenv = PVE
::RPCEnvironment
::get
();
2223 my $authuser = $rpcenv->get_user();
2225 my $node = extract_param
($param, 'node');
2227 my $vmid = extract_param
($param, 'vmid');
2229 my $skiplock = extract_param
($param, 'skiplock');
2230 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2231 if $skiplock && $authuser ne 'root@pam';
2233 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2238 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2243 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2246 __PACKAGE__-
>register_method({
2247 name
=> 'vm_shutdown',
2248 path
=> '{vmid}/status/shutdown',
2252 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2253 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2255 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2258 additionalProperties
=> 0,
2260 node
=> get_standard_option
('pve-node'),
2261 vmid
=> get_standard_option
('pve-vmid',
2262 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2263 skiplock
=> get_standard_option
('skiplock'),
2265 description
=> "Wait maximal timeout seconds.",
2271 description
=> "Make sure the VM stops.",
2277 description
=> "Do not deactivate storage volumes.",
2290 my $rpcenv = PVE
::RPCEnvironment
::get
();
2291 my $authuser = $rpcenv->get_user();
2293 my $node = extract_param
($param, 'node');
2294 my $vmid = extract_param
($param, 'vmid');
2296 my $skiplock = extract_param
($param, 'skiplock');
2297 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2298 if $skiplock && $authuser ne 'root@pam';
2300 my $keepActive = extract_param
($param, 'keepActive');
2301 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2302 if $keepActive && $authuser ne 'root@pam';
2304 my $storecfg = PVE
::Storage
::config
();
2308 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2309 # otherwise, we will infer a shutdown command, but run into the timeout,
2310 # then when the vm is resumed, it will instantly shutdown
2312 # checking the qmp status here to get feedback to the gui/cli/api
2313 # and the status query should not take too long
2314 my $qmpstatus = eval {
2315 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2316 mon_cmd
($vmid, "query-status");
2320 if (!$err && $qmpstatus->{status
} eq "paused") {
2321 if ($param->{forceStop
}) {
2322 warn "VM is paused - stop instead of shutdown\n";
2325 die "VM is paused - cannot shutdown\n";
2329 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2331 my $timeout = $param->{timeout
} // 60;
2335 print "Requesting HA stop for VM $vmid\n";
2337 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2338 PVE
::Tools
::run_command
($cmd);
2342 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2349 syslog
('info', "shutdown VM $vmid: $upid\n");
2351 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2352 $shutdown, $param->{forceStop
}, $keepActive);
2356 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2360 __PACKAGE__-
>register_method({
2361 name
=> 'vm_reboot',
2362 path
=> '{vmid}/status/reboot',
2366 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
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
}),
2377 description
=> "Wait maximal timeout seconds for the shutdown.",
2390 my $rpcenv = PVE
::RPCEnvironment
::get
();
2391 my $authuser = $rpcenv->get_user();
2393 my $node = extract_param
($param, 'node');
2394 my $vmid = extract_param
($param, 'vmid');
2396 my $qmpstatus = eval {
2397 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2398 mon_cmd
($vmid, "query-status");
2402 if (!$err && $qmpstatus->{status
} eq "paused") {
2403 die "VM is paused - cannot shutdown\n";
2406 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2411 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2412 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2416 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2419 __PACKAGE__-
>register_method({
2420 name
=> 'vm_suspend',
2421 path
=> '{vmid}/status/suspend',
2425 description
=> "Suspend virtual machine.",
2427 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2428 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2429 " on the storage for the vmstate.",
2430 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2433 additionalProperties
=> 0,
2435 node
=> get_standard_option
('pve-node'),
2436 vmid
=> get_standard_option
('pve-vmid',
2437 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2438 skiplock
=> get_standard_option
('skiplock'),
2443 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2445 statestorage
=> get_standard_option
('pve-storage-id', {
2446 description
=> "The storage for the VM state",
2447 requires
=> 'todisk',
2449 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2459 my $rpcenv = PVE
::RPCEnvironment
::get
();
2460 my $authuser = $rpcenv->get_user();
2462 my $node = extract_param
($param, 'node');
2463 my $vmid = extract_param
($param, 'vmid');
2465 my $todisk = extract_param
($param, 'todisk') // 0;
2467 my $statestorage = extract_param
($param, 'statestorage');
2469 my $skiplock = extract_param
($param, 'skiplock');
2470 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2471 if $skiplock && $authuser ne 'root@pam';
2473 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2475 die "Cannot suspend HA managed VM to disk\n"
2476 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2478 # early check for storage permission, for better user feedback
2480 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2482 if (!$statestorage) {
2483 # get statestorage from config if none is given
2484 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2485 my $storecfg = PVE
::Storage
::config
();
2486 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2489 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2495 syslog
('info', "suspend VM $vmid: $upid\n");
2497 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2502 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2503 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2506 __PACKAGE__-
>register_method({
2507 name
=> 'vm_resume',
2508 path
=> '{vmid}/status/resume',
2512 description
=> "Resume virtual machine.",
2514 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2517 additionalProperties
=> 0,
2519 node
=> get_standard_option
('pve-node'),
2520 vmid
=> get_standard_option
('pve-vmid',
2521 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2522 skiplock
=> get_standard_option
('skiplock'),
2523 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2533 my $rpcenv = PVE
::RPCEnvironment
::get
();
2535 my $authuser = $rpcenv->get_user();
2537 my $node = extract_param
($param, 'node');
2539 my $vmid = extract_param
($param, 'vmid');
2541 my $skiplock = extract_param
($param, 'skiplock');
2542 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2543 if $skiplock && $authuser ne 'root@pam';
2545 my $nocheck = extract_param
($param, 'nocheck');
2547 my $to_disk_suspended;
2549 PVE
::QemuConfig-
>lock_config($vmid, sub {
2550 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2551 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2555 die "VM $vmid not running\n"
2556 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2561 syslog
('info', "resume VM $vmid: $upid\n");
2563 if (!$to_disk_suspended) {
2564 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2566 my $storecfg = PVE
::Storage
::config
();
2567 PVE
::QemuServer
::vm_start
($storecfg, $vmid, undef, $skiplock);
2573 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2576 __PACKAGE__-
>register_method({
2577 name
=> 'vm_sendkey',
2578 path
=> '{vmid}/sendkey',
2582 description
=> "Send key event to virtual machine.",
2584 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2587 additionalProperties
=> 0,
2589 node
=> get_standard_option
('pve-node'),
2590 vmid
=> get_standard_option
('pve-vmid',
2591 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2592 skiplock
=> get_standard_option
('skiplock'),
2594 description
=> "The key (qemu monitor encoding).",
2599 returns
=> { type
=> 'null'},
2603 my $rpcenv = PVE
::RPCEnvironment
::get
();
2605 my $authuser = $rpcenv->get_user();
2607 my $node = extract_param
($param, 'node');
2609 my $vmid = extract_param
($param, 'vmid');
2611 my $skiplock = extract_param
($param, 'skiplock');
2612 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2613 if $skiplock && $authuser ne 'root@pam';
2615 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2620 __PACKAGE__-
>register_method({
2621 name
=> 'vm_feature',
2622 path
=> '{vmid}/feature',
2626 description
=> "Check if feature for virtual machine is available.",
2628 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2631 additionalProperties
=> 0,
2633 node
=> get_standard_option
('pve-node'),
2634 vmid
=> get_standard_option
('pve-vmid'),
2636 description
=> "Feature to check.",
2638 enum
=> [ 'snapshot', 'clone', 'copy' ],
2640 snapname
=> get_standard_option
('pve-snapshot-name', {
2648 hasFeature
=> { type
=> 'boolean' },
2651 items
=> { type
=> 'string' },
2658 my $node = extract_param
($param, 'node');
2660 my $vmid = extract_param
($param, 'vmid');
2662 my $snapname = extract_param
($param, 'snapname');
2664 my $feature = extract_param
($param, 'feature');
2666 my $running = PVE
::QemuServer
::check_running
($vmid);
2668 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2671 my $snap = $conf->{snapshots
}->{$snapname};
2672 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2675 my $storecfg = PVE
::Storage
::config
();
2677 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2678 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2681 hasFeature
=> $hasFeature,
2682 nodes
=> [ keys %$nodelist ],
2686 __PACKAGE__-
>register_method({
2688 path
=> '{vmid}/clone',
2692 description
=> "Create a copy of virtual machine/template.",
2694 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2695 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2696 "'Datastore.AllocateSpace' on any used storage.",
2699 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2701 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2702 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2707 additionalProperties
=> 0,
2709 node
=> get_standard_option
('pve-node'),
2710 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2711 newid
=> get_standard_option
('pve-vmid', {
2712 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2713 description
=> 'VMID for the clone.' }),
2716 type
=> 'string', format
=> 'dns-name',
2717 description
=> "Set a name for the new VM.",
2722 description
=> "Description for the new VM.",
2726 type
=> 'string', format
=> 'pve-poolid',
2727 description
=> "Add the new VM to the specified pool.",
2729 snapname
=> get_standard_option
('pve-snapshot-name', {
2732 storage
=> get_standard_option
('pve-storage-id', {
2733 description
=> "Target storage for full clone.",
2737 description
=> "Target format for file storage. Only valid for full clone.",
2740 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2745 description
=> "Create a full copy of all disks. This is always done when " .
2746 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2748 target
=> get_standard_option
('pve-node', {
2749 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2753 description
=> "Override I/O bandwidth limit (in KiB/s).",
2757 default => 'clone limit from datacenter or storage config',
2767 my $rpcenv = PVE
::RPCEnvironment
::get
();
2768 my $authuser = $rpcenv->get_user();
2770 my $node = extract_param
($param, 'node');
2771 my $vmid = extract_param
($param, 'vmid');
2772 my $newid = extract_param
($param, 'newid');
2773 my $pool = extract_param
($param, 'pool');
2774 $rpcenv->check_pool_exist($pool) if defined($pool);
2776 my $snapname = extract_param
($param, 'snapname');
2777 my $storage = extract_param
($param, 'storage');
2778 my $format = extract_param
($param, 'format');
2779 my $target = extract_param
($param, 'target');
2781 my $localnode = PVE
::INotify
::nodename
();
2783 if ($target && ($target eq $localnode || $target eq 'localhost')) {
2787 PVE
::Cluster
::check_node_exists
($target) if $target;
2789 my $storecfg = PVE
::Storage
::config
();
2792 # check if storage is enabled on local node
2793 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2795 # check if storage is available on target node
2796 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2797 # clone only works if target storage is shared
2798 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2799 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2803 PVE
::Cluster
::check_cfs_quorum
();
2805 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2807 # exclusive lock if VM is running - else shared lock is enough;
2808 my $shared_lock = $running ?
0 : 1;
2811 # do all tests after lock but before forking worker - if possible
2813 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2814 PVE
::QemuConfig-
>check_lock($conf);
2816 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2817 die "unexpected state change\n" if $verify_running != $running;
2819 die "snapshot '$snapname' does not exist\n"
2820 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2822 my $full = extract_param
($param, 'full') // !PVE
::QemuConfig-
>is_template($conf);
2824 die "parameter 'storage' not allowed for linked clones\n"
2825 if defined($storage) && !$full;
2827 die "parameter 'format' not allowed for linked clones\n"
2828 if defined($format) && !$full;
2830 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2832 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2834 die "can't clone VM to node '$target' (VM uses local storage)\n"
2835 if $target && !$sharedvm;
2837 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2838 die "unable to create VM $newid: config file already exists\n"
2841 my $newconf = { lock => 'clone' };
2846 foreach my $opt (keys %$oldconf) {
2847 my $value = $oldconf->{$opt};
2849 # do not copy snapshot related info
2850 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2851 $opt eq 'vmstate' || $opt eq 'snapstate';
2853 # no need to copy unused images, because VMID(owner) changes anyways
2854 next if $opt =~ m/^unused\d+$/;
2856 # always change MAC! address
2857 if ($opt =~ m/^net(\d+)$/) {
2858 my $net = PVE
::QemuServer
::parse_net
($value);
2859 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2860 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2861 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2862 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2863 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2864 die "unable to parse drive options for '$opt'\n" if !$drive;
2865 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2866 $newconf->{$opt} = $value; # simply copy configuration
2868 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2869 die "Full clone feature is not supported for drive '$opt'\n"
2870 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2871 $fullclone->{$opt} = 1;
2873 # not full means clone instead of copy
2874 die "Linked clone feature is not supported for drive '$opt'\n"
2875 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2877 $drives->{$opt} = $drive;
2878 push @$vollist, $drive->{file
};
2881 # copy everything else
2882 $newconf->{$opt} = $value;
2886 # auto generate a new uuid
2887 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2888 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2889 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2890 # auto generate a new vmgenid only if the option was set for template
2891 if ($newconf->{vmgenid
}) {
2892 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2895 delete $newconf->{template
};
2897 if ($param->{name
}) {
2898 $newconf->{name
} = $param->{name
};
2900 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
2903 if ($param->{description
}) {
2904 $newconf->{description
} = $param->{description
};
2907 # create empty/temp config - this fails if VM already exists on other node
2908 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
2909 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2914 my $newvollist = [];
2921 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2923 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2925 my $bwlimit = extract_param
($param, 'bwlimit');
2927 my $total_jobs = scalar(keys %{$drives});
2930 foreach my $opt (keys %$drives) {
2931 my $drive = $drives->{$opt};
2932 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2934 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
2935 my $storage_list = [ $src_sid ];
2936 push @$storage_list, $storage if defined($storage);
2937 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
2939 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2940 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2941 $jobs, $skipcomplete, $oldconf->{agent
}, $clonelimit);
2943 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
2945 PVE
::QemuConfig-
>write_config($newid, $newconf);
2949 delete $newconf->{lock};
2951 # do not write pending changes
2952 if (my @changes = keys %{$newconf->{pending
}}) {
2953 my $pending = join(',', @changes);
2954 warn "found pending changes for '$pending', discarding for clone\n";
2955 delete $newconf->{pending
};
2958 PVE
::QemuConfig-
>write_config($newid, $newconf);
2961 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2962 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2963 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2965 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2966 die "Failed to move config to node '$target' - rename failed: $!\n"
2967 if !rename($conffile, $newconffile);
2970 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2973 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2974 sleep 1; # some storage like rbd need to wait before release volume - really?
2976 foreach my $volid (@$newvollist) {
2977 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2981 PVE
::Firewall
::remove_vmfw_conf
($newid);
2983 unlink $conffile; # avoid races -> last thing before die
2985 die "clone failed: $err";
2991 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2993 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2996 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2997 # Aquire exclusive lock lock for $newid
2998 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3003 __PACKAGE__-
>register_method({
3004 name
=> 'move_vm_disk',
3005 path
=> '{vmid}/move_disk',
3009 description
=> "Move volume to different storage.",
3011 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
3013 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3014 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
3018 additionalProperties
=> 0,
3020 node
=> get_standard_option
('pve-node'),
3021 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3024 description
=> "The disk you want to move.",
3025 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3027 storage
=> get_standard_option
('pve-storage-id', {
3028 description
=> "Target storage.",
3029 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3033 description
=> "Target Format.",
3034 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3039 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3045 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3050 description
=> "Override I/O bandwidth limit (in KiB/s).",
3054 default => 'move limit from datacenter or storage config',
3060 description
=> "the task ID.",
3065 my $rpcenv = PVE
::RPCEnvironment
::get
();
3066 my $authuser = $rpcenv->get_user();
3068 my $node = extract_param
($param, 'node');
3069 my $vmid = extract_param
($param, 'vmid');
3070 my $digest = extract_param
($param, 'digest');
3071 my $disk = extract_param
($param, 'disk');
3072 my $storeid = extract_param
($param, 'storage');
3073 my $format = extract_param
($param, 'format');
3075 my $storecfg = PVE
::Storage
::config
();
3077 my $updatefn = sub {
3078 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3079 PVE
::QemuConfig-
>check_lock($conf);
3081 die "VM config checksum missmatch (file change by other user?)\n"
3082 if $digest && $digest ne $conf->{digest
};
3084 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3086 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3088 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3089 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3091 my $old_volid = $drive->{file
};
3093 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3094 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3098 die "you can't move to the same storage with same format\n" if $oldstoreid eq $storeid &&
3099 (!$format || !$oldfmt || $oldfmt eq $format);
3101 # this only checks snapshots because $disk is passed!
3102 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3103 die "you can't move a disk with snapshots and delete the source\n"
3104 if $snapshotted && $param->{delete};
3106 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3108 my $running = PVE
::QemuServer
::check_running
($vmid);
3110 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3113 my $newvollist = [];
3119 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3121 warn "moving disk with snapshots, snapshots will not be moved!\n"
3124 my $bwlimit = extract_param
($param, 'bwlimit');
3125 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3127 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3128 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit);
3130 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3132 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3134 # convert moved disk to base if part of template
3135 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3136 if PVE
::QemuConfig-
>is_template($conf);
3138 PVE
::QemuConfig-
>write_config($vmid, $conf);
3140 my $do_trim = PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
};
3141 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3142 eval { mon_cmd
($vmid, "guest-fstrim") };
3146 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3147 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3153 foreach my $volid (@$newvollist) {
3154 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3157 die "storage migration failed: $err";
3160 if ($param->{delete}) {
3162 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3163 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3169 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3172 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3175 my $check_vm_disks_local = sub {
3176 my ($storecfg, $vmconf, $vmid) = @_;
3178 my $local_disks = {};
3180 # add some more information to the disks e.g. cdrom
3181 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3182 my ($volid, $attr) = @_;
3184 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3186 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3187 return if $scfg->{shared
};
3189 # The shared attr here is just a special case where the vdisk
3190 # is marked as shared manually
3191 return if $attr->{shared
};
3192 return if $attr->{cdrom
} and $volid eq "none";
3194 if (exists $local_disks->{$volid}) {
3195 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3197 $local_disks->{$volid} = $attr;
3198 # ensure volid is present in case it's needed
3199 $local_disks->{$volid}->{volid
} = $volid;
3203 return $local_disks;
3206 __PACKAGE__-
>register_method({
3207 name
=> 'migrate_vm_precondition',
3208 path
=> '{vmid}/migrate',
3212 description
=> "Get preconditions for migration.",
3214 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3217 additionalProperties
=> 0,
3219 node
=> get_standard_option
('pve-node'),
3220 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3221 target
=> get_standard_option
('pve-node', {
3222 description
=> "Target node.",
3223 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3231 running
=> { type
=> 'boolean' },
3235 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3237 not_allowed_nodes
=> {
3240 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3244 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3246 local_resources
=> {
3248 description
=> "List local resources e.g. pci, usb"
3255 my $rpcenv = PVE
::RPCEnvironment
::get
();
3257 my $authuser = $rpcenv->get_user();
3259 PVE
::Cluster
::check_cfs_quorum
();
3263 my $vmid = extract_param
($param, 'vmid');
3264 my $target = extract_param
($param, 'target');
3265 my $localnode = PVE
::INotify
::nodename
();
3269 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3270 my $storecfg = PVE
::Storage
::config
();
3273 # try to detect errors early
3274 PVE
::QemuConfig-
>check_lock($vmconf);
3276 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3278 # if vm is not running, return target nodes where local storage is available
3279 # for offline migration
3280 if (!$res->{running
}) {
3281 $res->{allowed_nodes
} = [];
3282 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3283 delete $checked_nodes->{$localnode};
3285 foreach my $node (keys %$checked_nodes) {
3286 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3287 push @{$res->{allowed_nodes
}}, $node;
3291 $res->{not_allowed_nodes
} = $checked_nodes;
3295 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3296 $res->{local_disks
} = [ values %$local_disks ];;
3298 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3300 $res->{local_resources
} = $local_resources;
3307 __PACKAGE__-
>register_method({
3308 name
=> 'migrate_vm',
3309 path
=> '{vmid}/migrate',
3313 description
=> "Migrate virtual machine. Creates a new migration task.",
3315 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3318 additionalProperties
=> 0,
3320 node
=> get_standard_option
('pve-node'),
3321 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3322 target
=> get_standard_option
('pve-node', {
3323 description
=> "Target node.",
3324 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3328 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3333 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3338 enum
=> ['secure', 'insecure'],
3339 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3342 migration_network
=> {
3343 type
=> 'string', format
=> 'CIDR',
3344 description
=> "CIDR of the (sub) network that is used for migration.",
3347 "with-local-disks" => {
3349 description
=> "Enable live storage migration for local disk",
3352 targetstorage
=> get_standard_option
('pve-storage-id', {
3353 description
=> "Default target storage.",
3355 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3358 description
=> "Override I/O bandwidth limit (in KiB/s).",
3362 default => 'migrate limit from datacenter or storage config',
3368 description
=> "the task ID.",
3373 my $rpcenv = PVE
::RPCEnvironment
::get
();
3374 my $authuser = $rpcenv->get_user();
3376 my $target = extract_param
($param, 'target');
3378 my $localnode = PVE
::INotify
::nodename
();
3379 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3381 PVE
::Cluster
::check_cfs_quorum
();
3383 PVE
::Cluster
::check_node_exists
($target);
3385 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3387 my $vmid = extract_param
($param, 'vmid');
3389 raise_param_exc
({ force
=> "Only root may use this option." })
3390 if $param->{force
} && $authuser ne 'root@pam';
3392 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3393 if $param->{migration_type
} && $authuser ne 'root@pam';
3395 # allow root only until better network permissions are available
3396 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3397 if $param->{migration_network
} && $authuser ne 'root@pam';
3400 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3402 # try to detect errors early
3404 PVE
::QemuConfig-
>check_lock($conf);
3406 if (PVE
::QemuServer
::check_running
($vmid)) {
3407 die "can't migrate running VM without --online\n" if !$param->{online
};
3409 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
3410 $param->{online
} = 0;
3413 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3414 if !$param->{online
} && $param->{targetstorage
};
3416 my $storecfg = PVE
::Storage
::config
();
3418 if( $param->{targetstorage
}) {
3419 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3421 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3424 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3429 print "Requesting HA migration for VM $vmid to node $target\n";
3431 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3432 PVE
::Tools
::run_command
($cmd);
3436 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3441 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3445 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3448 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3453 __PACKAGE__-
>register_method({
3455 path
=> '{vmid}/monitor',
3459 description
=> "Execute Qemu monitor commands.",
3461 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3462 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3465 additionalProperties
=> 0,
3467 node
=> get_standard_option
('pve-node'),
3468 vmid
=> get_standard_option
('pve-vmid'),
3471 description
=> "The monitor command.",
3475 returns
=> { type
=> 'string'},
3479 my $rpcenv = PVE
::RPCEnvironment
::get
();
3480 my $authuser = $rpcenv->get_user();
3483 my $command = shift;
3484 return $command =~ m/^\s*info(\s+|$)/
3485 || $command =~ m/^\s*help\s*$/;
3488 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3489 if !&$is_ro($param->{command
});
3491 my $vmid = $param->{vmid
};
3493 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3497 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3499 $res = "ERROR: $@" if $@;
3504 __PACKAGE__-
>register_method({
3505 name
=> 'resize_vm',
3506 path
=> '{vmid}/resize',
3510 description
=> "Extend volume size.",
3512 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3515 additionalProperties
=> 0,
3517 node
=> get_standard_option
('pve-node'),
3518 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3519 skiplock
=> get_standard_option
('skiplock'),
3522 description
=> "The disk you want to resize.",
3523 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3527 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3528 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.",
3532 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3538 returns
=> { type
=> 'null'},
3542 my $rpcenv = PVE
::RPCEnvironment
::get
();
3544 my $authuser = $rpcenv->get_user();
3546 my $node = extract_param
($param, 'node');
3548 my $vmid = extract_param
($param, 'vmid');
3550 my $digest = extract_param
($param, 'digest');
3552 my $disk = extract_param
($param, 'disk');
3554 my $sizestr = extract_param
($param, 'size');
3556 my $skiplock = extract_param
($param, 'skiplock');
3557 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3558 if $skiplock && $authuser ne 'root@pam';
3560 my $storecfg = PVE
::Storage
::config
();
3562 my $updatefn = sub {
3564 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3566 die "checksum missmatch (file change by other user?)\n"
3567 if $digest && $digest ne $conf->{digest
};
3568 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3570 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3572 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3574 my (undef, undef, undef, undef, undef, undef, $format) =
3575 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3577 die "can't resize volume: $disk if snapshot exists\n"
3578 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3580 my $volid = $drive->{file
};
3582 die "disk '$disk' has no associated volume\n" if !$volid;
3584 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3586 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3588 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3590 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3591 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3593 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3595 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3596 my ($ext, $newsize, $unit) = ($1, $2, $4);
3599 $newsize = $newsize * 1024;
3600 } elsif ($unit eq 'M') {
3601 $newsize = $newsize * 1024 * 1024;
3602 } elsif ($unit eq 'G') {
3603 $newsize = $newsize * 1024 * 1024 * 1024;
3604 } elsif ($unit eq 'T') {
3605 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3608 $newsize += $size if $ext;
3609 $newsize = int($newsize);
3611 die "shrinking disks is not supported\n" if $newsize < $size;
3613 return if $size == $newsize;
3615 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3617 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3619 my $effective_size = eval { PVE
::Storage
::volume_size_info
($storecfg, $volid, 3); };
3620 $drive->{size
} = $effective_size // $newsize;
3621 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
3623 PVE
::QemuConfig-
>write_config($vmid, $conf);
3626 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3630 __PACKAGE__-
>register_method({
3631 name
=> 'snapshot_list',
3632 path
=> '{vmid}/snapshot',
3634 description
=> "List all snapshots.",
3636 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3639 protected
=> 1, # qemu pid files are only readable by root
3641 additionalProperties
=> 0,
3643 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3644 node
=> get_standard_option
('pve-node'),
3653 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3657 description
=> "Snapshot includes RAM.",
3662 description
=> "Snapshot description.",
3666 description
=> "Snapshot creation time",
3668 renderer
=> 'timestamp',
3672 description
=> "Parent snapshot identifier.",
3678 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3683 my $vmid = $param->{vmid
};
3685 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3686 my $snaphash = $conf->{snapshots
} || {};
3690 foreach my $name (keys %$snaphash) {
3691 my $d = $snaphash->{$name};
3694 snaptime
=> $d->{snaptime
} || 0,
3695 vmstate
=> $d->{vmstate
} ?
1 : 0,
3696 description
=> $d->{description
} || '',
3698 $item->{parent
} = $d->{parent
} if $d->{parent
};
3699 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3703 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3706 digest
=> $conf->{digest
},
3707 running
=> $running,
3708 description
=> "You are here!",
3710 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3712 push @$res, $current;
3717 __PACKAGE__-
>register_method({
3719 path
=> '{vmid}/snapshot',
3723 description
=> "Snapshot a VM.",
3725 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3728 additionalProperties
=> 0,
3730 node
=> get_standard_option
('pve-node'),
3731 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3732 snapname
=> get_standard_option
('pve-snapshot-name'),
3736 description
=> "Save the vmstate",
3741 description
=> "A textual description or comment.",
3747 description
=> "the task ID.",
3752 my $rpcenv = PVE
::RPCEnvironment
::get
();
3754 my $authuser = $rpcenv->get_user();
3756 my $node = extract_param
($param, 'node');
3758 my $vmid = extract_param
($param, 'vmid');
3760 my $snapname = extract_param
($param, 'snapname');
3762 die "unable to use snapshot name 'current' (reserved name)\n"
3763 if $snapname eq 'current';
3765 die "unable to use snapshot name 'pending' (reserved name)\n"
3766 if lc($snapname) eq 'pending';
3769 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3770 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3771 $param->{description
});
3774 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3777 __PACKAGE__-
>register_method({
3778 name
=> 'snapshot_cmd_idx',
3779 path
=> '{vmid}/snapshot/{snapname}',
3786 additionalProperties
=> 0,
3788 vmid
=> get_standard_option
('pve-vmid'),
3789 node
=> get_standard_option
('pve-node'),
3790 snapname
=> get_standard_option
('pve-snapshot-name'),
3799 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3806 push @$res, { cmd
=> 'rollback' };
3807 push @$res, { cmd
=> 'config' };
3812 __PACKAGE__-
>register_method({
3813 name
=> 'update_snapshot_config',
3814 path
=> '{vmid}/snapshot/{snapname}/config',
3818 description
=> "Update snapshot metadata.",
3820 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3823 additionalProperties
=> 0,
3825 node
=> get_standard_option
('pve-node'),
3826 vmid
=> get_standard_option
('pve-vmid'),
3827 snapname
=> get_standard_option
('pve-snapshot-name'),
3831 description
=> "A textual description or comment.",
3835 returns
=> { type
=> 'null' },
3839 my $rpcenv = PVE
::RPCEnvironment
::get
();
3841 my $authuser = $rpcenv->get_user();
3843 my $vmid = extract_param
($param, 'vmid');
3845 my $snapname = extract_param
($param, 'snapname');
3847 return undef if !defined($param->{description
});
3849 my $updatefn = sub {
3851 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3853 PVE
::QemuConfig-
>check_lock($conf);
3855 my $snap = $conf->{snapshots
}->{$snapname};
3857 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3859 $snap->{description
} = $param->{description
} if defined($param->{description
});
3861 PVE
::QemuConfig-
>write_config($vmid, $conf);
3864 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3869 __PACKAGE__-
>register_method({
3870 name
=> 'get_snapshot_config',
3871 path
=> '{vmid}/snapshot/{snapname}/config',
3874 description
=> "Get snapshot configuration",
3876 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
3879 additionalProperties
=> 0,
3881 node
=> get_standard_option
('pve-node'),
3882 vmid
=> get_standard_option
('pve-vmid'),
3883 snapname
=> get_standard_option
('pve-snapshot-name'),
3886 returns
=> { type
=> "object" },
3890 my $rpcenv = PVE
::RPCEnvironment
::get
();
3892 my $authuser = $rpcenv->get_user();
3894 my $vmid = extract_param
($param, 'vmid');
3896 my $snapname = extract_param
($param, 'snapname');
3898 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3900 my $snap = $conf->{snapshots
}->{$snapname};
3902 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3907 __PACKAGE__-
>register_method({
3909 path
=> '{vmid}/snapshot/{snapname}/rollback',
3913 description
=> "Rollback VM state to specified snapshot.",
3915 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3918 additionalProperties
=> 0,
3920 node
=> get_standard_option
('pve-node'),
3921 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3922 snapname
=> get_standard_option
('pve-snapshot-name'),
3927 description
=> "the task ID.",
3932 my $rpcenv = PVE
::RPCEnvironment
::get
();
3934 my $authuser = $rpcenv->get_user();
3936 my $node = extract_param
($param, 'node');
3938 my $vmid = extract_param
($param, 'vmid');
3940 my $snapname = extract_param
($param, 'snapname');
3943 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3944 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3948 # hold migration lock, this makes sure that nobody create replication snapshots
3949 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3952 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3955 __PACKAGE__-
>register_method({
3956 name
=> 'delsnapshot',
3957 path
=> '{vmid}/snapshot/{snapname}',
3961 description
=> "Delete a VM snapshot.",
3963 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3966 additionalProperties
=> 0,
3968 node
=> get_standard_option
('pve-node'),
3969 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3970 snapname
=> get_standard_option
('pve-snapshot-name'),
3974 description
=> "For removal from config file, even if removing disk snapshots fails.",
3980 description
=> "the task ID.",
3985 my $rpcenv = PVE
::RPCEnvironment
::get
();
3987 my $authuser = $rpcenv->get_user();
3989 my $node = extract_param
($param, 'node');
3991 my $vmid = extract_param
($param, 'vmid');
3993 my $snapname = extract_param
($param, 'snapname');
3996 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3997 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
4000 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
4003 __PACKAGE__-
>register_method({
4005 path
=> '{vmid}/template',
4009 description
=> "Create a Template.",
4011 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4012 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4015 additionalProperties
=> 0,
4017 node
=> get_standard_option
('pve-node'),
4018 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4022 description
=> "If you want to convert only 1 disk to base image.",
4023 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4028 returns
=> { type
=> 'null'},
4032 my $rpcenv = PVE
::RPCEnvironment
::get
();
4034 my $authuser = $rpcenv->get_user();
4036 my $node = extract_param
($param, 'node');
4038 my $vmid = extract_param
($param, 'vmid');
4040 my $disk = extract_param
($param, 'disk');
4042 my $updatefn = sub {
4044 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4046 PVE
::QemuConfig-
>check_lock($conf);
4048 die "unable to create template, because VM contains snapshots\n"
4049 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4051 die "you can't convert a template to a template\n"
4052 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4054 die "you can't convert a VM to template if VM is running\n"
4055 if PVE
::QemuServer
::check_running
($vmid);
4058 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4061 $conf->{template
} = 1;
4062 PVE
::QemuConfig-
>write_config($vmid, $conf);
4064 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4067 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4071 __PACKAGE__-
>register_method({
4072 name
=> 'cloudinit_generated_config_dump',
4073 path
=> '{vmid}/cloudinit/dump',
4076 description
=> "Get automatically generated cloudinit config.",
4078 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4081 additionalProperties
=> 0,
4083 node
=> get_standard_option
('pve-node'),
4084 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4086 description
=> 'Config type.',
4088 enum
=> ['user', 'network', 'meta'],
4098 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4100 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});