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};
406 __PACKAGE__-
>register_method({
410 description
=> "Create or restore a virtual machine.",
412 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
413 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
414 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
415 user
=> 'all', # check inside
420 additionalProperties
=> 0,
421 properties
=> PVE
::QemuServer
::json_config_properties
(
423 node
=> get_standard_option
('pve-node'),
424 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
426 description
=> "The backup file.",
430 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
432 storage
=> get_standard_option
('pve-storage-id', {
433 description
=> "Default storage.",
435 completion
=> \
&PVE
::QemuServer
::complete_storage
,
440 description
=> "Allow to overwrite existing VM.",
441 requires
=> 'archive',
446 description
=> "Assign a unique random ethernet address.",
447 requires
=> 'archive',
451 type
=> 'string', format
=> 'pve-poolid',
452 description
=> "Add the VM to the specified pool.",
455 description
=> "Override I/O bandwidth limit (in KiB/s).",
459 default => 'restore limit from datacenter or storage config',
465 description
=> "Start VM after it was created successfully.",
475 my $rpcenv = PVE
::RPCEnvironment
::get
();
476 my $authuser = $rpcenv->get_user();
478 my $node = extract_param
($param, 'node');
479 my $vmid = extract_param
($param, 'vmid');
481 my $archive = extract_param
($param, 'archive');
482 my $is_restore = !!$archive;
484 my $bwlimit = extract_param
($param, 'bwlimit');
485 my $force = extract_param
($param, 'force');
486 my $pool = extract_param
($param, 'pool');
487 my $start_after_create = extract_param
($param, 'start');
488 my $storage = extract_param
($param, 'storage');
489 my $unique = extract_param
($param, 'unique');
491 if (defined(my $ssh_keys = $param->{sshkeys
})) {
492 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
493 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
496 PVE
::Cluster
::check_cfs_quorum
();
498 my $filename = PVE
::QemuConfig-
>config_file($vmid);
499 my $storecfg = PVE
::Storage
::config
();
501 if (defined($pool)) {
502 $rpcenv->check_pool_exist($pool);
505 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
506 if defined($storage);
508 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
510 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
512 } elsif ($archive && $force && (-f
$filename) &&
513 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
514 # OK: user has VM.Backup permissions, and want to restore an existing VM
520 &$resolve_cdrom_alias($param);
522 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
524 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
526 foreach my $opt (keys %$param) {
527 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
528 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
529 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
531 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
532 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
536 PVE
::QemuServer
::add_random_macs
($param);
538 my $keystr = join(' ', keys %$param);
539 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
541 if ($archive eq '-') {
542 die "pipe requires cli environment\n"
543 if $rpcenv->{type
} ne 'cli';
545 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
546 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
550 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
552 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
553 die "$emsg $@" if $@;
555 my $restorefn = sub {
556 my $conf = PVE
::QemuConfig-
>load_config($vmid);
558 PVE
::QemuConfig-
>check_protection($conf, $emsg);
560 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
563 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
569 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
570 # Convert restored VM to template if backup was VM template
571 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
572 warn "Convert to template.\n";
573 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
577 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
580 # ensure no old replication state are exists
581 PVE
::ReplicationState
::delete_guest_states
($vmid);
583 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
585 if ($start_after_create) {
586 print "Execute autostart\n";
587 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
593 # ensure no old replication state are exists
594 PVE
::ReplicationState
::delete_guest_states
($vmid);
598 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
602 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
604 if (!$conf->{bootdisk
}) {
605 my $firstdisk = PVE
::QemuServer
::Drive
::resolve_first_disk
($conf);
606 $conf->{bootdisk
} = $firstdisk if $firstdisk;
609 # auto generate uuid if user did not specify smbios1 option
610 if (!$conf->{smbios1
}) {
611 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
614 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
615 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
618 PVE
::QemuConfig-
>write_config($vmid, $conf);
624 foreach my $volid (@$vollist) {
625 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
631 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
634 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
636 if ($start_after_create) {
637 print "Execute autostart\n";
638 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
643 my ($code, $worker_name);
645 $worker_name = 'qmrestore';
647 eval { $restorefn->() };
649 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
655 $worker_name = 'qmcreate';
657 eval { $createfn->() };
660 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
661 unlink($conffile) or die "failed to remove config file: $!\n";
669 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
672 __PACKAGE__-
>register_method({
677 description
=> "Directory index",
682 additionalProperties
=> 0,
684 node
=> get_standard_option
('pve-node'),
685 vmid
=> get_standard_option
('pve-vmid'),
693 subdir
=> { type
=> 'string' },
696 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
702 { subdir
=> 'config' },
703 { subdir
=> 'pending' },
704 { subdir
=> 'status' },
705 { subdir
=> 'unlink' },
706 { subdir
=> 'vncproxy' },
707 { subdir
=> 'termproxy' },
708 { subdir
=> 'migrate' },
709 { subdir
=> 'resize' },
710 { subdir
=> 'move' },
712 { subdir
=> 'rrddata' },
713 { subdir
=> 'monitor' },
714 { subdir
=> 'agent' },
715 { subdir
=> 'snapshot' },
716 { subdir
=> 'spiceproxy' },
717 { subdir
=> 'sendkey' },
718 { subdir
=> 'firewall' },
724 __PACKAGE__-
>register_method ({
725 subclass
=> "PVE::API2::Firewall::VM",
726 path
=> '{vmid}/firewall',
729 __PACKAGE__-
>register_method ({
730 subclass
=> "PVE::API2::Qemu::Agent",
731 path
=> '{vmid}/agent',
734 __PACKAGE__-
>register_method({
736 path
=> '{vmid}/rrd',
738 protected
=> 1, # fixme: can we avoid that?
740 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
742 description
=> "Read VM RRD statistics (returns PNG)",
744 additionalProperties
=> 0,
746 node
=> get_standard_option
('pve-node'),
747 vmid
=> get_standard_option
('pve-vmid'),
749 description
=> "Specify the time frame you are interested in.",
751 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
754 description
=> "The list of datasources you want to display.",
755 type
=> 'string', format
=> 'pve-configid-list',
758 description
=> "The RRD consolidation function",
760 enum
=> [ 'AVERAGE', 'MAX' ],
768 filename
=> { type
=> 'string' },
774 return PVE
::RRD
::create_rrd_graph
(
775 "pve2-vm/$param->{vmid}", $param->{timeframe
},
776 $param->{ds
}, $param->{cf
});
780 __PACKAGE__-
>register_method({
782 path
=> '{vmid}/rrddata',
784 protected
=> 1, # fixme: can we avoid that?
786 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
788 description
=> "Read VM RRD statistics",
790 additionalProperties
=> 0,
792 node
=> get_standard_option
('pve-node'),
793 vmid
=> get_standard_option
('pve-vmid'),
795 description
=> "Specify the time frame you are interested in.",
797 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
800 description
=> "The RRD consolidation function",
802 enum
=> [ 'AVERAGE', 'MAX' ],
817 return PVE
::RRD
::create_rrd_data
(
818 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
822 __PACKAGE__-
>register_method({
824 path
=> '{vmid}/config',
827 description
=> "Get the virtual machine configuration with pending configuration " .
828 "changes applied. Set the 'current' parameter to get the current configuration instead.",
830 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
833 additionalProperties
=> 0,
835 node
=> get_standard_option
('pve-node'),
836 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
838 description
=> "Get current values (instead of pending values).",
843 snapshot
=> get_standard_option
('pve-snapshot-name', {
844 description
=> "Fetch config values from given snapshot.",
847 my ($cmd, $pname, $cur, $args) = @_;
848 PVE
::QemuConfig-
>snapshot_list($args->[0]);
854 description
=> "The VM configuration.",
856 properties
=> PVE
::QemuServer
::json_config_properties
({
859 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
866 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
867 current
=> "cannot use 'snapshot' parameter with 'current'"})
868 if ($param->{snapshot
} && $param->{current
});
871 if ($param->{snapshot
}) {
872 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
874 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
876 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
881 __PACKAGE__-
>register_method({
882 name
=> 'vm_pending',
883 path
=> '{vmid}/pending',
886 description
=> "Get the virtual machine configuration with both current and pending values.",
888 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
891 additionalProperties
=> 0,
893 node
=> get_standard_option
('pve-node'),
894 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
903 description
=> "Configuration option name.",
907 description
=> "Current value.",
912 description
=> "Pending value.",
917 description
=> "Indicates a pending delete request if present and not 0. " .
918 "The value 2 indicates a force-delete request.",
930 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
932 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
934 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
935 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
937 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
940 # POST/PUT {vmid}/config implementation
942 # The original API used PUT (idempotent) an we assumed that all operations
943 # are fast. But it turned out that almost any configuration change can
944 # involve hot-plug actions, or disk alloc/free. Such actions can take long
945 # time to complete and have side effects (not idempotent).
947 # The new implementation uses POST and forks a worker process. We added
948 # a new option 'background_delay'. If specified we wait up to
949 # 'background_delay' second for the worker task to complete. It returns null
950 # if the task is finished within that time, else we return the UPID.
952 my $update_vm_api = sub {
953 my ($param, $sync) = @_;
955 my $rpcenv = PVE
::RPCEnvironment
::get
();
957 my $authuser = $rpcenv->get_user();
959 my $node = extract_param
($param, 'node');
961 my $vmid = extract_param
($param, 'vmid');
963 my $digest = extract_param
($param, 'digest');
965 my $background_delay = extract_param
($param, 'background_delay');
967 if (defined(my $cipassword = $param->{cipassword
})) {
968 # Same logic as in cloud-init (but with the regex fixed...)
969 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
970 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
973 my @paramarr = (); # used for log message
974 foreach my $key (sort keys %$param) {
975 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
976 push @paramarr, "-$key", $value;
979 my $skiplock = extract_param
($param, 'skiplock');
980 raise_param_exc
({ skiplock
=> "Only root may use this option." })
981 if $skiplock && $authuser ne 'root@pam';
983 my $delete_str = extract_param
($param, 'delete');
985 my $revert_str = extract_param
($param, 'revert');
987 my $force = extract_param
($param, 'force');
989 if (defined(my $ssh_keys = $param->{sshkeys
})) {
990 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
991 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
994 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
996 my $storecfg = PVE
::Storage
::config
();
998 my $defaults = PVE
::QemuServer
::load_defaults
();
1000 &$resolve_cdrom_alias($param);
1002 # now try to verify all parameters
1005 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1006 if (!PVE
::QemuServer
::option_exists
($opt)) {
1007 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1010 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1011 "-revert $opt' at the same time" })
1012 if defined($param->{$opt});
1014 $revert->{$opt} = 1;
1018 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1019 $opt = 'ide2' if $opt eq 'cdrom';
1021 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1022 "-delete $opt' at the same time" })
1023 if defined($param->{$opt});
1025 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1026 "-revert $opt' at the same time" })
1029 if (!PVE
::QemuServer
::option_exists
($opt)) {
1030 raise_param_exc
({ delete => "unknown option '$opt'" });
1036 my $repl_conf = PVE
::ReplicationConfig-
>new();
1037 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1038 my $check_replication = sub {
1040 return if !$is_replicated;
1041 my $volid = $drive->{file
};
1042 return if !$volid || !($drive->{replicate
}//1);
1043 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1045 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1046 return if $volname eq 'cloudinit';
1049 if ($volid =~ $NEW_DISK_RE) {
1051 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1053 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1055 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1056 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1057 return if $scfg->{shared
};
1058 die "cannot add non-replicatable volume to a replicated VM\n";
1061 foreach my $opt (keys %$param) {
1062 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1063 # cleanup drive path
1064 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1065 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1066 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1067 $check_replication->($drive);
1068 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
1069 } elsif ($opt =~ m/^net(\d+)$/) {
1071 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1072 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1073 } elsif ($opt eq 'vmgenid') {
1074 if ($param->{$opt} eq '1') {
1075 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1077 } elsif ($opt eq 'hookscript') {
1078 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1079 raise_param_exc
({ $opt => $@ }) if $@;
1083 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1085 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1087 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1089 my $updatefn = sub {
1091 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1093 die "checksum missmatch (file change by other user?)\n"
1094 if $digest && $digest ne $conf->{digest
};
1096 # FIXME: 'suspended' lock should probabyl be a state or "weak" lock?!
1097 if (scalar(@delete) && grep { $_ eq 'vmstate'} @delete) {
1098 if (defined($conf->{lock}) && $conf->{lock} eq 'suspended') {
1099 delete $conf->{lock}; # for check lock check, not written out
1100 push @delete, 'lock'; # this is the real deal to write it out
1102 push @delete, 'runningmachine' if $conf->{runningmachine
};
1105 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1107 foreach my $opt (keys %$revert) {
1108 if (defined($conf->{$opt})) {
1109 $param->{$opt} = $conf->{$opt};
1110 } elsif (defined($conf->{pending
}->{$opt})) {
1115 if ($param->{memory
} || defined($param->{balloon
})) {
1116 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1117 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1119 die "balloon value too large (must be smaller than assigned memory)\n"
1120 if $balloon && $balloon > $maxmem;
1123 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1127 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1129 # write updates to pending section
1131 my $modified = {}; # record what $option we modify
1133 foreach my $opt (@delete) {
1134 $modified->{$opt} = 1;
1135 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1137 # value of what we want to delete, independent if pending or not
1138 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1139 if (!defined($val)) {
1140 warn "cannot delete '$opt' - not set in current configuration!\n";
1141 $modified->{$opt} = 0;
1144 my $is_pending_val = defined($conf->{pending
}->{$opt});
1145 delete $conf->{pending
}->{$opt};
1147 if ($opt =~ m/^unused/) {
1148 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1149 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1150 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1151 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1152 delete $conf->{$opt};
1153 PVE
::QemuConfig-
>write_config($vmid, $conf);
1155 } elsif ($opt eq 'vmstate') {
1156 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1157 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1158 delete $conf->{$opt};
1159 PVE
::QemuConfig-
>write_config($vmid, $conf);
1161 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1162 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1163 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1164 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1166 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1167 PVE
::QemuConfig-
>write_config($vmid, $conf);
1168 } elsif ($opt =~ m/^serial\d+$/) {
1169 if ($val eq 'socket') {
1170 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1171 } elsif ($authuser ne 'root@pam') {
1172 die "only root can delete '$opt' config for real devices\n";
1174 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1175 PVE
::QemuConfig-
>write_config($vmid, $conf);
1176 } elsif ($opt =~ m/^usb\d+$/) {
1177 if ($val =~ m/spice/) {
1178 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1179 } elsif ($authuser ne 'root@pam') {
1180 die "only root can delete '$opt' config for real devices\n";
1182 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1183 PVE
::QemuConfig-
>write_config($vmid, $conf);
1185 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1186 PVE
::QemuConfig-
>write_config($vmid, $conf);
1190 foreach my $opt (keys %$param) { # add/change
1191 $modified->{$opt} = 1;
1192 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1193 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1195 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1197 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1198 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1199 # FIXME: cloudinit: CDROM or Disk?
1200 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1201 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1203 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1205 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1206 if defined($conf->{pending
}->{$opt});
1208 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1209 } elsif ($opt =~ m/^serial\d+/) {
1210 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1211 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1212 } elsif ($authuser ne 'root@pam') {
1213 die "only root can modify '$opt' config for real devices\n";
1215 $conf->{pending
}->{$opt} = $param->{$opt};
1216 } elsif ($opt =~ m/^usb\d+/) {
1217 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1218 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1219 } elsif ($authuser ne 'root@pam') {
1220 die "only root can modify '$opt' config for real devices\n";
1222 $conf->{pending
}->{$opt} = $param->{$opt};
1224 $conf->{pending
}->{$opt} = $param->{$opt};
1226 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1227 PVE
::QemuConfig-
>write_config($vmid, $conf);
1230 # remove pending changes when nothing changed
1231 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1232 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1233 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1235 return if !scalar(keys %{$conf->{pending
}});
1237 my $running = PVE
::QemuServer
::check_running
($vmid);
1239 # apply pending changes
1241 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1245 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1247 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running, $errors);
1249 raise_param_exc
($errors) if scalar(keys %$errors);
1258 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1260 if ($background_delay) {
1262 # Note: It would be better to do that in the Event based HTTPServer
1263 # to avoid blocking call to sleep.
1265 my $end_time = time() + $background_delay;
1267 my $task = PVE
::Tools
::upid_decode
($upid);
1270 while (time() < $end_time) {
1271 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1273 sleep(1); # this gets interrupted when child process ends
1277 my $status = PVE
::Tools
::upid_read_status
($upid);
1278 return undef if $status eq 'OK';
1287 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1290 my $vm_config_perm_list = [
1295 'VM.Config.Network',
1297 'VM.Config.Options',
1300 __PACKAGE__-
>register_method({
1301 name
=> 'update_vm_async',
1302 path
=> '{vmid}/config',
1306 description
=> "Set virtual machine options (asynchrounous API).",
1308 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1311 additionalProperties
=> 0,
1312 properties
=> PVE
::QemuServer
::json_config_properties
(
1314 node
=> get_standard_option
('pve-node'),
1315 vmid
=> get_standard_option
('pve-vmid'),
1316 skiplock
=> get_standard_option
('skiplock'),
1318 type
=> 'string', format
=> 'pve-configid-list',
1319 description
=> "A list of settings you want to delete.",
1323 type
=> 'string', format
=> 'pve-configid-list',
1324 description
=> "Revert a pending change.",
1329 description
=> $opt_force_description,
1331 requires
=> 'delete',
1335 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1339 background_delay
=> {
1341 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1352 code
=> $update_vm_api,
1355 __PACKAGE__-
>register_method({
1356 name
=> 'update_vm',
1357 path
=> '{vmid}/config',
1361 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1363 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1366 additionalProperties
=> 0,
1367 properties
=> PVE
::QemuServer
::json_config_properties
(
1369 node
=> get_standard_option
('pve-node'),
1370 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1371 skiplock
=> get_standard_option
('skiplock'),
1373 type
=> 'string', format
=> 'pve-configid-list',
1374 description
=> "A list of settings you want to delete.",
1378 type
=> 'string', format
=> 'pve-configid-list',
1379 description
=> "Revert a pending change.",
1384 description
=> $opt_force_description,
1386 requires
=> 'delete',
1390 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1396 returns
=> { type
=> 'null' },
1399 &$update_vm_api($param, 1);
1404 __PACKAGE__-
>register_method({
1405 name
=> 'destroy_vm',
1410 description
=> "Destroy the vm (also delete all used/owned volumes).",
1412 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1415 additionalProperties
=> 0,
1417 node
=> get_standard_option
('pve-node'),
1418 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1419 skiplock
=> get_standard_option
('skiplock'),
1422 description
=> "Remove vmid from backup cron jobs.",
1433 my $rpcenv = PVE
::RPCEnvironment
::get
();
1434 my $authuser = $rpcenv->get_user();
1435 my $vmid = $param->{vmid
};
1437 my $skiplock = $param->{skiplock
};
1438 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1439 if $skiplock && $authuser ne 'root@pam';
1442 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1443 my $storecfg = PVE
::Storage
::config
();
1444 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1445 die "unable to remove VM $vmid - used in HA resources\n"
1446 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1448 if (!$param->{purge
}) {
1449 # don't allow destroy if with replication jobs but no purge param
1450 my $repl_conf = PVE
::ReplicationConfig-
>new();
1451 $repl_conf->check_for_existing_jobs($vmid);
1454 # early tests (repeat after locking)
1455 die "VM $vmid is running - destroy failed\n"
1456 if PVE
::QemuServer
::check_running
($vmid);
1461 syslog
('info', "destroy VM $vmid: $upid\n");
1462 PVE
::QemuConfig-
>lock_config($vmid, sub {
1463 die "VM $vmid is running - destroy failed\n"
1464 if (PVE
::QemuServer
::check_running
($vmid));
1466 PVE
::QemuServer
::destroy_vm
($storecfg, $vmid, $skiplock, { lock => 'destroyed' });
1468 PVE
::AccessControl
::remove_vm_access
($vmid);
1469 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1470 if ($param->{purge
}) {
1471 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1472 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1475 # only now remove the zombie config, else we can have reuse race
1476 PVE
::QemuConfig-
>destroy_config($vmid);
1480 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1483 __PACKAGE__-
>register_method({
1485 path
=> '{vmid}/unlink',
1489 description
=> "Unlink/delete disk images.",
1491 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1494 additionalProperties
=> 0,
1496 node
=> get_standard_option
('pve-node'),
1497 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1499 type
=> 'string', format
=> 'pve-configid-list',
1500 description
=> "A list of disk IDs you want to delete.",
1504 description
=> $opt_force_description,
1509 returns
=> { type
=> 'null'},
1513 $param->{delete} = extract_param
($param, 'idlist');
1515 __PACKAGE__-
>update_vm($param);
1522 __PACKAGE__-
>register_method({
1524 path
=> '{vmid}/vncproxy',
1528 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1530 description
=> "Creates a TCP VNC proxy connections.",
1532 additionalProperties
=> 0,
1534 node
=> get_standard_option
('pve-node'),
1535 vmid
=> get_standard_option
('pve-vmid'),
1539 description
=> "starts websockify instead of vncproxy",
1544 additionalProperties
=> 0,
1546 user
=> { type
=> 'string' },
1547 ticket
=> { type
=> 'string' },
1548 cert
=> { type
=> 'string' },
1549 port
=> { type
=> 'integer' },
1550 upid
=> { type
=> 'string' },
1556 my $rpcenv = PVE
::RPCEnvironment
::get
();
1558 my $authuser = $rpcenv->get_user();
1560 my $vmid = $param->{vmid
};
1561 my $node = $param->{node
};
1562 my $websocket = $param->{websocket
};
1564 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1565 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1567 my $authpath = "/vms/$vmid";
1569 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1571 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1577 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1578 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1579 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1580 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1581 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1583 $family = PVE
::Tools
::get_host_address_family
($node);
1586 my $port = PVE
::Tools
::next_vnc_port
($family);
1593 syslog
('info', "starting vnc proxy $upid\n");
1599 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1601 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1602 '-timeout', $timeout, '-authpath', $authpath,
1603 '-perm', 'Sys.Console'];
1605 if ($param->{websocket
}) {
1606 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1607 push @$cmd, '-notls', '-listen', 'localhost';
1610 push @$cmd, '-c', @$remcmd, @$termcmd;
1612 PVE
::Tools
::run_command
($cmd);
1616 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1618 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1620 my $sock = IO
::Socket
::IP-
>new(
1625 GetAddrInfoFlags
=> 0,
1626 ) or die "failed to create socket: $!\n";
1627 # Inside the worker we shouldn't have any previous alarms
1628 # running anyway...:
1630 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1632 accept(my $cli, $sock) or die "connection failed: $!\n";
1635 if (PVE
::Tools
::run_command
($cmd,
1636 output
=> '>&'.fileno($cli),
1637 input
=> '<&'.fileno($cli),
1640 die "Failed to run vncproxy.\n";
1647 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1649 PVE
::Tools
::wait_for_vnc_port
($port);
1660 __PACKAGE__-
>register_method({
1661 name
=> 'termproxy',
1662 path
=> '{vmid}/termproxy',
1666 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1668 description
=> "Creates a TCP proxy connections.",
1670 additionalProperties
=> 0,
1672 node
=> get_standard_option
('pve-node'),
1673 vmid
=> get_standard_option
('pve-vmid'),
1677 enum
=> [qw(serial0 serial1 serial2 serial3)],
1678 description
=> "opens a serial terminal (defaults to display)",
1683 additionalProperties
=> 0,
1685 user
=> { type
=> 'string' },
1686 ticket
=> { type
=> 'string' },
1687 port
=> { type
=> 'integer' },
1688 upid
=> { type
=> 'string' },
1694 my $rpcenv = PVE
::RPCEnvironment
::get
();
1696 my $authuser = $rpcenv->get_user();
1698 my $vmid = $param->{vmid
};
1699 my $node = $param->{node
};
1700 my $serial = $param->{serial
};
1702 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1704 if (!defined($serial)) {
1705 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1706 $serial = $conf->{vga
};
1710 my $authpath = "/vms/$vmid";
1712 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1717 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1718 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1719 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1720 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1721 push @$remcmd, '--';
1723 $family = PVE
::Tools
::get_host_address_family
($node);
1726 my $port = PVE
::Tools
::next_vnc_port
($family);
1728 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1729 push @$termcmd, '-iface', $serial if $serial;
1734 syslog
('info', "starting qemu termproxy $upid\n");
1736 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1737 '--perm', 'VM.Console', '--'];
1738 push @$cmd, @$remcmd, @$termcmd;
1740 PVE
::Tools
::run_command
($cmd);
1743 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1745 PVE
::Tools
::wait_for_vnc_port
($port);
1755 __PACKAGE__-
>register_method({
1756 name
=> 'vncwebsocket',
1757 path
=> '{vmid}/vncwebsocket',
1760 description
=> "You also need to pass a valid ticket (vncticket).",
1761 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1763 description
=> "Opens a weksocket for VNC traffic.",
1765 additionalProperties
=> 0,
1767 node
=> get_standard_option
('pve-node'),
1768 vmid
=> get_standard_option
('pve-vmid'),
1770 description
=> "Ticket from previous call to vncproxy.",
1775 description
=> "Port number returned by previous vncproxy call.",
1785 port
=> { type
=> 'string' },
1791 my $rpcenv = PVE
::RPCEnvironment
::get
();
1793 my $authuser = $rpcenv->get_user();
1795 my $vmid = $param->{vmid
};
1796 my $node = $param->{node
};
1798 my $authpath = "/vms/$vmid";
1800 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1802 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1804 # Note: VNC ports are acessible from outside, so we do not gain any
1805 # security if we verify that $param->{port} belongs to VM $vmid. This
1806 # check is done by verifying the VNC ticket (inside VNC protocol).
1808 my $port = $param->{port
};
1810 return { port
=> $port };
1813 __PACKAGE__-
>register_method({
1814 name
=> 'spiceproxy',
1815 path
=> '{vmid}/spiceproxy',
1820 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1822 description
=> "Returns a SPICE configuration to connect to the VM.",
1824 additionalProperties
=> 0,
1826 node
=> get_standard_option
('pve-node'),
1827 vmid
=> get_standard_option
('pve-vmid'),
1828 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1831 returns
=> get_standard_option
('remote-viewer-config'),
1835 my $rpcenv = PVE
::RPCEnvironment
::get
();
1837 my $authuser = $rpcenv->get_user();
1839 my $vmid = $param->{vmid
};
1840 my $node = $param->{node
};
1841 my $proxy = $param->{proxy
};
1843 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1844 my $title = "VM $vmid";
1845 $title .= " - ". $conf->{name
} if $conf->{name
};
1847 my $port = PVE
::QemuServer
::spice_port
($vmid);
1849 my ($ticket, undef, $remote_viewer_config) =
1850 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1852 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1853 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1855 return $remote_viewer_config;
1858 __PACKAGE__-
>register_method({
1860 path
=> '{vmid}/status',
1863 description
=> "Directory index",
1868 additionalProperties
=> 0,
1870 node
=> get_standard_option
('pve-node'),
1871 vmid
=> get_standard_option
('pve-vmid'),
1879 subdir
=> { type
=> 'string' },
1882 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1888 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1891 { subdir
=> 'current' },
1892 { subdir
=> 'start' },
1893 { subdir
=> 'stop' },
1894 { subdir
=> 'reset' },
1895 { subdir
=> 'shutdown' },
1896 { subdir
=> 'suspend' },
1897 { subdir
=> 'reboot' },
1903 __PACKAGE__-
>register_method({
1904 name
=> 'vm_status',
1905 path
=> '{vmid}/status/current',
1908 protected
=> 1, # qemu pid files are only readable by root
1909 description
=> "Get virtual machine status.",
1911 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1914 additionalProperties
=> 0,
1916 node
=> get_standard_option
('pve-node'),
1917 vmid
=> get_standard_option
('pve-vmid'),
1923 %$PVE::QemuServer
::vmstatus_return_properties
,
1925 description
=> "HA manager service status.",
1929 description
=> "Qemu VGA configuration supports spice.",
1934 description
=> "Qemu GuestAgent enabled in config.",
1944 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1946 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1947 my $status = $vmstatus->{$param->{vmid
}};
1949 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1951 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1952 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1957 __PACKAGE__-
>register_method({
1959 path
=> '{vmid}/status/start',
1963 description
=> "Start virtual machine.",
1965 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1968 additionalProperties
=> 0,
1970 node
=> get_standard_option
('pve-node'),
1971 vmid
=> get_standard_option
('pve-vmid',
1972 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1973 skiplock
=> get_standard_option
('skiplock'),
1974 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1975 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1978 enum
=> ['secure', 'insecure'],
1979 description
=> "Migration traffic is encrypted using an SSH " .
1980 "tunnel by default. On secure, completely private networks " .
1981 "this can be disabled to increase performance.",
1984 migration_network
=> {
1985 type
=> 'string', format
=> 'CIDR',
1986 description
=> "CIDR of the (sub) network that is used for migration.",
1989 machine
=> get_standard_option
('pve-qemu-machine'),
1991 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1996 description
=> "Wait maximal timeout seconds.",
1999 default => 'max(30, vm memory in GiB)',
2010 my $rpcenv = PVE
::RPCEnvironment
::get
();
2011 my $authuser = $rpcenv->get_user();
2013 my $node = extract_param
($param, 'node');
2014 my $vmid = extract_param
($param, 'vmid');
2015 my $timeout = extract_param
($param, 'timeout');
2017 my $machine = extract_param
($param, 'machine');
2019 my $get_root_param = sub {
2020 my $value = extract_param
($param, $_[0]);
2021 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2022 if $value && $authuser ne 'root@pam';
2026 my $stateuri = $get_root_param->('stateuri');
2027 my $skiplock = $get_root_param->('skiplock');
2028 my $migratedfrom = $get_root_param->('migratedfrom');
2029 my $migration_type = $get_root_param->('migration_type');
2030 my $migration_network = $get_root_param->('migration_network');
2031 my $targetstorage = $get_root_param->('targetstorage');
2033 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2034 if $targetstorage && !$migratedfrom;
2036 # read spice ticket from STDIN
2038 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2039 if (defined(my $line = <STDIN
>)) {
2041 $spice_ticket = $line;
2045 PVE
::Cluster
::check_cfs_quorum
();
2047 my $storecfg = PVE
::Storage
::config
();
2049 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2053 print "Requesting HA start for VM $vmid\n";
2055 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2056 PVE
::Tools
::run_command
($cmd);
2060 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2067 syslog
('info', "start VM $vmid: $upid\n");
2069 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef, $machine,
2070 $spice_ticket, $migration_network, $migration_type, $targetstorage, $timeout);
2074 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2078 __PACKAGE__-
>register_method({
2080 path
=> '{vmid}/status/stop',
2084 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2085 "is akin to pulling the power plug of a running computer and may damage the VM data",
2087 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2090 additionalProperties
=> 0,
2092 node
=> get_standard_option
('pve-node'),
2093 vmid
=> get_standard_option
('pve-vmid',
2094 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2095 skiplock
=> get_standard_option
('skiplock'),
2096 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2098 description
=> "Wait maximal timeout seconds.",
2104 description
=> "Do not deactivate storage volumes.",
2117 my $rpcenv = PVE
::RPCEnvironment
::get
();
2118 my $authuser = $rpcenv->get_user();
2120 my $node = extract_param
($param, 'node');
2121 my $vmid = extract_param
($param, 'vmid');
2123 my $skiplock = extract_param
($param, 'skiplock');
2124 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2125 if $skiplock && $authuser ne 'root@pam';
2127 my $keepActive = extract_param
($param, 'keepActive');
2128 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2129 if $keepActive && $authuser ne 'root@pam';
2131 my $migratedfrom = extract_param
($param, 'migratedfrom');
2132 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2133 if $migratedfrom && $authuser ne 'root@pam';
2136 my $storecfg = PVE
::Storage
::config
();
2138 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2143 print "Requesting HA stop for VM $vmid\n";
2145 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2146 PVE
::Tools
::run_command
($cmd);
2150 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2156 syslog
('info', "stop VM $vmid: $upid\n");
2158 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2159 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2163 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2167 __PACKAGE__-
>register_method({
2169 path
=> '{vmid}/status/reset',
2173 description
=> "Reset virtual machine.",
2175 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2178 additionalProperties
=> 0,
2180 node
=> get_standard_option
('pve-node'),
2181 vmid
=> get_standard_option
('pve-vmid',
2182 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2183 skiplock
=> get_standard_option
('skiplock'),
2192 my $rpcenv = PVE
::RPCEnvironment
::get
();
2194 my $authuser = $rpcenv->get_user();
2196 my $node = extract_param
($param, 'node');
2198 my $vmid = extract_param
($param, 'vmid');
2200 my $skiplock = extract_param
($param, 'skiplock');
2201 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2202 if $skiplock && $authuser ne 'root@pam';
2204 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2209 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2214 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2217 __PACKAGE__-
>register_method({
2218 name
=> 'vm_shutdown',
2219 path
=> '{vmid}/status/shutdown',
2223 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2224 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2226 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2229 additionalProperties
=> 0,
2231 node
=> get_standard_option
('pve-node'),
2232 vmid
=> get_standard_option
('pve-vmid',
2233 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2234 skiplock
=> get_standard_option
('skiplock'),
2236 description
=> "Wait maximal timeout seconds.",
2242 description
=> "Make sure the VM stops.",
2248 description
=> "Do not deactivate storage volumes.",
2261 my $rpcenv = PVE
::RPCEnvironment
::get
();
2262 my $authuser = $rpcenv->get_user();
2264 my $node = extract_param
($param, 'node');
2265 my $vmid = extract_param
($param, 'vmid');
2267 my $skiplock = extract_param
($param, 'skiplock');
2268 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2269 if $skiplock && $authuser ne 'root@pam';
2271 my $keepActive = extract_param
($param, 'keepActive');
2272 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2273 if $keepActive && $authuser ne 'root@pam';
2275 my $storecfg = PVE
::Storage
::config
();
2279 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2280 # otherwise, we will infer a shutdown command, but run into the timeout,
2281 # then when the vm is resumed, it will instantly shutdown
2283 # checking the qmp status here to get feedback to the gui/cli/api
2284 # and the status query should not take too long
2285 my $qmpstatus = eval {
2286 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2287 mon_cmd
($vmid, "query-status");
2291 if (!$err && $qmpstatus->{status
} eq "paused") {
2292 if ($param->{forceStop
}) {
2293 warn "VM is paused - stop instead of shutdown\n";
2296 die "VM is paused - cannot shutdown\n";
2300 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2302 my $timeout = $param->{timeout
} // 60;
2306 print "Requesting HA stop for VM $vmid\n";
2308 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2309 PVE
::Tools
::run_command
($cmd);
2313 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2320 syslog
('info', "shutdown VM $vmid: $upid\n");
2322 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2323 $shutdown, $param->{forceStop
}, $keepActive);
2327 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2331 __PACKAGE__-
>register_method({
2332 name
=> 'vm_reboot',
2333 path
=> '{vmid}/status/reboot',
2337 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2339 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2342 additionalProperties
=> 0,
2344 node
=> get_standard_option
('pve-node'),
2345 vmid
=> get_standard_option
('pve-vmid',
2346 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2348 description
=> "Wait maximal timeout seconds for the shutdown.",
2361 my $rpcenv = PVE
::RPCEnvironment
::get
();
2362 my $authuser = $rpcenv->get_user();
2364 my $node = extract_param
($param, 'node');
2365 my $vmid = extract_param
($param, 'vmid');
2367 my $qmpstatus = eval {
2368 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2369 mon_cmd
($vmid, "query-status");
2373 if (!$err && $qmpstatus->{status
} eq "paused") {
2374 die "VM is paused - cannot shutdown\n";
2377 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2382 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2383 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2387 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2390 __PACKAGE__-
>register_method({
2391 name
=> 'vm_suspend',
2392 path
=> '{vmid}/status/suspend',
2396 description
=> "Suspend virtual machine.",
2398 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2399 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2400 " on the storage for the vmstate.",
2401 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2404 additionalProperties
=> 0,
2406 node
=> get_standard_option
('pve-node'),
2407 vmid
=> get_standard_option
('pve-vmid',
2408 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2409 skiplock
=> get_standard_option
('skiplock'),
2414 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2416 statestorage
=> get_standard_option
('pve-storage-id', {
2417 description
=> "The storage for the VM state",
2418 requires
=> 'todisk',
2420 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2430 my $rpcenv = PVE
::RPCEnvironment
::get
();
2431 my $authuser = $rpcenv->get_user();
2433 my $node = extract_param
($param, 'node');
2434 my $vmid = extract_param
($param, 'vmid');
2436 my $todisk = extract_param
($param, 'todisk') // 0;
2438 my $statestorage = extract_param
($param, 'statestorage');
2440 my $skiplock = extract_param
($param, 'skiplock');
2441 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2442 if $skiplock && $authuser ne 'root@pam';
2444 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2446 die "Cannot suspend HA managed VM to disk\n"
2447 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2449 # early check for storage permission, for better user feedback
2451 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2453 if (!$statestorage) {
2454 # get statestorage from config if none is given
2455 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2456 my $storecfg = PVE
::Storage
::config
();
2457 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2460 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2466 syslog
('info', "suspend VM $vmid: $upid\n");
2468 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2473 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2474 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2477 __PACKAGE__-
>register_method({
2478 name
=> 'vm_resume',
2479 path
=> '{vmid}/status/resume',
2483 description
=> "Resume virtual machine.",
2485 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2488 additionalProperties
=> 0,
2490 node
=> get_standard_option
('pve-node'),
2491 vmid
=> get_standard_option
('pve-vmid',
2492 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2493 skiplock
=> get_standard_option
('skiplock'),
2494 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2504 my $rpcenv = PVE
::RPCEnvironment
::get
();
2506 my $authuser = $rpcenv->get_user();
2508 my $node = extract_param
($param, 'node');
2510 my $vmid = extract_param
($param, 'vmid');
2512 my $skiplock = extract_param
($param, 'skiplock');
2513 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2514 if $skiplock && $authuser ne 'root@pam';
2516 my $nocheck = extract_param
($param, 'nocheck');
2518 my $to_disk_suspended;
2520 PVE
::QemuConfig-
>lock_config($vmid, sub {
2521 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2522 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2526 die "VM $vmid not running\n"
2527 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2532 syslog
('info', "resume VM $vmid: $upid\n");
2534 if (!$to_disk_suspended) {
2535 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2537 my $storecfg = PVE
::Storage
::config
();
2538 PVE
::QemuServer
::vm_start
($storecfg, $vmid, undef, $skiplock);
2544 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2547 __PACKAGE__-
>register_method({
2548 name
=> 'vm_sendkey',
2549 path
=> '{vmid}/sendkey',
2553 description
=> "Send key event to virtual machine.",
2555 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2558 additionalProperties
=> 0,
2560 node
=> get_standard_option
('pve-node'),
2561 vmid
=> get_standard_option
('pve-vmid',
2562 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2563 skiplock
=> get_standard_option
('skiplock'),
2565 description
=> "The key (qemu monitor encoding).",
2570 returns
=> { type
=> 'null'},
2574 my $rpcenv = PVE
::RPCEnvironment
::get
();
2576 my $authuser = $rpcenv->get_user();
2578 my $node = extract_param
($param, 'node');
2580 my $vmid = extract_param
($param, 'vmid');
2582 my $skiplock = extract_param
($param, 'skiplock');
2583 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2584 if $skiplock && $authuser ne 'root@pam';
2586 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2591 __PACKAGE__-
>register_method({
2592 name
=> 'vm_feature',
2593 path
=> '{vmid}/feature',
2597 description
=> "Check if feature for virtual machine is available.",
2599 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2602 additionalProperties
=> 0,
2604 node
=> get_standard_option
('pve-node'),
2605 vmid
=> get_standard_option
('pve-vmid'),
2607 description
=> "Feature to check.",
2609 enum
=> [ 'snapshot', 'clone', 'copy' ],
2611 snapname
=> get_standard_option
('pve-snapshot-name', {
2619 hasFeature
=> { type
=> 'boolean' },
2622 items
=> { type
=> 'string' },
2629 my $node = extract_param
($param, 'node');
2631 my $vmid = extract_param
($param, 'vmid');
2633 my $snapname = extract_param
($param, 'snapname');
2635 my $feature = extract_param
($param, 'feature');
2637 my $running = PVE
::QemuServer
::check_running
($vmid);
2639 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2642 my $snap = $conf->{snapshots
}->{$snapname};
2643 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2646 my $storecfg = PVE
::Storage
::config
();
2648 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2649 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2652 hasFeature
=> $hasFeature,
2653 nodes
=> [ keys %$nodelist ],
2657 __PACKAGE__-
>register_method({
2659 path
=> '{vmid}/clone',
2663 description
=> "Create a copy of virtual machine/template.",
2665 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2666 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2667 "'Datastore.AllocateSpace' on any used storage.",
2670 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2672 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2673 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2678 additionalProperties
=> 0,
2680 node
=> get_standard_option
('pve-node'),
2681 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2682 newid
=> get_standard_option
('pve-vmid', {
2683 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2684 description
=> 'VMID for the clone.' }),
2687 type
=> 'string', format
=> 'dns-name',
2688 description
=> "Set a name for the new VM.",
2693 description
=> "Description for the new VM.",
2697 type
=> 'string', format
=> 'pve-poolid',
2698 description
=> "Add the new VM to the specified pool.",
2700 snapname
=> get_standard_option
('pve-snapshot-name', {
2703 storage
=> get_standard_option
('pve-storage-id', {
2704 description
=> "Target storage for full clone.",
2708 description
=> "Target format for file storage. Only valid for full clone.",
2711 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2716 description
=> "Create a full copy of all disks. This is always done when " .
2717 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2719 target
=> get_standard_option
('pve-node', {
2720 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2724 description
=> "Override I/O bandwidth limit (in KiB/s).",
2728 default => 'clone limit from datacenter or storage config',
2738 my $rpcenv = PVE
::RPCEnvironment
::get
();
2739 my $authuser = $rpcenv->get_user();
2741 my $node = extract_param
($param, 'node');
2742 my $vmid = extract_param
($param, 'vmid');
2743 my $newid = extract_param
($param, 'newid');
2744 my $pool = extract_param
($param, 'pool');
2745 $rpcenv->check_pool_exist($pool) if defined($pool);
2747 my $snapname = extract_param
($param, 'snapname');
2748 my $storage = extract_param
($param, 'storage');
2749 my $format = extract_param
($param, 'format');
2750 my $target = extract_param
($param, 'target');
2752 my $localnode = PVE
::INotify
::nodename
();
2754 if ($target && ($target eq $localnode || $target eq 'localhost')) {
2758 PVE
::Cluster
::check_node_exists
($target) if $target;
2760 my $storecfg = PVE
::Storage
::config
();
2763 # check if storage is enabled on local node
2764 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2766 # check if storage is available on target node
2767 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2768 # clone only works if target storage is shared
2769 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2770 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2774 PVE
::Cluster
::check_cfs_quorum
();
2776 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2778 # exclusive lock if VM is running - else shared lock is enough;
2779 my $shared_lock = $running ?
0 : 1;
2782 # do all tests after lock but before forking worker - if possible
2784 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2785 PVE
::QemuConfig-
>check_lock($conf);
2787 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2788 die "unexpected state change\n" if $verify_running != $running;
2790 die "snapshot '$snapname' does not exist\n"
2791 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2793 my $full = extract_param
($param, 'full') // !PVE
::QemuConfig-
>is_template($conf);
2795 die "parameter 'storage' not allowed for linked clones\n"
2796 if defined($storage) && !$full;
2798 die "parameter 'format' not allowed for linked clones\n"
2799 if defined($format) && !$full;
2801 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2803 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2805 die "can't clone VM to node '$target' (VM uses local storage)\n"
2806 if $target && !$sharedvm;
2808 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2809 die "unable to create VM $newid: config file already exists\n"
2812 my $newconf = { lock => 'clone' };
2817 foreach my $opt (keys %$oldconf) {
2818 my $value = $oldconf->{$opt};
2820 # do not copy snapshot related info
2821 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2822 $opt eq 'vmstate' || $opt eq 'snapstate';
2824 # no need to copy unused images, because VMID(owner) changes anyways
2825 next if $opt =~ m/^unused\d+$/;
2827 # always change MAC! address
2828 if ($opt =~ m/^net(\d+)$/) {
2829 my $net = PVE
::QemuServer
::parse_net
($value);
2830 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2831 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2832 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2833 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2834 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2835 die "unable to parse drive options for '$opt'\n" if !$drive;
2836 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2837 $newconf->{$opt} = $value; # simply copy configuration
2839 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2840 die "Full clone feature is not supported for drive '$opt'\n"
2841 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2842 $fullclone->{$opt} = 1;
2844 # not full means clone instead of copy
2845 die "Linked clone feature is not supported for drive '$opt'\n"
2846 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2848 $drives->{$opt} = $drive;
2849 push @$vollist, $drive->{file
};
2852 # copy everything else
2853 $newconf->{$opt} = $value;
2857 # auto generate a new uuid
2858 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2859 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2860 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2861 # auto generate a new vmgenid only if the option was set for template
2862 if ($newconf->{vmgenid
}) {
2863 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2866 delete $newconf->{template
};
2868 if ($param->{name
}) {
2869 $newconf->{name
} = $param->{name
};
2871 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
2874 if ($param->{description
}) {
2875 $newconf->{description
} = $param->{description
};
2878 # create empty/temp config - this fails if VM already exists on other node
2879 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
2880 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2885 my $newvollist = [];
2892 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2894 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2896 my $bwlimit = extract_param
($param, 'bwlimit');
2898 my $total_jobs = scalar(keys %{$drives});
2901 foreach my $opt (keys %$drives) {
2902 my $drive = $drives->{$opt};
2903 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2905 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
2906 my $storage_list = [ $src_sid ];
2907 push @$storage_list, $storage if defined($storage);
2908 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
2910 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2911 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2912 $jobs, $skipcomplete, $oldconf->{agent
}, $clonelimit);
2914 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
2916 PVE
::QemuConfig-
>write_config($newid, $newconf);
2920 delete $newconf->{lock};
2922 # do not write pending changes
2923 if (my @changes = keys %{$newconf->{pending
}}) {
2924 my $pending = join(',', @changes);
2925 warn "found pending changes for '$pending', discarding for clone\n";
2926 delete $newconf->{pending
};
2929 PVE
::QemuConfig-
>write_config($newid, $newconf);
2932 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2933 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2934 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2936 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2937 die "Failed to move config to node '$target' - rename failed: $!\n"
2938 if !rename($conffile, $newconffile);
2941 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2944 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2945 sleep 1; # some storage like rbd need to wait before release volume - really?
2947 foreach my $volid (@$newvollist) {
2948 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2952 PVE
::Firewall
::remove_vmfw_conf
($newid);
2954 unlink $conffile; # avoid races -> last thing before die
2956 die "clone failed: $err";
2962 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2964 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2967 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2968 # Aquire exclusive lock lock for $newid
2969 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2974 __PACKAGE__-
>register_method({
2975 name
=> 'move_vm_disk',
2976 path
=> '{vmid}/move_disk',
2980 description
=> "Move volume to different storage.",
2982 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2984 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2985 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2989 additionalProperties
=> 0,
2991 node
=> get_standard_option
('pve-node'),
2992 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2995 description
=> "The disk you want to move.",
2996 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
2998 storage
=> get_standard_option
('pve-storage-id', {
2999 description
=> "Target storage.",
3000 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3004 description
=> "Target Format.",
3005 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3010 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3016 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3021 description
=> "Override I/O bandwidth limit (in KiB/s).",
3025 default => 'move limit from datacenter or storage config',
3031 description
=> "the task ID.",
3036 my $rpcenv = PVE
::RPCEnvironment
::get
();
3037 my $authuser = $rpcenv->get_user();
3039 my $node = extract_param
($param, 'node');
3040 my $vmid = extract_param
($param, 'vmid');
3041 my $digest = extract_param
($param, 'digest');
3042 my $disk = extract_param
($param, 'disk');
3043 my $storeid = extract_param
($param, 'storage');
3044 my $format = extract_param
($param, 'format');
3046 my $storecfg = PVE
::Storage
::config
();
3048 my $updatefn = sub {
3049 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3050 PVE
::QemuConfig-
>check_lock($conf);
3052 die "VM config checksum missmatch (file change by other user?)\n"
3053 if $digest && $digest ne $conf->{digest
};
3055 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3057 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3059 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3060 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3062 my $old_volid = $drive->{file
};
3064 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3065 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3069 die "you can't move to the same storage with same format\n" if $oldstoreid eq $storeid &&
3070 (!$format || !$oldfmt || $oldfmt eq $format);
3072 # this only checks snapshots because $disk is passed!
3073 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3074 die "you can't move a disk with snapshots and delete the source\n"
3075 if $snapshotted && $param->{delete};
3077 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3079 my $running = PVE
::QemuServer
::check_running
($vmid);
3081 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3084 my $newvollist = [];
3090 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3092 warn "moving disk with snapshots, snapshots will not be moved!\n"
3095 my $bwlimit = extract_param
($param, 'bwlimit');
3096 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3098 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3099 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit);
3101 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3103 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3105 # convert moved disk to base if part of template
3106 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3107 if PVE
::QemuConfig-
>is_template($conf);
3109 PVE
::QemuConfig-
>write_config($vmid, $conf);
3111 my $do_trim = PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
};
3112 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3113 eval { mon_cmd
($vmid, "guest-fstrim") };
3117 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3118 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3124 foreach my $volid (@$newvollist) {
3125 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3128 die "storage migration failed: $err";
3131 if ($param->{delete}) {
3133 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3134 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3140 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3143 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3146 my $check_vm_disks_local = sub {
3147 my ($storecfg, $vmconf, $vmid) = @_;
3149 my $local_disks = {};
3151 # add some more information to the disks e.g. cdrom
3152 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3153 my ($volid, $attr) = @_;
3155 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3157 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3158 return if $scfg->{shared
};
3160 # The shared attr here is just a special case where the vdisk
3161 # is marked as shared manually
3162 return if $attr->{shared
};
3163 return if $attr->{cdrom
} and $volid eq "none";
3165 if (exists $local_disks->{$volid}) {
3166 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3168 $local_disks->{$volid} = $attr;
3169 # ensure volid is present in case it's needed
3170 $local_disks->{$volid}->{volid
} = $volid;
3174 return $local_disks;
3177 __PACKAGE__-
>register_method({
3178 name
=> 'migrate_vm_precondition',
3179 path
=> '{vmid}/migrate',
3183 description
=> "Get preconditions for migration.",
3185 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3188 additionalProperties
=> 0,
3190 node
=> get_standard_option
('pve-node'),
3191 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3192 target
=> get_standard_option
('pve-node', {
3193 description
=> "Target node.",
3194 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3202 running
=> { type
=> 'boolean' },
3206 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3208 not_allowed_nodes
=> {
3211 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3215 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3217 local_resources
=> {
3219 description
=> "List local resources e.g. pci, usb"
3226 my $rpcenv = PVE
::RPCEnvironment
::get
();
3228 my $authuser = $rpcenv->get_user();
3230 PVE
::Cluster
::check_cfs_quorum
();
3234 my $vmid = extract_param
($param, 'vmid');
3235 my $target = extract_param
($param, 'target');
3236 my $localnode = PVE
::INotify
::nodename
();
3240 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3241 my $storecfg = PVE
::Storage
::config
();
3244 # try to detect errors early
3245 PVE
::QemuConfig-
>check_lock($vmconf);
3247 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3249 # if vm is not running, return target nodes where local storage is available
3250 # for offline migration
3251 if (!$res->{running
}) {
3252 $res->{allowed_nodes
} = [];
3253 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3254 delete $checked_nodes->{$localnode};
3256 foreach my $node (keys %$checked_nodes) {
3257 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3258 push @{$res->{allowed_nodes
}}, $node;
3262 $res->{not_allowed_nodes
} = $checked_nodes;
3266 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3267 $res->{local_disks
} = [ values %$local_disks ];;
3269 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3271 $res->{local_resources
} = $local_resources;
3278 __PACKAGE__-
>register_method({
3279 name
=> 'migrate_vm',
3280 path
=> '{vmid}/migrate',
3284 description
=> "Migrate virtual machine. Creates a new migration task.",
3286 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3289 additionalProperties
=> 0,
3291 node
=> get_standard_option
('pve-node'),
3292 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3293 target
=> get_standard_option
('pve-node', {
3294 description
=> "Target node.",
3295 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3299 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3304 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3309 enum
=> ['secure', 'insecure'],
3310 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3313 migration_network
=> {
3314 type
=> 'string', format
=> 'CIDR',
3315 description
=> "CIDR of the (sub) network that is used for migration.",
3318 "with-local-disks" => {
3320 description
=> "Enable live storage migration for local disk",
3323 targetstorage
=> get_standard_option
('pve-storage-id', {
3324 description
=> "Default target storage.",
3326 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3329 description
=> "Override I/O bandwidth limit (in KiB/s).",
3333 default => 'migrate limit from datacenter or storage config',
3339 description
=> "the task ID.",
3344 my $rpcenv = PVE
::RPCEnvironment
::get
();
3345 my $authuser = $rpcenv->get_user();
3347 my $target = extract_param
($param, 'target');
3349 my $localnode = PVE
::INotify
::nodename
();
3350 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3352 PVE
::Cluster
::check_cfs_quorum
();
3354 PVE
::Cluster
::check_node_exists
($target);
3356 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3358 my $vmid = extract_param
($param, 'vmid');
3360 raise_param_exc
({ force
=> "Only root may use this option." })
3361 if $param->{force
} && $authuser ne 'root@pam';
3363 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3364 if $param->{migration_type
} && $authuser ne 'root@pam';
3366 # allow root only until better network permissions are available
3367 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3368 if $param->{migration_network
} && $authuser ne 'root@pam';
3371 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3373 # try to detect errors early
3375 PVE
::QemuConfig-
>check_lock($conf);
3377 if (PVE
::QemuServer
::check_running
($vmid)) {
3378 die "can't migrate running VM without --online\n" if !$param->{online
};
3380 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
3381 $param->{online
} = 0;
3384 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3385 if !$param->{online
} && $param->{targetstorage
};
3387 my $storecfg = PVE
::Storage
::config
();
3389 if( $param->{targetstorage
}) {
3390 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3392 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3395 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3400 print "Requesting HA migration for VM $vmid to node $target\n";
3402 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3403 PVE
::Tools
::run_command
($cmd);
3407 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3412 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3416 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3419 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3424 __PACKAGE__-
>register_method({
3426 path
=> '{vmid}/monitor',
3430 description
=> "Execute Qemu monitor commands.",
3432 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3433 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3436 additionalProperties
=> 0,
3438 node
=> get_standard_option
('pve-node'),
3439 vmid
=> get_standard_option
('pve-vmid'),
3442 description
=> "The monitor command.",
3446 returns
=> { type
=> 'string'},
3450 my $rpcenv = PVE
::RPCEnvironment
::get
();
3451 my $authuser = $rpcenv->get_user();
3454 my $command = shift;
3455 return $command =~ m/^\s*info(\s+|$)/
3456 || $command =~ m/^\s*help\s*$/;
3459 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3460 if !&$is_ro($param->{command
});
3462 my $vmid = $param->{vmid
};
3464 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3468 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3470 $res = "ERROR: $@" if $@;
3475 __PACKAGE__-
>register_method({
3476 name
=> 'resize_vm',
3477 path
=> '{vmid}/resize',
3481 description
=> "Extend volume size.",
3483 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3486 additionalProperties
=> 0,
3488 node
=> get_standard_option
('pve-node'),
3489 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3490 skiplock
=> get_standard_option
('skiplock'),
3493 description
=> "The disk you want to resize.",
3494 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3498 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3499 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.",
3503 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3509 returns
=> { type
=> 'null'},
3513 my $rpcenv = PVE
::RPCEnvironment
::get
();
3515 my $authuser = $rpcenv->get_user();
3517 my $node = extract_param
($param, 'node');
3519 my $vmid = extract_param
($param, 'vmid');
3521 my $digest = extract_param
($param, 'digest');
3523 my $disk = extract_param
($param, 'disk');
3525 my $sizestr = extract_param
($param, 'size');
3527 my $skiplock = extract_param
($param, 'skiplock');
3528 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3529 if $skiplock && $authuser ne 'root@pam';
3531 my $storecfg = PVE
::Storage
::config
();
3533 my $updatefn = sub {
3535 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3537 die "checksum missmatch (file change by other user?)\n"
3538 if $digest && $digest ne $conf->{digest
};
3539 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3541 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3543 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3545 my (undef, undef, undef, undef, undef, undef, $format) =
3546 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3548 die "can't resize volume: $disk if snapshot exists\n"
3549 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3551 my $volid = $drive->{file
};
3553 die "disk '$disk' has no associated volume\n" if !$volid;
3555 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3557 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3559 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3561 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3562 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3564 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3566 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3567 my ($ext, $newsize, $unit) = ($1, $2, $4);
3570 $newsize = $newsize * 1024;
3571 } elsif ($unit eq 'M') {
3572 $newsize = $newsize * 1024 * 1024;
3573 } elsif ($unit eq 'G') {
3574 $newsize = $newsize * 1024 * 1024 * 1024;
3575 } elsif ($unit eq 'T') {
3576 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3579 $newsize += $size if $ext;
3580 $newsize = int($newsize);
3582 die "shrinking disks is not supported\n" if $newsize < $size;
3584 return if $size == $newsize;
3586 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3588 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3590 my $effective_size = eval { PVE
::Storage
::volume_size_info
($storecfg, $volid, 3); };
3591 $drive->{size
} = $effective_size // $newsize;
3592 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
3594 PVE
::QemuConfig-
>write_config($vmid, $conf);
3597 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3601 __PACKAGE__-
>register_method({
3602 name
=> 'snapshot_list',
3603 path
=> '{vmid}/snapshot',
3605 description
=> "List all snapshots.",
3607 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3610 protected
=> 1, # qemu pid files are only readable by root
3612 additionalProperties
=> 0,
3614 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3615 node
=> get_standard_option
('pve-node'),
3624 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3628 description
=> "Snapshot includes RAM.",
3633 description
=> "Snapshot description.",
3637 description
=> "Snapshot creation time",
3639 renderer
=> 'timestamp',
3643 description
=> "Parent snapshot identifier.",
3649 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3654 my $vmid = $param->{vmid
};
3656 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3657 my $snaphash = $conf->{snapshots
} || {};
3661 foreach my $name (keys %$snaphash) {
3662 my $d = $snaphash->{$name};
3665 snaptime
=> $d->{snaptime
} || 0,
3666 vmstate
=> $d->{vmstate
} ?
1 : 0,
3667 description
=> $d->{description
} || '',
3669 $item->{parent
} = $d->{parent
} if $d->{parent
};
3670 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3674 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3677 digest
=> $conf->{digest
},
3678 running
=> $running,
3679 description
=> "You are here!",
3681 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3683 push @$res, $current;
3688 __PACKAGE__-
>register_method({
3690 path
=> '{vmid}/snapshot',
3694 description
=> "Snapshot a VM.",
3696 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3699 additionalProperties
=> 0,
3701 node
=> get_standard_option
('pve-node'),
3702 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3703 snapname
=> get_standard_option
('pve-snapshot-name'),
3707 description
=> "Save the vmstate",
3712 description
=> "A textual description or comment.",
3718 description
=> "the task ID.",
3723 my $rpcenv = PVE
::RPCEnvironment
::get
();
3725 my $authuser = $rpcenv->get_user();
3727 my $node = extract_param
($param, 'node');
3729 my $vmid = extract_param
($param, 'vmid');
3731 my $snapname = extract_param
($param, 'snapname');
3733 die "unable to use snapshot name 'current' (reserved name)\n"
3734 if $snapname eq 'current';
3736 die "unable to use snapshot name 'pending' (reserved name)\n"
3737 if lc($snapname) eq 'pending';
3740 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3741 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3742 $param->{description
});
3745 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3748 __PACKAGE__-
>register_method({
3749 name
=> 'snapshot_cmd_idx',
3750 path
=> '{vmid}/snapshot/{snapname}',
3757 additionalProperties
=> 0,
3759 vmid
=> get_standard_option
('pve-vmid'),
3760 node
=> get_standard_option
('pve-node'),
3761 snapname
=> get_standard_option
('pve-snapshot-name'),
3770 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3777 push @$res, { cmd
=> 'rollback' };
3778 push @$res, { cmd
=> 'config' };
3783 __PACKAGE__-
>register_method({
3784 name
=> 'update_snapshot_config',
3785 path
=> '{vmid}/snapshot/{snapname}/config',
3789 description
=> "Update snapshot metadata.",
3791 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3794 additionalProperties
=> 0,
3796 node
=> get_standard_option
('pve-node'),
3797 vmid
=> get_standard_option
('pve-vmid'),
3798 snapname
=> get_standard_option
('pve-snapshot-name'),
3802 description
=> "A textual description or comment.",
3806 returns
=> { type
=> 'null' },
3810 my $rpcenv = PVE
::RPCEnvironment
::get
();
3812 my $authuser = $rpcenv->get_user();
3814 my $vmid = extract_param
($param, 'vmid');
3816 my $snapname = extract_param
($param, 'snapname');
3818 return undef if !defined($param->{description
});
3820 my $updatefn = sub {
3822 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3824 PVE
::QemuConfig-
>check_lock($conf);
3826 my $snap = $conf->{snapshots
}->{$snapname};
3828 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3830 $snap->{description
} = $param->{description
} if defined($param->{description
});
3832 PVE
::QemuConfig-
>write_config($vmid, $conf);
3835 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3840 __PACKAGE__-
>register_method({
3841 name
=> 'get_snapshot_config',
3842 path
=> '{vmid}/snapshot/{snapname}/config',
3845 description
=> "Get snapshot configuration",
3847 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
3850 additionalProperties
=> 0,
3852 node
=> get_standard_option
('pve-node'),
3853 vmid
=> get_standard_option
('pve-vmid'),
3854 snapname
=> get_standard_option
('pve-snapshot-name'),
3857 returns
=> { type
=> "object" },
3861 my $rpcenv = PVE
::RPCEnvironment
::get
();
3863 my $authuser = $rpcenv->get_user();
3865 my $vmid = extract_param
($param, 'vmid');
3867 my $snapname = extract_param
($param, 'snapname');
3869 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3871 my $snap = $conf->{snapshots
}->{$snapname};
3873 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3878 __PACKAGE__-
>register_method({
3880 path
=> '{vmid}/snapshot/{snapname}/rollback',
3884 description
=> "Rollback VM state to specified snapshot.",
3886 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3889 additionalProperties
=> 0,
3891 node
=> get_standard_option
('pve-node'),
3892 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3893 snapname
=> get_standard_option
('pve-snapshot-name'),
3898 description
=> "the task ID.",
3903 my $rpcenv = PVE
::RPCEnvironment
::get
();
3905 my $authuser = $rpcenv->get_user();
3907 my $node = extract_param
($param, 'node');
3909 my $vmid = extract_param
($param, 'vmid');
3911 my $snapname = extract_param
($param, 'snapname');
3914 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3915 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3919 # hold migration lock, this makes sure that nobody create replication snapshots
3920 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3923 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3926 __PACKAGE__-
>register_method({
3927 name
=> 'delsnapshot',
3928 path
=> '{vmid}/snapshot/{snapname}',
3932 description
=> "Delete a VM snapshot.",
3934 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3937 additionalProperties
=> 0,
3939 node
=> get_standard_option
('pve-node'),
3940 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3941 snapname
=> get_standard_option
('pve-snapshot-name'),
3945 description
=> "For removal from config file, even if removing disk snapshots fails.",
3951 description
=> "the task ID.",
3956 my $rpcenv = PVE
::RPCEnvironment
::get
();
3958 my $authuser = $rpcenv->get_user();
3960 my $node = extract_param
($param, 'node');
3962 my $vmid = extract_param
($param, 'vmid');
3964 my $snapname = extract_param
($param, 'snapname');
3967 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3968 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3971 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3974 __PACKAGE__-
>register_method({
3976 path
=> '{vmid}/template',
3980 description
=> "Create a Template.",
3982 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3983 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3986 additionalProperties
=> 0,
3988 node
=> get_standard_option
('pve-node'),
3989 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3993 description
=> "If you want to convert only 1 disk to base image.",
3994 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3999 returns
=> { type
=> 'null'},
4003 my $rpcenv = PVE
::RPCEnvironment
::get
();
4005 my $authuser = $rpcenv->get_user();
4007 my $node = extract_param
($param, 'node');
4009 my $vmid = extract_param
($param, 'vmid');
4011 my $disk = extract_param
($param, 'disk');
4013 my $updatefn = sub {
4015 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4017 PVE
::QemuConfig-
>check_lock($conf);
4019 die "unable to create template, because VM contains snapshots\n"
4020 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4022 die "you can't convert a template to a template\n"
4023 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4025 die "you can't convert a VM to template if VM is running\n"
4026 if PVE
::QemuServer
::check_running
($vmid);
4029 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4032 $conf->{template
} = 1;
4033 PVE
::QemuConfig-
>write_config($vmid, $conf);
4035 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4038 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4042 __PACKAGE__-
>register_method({
4043 name
=> 'cloudinit_generated_config_dump',
4044 path
=> '{vmid}/cloudinit/dump',
4047 description
=> "Get automatically generated cloudinit config.",
4049 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4052 additionalProperties
=> 0,
4054 node
=> get_standard_option
('pve-node'),
4055 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4057 description
=> 'Config type.',
4059 enum
=> ['user', 'network', 'meta'],
4069 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4071 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});