1 package PVE
::API2
::Qemu
;
12 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
14 use PVE
::Tools
qw(extract_param);
15 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
17 use PVE
::JSONSchema
qw(get_standard_option);
19 use PVE
::ReplicationConfig
;
20 use PVE
::GuestHelpers
;
24 use PVE
::RPCEnvironment
;
25 use PVE
::AccessControl
;
29 use PVE
::API2
::Firewall
::VM
;
30 use PVE
::API2
::Qemu
::Agent
;
33 if (!$ENV{PVE_GENERATING_DOCS
}) {
34 require PVE
::HA
::Env
::PVE2
;
35 import PVE
::HA
::Env
::PVE2
;
36 require PVE
::HA
::Config
;
37 import PVE
::HA
::Config
;
41 use Data
::Dumper
; # fixme: remove
43 use base
qw(PVE::RESTHandler);
45 my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal.";
47 my $resolve_cdrom_alias = sub {
50 if (my $value = $param->{cdrom
}) {
51 $value .= ",media=cdrom" if $value !~ m/media=/;
52 $param->{ide2
} = $value;
53 delete $param->{cdrom
};
57 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
58 my $check_storage_access = sub {
59 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
61 PVE
::QemuServer
::foreach_drive
($settings, sub {
62 my ($ds, $drive) = @_;
64 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
66 my $volid = $drive->{file
};
68 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit')) {
70 } elsif ($volid =~ m/^(([^:\s]+):)?(cloudinit)$/) {
72 } elsif ($isCDROM && ($volid eq 'cdrom')) {
73 $rpcenv->check($authuser, "/", ['Sys.Console']);
74 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
75 my ($storeid, $size) = ($2 || $default_storage, $3);
76 die "no storage ID specified (and no default storage)\n" if !$storeid;
77 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
78 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
79 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
80 if !$scfg->{content
}->{images
};
82 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
86 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
87 if defined($settings->{vmstatestorage
});
90 my $check_storage_access_clone = sub {
91 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
95 PVE
::QemuServer
::foreach_drive
($conf, sub {
96 my ($ds, $drive) = @_;
98 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
100 my $volid = $drive->{file
};
102 return if !$volid || $volid eq 'none';
105 if ($volid eq 'cdrom') {
106 $rpcenv->check($authuser, "/", ['Sys.Console']);
108 # we simply allow access
109 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
110 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
111 $sharedvm = 0 if !$scfg->{shared
};
115 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
116 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
117 $sharedvm = 0 if !$scfg->{shared
};
119 $sid = $storage if $storage;
120 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
124 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
125 if defined($conf->{vmstatestorage
});
130 # Note: $pool is only needed when creating a VM, because pool permissions
131 # are automatically inherited if VM already exists inside a pool.
132 my $create_disks = sub {
133 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
140 my ($ds, $disk) = @_;
142 my $volid = $disk->{file
};
144 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
145 delete $disk->{size
};
146 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
147 } elsif ($volid =~ m!^(?:([^/:\s]+):)?cloudinit$!) {
148 my $storeid = $1 || $default_storage;
149 die "no storage ID specified (and no default storage)\n" if !$storeid;
150 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
151 my $name = "vm-$vmid-cloudinit";
155 $fmt = $disk->{format
} // "qcow2";
158 $fmt = $disk->{format
} // "raw";
161 # Initial disk created with 4 MB and aligned to 4MB on regeneration
162 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
163 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
164 $disk->{file
} = $volid;
165 $disk->{media
} = 'cdrom';
166 push @$vollist, $volid;
167 delete $disk->{format
}; # no longer needed
168 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
169 } elsif ($volid =~ $NEW_DISK_RE) {
170 my ($storeid, $size) = ($2 || $default_storage, $3);
171 die "no storage ID specified (and no default storage)\n" if !$storeid;
172 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
173 my $fmt = $disk->{format
} || $defformat;
175 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
178 if ($ds eq 'efidisk0') {
179 ($volid, $size) = PVE
::QemuServer
::create_efidisk
($storecfg, $storeid, $vmid, $fmt, $arch);
181 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
183 push @$vollist, $volid;
184 $disk->{file
} = $volid;
185 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
186 delete $disk->{format
}; # no longer needed
187 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
190 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
192 my $volid_is_new = 1;
195 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
196 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
201 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
203 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
205 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
207 die "volume $volid does not exists\n" if !$size;
209 $disk->{size
} = $size;
212 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
216 eval { PVE
::QemuServer
::foreach_drive
($settings, $code); };
218 # free allocated images on error
220 syslog
('err', "VM $vmid creating disks failed");
221 foreach my $volid (@$vollist) {
222 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
228 # modify vm config if everything went well
229 foreach my $ds (keys %$res) {
230 $conf->{$ds} = $res->{$ds};
247 my $memoryoptions = {
253 my $hwtypeoptions = {
266 my $generaloptions = {
273 'migrate_downtime' => 1,
274 'migrate_speed' => 1,
286 my $vmpoweroptions = {
293 'vmstatestorage' => 1,
296 my $cloudinitoptions = {
306 my $check_vm_modify_config_perm = sub {
307 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
309 return 1 if $authuser eq 'root@pam';
311 foreach my $opt (@$key_list) {
312 # some checks (e.g., disk, serial port, usb) need to be done somewhere
313 # else, as there the permission can be value dependend
314 next if PVE
::QemuServer
::is_valid_drivename
($opt);
315 next if $opt eq 'cdrom';
316 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
319 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
320 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
321 } elsif ($memoryoptions->{$opt}) {
322 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
323 } elsif ($hwtypeoptions->{$opt}) {
324 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
325 } elsif ($generaloptions->{$opt}) {
326 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
327 # special case for startup since it changes host behaviour
328 if ($opt eq 'startup') {
329 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
331 } elsif ($vmpoweroptions->{$opt}) {
332 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
333 } elsif ($diskoptions->{$opt}) {
334 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
335 } elsif ($cloudinitoptions->{$opt} || ($opt =~ m/^(?:net|ipconfig)\d+$/)) {
336 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
338 # catches hostpci\d+, args, lock, etc.
339 # new options will be checked here
340 die "only root can set '$opt' config\n";
347 __PACKAGE__-
>register_method({
351 description
=> "Virtual machine index (per node).",
353 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
357 protected
=> 1, # qemu pid files are only readable by root
359 additionalProperties
=> 0,
361 node
=> get_standard_option
('pve-node'),
365 description
=> "Determine the full status of active VMs.",
373 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
375 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
380 my $rpcenv = PVE
::RPCEnvironment
::get
();
381 my $authuser = $rpcenv->get_user();
383 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
386 foreach my $vmid (keys %$vmstatus) {
387 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
389 my $data = $vmstatus->{$vmid};
398 __PACKAGE__-
>register_method({
402 description
=> "Create or restore a virtual machine.",
404 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
405 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
406 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
407 user
=> 'all', # check inside
412 additionalProperties
=> 0,
413 properties
=> PVE
::QemuServer
::json_config_properties
(
415 node
=> get_standard_option
('pve-node'),
416 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
418 description
=> "The backup file.",
422 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
424 storage
=> get_standard_option
('pve-storage-id', {
425 description
=> "Default storage.",
427 completion
=> \
&PVE
::QemuServer
::complete_storage
,
432 description
=> "Allow to overwrite existing VM.",
433 requires
=> 'archive',
438 description
=> "Assign a unique random ethernet address.",
439 requires
=> 'archive',
443 type
=> 'string', format
=> 'pve-poolid',
444 description
=> "Add the VM to the specified pool.",
447 description
=> "Override I/O bandwidth limit (in KiB/s).",
451 default => 'restore limit from datacenter or storage config',
457 description
=> "Start VM after it was created successfully.",
467 my $rpcenv = PVE
::RPCEnvironment
::get
();
469 my $authuser = $rpcenv->get_user();
471 my $node = extract_param
($param, 'node');
473 my $vmid = extract_param
($param, 'vmid');
475 my $archive = extract_param
($param, 'archive');
476 my $is_restore = !!$archive;
478 my $storage = extract_param
($param, 'storage');
480 my $force = extract_param
($param, 'force');
482 my $unique = extract_param
($param, 'unique');
484 my $pool = extract_param
($param, 'pool');
486 my $bwlimit = extract_param
($param, 'bwlimit');
488 my $start_after_create = extract_param
($param, 'start');
490 my $filename = PVE
::QemuConfig-
>config_file($vmid);
492 my $storecfg = PVE
::Storage
::config
();
494 if (defined(my $ssh_keys = $param->{sshkeys
})) {
495 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
496 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
499 PVE
::Cluster
::check_cfs_quorum
();
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
($vmid, $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;
579 if ($start_after_create) {
580 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
585 # ensure no old replication state are exists
586 PVE
::ReplicationState
::delete_guest_states
($vmid);
588 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
592 # ensure no old replication state are exists
593 PVE
::ReplicationState
::delete_guest_states
($vmid);
601 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
605 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
607 if (!$conf->{bootdisk
}) {
608 my $firstdisk = PVE
::QemuServer
::resolve_first_disk
($conf);
609 $conf->{bootdisk
} = $firstdisk if $firstdisk;
612 # auto generate uuid if user did not specify smbios1 option
613 if (!$conf->{smbios1
}) {
614 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
617 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
618 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
621 PVE
::QemuConfig-
>write_config($vmid, $conf);
627 foreach my $volid (@$vollist) {
628 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
634 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
637 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
639 if ($start_after_create) {
640 print "Execute autostart\n";
641 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
646 my ($code, $worker_name);
648 $worker_name = 'qmrestore';
650 eval { $restorefn->() };
652 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
658 $worker_name = 'qmcreate';
660 eval { $createfn->() };
663 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
664 unlink($conffile) or die "failed to remove config file: $!\n";
672 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
675 __PACKAGE__-
>register_method({
680 description
=> "Directory index",
685 additionalProperties
=> 0,
687 node
=> get_standard_option
('pve-node'),
688 vmid
=> get_standard_option
('pve-vmid'),
696 subdir
=> { type
=> 'string' },
699 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
705 { subdir
=> 'config' },
706 { subdir
=> 'pending' },
707 { subdir
=> 'status' },
708 { subdir
=> 'unlink' },
709 { subdir
=> 'vncproxy' },
710 { subdir
=> 'termproxy' },
711 { subdir
=> 'migrate' },
712 { subdir
=> 'resize' },
713 { subdir
=> 'move' },
715 { subdir
=> 'rrddata' },
716 { subdir
=> 'monitor' },
717 { subdir
=> 'agent' },
718 { subdir
=> 'snapshot' },
719 { subdir
=> 'spiceproxy' },
720 { subdir
=> 'sendkey' },
721 { subdir
=> 'firewall' },
727 __PACKAGE__-
>register_method ({
728 subclass
=> "PVE::API2::Firewall::VM",
729 path
=> '{vmid}/firewall',
732 __PACKAGE__-
>register_method ({
733 subclass
=> "PVE::API2::Qemu::Agent",
734 path
=> '{vmid}/agent',
737 __PACKAGE__-
>register_method({
739 path
=> '{vmid}/rrd',
741 protected
=> 1, # fixme: can we avoid that?
743 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
745 description
=> "Read VM RRD statistics (returns PNG)",
747 additionalProperties
=> 0,
749 node
=> get_standard_option
('pve-node'),
750 vmid
=> get_standard_option
('pve-vmid'),
752 description
=> "Specify the time frame you are interested in.",
754 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
757 description
=> "The list of datasources you want to display.",
758 type
=> 'string', format
=> 'pve-configid-list',
761 description
=> "The RRD consolidation function",
763 enum
=> [ 'AVERAGE', 'MAX' ],
771 filename
=> { type
=> 'string' },
777 return PVE
::Cluster
::create_rrd_graph
(
778 "pve2-vm/$param->{vmid}", $param->{timeframe
},
779 $param->{ds
}, $param->{cf
});
783 __PACKAGE__-
>register_method({
785 path
=> '{vmid}/rrddata',
787 protected
=> 1, # fixme: can we avoid that?
789 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
791 description
=> "Read VM RRD statistics",
793 additionalProperties
=> 0,
795 node
=> get_standard_option
('pve-node'),
796 vmid
=> get_standard_option
('pve-vmid'),
798 description
=> "Specify the time frame you are interested in.",
800 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
803 description
=> "The RRD consolidation function",
805 enum
=> [ 'AVERAGE', 'MAX' ],
820 return PVE
::Cluster
::create_rrd_data
(
821 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
825 __PACKAGE__-
>register_method({
827 path
=> '{vmid}/config',
830 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
832 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
835 additionalProperties
=> 0,
837 node
=> get_standard_option
('pve-node'),
838 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
840 description
=> "Get current values (instead of pending values).",
845 snapshot
=> get_standard_option
('pve-snapshot-name', {
846 description
=> "Fetch config values from given snapshot.",
849 my ($cmd, $pname, $cur, $args) = @_;
850 PVE
::QemuConfig-
>snapshot_list($args->[0]);
856 description
=> "The current VM configuration.",
858 properties
=> PVE
::QemuServer
::json_config_properties
({
861 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
868 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
870 if (my $snapname = $param->{snapshot
}) {
871 my $snapshot = $conf->{snapshots
}->{$snapname};
872 die "snapshot '$snapname' does not exist\n" if !defined($snapshot);
874 $snapshot->{digest
} = $conf->{digest
}; # keep file digest for API
879 delete $conf->{snapshots
};
881 if (!$param->{current
}) {
882 foreach my $opt (keys %{$conf->{pending
}}) {
883 next if $opt eq 'delete';
884 my $value = $conf->{pending
}->{$opt};
885 next if ref($value); # just to be sure
886 $conf->{$opt} = $value;
888 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
889 foreach my $opt (keys %$pending_delete_hash) {
890 delete $conf->{$opt} if $conf->{$opt};
894 delete $conf->{pending
};
896 # hide cloudinit password
897 if ($conf->{cipassword
}) {
898 $conf->{cipassword
} = '**********';
904 __PACKAGE__-
>register_method({
905 name
=> 'vm_pending',
906 path
=> '{vmid}/pending',
909 description
=> "Get virtual machine configuration, including pending changes.",
911 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
914 additionalProperties
=> 0,
916 node
=> get_standard_option
('pve-node'),
917 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
926 description
=> "Configuration option name.",
930 description
=> "Current value.",
935 description
=> "Pending value.",
940 description
=> "Indicates a pending delete request if present and not 0. " .
941 "The value 2 indicates a force-delete request.",
953 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
955 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
959 foreach my $opt (keys %$conf) {
960 next if ref($conf->{$opt});
961 my $item = { key
=> $opt };
962 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
963 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
964 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
966 # hide cloudinit password
967 if ($opt eq 'cipassword') {
968 $item->{value
} = '**********' if defined($item->{value
});
969 # the trailing space so that the pending string is different
970 $item->{pending
} = '********** ' if defined($item->{pending
});
975 foreach my $opt (keys %{$conf->{pending
}}) {
976 next if $opt eq 'delete';
977 next if ref($conf->{pending
}->{$opt}); # just to be sure
978 next if defined($conf->{$opt});
979 my $item = { key
=> $opt };
980 $item->{pending
} = $conf->{pending
}->{$opt};
982 # hide cloudinit password
983 if ($opt eq 'cipassword') {
984 $item->{pending
} = '**********' if defined($item->{pending
});
989 while (my ($opt, $force) = each %$pending_delete_hash) {
990 next if $conf->{pending
}->{$opt}; # just to be sure
991 next if $conf->{$opt};
992 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
999 # POST/PUT {vmid}/config implementation
1001 # The original API used PUT (idempotent) an we assumed that all operations
1002 # are fast. But it turned out that almost any configuration change can
1003 # involve hot-plug actions, or disk alloc/free. Such actions can take long
1004 # time to complete and have side effects (not idempotent).
1006 # The new implementation uses POST and forks a worker process. We added
1007 # a new option 'background_delay'. If specified we wait up to
1008 # 'background_delay' second for the worker task to complete. It returns null
1009 # if the task is finished within that time, else we return the UPID.
1011 my $update_vm_api = sub {
1012 my ($param, $sync) = @_;
1014 my $rpcenv = PVE
::RPCEnvironment
::get
();
1016 my $authuser = $rpcenv->get_user();
1018 my $node = extract_param
($param, 'node');
1020 my $vmid = extract_param
($param, 'vmid');
1022 my $digest = extract_param
($param, 'digest');
1024 my $background_delay = extract_param
($param, 'background_delay');
1026 if (defined(my $cipassword = $param->{cipassword
})) {
1027 # Same logic as in cloud-init (but with the regex fixed...)
1028 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
1029 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1032 my @paramarr = (); # used for log message
1033 foreach my $key (sort keys %$param) {
1034 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1035 push @paramarr, "-$key", $value;
1038 my $skiplock = extract_param
($param, 'skiplock');
1039 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1040 if $skiplock && $authuser ne 'root@pam';
1042 my $delete_str = extract_param
($param, 'delete');
1044 my $revert_str = extract_param
($param, 'revert');
1046 my $force = extract_param
($param, 'force');
1048 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1049 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1050 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1053 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1055 my $storecfg = PVE
::Storage
::config
();
1057 my $defaults = PVE
::QemuServer
::load_defaults
();
1059 &$resolve_cdrom_alias($param);
1061 # now try to verify all parameters
1064 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1065 if (!PVE
::QemuServer
::option_exists
($opt)) {
1066 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1069 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1070 "-revert $opt' at the same time" })
1071 if defined($param->{$opt});
1073 $revert->{$opt} = 1;
1077 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1078 $opt = 'ide2' if $opt eq 'cdrom';
1080 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1081 "-delete $opt' at the same time" })
1082 if defined($param->{$opt});
1084 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1085 "-revert $opt' at the same time" })
1088 if (!PVE
::QemuServer
::option_exists
($opt)) {
1089 raise_param_exc
({ delete => "unknown option '$opt'" });
1095 my $repl_conf = PVE
::ReplicationConfig-
>new();
1096 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1097 my $check_replication = sub {
1099 return if !$is_replicated;
1100 my $volid = $drive->{file
};
1101 return if !$volid || !($drive->{replicate
}//1);
1102 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1103 my ($storeid, $format);
1104 if ($volid =~ $NEW_DISK_RE) {
1106 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1108 ($storeid, undef) = PVE
::Storage
::parse_volume_id
($volid, 1);
1109 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1111 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1112 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1113 return if $scfg->{shared
};
1114 die "cannot add non-replicatable volume to a replicated VM\n";
1117 foreach my $opt (keys %$param) {
1118 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1119 # cleanup drive path
1120 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1121 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1122 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1123 $check_replication->($drive);
1124 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1125 } elsif ($opt =~ m/^net(\d+)$/) {
1127 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1128 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1129 } elsif ($opt eq 'vmgenid') {
1130 if ($param->{$opt} eq '1') {
1131 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1133 } elsif ($opt eq 'hookscript') {
1134 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1135 raise_param_exc
({ $opt => $@ }) if $@;
1139 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1141 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1143 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1145 my $updatefn = sub {
1147 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1149 die "checksum missmatch (file change by other user?)\n"
1150 if $digest && $digest ne $conf->{digest
};
1152 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1154 foreach my $opt (keys %$revert) {
1155 if (defined($conf->{$opt})) {
1156 $param->{$opt} = $conf->{$opt};
1157 } elsif (defined($conf->{pending
}->{$opt})) {
1162 if ($param->{memory
} || defined($param->{balloon
})) {
1163 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1164 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1166 die "balloon value too large (must be smaller than assigned memory)\n"
1167 if $balloon && $balloon > $maxmem;
1170 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1174 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1176 # write updates to pending section
1178 my $modified = {}; # record what $option we modify
1180 foreach my $opt (@delete) {
1181 $modified->{$opt} = 1;
1182 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1184 # value of what we want to delete, independent if pending or not
1185 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1186 if (!defined($val)) {
1187 warn "cannot delete '$opt' - not set in current configuration!\n";
1188 $modified->{$opt} = 0;
1191 my $is_pending_val = defined($conf->{pending
}->{$opt});
1193 if ($opt =~ m/^unused/) {
1194 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1195 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1196 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1197 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1198 delete $conf->{$opt};
1199 PVE
::QemuConfig-
>write_config($vmid, $conf);
1201 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1202 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1203 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1204 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1206 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1207 PVE
::QemuConfig-
>write_config($vmid, $conf);
1208 } elsif ($opt =~ m/^serial\d+$/) {
1209 if ($val eq 'socket') {
1210 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1211 } elsif ($authuser ne 'root@pam') {
1212 die "only root can delete '$opt' config for real devices\n";
1214 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1215 PVE
::QemuConfig-
>write_config($vmid, $conf);
1216 } elsif ($opt =~ m/^usb\d+$/) {
1217 if ($val =~ m/spice/) {
1218 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1219 } elsif ($authuser ne 'root@pam') {
1220 die "only root can delete '$opt' config for real devices\n";
1222 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1223 PVE
::QemuConfig-
>write_config($vmid, $conf);
1225 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1226 PVE
::QemuConfig-
>write_config($vmid, $conf);
1230 foreach my $opt (keys %$param) { # add/change
1231 $modified->{$opt} = 1;
1232 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1233 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1235 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
1237 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1238 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1239 # FIXME: cloudinit: CDROM or Disk?
1240 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1241 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1243 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1245 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1246 if defined($conf->{pending
}->{$opt});
1248 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1249 } elsif ($opt =~ m/^serial\d+/) {
1250 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1251 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1252 } elsif ($authuser ne 'root@pam') {
1253 die "only root can modify '$opt' config for real devices\n";
1255 $conf->{pending
}->{$opt} = $param->{$opt};
1256 } elsif ($opt =~ m/^usb\d+/) {
1257 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1258 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1259 } elsif ($authuser ne 'root@pam') {
1260 die "only root can modify '$opt' config for real devices\n";
1262 $conf->{pending
}->{$opt} = $param->{$opt};
1264 $conf->{pending
}->{$opt} = $param->{$opt};
1266 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1267 PVE
::QemuConfig-
>write_config($vmid, $conf);
1270 # remove pending changes when nothing changed
1271 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1272 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1273 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1275 return if !scalar(keys %{$conf->{pending
}});
1277 my $running = PVE
::QemuServer
::check_running
($vmid);
1279 # apply pending changes
1281 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1285 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1286 raise_param_exc
($errors) if scalar(keys %$errors);
1288 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1298 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1300 if ($background_delay) {
1302 # Note: It would be better to do that in the Event based HTTPServer
1303 # to avoid blocking call to sleep.
1305 my $end_time = time() + $background_delay;
1307 my $task = PVE
::Tools
::upid_decode
($upid);
1310 while (time() < $end_time) {
1311 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1313 sleep(1); # this gets interrupted when child process ends
1317 my $status = PVE
::Tools
::upid_read_status
($upid);
1318 return undef if $status eq 'OK';
1327 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1330 my $vm_config_perm_list = [
1335 'VM.Config.Network',
1337 'VM.Config.Options',
1340 __PACKAGE__-
>register_method({
1341 name
=> 'update_vm_async',
1342 path
=> '{vmid}/config',
1346 description
=> "Set virtual machine options (asynchrounous API).",
1348 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1351 additionalProperties
=> 0,
1352 properties
=> PVE
::QemuServer
::json_config_properties
(
1354 node
=> get_standard_option
('pve-node'),
1355 vmid
=> get_standard_option
('pve-vmid'),
1356 skiplock
=> get_standard_option
('skiplock'),
1358 type
=> 'string', format
=> 'pve-configid-list',
1359 description
=> "A list of settings you want to delete.",
1363 type
=> 'string', format
=> 'pve-configid-list',
1364 description
=> "Revert a pending change.",
1369 description
=> $opt_force_description,
1371 requires
=> 'delete',
1375 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1379 background_delay
=> {
1381 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1392 code
=> $update_vm_api,
1395 __PACKAGE__-
>register_method({
1396 name
=> 'update_vm',
1397 path
=> '{vmid}/config',
1401 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1403 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1406 additionalProperties
=> 0,
1407 properties
=> PVE
::QemuServer
::json_config_properties
(
1409 node
=> get_standard_option
('pve-node'),
1410 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1411 skiplock
=> get_standard_option
('skiplock'),
1413 type
=> 'string', format
=> 'pve-configid-list',
1414 description
=> "A list of settings you want to delete.",
1418 type
=> 'string', format
=> 'pve-configid-list',
1419 description
=> "Revert a pending change.",
1424 description
=> $opt_force_description,
1426 requires
=> 'delete',
1430 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1436 returns
=> { type
=> 'null' },
1439 &$update_vm_api($param, 1);
1444 __PACKAGE__-
>register_method({
1445 name
=> 'destroy_vm',
1450 description
=> "Destroy the vm (also delete all used/owned volumes).",
1452 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1455 additionalProperties
=> 0,
1457 node
=> get_standard_option
('pve-node'),
1458 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1459 skiplock
=> get_standard_option
('skiplock'),
1468 my $rpcenv = PVE
::RPCEnvironment
::get
();
1469 my $authuser = $rpcenv->get_user();
1470 my $vmid = $param->{vmid
};
1472 my $skiplock = $param->{skiplock
};
1473 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1474 if $skiplock && $authuser ne 'root@pam';
1477 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1478 my $storecfg = PVE
::Storage
::config
();
1479 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1480 die "unable to remove VM $vmid - used in HA resources\n"
1481 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1483 # do not allow destroy if there are replication jobs
1484 my $repl_conf = PVE
::ReplicationConfig-
>new();
1485 $repl_conf->check_for_existing_jobs($vmid);
1487 # early tests (repeat after locking)
1488 die "VM $vmid is running - destroy failed\n"
1489 if PVE
::QemuServer
::check_running
($vmid);
1494 syslog
('info', "destroy VM $vmid: $upid\n");
1495 PVE
::QemuConfig-
>lock_config($vmid, sub {
1496 die "VM $vmid is running - destroy failed\n"
1497 if (PVE
::QemuServer
::check_running
($vmid));
1498 PVE
::QemuServer
::destroy_vm
($storecfg, $vmid, 1, $skiplock);
1499 PVE
::AccessControl
::remove_vm_access
($vmid);
1500 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1501 unlink PVE
::QemuConfig-
>config_file($vmid)
1502 or die "Removal of VM $vmid config file failed: $!\n";
1506 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1509 __PACKAGE__-
>register_method({
1511 path
=> '{vmid}/unlink',
1515 description
=> "Unlink/delete disk images.",
1517 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1520 additionalProperties
=> 0,
1522 node
=> get_standard_option
('pve-node'),
1523 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1525 type
=> 'string', format
=> 'pve-configid-list',
1526 description
=> "A list of disk IDs you want to delete.",
1530 description
=> $opt_force_description,
1535 returns
=> { type
=> 'null'},
1539 $param->{delete} = extract_param
($param, 'idlist');
1541 __PACKAGE__-
>update_vm($param);
1548 __PACKAGE__-
>register_method({
1550 path
=> '{vmid}/vncproxy',
1554 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1556 description
=> "Creates a TCP VNC proxy connections.",
1558 additionalProperties
=> 0,
1560 node
=> get_standard_option
('pve-node'),
1561 vmid
=> get_standard_option
('pve-vmid'),
1565 description
=> "starts websockify instead of vncproxy",
1570 additionalProperties
=> 0,
1572 user
=> { type
=> 'string' },
1573 ticket
=> { type
=> 'string' },
1574 cert
=> { type
=> 'string' },
1575 port
=> { type
=> 'integer' },
1576 upid
=> { type
=> 'string' },
1582 my $rpcenv = PVE
::RPCEnvironment
::get
();
1584 my $authuser = $rpcenv->get_user();
1586 my $vmid = $param->{vmid
};
1587 my $node = $param->{node
};
1588 my $websocket = $param->{websocket
};
1590 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1591 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1593 my $authpath = "/vms/$vmid";
1595 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1597 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1603 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1604 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1605 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1606 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1607 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1609 $family = PVE
::Tools
::get_host_address_family
($node);
1612 my $port = PVE
::Tools
::next_vnc_port
($family);
1619 syslog
('info', "starting vnc proxy $upid\n");
1625 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1627 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1628 '-timeout', $timeout, '-authpath', $authpath,
1629 '-perm', 'Sys.Console'];
1631 if ($param->{websocket
}) {
1632 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1633 push @$cmd, '-notls', '-listen', 'localhost';
1636 push @$cmd, '-c', @$remcmd, @$termcmd;
1638 PVE
::Tools
::run_command
($cmd);
1642 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1644 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1646 my $sock = IO
::Socket
::IP-
>new(
1651 GetAddrInfoFlags
=> 0,
1652 ) or die "failed to create socket: $!\n";
1653 # Inside the worker we shouldn't have any previous alarms
1654 # running anyway...:
1656 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1658 accept(my $cli, $sock) or die "connection failed: $!\n";
1661 if (PVE
::Tools
::run_command
($cmd,
1662 output
=> '>&'.fileno($cli),
1663 input
=> '<&'.fileno($cli),
1666 die "Failed to run vncproxy.\n";
1673 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1675 PVE
::Tools
::wait_for_vnc_port
($port);
1686 __PACKAGE__-
>register_method({
1687 name
=> 'termproxy',
1688 path
=> '{vmid}/termproxy',
1692 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1694 description
=> "Creates a TCP proxy connections.",
1696 additionalProperties
=> 0,
1698 node
=> get_standard_option
('pve-node'),
1699 vmid
=> get_standard_option
('pve-vmid'),
1703 enum
=> [qw(serial0 serial1 serial2 serial3)],
1704 description
=> "opens a serial terminal (defaults to display)",
1709 additionalProperties
=> 0,
1711 user
=> { type
=> 'string' },
1712 ticket
=> { type
=> 'string' },
1713 port
=> { type
=> 'integer' },
1714 upid
=> { type
=> 'string' },
1720 my $rpcenv = PVE
::RPCEnvironment
::get
();
1722 my $authuser = $rpcenv->get_user();
1724 my $vmid = $param->{vmid
};
1725 my $node = $param->{node
};
1726 my $serial = $param->{serial
};
1728 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1730 if (!defined($serial)) {
1731 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1732 $serial = $conf->{vga
};
1736 my $authpath = "/vms/$vmid";
1738 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1743 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1744 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1745 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1746 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, '-t');
1747 push @$remcmd, '--';
1749 $family = PVE
::Tools
::get_host_address_family
($node);
1752 my $port = PVE
::Tools
::next_vnc_port
($family);
1754 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1755 push @$termcmd, '-iface', $serial if $serial;
1760 syslog
('info', "starting qemu termproxy $upid\n");
1762 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1763 '--perm', 'VM.Console', '--'];
1764 push @$cmd, @$remcmd, @$termcmd;
1766 PVE
::Tools
::run_command
($cmd);
1769 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1771 PVE
::Tools
::wait_for_vnc_port
($port);
1781 __PACKAGE__-
>register_method({
1782 name
=> 'vncwebsocket',
1783 path
=> '{vmid}/vncwebsocket',
1786 description
=> "You also need to pass a valid ticket (vncticket).",
1787 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1789 description
=> "Opens a weksocket for VNC traffic.",
1791 additionalProperties
=> 0,
1793 node
=> get_standard_option
('pve-node'),
1794 vmid
=> get_standard_option
('pve-vmid'),
1796 description
=> "Ticket from previous call to vncproxy.",
1801 description
=> "Port number returned by previous vncproxy call.",
1811 port
=> { type
=> 'string' },
1817 my $rpcenv = PVE
::RPCEnvironment
::get
();
1819 my $authuser = $rpcenv->get_user();
1821 my $vmid = $param->{vmid
};
1822 my $node = $param->{node
};
1824 my $authpath = "/vms/$vmid";
1826 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1828 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1830 # Note: VNC ports are acessible from outside, so we do not gain any
1831 # security if we verify that $param->{port} belongs to VM $vmid. This
1832 # check is done by verifying the VNC ticket (inside VNC protocol).
1834 my $port = $param->{port
};
1836 return { port
=> $port };
1839 __PACKAGE__-
>register_method({
1840 name
=> 'spiceproxy',
1841 path
=> '{vmid}/spiceproxy',
1846 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1848 description
=> "Returns a SPICE configuration to connect to the VM.",
1850 additionalProperties
=> 0,
1852 node
=> get_standard_option
('pve-node'),
1853 vmid
=> get_standard_option
('pve-vmid'),
1854 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1857 returns
=> get_standard_option
('remote-viewer-config'),
1861 my $rpcenv = PVE
::RPCEnvironment
::get
();
1863 my $authuser = $rpcenv->get_user();
1865 my $vmid = $param->{vmid
};
1866 my $node = $param->{node
};
1867 my $proxy = $param->{proxy
};
1869 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1870 my $title = "VM $vmid";
1871 $title .= " - ". $conf->{name
} if $conf->{name
};
1873 my $port = PVE
::QemuServer
::spice_port
($vmid);
1875 my ($ticket, undef, $remote_viewer_config) =
1876 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1878 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1879 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1881 return $remote_viewer_config;
1884 __PACKAGE__-
>register_method({
1886 path
=> '{vmid}/status',
1889 description
=> "Directory index",
1894 additionalProperties
=> 0,
1896 node
=> get_standard_option
('pve-node'),
1897 vmid
=> get_standard_option
('pve-vmid'),
1905 subdir
=> { type
=> 'string' },
1908 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1914 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1917 { subdir
=> 'current' },
1918 { subdir
=> 'start' },
1919 { subdir
=> 'stop' },
1920 { subdir
=> 'reset' },
1921 { subdir
=> 'shutdown' },
1922 { subdir
=> 'suspend' },
1923 { subdir
=> 'reboot' },
1929 __PACKAGE__-
>register_method({
1930 name
=> 'vm_status',
1931 path
=> '{vmid}/status/current',
1934 protected
=> 1, # qemu pid files are only readable by root
1935 description
=> "Get virtual machine status.",
1937 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1940 additionalProperties
=> 0,
1942 node
=> get_standard_option
('pve-node'),
1943 vmid
=> get_standard_option
('pve-vmid'),
1949 %$PVE::QemuServer
::vmstatus_return_properties
,
1951 description
=> "HA manager service status.",
1955 description
=> "Qemu VGA configuration supports spice.",
1960 description
=> "Qemu GuestAgent enabled in config.",
1970 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1972 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1973 my $status = $vmstatus->{$param->{vmid
}};
1975 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1977 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1978 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1983 __PACKAGE__-
>register_method({
1985 path
=> '{vmid}/status/start',
1989 description
=> "Start virtual machine.",
1991 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1994 additionalProperties
=> 0,
1996 node
=> get_standard_option
('pve-node'),
1997 vmid
=> get_standard_option
('pve-vmid',
1998 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1999 skiplock
=> get_standard_option
('skiplock'),
2000 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2001 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2004 enum
=> ['secure', 'insecure'],
2005 description
=> "Migration traffic is encrypted using an SSH " .
2006 "tunnel by default. On secure, completely private networks " .
2007 "this can be disabled to increase performance.",
2010 migration_network
=> {
2011 type
=> 'string', format
=> 'CIDR',
2012 description
=> "CIDR of the (sub) network that is used for migration.",
2015 machine
=> get_standard_option
('pve-qm-machine'),
2017 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
2029 my $rpcenv = PVE
::RPCEnvironment
::get
();
2030 my $authuser = $rpcenv->get_user();
2032 my $node = extract_param
($param, 'node');
2033 my $vmid = extract_param
($param, 'vmid');
2035 my $machine = extract_param
($param, 'machine');
2037 my $get_root_param = sub {
2038 my $value = extract_param
($param, $_[0]);
2039 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2040 if $value && $authuser ne 'root@pam';
2044 my $stateuri = $get_root_param->('stateuri');
2045 my $skiplock = $get_root_param->('skiplock');
2046 my $migratedfrom = $get_root_param->('migratedfrom');
2047 my $migration_type = $get_root_param->('migration_type');
2048 my $migration_network = $get_root_param->('migration_network');
2049 my $targetstorage = $get_root_param->('targetstorage');
2051 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2052 if $targetstorage && !$migratedfrom;
2054 # read spice ticket from STDIN
2056 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2057 if (defined(my $line = <STDIN
>)) {
2059 $spice_ticket = $line;
2063 PVE
::Cluster
::check_cfs_quorum
();
2065 my $storecfg = PVE
::Storage
::config
();
2067 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2071 print "Requesting HA start for VM $vmid\n";
2073 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2074 PVE
::Tools
::run_command
($cmd);
2078 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2085 syslog
('info', "start VM $vmid: $upid\n");
2087 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2088 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
2092 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2096 __PACKAGE__-
>register_method({
2098 path
=> '{vmid}/status/stop',
2102 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2103 "is akin to pulling the power plug of a running computer and may damage the VM data",
2105 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2108 additionalProperties
=> 0,
2110 node
=> get_standard_option
('pve-node'),
2111 vmid
=> get_standard_option
('pve-vmid',
2112 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2113 skiplock
=> get_standard_option
('skiplock'),
2114 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2116 description
=> "Wait maximal timeout seconds.",
2122 description
=> "Do not deactivate storage volumes.",
2135 my $rpcenv = PVE
::RPCEnvironment
::get
();
2136 my $authuser = $rpcenv->get_user();
2138 my $node = extract_param
($param, 'node');
2139 my $vmid = extract_param
($param, 'vmid');
2141 my $skiplock = extract_param
($param, 'skiplock');
2142 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2143 if $skiplock && $authuser ne 'root@pam';
2145 my $keepActive = extract_param
($param, 'keepActive');
2146 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2147 if $keepActive && $authuser ne 'root@pam';
2149 my $migratedfrom = extract_param
($param, 'migratedfrom');
2150 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2151 if $migratedfrom && $authuser ne 'root@pam';
2154 my $storecfg = PVE
::Storage
::config
();
2156 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2161 print "Requesting HA stop for VM $vmid\n";
2163 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'stopped'];
2164 PVE
::Tools
::run_command
($cmd);
2168 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2174 syslog
('info', "stop VM $vmid: $upid\n");
2176 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2177 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2181 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2185 __PACKAGE__-
>register_method({
2187 path
=> '{vmid}/status/reset',
2191 description
=> "Reset virtual machine.",
2193 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2196 additionalProperties
=> 0,
2198 node
=> get_standard_option
('pve-node'),
2199 vmid
=> get_standard_option
('pve-vmid',
2200 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2201 skiplock
=> get_standard_option
('skiplock'),
2210 my $rpcenv = PVE
::RPCEnvironment
::get
();
2212 my $authuser = $rpcenv->get_user();
2214 my $node = extract_param
($param, 'node');
2216 my $vmid = extract_param
($param, 'vmid');
2218 my $skiplock = extract_param
($param, 'skiplock');
2219 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2220 if $skiplock && $authuser ne 'root@pam';
2222 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2227 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2232 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2235 __PACKAGE__-
>register_method({
2236 name
=> 'vm_shutdown',
2237 path
=> '{vmid}/status/shutdown',
2241 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2242 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2244 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2247 additionalProperties
=> 0,
2249 node
=> get_standard_option
('pve-node'),
2250 vmid
=> get_standard_option
('pve-vmid',
2251 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2252 skiplock
=> get_standard_option
('skiplock'),
2254 description
=> "Wait maximal timeout seconds.",
2260 description
=> "Make sure the VM stops.",
2266 description
=> "Do not deactivate storage volumes.",
2279 my $rpcenv = PVE
::RPCEnvironment
::get
();
2280 my $authuser = $rpcenv->get_user();
2282 my $node = extract_param
($param, 'node');
2283 my $vmid = extract_param
($param, 'vmid');
2285 my $skiplock = extract_param
($param, 'skiplock');
2286 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2287 if $skiplock && $authuser ne 'root@pam';
2289 my $keepActive = extract_param
($param, 'keepActive');
2290 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2291 if $keepActive && $authuser ne 'root@pam';
2293 my $storecfg = PVE
::Storage
::config
();
2297 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2298 # otherwise, we will infer a shutdown command, but run into the timeout,
2299 # then when the vm is resumed, it will instantly shutdown
2301 # checking the qmp status here to get feedback to the gui/cli/api
2302 # and the status query should not take too long
2303 my $qmpstatus = eval {
2304 PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2308 if (!$err && $qmpstatus->{status
} eq "paused") {
2309 if ($param->{forceStop
}) {
2310 warn "VM is paused - stop instead of shutdown\n";
2313 die "VM is paused - cannot shutdown\n";
2317 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2322 print "Requesting HA stop for VM $vmid\n";
2324 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'stopped'];
2325 PVE
::Tools
::run_command
($cmd);
2329 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2336 syslog
('info', "shutdown VM $vmid: $upid\n");
2338 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2339 $shutdown, $param->{forceStop
}, $keepActive);
2343 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2347 __PACKAGE__-
>register_method({
2348 name
=> 'vm_reboot',
2349 path
=> '{vmid}/status/reboot',
2353 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2355 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2358 additionalProperties
=> 0,
2360 node
=> get_standard_option
('pve-node'),
2361 vmid
=> get_standard_option
('pve-vmid',
2362 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2364 description
=> "Wait maximal timeout seconds for the shutdown.",
2377 my $rpcenv = PVE
::RPCEnvironment
::get
();
2378 my $authuser = $rpcenv->get_user();
2380 my $node = extract_param
($param, 'node');
2381 my $vmid = extract_param
($param, 'vmid');
2383 my $qmpstatus = eval {
2384 PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2388 if (!$err && $qmpstatus->{status
} eq "paused") {
2389 die "VM is paused - cannot shutdown\n";
2392 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2397 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2398 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2402 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2405 __PACKAGE__-
>register_method({
2406 name
=> 'vm_suspend',
2407 path
=> '{vmid}/status/suspend',
2411 description
=> "Suspend virtual machine.",
2413 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2416 additionalProperties
=> 0,
2418 node
=> get_standard_option
('pve-node'),
2419 vmid
=> get_standard_option
('pve-vmid',
2420 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2421 skiplock
=> get_standard_option
('skiplock'),
2426 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2428 statestorage
=> get_standard_option
('pve-storage-id', {
2429 description
=> "The storage for the VM state",
2430 requires
=> 'todisk',
2432 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2442 my $rpcenv = PVE
::RPCEnvironment
::get
();
2443 my $authuser = $rpcenv->get_user();
2445 my $node = extract_param
($param, 'node');
2446 my $vmid = extract_param
($param, 'vmid');
2448 my $todisk = extract_param
($param, 'todisk') // 0;
2450 my $statestorage = extract_param
($param, 'statestorage');
2452 my $skiplock = extract_param
($param, 'skiplock');
2453 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2454 if $skiplock && $authuser ne 'root@pam';
2456 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2458 die "Cannot suspend HA managed VM to disk\n"
2459 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2464 syslog
('info', "suspend VM $vmid: $upid\n");
2466 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2471 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2472 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2475 __PACKAGE__-
>register_method({
2476 name
=> 'vm_resume',
2477 path
=> '{vmid}/status/resume',
2481 description
=> "Resume virtual machine.",
2483 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2486 additionalProperties
=> 0,
2488 node
=> get_standard_option
('pve-node'),
2489 vmid
=> get_standard_option
('pve-vmid',
2490 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2491 skiplock
=> get_standard_option
('skiplock'),
2492 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2502 my $rpcenv = PVE
::RPCEnvironment
::get
();
2504 my $authuser = $rpcenv->get_user();
2506 my $node = extract_param
($param, 'node');
2508 my $vmid = extract_param
($param, 'vmid');
2510 my $skiplock = extract_param
($param, 'skiplock');
2511 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2512 if $skiplock && $authuser ne 'root@pam';
2514 my $nocheck = extract_param
($param, 'nocheck');
2516 my $to_disk_suspended;
2518 PVE
::QemuConfig-
>lock_config($vmid, sub {
2519 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2520 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2524 die "VM $vmid not running\n"
2525 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2530 syslog
('info', "resume VM $vmid: $upid\n");
2532 if (!$to_disk_suspended) {
2533 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2535 my $storecfg = PVE
::Storage
::config
();
2536 PVE
::QemuServer
::vm_start
($storecfg, $vmid, undef, $skiplock);
2542 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2545 __PACKAGE__-
>register_method({
2546 name
=> 'vm_sendkey',
2547 path
=> '{vmid}/sendkey',
2551 description
=> "Send key event to virtual machine.",
2553 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2556 additionalProperties
=> 0,
2558 node
=> get_standard_option
('pve-node'),
2559 vmid
=> get_standard_option
('pve-vmid',
2560 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2561 skiplock
=> get_standard_option
('skiplock'),
2563 description
=> "The key (qemu monitor encoding).",
2568 returns
=> { type
=> 'null'},
2572 my $rpcenv = PVE
::RPCEnvironment
::get
();
2574 my $authuser = $rpcenv->get_user();
2576 my $node = extract_param
($param, 'node');
2578 my $vmid = extract_param
($param, 'vmid');
2580 my $skiplock = extract_param
($param, 'skiplock');
2581 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2582 if $skiplock && $authuser ne 'root@pam';
2584 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2589 __PACKAGE__-
>register_method({
2590 name
=> 'vm_feature',
2591 path
=> '{vmid}/feature',
2595 description
=> "Check if feature for virtual machine is available.",
2597 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2600 additionalProperties
=> 0,
2602 node
=> get_standard_option
('pve-node'),
2603 vmid
=> get_standard_option
('pve-vmid'),
2605 description
=> "Feature to check.",
2607 enum
=> [ 'snapshot', 'clone', 'copy' ],
2609 snapname
=> get_standard_option
('pve-snapshot-name', {
2617 hasFeature
=> { type
=> 'boolean' },
2620 items
=> { type
=> 'string' },
2627 my $node = extract_param
($param, 'node');
2629 my $vmid = extract_param
($param, 'vmid');
2631 my $snapname = extract_param
($param, 'snapname');
2633 my $feature = extract_param
($param, 'feature');
2635 my $running = PVE
::QemuServer
::check_running
($vmid);
2637 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2640 my $snap = $conf->{snapshots
}->{$snapname};
2641 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2644 my $storecfg = PVE
::Storage
::config
();
2646 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2647 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2650 hasFeature
=> $hasFeature,
2651 nodes
=> [ keys %$nodelist ],
2655 __PACKAGE__-
>register_method({
2657 path
=> '{vmid}/clone',
2661 description
=> "Create a copy of virtual machine/template.",
2663 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2664 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2665 "'Datastore.AllocateSpace' on any used storage.",
2668 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2670 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2671 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2676 additionalProperties
=> 0,
2678 node
=> get_standard_option
('pve-node'),
2679 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2680 newid
=> get_standard_option
('pve-vmid', {
2681 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2682 description
=> 'VMID for the clone.' }),
2685 type
=> 'string', format
=> 'dns-name',
2686 description
=> "Set a name for the new VM.",
2691 description
=> "Description for the new VM.",
2695 type
=> 'string', format
=> 'pve-poolid',
2696 description
=> "Add the new VM to the specified pool.",
2698 snapname
=> get_standard_option
('pve-snapshot-name', {
2701 storage
=> get_standard_option
('pve-storage-id', {
2702 description
=> "Target storage for full clone.",
2706 description
=> "Target format for file storage. Only valid for full clone.",
2709 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2714 description
=> "Create a full copy of all disks. This is always done when " .
2715 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2717 target
=> get_standard_option
('pve-node', {
2718 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2722 description
=> "Override I/O bandwidth limit (in KiB/s).",
2726 default => 'clone limit from datacenter or storage config',
2736 my $rpcenv = PVE
::RPCEnvironment
::get
();
2738 my $authuser = $rpcenv->get_user();
2740 my $node = extract_param
($param, 'node');
2742 my $vmid = extract_param
($param, 'vmid');
2744 my $newid = extract_param
($param, 'newid');
2746 my $pool = extract_param
($param, 'pool');
2748 if (defined($pool)) {
2749 $rpcenv->check_pool_exist($pool);
2752 my $snapname = extract_param
($param, 'snapname');
2754 my $storage = extract_param
($param, 'storage');
2756 my $format = extract_param
($param, 'format');
2758 my $target = extract_param
($param, 'target');
2760 my $localnode = PVE
::INotify
::nodename
();
2762 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2764 PVE
::Cluster
::check_node_exists
($target) if $target;
2766 my $storecfg = PVE
::Storage
::config
();
2769 # check if storage is enabled on local node
2770 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2772 # check if storage is available on target node
2773 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2774 # clone only works if target storage is shared
2775 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2776 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2780 PVE
::Cluster
::check_cfs_quorum
();
2782 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2784 # exclusive lock if VM is running - else shared lock is enough;
2785 my $shared_lock = $running ?
0 : 1;
2789 # do all tests after lock
2790 # we also try to do all tests before we fork the worker
2792 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2794 PVE
::QemuConfig-
>check_lock($conf);
2796 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2798 die "unexpected state change\n" if $verify_running != $running;
2800 die "snapshot '$snapname' does not exist\n"
2801 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2803 my $full = extract_param
($param, 'full');
2804 if (!defined($full)) {
2805 $full = !PVE
::QemuConfig-
>is_template($conf);
2808 die "parameter 'storage' not allowed for linked clones\n"
2809 if defined($storage) && !$full;
2811 die "parameter 'format' not allowed for linked clones\n"
2812 if defined($format) && !$full;
2814 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2816 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2818 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2820 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2822 die "unable to create VM $newid: config file already exists\n"
2825 my $newconf = { lock => 'clone' };
2830 foreach my $opt (keys %$oldconf) {
2831 my $value = $oldconf->{$opt};
2833 # do not copy snapshot related info
2834 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2835 $opt eq 'vmstate' || $opt eq 'snapstate';
2837 # no need to copy unused images, because VMID(owner) changes anyways
2838 next if $opt =~ m/^unused\d+$/;
2840 # always change MAC! address
2841 if ($opt =~ m/^net(\d+)$/) {
2842 my $net = PVE
::QemuServer
::parse_net
($value);
2843 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2844 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2845 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2846 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2847 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2848 die "unable to parse drive options for '$opt'\n" if !$drive;
2849 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2850 $newconf->{$opt} = $value; # simply copy configuration
2853 die "Full clone feature is not supported for drive '$opt'\n"
2854 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2855 $fullclone->{$opt} = 1;
2857 # not full means clone instead of copy
2858 die "Linked clone feature is not supported for drive '$opt'\n"
2859 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2861 $drives->{$opt} = $drive;
2862 push @$vollist, $drive->{file
};
2865 # copy everything else
2866 $newconf->{$opt} = $value;
2870 # auto generate a new uuid
2871 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2872 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2873 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2875 # auto generate a new vmgenid if the option was set
2876 if ($newconf->{vmgenid
}) {
2877 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2880 delete $newconf->{template
};
2882 if ($param->{name
}) {
2883 $newconf->{name
} = $param->{name
};
2885 if ($oldconf->{name
}) {
2886 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2888 $newconf->{name
} = "Copy-of-VM-$vmid";
2892 if ($param->{description
}) {
2893 $newconf->{description
} = $param->{description
};
2896 # create empty/temp config - this fails if VM already exists on other node
2897 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2902 my $newvollist = [];
2909 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2911 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2913 my $bwlimit = extract_param
($param, 'bwlimit');
2915 my $total_jobs = scalar(keys %{$drives});
2918 foreach my $opt (keys %$drives) {
2919 my $drive = $drives->{$opt};
2920 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2922 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
2923 my $storage_list = [ $src_sid ];
2924 push @$storage_list, $storage if defined($storage);
2925 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
2927 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2928 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2929 $jobs, $skipcomplete, $oldconf->{agent
}, $clonelimit);
2931 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2933 PVE
::QemuConfig-
>write_config($newid, $newconf);
2937 delete $newconf->{lock};
2939 # do not write pending changes
2940 if (my @changes = keys %{$newconf->{pending
}}) {
2941 my $pending = join(',', @changes);
2942 warn "found pending changes for '$pending', discarding for clone\n";
2943 delete $newconf->{pending
};
2946 PVE
::QemuConfig-
>write_config($newid, $newconf);
2949 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2950 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2951 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2953 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2954 die "Failed to move config to node '$target' - rename failed: $!\n"
2955 if !rename($conffile, $newconffile);
2958 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2963 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2965 sleep 1; # some storage like rbd need to wait before release volume - really?
2967 foreach my $volid (@$newvollist) {
2968 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2971 die "clone failed: $err";
2977 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2979 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2982 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2983 # Aquire exclusive lock lock for $newid
2984 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2989 __PACKAGE__-
>register_method({
2990 name
=> 'move_vm_disk',
2991 path
=> '{vmid}/move_disk',
2995 description
=> "Move volume to different storage.",
2997 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2999 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3000 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
3004 additionalProperties
=> 0,
3006 node
=> get_standard_option
('pve-node'),
3007 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3010 description
=> "The disk you want to move.",
3011 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
3013 storage
=> get_standard_option
('pve-storage-id', {
3014 description
=> "Target storage.",
3015 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3019 description
=> "Target Format.",
3020 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3025 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3031 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3036 description
=> "Override I/O bandwidth limit (in KiB/s).",
3040 default => 'move limit from datacenter or storage config',
3046 description
=> "the task ID.",
3051 my $rpcenv = PVE
::RPCEnvironment
::get
();
3053 my $authuser = $rpcenv->get_user();
3055 my $node = extract_param
($param, 'node');
3057 my $vmid = extract_param
($param, 'vmid');
3059 my $digest = extract_param
($param, 'digest');
3061 my $disk = extract_param
($param, 'disk');
3063 my $storeid = extract_param
($param, 'storage');
3065 my $format = extract_param
($param, 'format');
3067 my $storecfg = PVE
::Storage
::config
();
3069 my $updatefn = sub {
3071 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3073 PVE
::QemuConfig-
>check_lock($conf);
3075 die "checksum missmatch (file change by other user?)\n"
3076 if $digest && $digest ne $conf->{digest
};
3078 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3080 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3082 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
3084 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3087 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3088 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3092 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
3093 (!$format || !$oldfmt || $oldfmt eq $format);
3095 # this only checks snapshots because $disk is passed!
3096 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3097 die "you can't move a disk with snapshots and delete the source\n"
3098 if $snapshotted && $param->{delete};
3100 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3102 my $running = PVE
::QemuServer
::check_running
($vmid);
3104 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3108 my $newvollist = [];
3114 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3116 warn "moving disk with snapshots, snapshots will not be moved!\n"
3119 my $bwlimit = extract_param
($param, 'bwlimit');
3120 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3122 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3123 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit);
3125 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
3127 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3129 # convert moved disk to base if part of template
3130 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3131 if PVE
::QemuConfig-
>is_template($conf);
3133 PVE
::QemuConfig-
>write_config($vmid, $conf);
3135 if ($running && PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
} && PVE
::QemuServer
::qga_check_running
($vmid)) {
3136 eval { PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-fstrim"); };
3140 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3141 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3148 foreach my $volid (@$newvollist) {
3149 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3152 die "storage migration failed: $err";
3155 if ($param->{delete}) {
3157 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3158 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3164 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3167 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3170 my $check_vm_disks_local = sub {
3171 my ($storecfg, $vmconf, $vmid) = @_;
3173 my $local_disks = {};
3175 # add some more information to the disks e.g. cdrom
3176 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3177 my ($volid, $attr) = @_;
3179 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3181 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3182 return if $scfg->{shared
};
3184 # The shared attr here is just a special case where the vdisk
3185 # is marked as shared manually
3186 return if $attr->{shared
};
3187 return if $attr->{cdrom
} and $volid eq "none";
3189 if (exists $local_disks->{$volid}) {
3190 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3192 $local_disks->{$volid} = $attr;
3193 # ensure volid is present in case it's needed
3194 $local_disks->{$volid}->{volid
} = $volid;
3198 return $local_disks;
3201 __PACKAGE__-
>register_method({
3202 name
=> 'migrate_vm_precondition',
3203 path
=> '{vmid}/migrate',
3207 description
=> "Get preconditions for migration.",
3209 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3212 additionalProperties
=> 0,
3214 node
=> get_standard_option
('pve-node'),
3215 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3216 target
=> get_standard_option
('pve-node', {
3217 description
=> "Target node.",
3218 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3226 running
=> { type
=> 'boolean' },
3230 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3232 not_allowed_nodes
=> {
3235 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3239 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3241 local_resources
=> {
3243 description
=> "List local resources e.g. pci, usb"
3250 my $rpcenv = PVE
::RPCEnvironment
::get
();
3252 my $authuser = $rpcenv->get_user();
3254 PVE
::Cluster
::check_cfs_quorum
();
3258 my $vmid = extract_param
($param, 'vmid');
3259 my $target = extract_param
($param, 'target');
3260 my $localnode = PVE
::INotify
::nodename
();
3264 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3265 my $storecfg = PVE
::Storage
::config
();
3268 # try to detect errors early
3269 PVE
::QemuConfig-
>check_lock($vmconf);
3271 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3273 # if vm is not running, return target nodes where local storage is available
3274 # for offline migration
3275 if (!$res->{running
}) {
3276 $res->{allowed_nodes
} = [];
3277 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3278 delete $checked_nodes->{$localnode};
3280 foreach my $node (keys %$checked_nodes) {
3281 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3282 push @{$res->{allowed_nodes
}}, $node;
3286 $res->{not_allowed_nodes
} = $checked_nodes;
3290 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3291 $res->{local_disks
} = [ values %$local_disks ];;
3293 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3295 $res->{local_resources
} = $local_resources;
3302 __PACKAGE__-
>register_method({
3303 name
=> 'migrate_vm',
3304 path
=> '{vmid}/migrate',
3308 description
=> "Migrate virtual machine. Creates a new migration task.",
3310 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3313 additionalProperties
=> 0,
3315 node
=> get_standard_option
('pve-node'),
3316 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3317 target
=> get_standard_option
('pve-node', {
3318 description
=> "Target node.",
3319 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3323 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3328 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3333 enum
=> ['secure', 'insecure'],
3334 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3337 migration_network
=> {
3338 type
=> 'string', format
=> 'CIDR',
3339 description
=> "CIDR of the (sub) network that is used for migration.",
3342 "with-local-disks" => {
3344 description
=> "Enable live storage migration for local disk",
3347 targetstorage
=> get_standard_option
('pve-storage-id', {
3348 description
=> "Default target storage.",
3350 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3353 description
=> "Override I/O bandwidth limit (in KiB/s).",
3357 default => 'migrate limit from datacenter or storage config',
3363 description
=> "the task ID.",
3368 my $rpcenv = PVE
::RPCEnvironment
::get
();
3369 my $authuser = $rpcenv->get_user();
3371 my $target = extract_param
($param, 'target');
3373 my $localnode = PVE
::INotify
::nodename
();
3374 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3376 PVE
::Cluster
::check_cfs_quorum
();
3378 PVE
::Cluster
::check_node_exists
($target);
3380 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3382 my $vmid = extract_param
($param, 'vmid');
3384 raise_param_exc
({ force
=> "Only root may use this option." })
3385 if $param->{force
} && $authuser ne 'root@pam';
3387 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3388 if $param->{migration_type
} && $authuser ne 'root@pam';
3390 # allow root only until better network permissions are available
3391 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3392 if $param->{migration_network
} && $authuser ne 'root@pam';
3395 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3397 # try to detect errors early
3399 PVE
::QemuConfig-
>check_lock($conf);
3401 if (PVE
::QemuServer
::check_running
($vmid)) {
3402 die "can't migrate running VM without --online\n" if !$param->{online
};
3404 warn "VM isn't running. Doing offline migration instead\n." if $param->{online
};
3405 $param->{online
} = 0;
3408 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3409 if !$param->{online
} && $param->{targetstorage
};
3411 my $storecfg = PVE
::Storage
::config
();
3413 if( $param->{targetstorage
}) {
3414 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3416 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3419 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3424 print "Requesting HA migration for VM $vmid to node $target\n";
3426 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3427 PVE
::Tools
::run_command
($cmd);
3431 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3436 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3440 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3443 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3448 __PACKAGE__-
>register_method({
3450 path
=> '{vmid}/monitor',
3454 description
=> "Execute Qemu monitor commands.",
3456 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3457 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3460 additionalProperties
=> 0,
3462 node
=> get_standard_option
('pve-node'),
3463 vmid
=> get_standard_option
('pve-vmid'),
3466 description
=> "The monitor command.",
3470 returns
=> { type
=> 'string'},
3474 my $rpcenv = PVE
::RPCEnvironment
::get
();
3475 my $authuser = $rpcenv->get_user();
3478 my $command = shift;
3479 return $command =~ m/^\s*info(\s+|$)/
3480 || $command =~ m/^\s*help\s*$/;
3483 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3484 if !&$is_ro($param->{command
});
3486 my $vmid = $param->{vmid
};
3488 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3492 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3494 $res = "ERROR: $@" if $@;
3499 __PACKAGE__-
>register_method({
3500 name
=> 'resize_vm',
3501 path
=> '{vmid}/resize',
3505 description
=> "Extend volume size.",
3507 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3510 additionalProperties
=> 0,
3512 node
=> get_standard_option
('pve-node'),
3513 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3514 skiplock
=> get_standard_option
('skiplock'),
3517 description
=> "The disk you want to resize.",
3518 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3522 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3523 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.",
3527 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3533 returns
=> { type
=> 'null'},
3537 my $rpcenv = PVE
::RPCEnvironment
::get
();
3539 my $authuser = $rpcenv->get_user();
3541 my $node = extract_param
($param, 'node');
3543 my $vmid = extract_param
($param, 'vmid');
3545 my $digest = extract_param
($param, 'digest');
3547 my $disk = extract_param
($param, 'disk');
3549 my $sizestr = extract_param
($param, 'size');
3551 my $skiplock = extract_param
($param, 'skiplock');
3552 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3553 if $skiplock && $authuser ne 'root@pam';
3555 my $storecfg = PVE
::Storage
::config
();
3557 my $updatefn = sub {
3559 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3561 die "checksum missmatch (file change by other user?)\n"
3562 if $digest && $digest ne $conf->{digest
};
3563 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3565 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3567 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3569 my (undef, undef, undef, undef, undef, undef, $format) =
3570 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3572 die "can't resize volume: $disk if snapshot exists\n"
3573 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3575 my $volid = $drive->{file
};
3577 die "disk '$disk' has no associated volume\n" if !$volid;
3579 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3581 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3583 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3585 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3586 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3588 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3590 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3591 my ($ext, $newsize, $unit) = ($1, $2, $4);
3594 $newsize = $newsize * 1024;
3595 } elsif ($unit eq 'M') {
3596 $newsize = $newsize * 1024 * 1024;
3597 } elsif ($unit eq 'G') {
3598 $newsize = $newsize * 1024 * 1024 * 1024;
3599 } elsif ($unit eq 'T') {
3600 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3603 $newsize += $size if $ext;
3604 $newsize = int($newsize);
3606 die "shrinking disks is not supported\n" if $newsize < $size;
3608 return if $size == $newsize;
3610 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3612 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3614 $drive->{size
} = $newsize;
3615 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3617 PVE
::QemuConfig-
>write_config($vmid, $conf);
3620 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3624 __PACKAGE__-
>register_method({
3625 name
=> 'snapshot_list',
3626 path
=> '{vmid}/snapshot',
3628 description
=> "List all snapshots.",
3630 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3633 protected
=> 1, # qemu pid files are only readable by root
3635 additionalProperties
=> 0,
3637 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3638 node
=> get_standard_option
('pve-node'),
3647 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3651 description
=> "Snapshot includes RAM.",
3656 description
=> "Snapshot description.",
3660 description
=> "Snapshot creation time",
3662 renderer
=> 'timestamp',
3666 description
=> "Parent snapshot identifier.",
3672 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3677 my $vmid = $param->{vmid
};
3679 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3680 my $snaphash = $conf->{snapshots
} || {};
3684 foreach my $name (keys %$snaphash) {
3685 my $d = $snaphash->{$name};
3688 snaptime
=> $d->{snaptime
} || 0,
3689 vmstate
=> $d->{vmstate
} ?
1 : 0,
3690 description
=> $d->{description
} || '',
3692 $item->{parent
} = $d->{parent
} if $d->{parent
};
3693 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3697 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3700 digest
=> $conf->{digest
},
3701 running
=> $running,
3702 description
=> "You are here!",
3704 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3706 push @$res, $current;
3711 __PACKAGE__-
>register_method({
3713 path
=> '{vmid}/snapshot',
3717 description
=> "Snapshot a VM.",
3719 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3722 additionalProperties
=> 0,
3724 node
=> get_standard_option
('pve-node'),
3725 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3726 snapname
=> get_standard_option
('pve-snapshot-name'),
3730 description
=> "Save the vmstate",
3735 description
=> "A textual description or comment.",
3741 description
=> "the task ID.",
3746 my $rpcenv = PVE
::RPCEnvironment
::get
();
3748 my $authuser = $rpcenv->get_user();
3750 my $node = extract_param
($param, 'node');
3752 my $vmid = extract_param
($param, 'vmid');
3754 my $snapname = extract_param
($param, 'snapname');
3756 die "unable to use snapshot name 'current' (reserved name)\n"
3757 if $snapname eq 'current';
3760 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3761 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3762 $param->{description
});
3765 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3768 __PACKAGE__-
>register_method({
3769 name
=> 'snapshot_cmd_idx',
3770 path
=> '{vmid}/snapshot/{snapname}',
3777 additionalProperties
=> 0,
3779 vmid
=> get_standard_option
('pve-vmid'),
3780 node
=> get_standard_option
('pve-node'),
3781 snapname
=> get_standard_option
('pve-snapshot-name'),
3790 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3797 push @$res, { cmd
=> 'rollback' };
3798 push @$res, { cmd
=> 'config' };
3803 __PACKAGE__-
>register_method({
3804 name
=> 'update_snapshot_config',
3805 path
=> '{vmid}/snapshot/{snapname}/config',
3809 description
=> "Update snapshot metadata.",
3811 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3814 additionalProperties
=> 0,
3816 node
=> get_standard_option
('pve-node'),
3817 vmid
=> get_standard_option
('pve-vmid'),
3818 snapname
=> get_standard_option
('pve-snapshot-name'),
3822 description
=> "A textual description or comment.",
3826 returns
=> { type
=> 'null' },
3830 my $rpcenv = PVE
::RPCEnvironment
::get
();
3832 my $authuser = $rpcenv->get_user();
3834 my $vmid = extract_param
($param, 'vmid');
3836 my $snapname = extract_param
($param, 'snapname');
3838 return undef if !defined($param->{description
});
3840 my $updatefn = sub {
3842 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3844 PVE
::QemuConfig-
>check_lock($conf);
3846 my $snap = $conf->{snapshots
}->{$snapname};
3848 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3850 $snap->{description
} = $param->{description
} if defined($param->{description
});
3852 PVE
::QemuConfig-
>write_config($vmid, $conf);
3855 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3860 __PACKAGE__-
>register_method({
3861 name
=> 'get_snapshot_config',
3862 path
=> '{vmid}/snapshot/{snapname}/config',
3865 description
=> "Get snapshot configuration",
3867 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3870 additionalProperties
=> 0,
3872 node
=> get_standard_option
('pve-node'),
3873 vmid
=> get_standard_option
('pve-vmid'),
3874 snapname
=> get_standard_option
('pve-snapshot-name'),
3877 returns
=> { type
=> "object" },
3881 my $rpcenv = PVE
::RPCEnvironment
::get
();
3883 my $authuser = $rpcenv->get_user();
3885 my $vmid = extract_param
($param, 'vmid');
3887 my $snapname = extract_param
($param, 'snapname');
3889 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3891 my $snap = $conf->{snapshots
}->{$snapname};
3893 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3898 __PACKAGE__-
>register_method({
3900 path
=> '{vmid}/snapshot/{snapname}/rollback',
3904 description
=> "Rollback VM state to specified snapshot.",
3906 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3909 additionalProperties
=> 0,
3911 node
=> get_standard_option
('pve-node'),
3912 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3913 snapname
=> get_standard_option
('pve-snapshot-name'),
3918 description
=> "the task ID.",
3923 my $rpcenv = PVE
::RPCEnvironment
::get
();
3925 my $authuser = $rpcenv->get_user();
3927 my $node = extract_param
($param, 'node');
3929 my $vmid = extract_param
($param, 'vmid');
3931 my $snapname = extract_param
($param, 'snapname');
3934 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3935 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3939 # hold migration lock, this makes sure that nobody create replication snapshots
3940 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3943 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3946 __PACKAGE__-
>register_method({
3947 name
=> 'delsnapshot',
3948 path
=> '{vmid}/snapshot/{snapname}',
3952 description
=> "Delete a VM snapshot.",
3954 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3957 additionalProperties
=> 0,
3959 node
=> get_standard_option
('pve-node'),
3960 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3961 snapname
=> get_standard_option
('pve-snapshot-name'),
3965 description
=> "For removal from config file, even if removing disk snapshots fails.",
3971 description
=> "the task ID.",
3976 my $rpcenv = PVE
::RPCEnvironment
::get
();
3978 my $authuser = $rpcenv->get_user();
3980 my $node = extract_param
($param, 'node');
3982 my $vmid = extract_param
($param, 'vmid');
3984 my $snapname = extract_param
($param, 'snapname');
3987 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3988 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3991 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3994 __PACKAGE__-
>register_method({
3996 path
=> '{vmid}/template',
4000 description
=> "Create a Template.",
4002 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4003 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4006 additionalProperties
=> 0,
4008 node
=> get_standard_option
('pve-node'),
4009 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4013 description
=> "If you want to convert only 1 disk to base image.",
4014 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
4019 returns
=> { type
=> 'null'},
4023 my $rpcenv = PVE
::RPCEnvironment
::get
();
4025 my $authuser = $rpcenv->get_user();
4027 my $node = extract_param
($param, 'node');
4029 my $vmid = extract_param
($param, 'vmid');
4031 my $disk = extract_param
($param, 'disk');
4033 my $updatefn = sub {
4035 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4037 PVE
::QemuConfig-
>check_lock($conf);
4039 die "unable to create template, because VM contains snapshots\n"
4040 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4042 die "you can't convert a template to a template\n"
4043 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4045 die "you can't convert a VM to template if VM is running\n"
4046 if PVE
::QemuServer
::check_running
($vmid);
4049 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4052 $conf->{template
} = 1;
4053 PVE
::QemuConfig-
>write_config($vmid, $conf);
4055 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4058 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4062 __PACKAGE__-
>register_method({
4063 name
=> 'cloudinit_generated_config_dump',
4064 path
=> '{vmid}/cloudinit/dump',
4067 description
=> "Get automatically generated cloudinit config.",
4069 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4072 additionalProperties
=> 0,
4074 node
=> get_standard_option
('pve-node'),
4075 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4077 description
=> 'Config type.',
4079 enum
=> ['user', 'network', 'meta'],
4089 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4091 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});