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 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
869 current
=> "cannot use 'snapshot' parameter with 'current'"})
870 if ($param->{snapshot
} && $param->{current
});
873 if ($param->{snapshot
}) {
874 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
876 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
878 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
883 __PACKAGE__-
>register_method({
884 name
=> 'vm_pending',
885 path
=> '{vmid}/pending',
888 description
=> "Get virtual machine configuration, including pending changes.",
890 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
893 additionalProperties
=> 0,
895 node
=> get_standard_option
('pve-node'),
896 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
905 description
=> "Configuration option name.",
909 description
=> "Current value.",
914 description
=> "Pending value.",
919 description
=> "Indicates a pending delete request if present and not 0. " .
920 "The value 2 indicates a force-delete request.",
932 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
934 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
936 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
937 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
939 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
942 # POST/PUT {vmid}/config implementation
944 # The original API used PUT (idempotent) an we assumed that all operations
945 # are fast. But it turned out that almost any configuration change can
946 # involve hot-plug actions, or disk alloc/free. Such actions can take long
947 # time to complete and have side effects (not idempotent).
949 # The new implementation uses POST and forks a worker process. We added
950 # a new option 'background_delay'. If specified we wait up to
951 # 'background_delay' second for the worker task to complete. It returns null
952 # if the task is finished within that time, else we return the UPID.
954 my $update_vm_api = sub {
955 my ($param, $sync) = @_;
957 my $rpcenv = PVE
::RPCEnvironment
::get
();
959 my $authuser = $rpcenv->get_user();
961 my $node = extract_param
($param, 'node');
963 my $vmid = extract_param
($param, 'vmid');
965 my $digest = extract_param
($param, 'digest');
967 my $background_delay = extract_param
($param, 'background_delay');
969 if (defined(my $cipassword = $param->{cipassword
})) {
970 # Same logic as in cloud-init (but with the regex fixed...)
971 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
972 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
975 my @paramarr = (); # used for log message
976 foreach my $key (sort keys %$param) {
977 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
978 push @paramarr, "-$key", $value;
981 my $skiplock = extract_param
($param, 'skiplock');
982 raise_param_exc
({ skiplock
=> "Only root may use this option." })
983 if $skiplock && $authuser ne 'root@pam';
985 my $delete_str = extract_param
($param, 'delete');
987 my $revert_str = extract_param
($param, 'revert');
989 my $force = extract_param
($param, 'force');
991 if (defined(my $ssh_keys = $param->{sshkeys
})) {
992 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
993 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
996 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
998 my $storecfg = PVE
::Storage
::config
();
1000 my $defaults = PVE
::QemuServer
::load_defaults
();
1002 &$resolve_cdrom_alias($param);
1004 # now try to verify all parameters
1007 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1008 if (!PVE
::QemuServer
::option_exists
($opt)) {
1009 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1012 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1013 "-revert $opt' at the same time" })
1014 if defined($param->{$opt});
1016 $revert->{$opt} = 1;
1020 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1021 $opt = 'ide2' if $opt eq 'cdrom';
1023 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1024 "-delete $opt' at the same time" })
1025 if defined($param->{$opt});
1027 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1028 "-revert $opt' at the same time" })
1031 if (!PVE
::QemuServer
::option_exists
($opt)) {
1032 raise_param_exc
({ delete => "unknown option '$opt'" });
1038 my $repl_conf = PVE
::ReplicationConfig-
>new();
1039 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1040 my $check_replication = sub {
1042 return if !$is_replicated;
1043 my $volid = $drive->{file
};
1044 return if !$volid || !($drive->{replicate
}//1);
1045 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1046 my ($storeid, $format);
1047 if ($volid =~ $NEW_DISK_RE) {
1049 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1051 ($storeid, undef) = PVE
::Storage
::parse_volume_id
($volid, 1);
1052 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1054 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1055 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1056 return if $scfg->{shared
};
1057 die "cannot add non-replicatable volume to a replicated VM\n";
1060 foreach my $opt (keys %$param) {
1061 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1062 # cleanup drive path
1063 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1064 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1065 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1066 $check_replication->($drive);
1067 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1068 } elsif ($opt =~ m/^net(\d+)$/) {
1070 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1071 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1072 } elsif ($opt eq 'vmgenid') {
1073 if ($param->{$opt} eq '1') {
1074 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1076 } elsif ($opt eq 'hookscript') {
1077 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1078 raise_param_exc
({ $opt => $@ }) if $@;
1082 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1084 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1086 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1088 my $updatefn = sub {
1090 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1092 die "checksum missmatch (file change by other user?)\n"
1093 if $digest && $digest ne $conf->{digest
};
1095 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1097 foreach my $opt (keys %$revert) {
1098 if (defined($conf->{$opt})) {
1099 $param->{$opt} = $conf->{$opt};
1100 } elsif (defined($conf->{pending
}->{$opt})) {
1105 if ($param->{memory
} || defined($param->{balloon
})) {
1106 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1107 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1109 die "balloon value too large (must be smaller than assigned memory)\n"
1110 if $balloon && $balloon > $maxmem;
1113 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1117 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1119 # write updates to pending section
1121 my $modified = {}; # record what $option we modify
1123 foreach my $opt (@delete) {
1124 $modified->{$opt} = 1;
1125 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1127 # value of what we want to delete, independent if pending or not
1128 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1129 if (!defined($val)) {
1130 warn "cannot delete '$opt' - not set in current configuration!\n";
1131 $modified->{$opt} = 0;
1134 my $is_pending_val = defined($conf->{pending
}->{$opt});
1136 if ($opt =~ m/^unused/) {
1137 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1138 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1139 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1140 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1141 delete $conf->{$opt};
1142 PVE
::QemuConfig-
>write_config($vmid, $conf);
1144 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1145 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1146 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1147 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1149 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1150 PVE
::QemuConfig-
>write_config($vmid, $conf);
1151 } elsif ($opt =~ m/^serial\d+$/) {
1152 if ($val eq 'socket') {
1153 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1154 } elsif ($authuser ne 'root@pam') {
1155 die "only root can delete '$opt' config for real devices\n";
1157 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1158 PVE
::QemuConfig-
>write_config($vmid, $conf);
1159 } elsif ($opt =~ m/^usb\d+$/) {
1160 if ($val =~ m/spice/) {
1161 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1162 } elsif ($authuser ne 'root@pam') {
1163 die "only root can delete '$opt' config for real devices\n";
1165 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1166 PVE
::QemuConfig-
>write_config($vmid, $conf);
1168 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1169 PVE
::QemuConfig-
>write_config($vmid, $conf);
1173 foreach my $opt (keys %$param) { # add/change
1174 $modified->{$opt} = 1;
1175 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1176 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1178 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
1180 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1181 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1182 # FIXME: cloudinit: CDROM or Disk?
1183 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1184 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1186 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1188 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1189 if defined($conf->{pending
}->{$opt});
1191 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1192 } elsif ($opt =~ m/^serial\d+/) {
1193 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1194 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1195 } elsif ($authuser ne 'root@pam') {
1196 die "only root can modify '$opt' config for real devices\n";
1198 $conf->{pending
}->{$opt} = $param->{$opt};
1199 } elsif ($opt =~ m/^usb\d+/) {
1200 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1201 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1202 } elsif ($authuser ne 'root@pam') {
1203 die "only root can modify '$opt' config for real devices\n";
1205 $conf->{pending
}->{$opt} = $param->{$opt};
1207 $conf->{pending
}->{$opt} = $param->{$opt};
1209 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1210 PVE
::QemuConfig-
>write_config($vmid, $conf);
1213 # remove pending changes when nothing changed
1214 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1215 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1216 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1218 return if !scalar(keys %{$conf->{pending
}});
1220 my $running = PVE
::QemuServer
::check_running
($vmid);
1222 # apply pending changes
1224 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1228 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1229 raise_param_exc
($errors) if scalar(keys %$errors);
1231 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1241 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1243 if ($background_delay) {
1245 # Note: It would be better to do that in the Event based HTTPServer
1246 # to avoid blocking call to sleep.
1248 my $end_time = time() + $background_delay;
1250 my $task = PVE
::Tools
::upid_decode
($upid);
1253 while (time() < $end_time) {
1254 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1256 sleep(1); # this gets interrupted when child process ends
1260 my $status = PVE
::Tools
::upid_read_status
($upid);
1261 return undef if $status eq 'OK';
1270 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1273 my $vm_config_perm_list = [
1278 'VM.Config.Network',
1280 'VM.Config.Options',
1283 __PACKAGE__-
>register_method({
1284 name
=> 'update_vm_async',
1285 path
=> '{vmid}/config',
1289 description
=> "Set virtual machine options (asynchrounous API).",
1291 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1294 additionalProperties
=> 0,
1295 properties
=> PVE
::QemuServer
::json_config_properties
(
1297 node
=> get_standard_option
('pve-node'),
1298 vmid
=> get_standard_option
('pve-vmid'),
1299 skiplock
=> get_standard_option
('skiplock'),
1301 type
=> 'string', format
=> 'pve-configid-list',
1302 description
=> "A list of settings you want to delete.",
1306 type
=> 'string', format
=> 'pve-configid-list',
1307 description
=> "Revert a pending change.",
1312 description
=> $opt_force_description,
1314 requires
=> 'delete',
1318 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1322 background_delay
=> {
1324 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1335 code
=> $update_vm_api,
1338 __PACKAGE__-
>register_method({
1339 name
=> 'update_vm',
1340 path
=> '{vmid}/config',
1344 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1346 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1349 additionalProperties
=> 0,
1350 properties
=> PVE
::QemuServer
::json_config_properties
(
1352 node
=> get_standard_option
('pve-node'),
1353 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1354 skiplock
=> get_standard_option
('skiplock'),
1356 type
=> 'string', format
=> 'pve-configid-list',
1357 description
=> "A list of settings you want to delete.",
1361 type
=> 'string', format
=> 'pve-configid-list',
1362 description
=> "Revert a pending change.",
1367 description
=> $opt_force_description,
1369 requires
=> 'delete',
1373 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1379 returns
=> { type
=> 'null' },
1382 &$update_vm_api($param, 1);
1387 __PACKAGE__-
>register_method({
1388 name
=> 'destroy_vm',
1393 description
=> "Destroy the vm (also delete all used/owned volumes).",
1395 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1398 additionalProperties
=> 0,
1400 node
=> get_standard_option
('pve-node'),
1401 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1402 skiplock
=> get_standard_option
('skiplock'),
1411 my $rpcenv = PVE
::RPCEnvironment
::get
();
1412 my $authuser = $rpcenv->get_user();
1413 my $vmid = $param->{vmid
};
1415 my $skiplock = $param->{skiplock
};
1416 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1417 if $skiplock && $authuser ne 'root@pam';
1420 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1421 my $storecfg = PVE
::Storage
::config
();
1422 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1423 die "unable to remove VM $vmid - used in HA resources\n"
1424 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1426 # do not allow destroy if there are replication jobs
1427 my $repl_conf = PVE
::ReplicationConfig-
>new();
1428 $repl_conf->check_for_existing_jobs($vmid);
1430 # early tests (repeat after locking)
1431 die "VM $vmid is running - destroy failed\n"
1432 if PVE
::QemuServer
::check_running
($vmid);
1437 syslog
('info', "destroy VM $vmid: $upid\n");
1438 PVE
::QemuConfig-
>lock_config($vmid, sub {
1439 die "VM $vmid is running - destroy failed\n"
1440 if (PVE
::QemuServer
::check_running
($vmid));
1441 PVE
::QemuServer
::destroy_vm
($storecfg, $vmid, 1, $skiplock);
1442 PVE
::AccessControl
::remove_vm_access
($vmid);
1443 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1445 # only now remove the zombie config, else we can have reuse race
1446 PVE
::QemuConfig-
>destroy_config($vmid);
1450 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1453 __PACKAGE__-
>register_method({
1455 path
=> '{vmid}/unlink',
1459 description
=> "Unlink/delete disk images.",
1461 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1464 additionalProperties
=> 0,
1466 node
=> get_standard_option
('pve-node'),
1467 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1469 type
=> 'string', format
=> 'pve-configid-list',
1470 description
=> "A list of disk IDs you want to delete.",
1474 description
=> $opt_force_description,
1479 returns
=> { type
=> 'null'},
1483 $param->{delete} = extract_param
($param, 'idlist');
1485 __PACKAGE__-
>update_vm($param);
1492 __PACKAGE__-
>register_method({
1494 path
=> '{vmid}/vncproxy',
1498 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1500 description
=> "Creates a TCP VNC proxy connections.",
1502 additionalProperties
=> 0,
1504 node
=> get_standard_option
('pve-node'),
1505 vmid
=> get_standard_option
('pve-vmid'),
1509 description
=> "starts websockify instead of vncproxy",
1514 additionalProperties
=> 0,
1516 user
=> { type
=> 'string' },
1517 ticket
=> { type
=> 'string' },
1518 cert
=> { type
=> 'string' },
1519 port
=> { type
=> 'integer' },
1520 upid
=> { type
=> 'string' },
1526 my $rpcenv = PVE
::RPCEnvironment
::get
();
1528 my $authuser = $rpcenv->get_user();
1530 my $vmid = $param->{vmid
};
1531 my $node = $param->{node
};
1532 my $websocket = $param->{websocket
};
1534 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1535 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1537 my $authpath = "/vms/$vmid";
1539 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1541 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1547 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1548 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1549 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1550 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1551 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1553 $family = PVE
::Tools
::get_host_address_family
($node);
1556 my $port = PVE
::Tools
::next_vnc_port
($family);
1563 syslog
('info', "starting vnc proxy $upid\n");
1569 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1571 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1572 '-timeout', $timeout, '-authpath', $authpath,
1573 '-perm', 'Sys.Console'];
1575 if ($param->{websocket
}) {
1576 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1577 push @$cmd, '-notls', '-listen', 'localhost';
1580 push @$cmd, '-c', @$remcmd, @$termcmd;
1582 PVE
::Tools
::run_command
($cmd);
1586 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1588 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1590 my $sock = IO
::Socket
::IP-
>new(
1595 GetAddrInfoFlags
=> 0,
1596 ) or die "failed to create socket: $!\n";
1597 # Inside the worker we shouldn't have any previous alarms
1598 # running anyway...:
1600 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1602 accept(my $cli, $sock) or die "connection failed: $!\n";
1605 if (PVE
::Tools
::run_command
($cmd,
1606 output
=> '>&'.fileno($cli),
1607 input
=> '<&'.fileno($cli),
1610 die "Failed to run vncproxy.\n";
1617 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1619 PVE
::Tools
::wait_for_vnc_port
($port);
1630 __PACKAGE__-
>register_method({
1631 name
=> 'termproxy',
1632 path
=> '{vmid}/termproxy',
1636 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1638 description
=> "Creates a TCP proxy connections.",
1640 additionalProperties
=> 0,
1642 node
=> get_standard_option
('pve-node'),
1643 vmid
=> get_standard_option
('pve-vmid'),
1647 enum
=> [qw(serial0 serial1 serial2 serial3)],
1648 description
=> "opens a serial terminal (defaults to display)",
1653 additionalProperties
=> 0,
1655 user
=> { type
=> 'string' },
1656 ticket
=> { type
=> 'string' },
1657 port
=> { type
=> 'integer' },
1658 upid
=> { type
=> 'string' },
1664 my $rpcenv = PVE
::RPCEnvironment
::get
();
1666 my $authuser = $rpcenv->get_user();
1668 my $vmid = $param->{vmid
};
1669 my $node = $param->{node
};
1670 my $serial = $param->{serial
};
1672 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1674 if (!defined($serial)) {
1675 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1676 $serial = $conf->{vga
};
1680 my $authpath = "/vms/$vmid";
1682 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1687 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1688 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1689 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1690 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, '-t');
1691 push @$remcmd, '--';
1693 $family = PVE
::Tools
::get_host_address_family
($node);
1696 my $port = PVE
::Tools
::next_vnc_port
($family);
1698 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1699 push @$termcmd, '-iface', $serial if $serial;
1704 syslog
('info', "starting qemu termproxy $upid\n");
1706 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1707 '--perm', 'VM.Console', '--'];
1708 push @$cmd, @$remcmd, @$termcmd;
1710 PVE
::Tools
::run_command
($cmd);
1713 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1715 PVE
::Tools
::wait_for_vnc_port
($port);
1725 __PACKAGE__-
>register_method({
1726 name
=> 'vncwebsocket',
1727 path
=> '{vmid}/vncwebsocket',
1730 description
=> "You also need to pass a valid ticket (vncticket).",
1731 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1733 description
=> "Opens a weksocket for VNC traffic.",
1735 additionalProperties
=> 0,
1737 node
=> get_standard_option
('pve-node'),
1738 vmid
=> get_standard_option
('pve-vmid'),
1740 description
=> "Ticket from previous call to vncproxy.",
1745 description
=> "Port number returned by previous vncproxy call.",
1755 port
=> { type
=> 'string' },
1761 my $rpcenv = PVE
::RPCEnvironment
::get
();
1763 my $authuser = $rpcenv->get_user();
1765 my $vmid = $param->{vmid
};
1766 my $node = $param->{node
};
1768 my $authpath = "/vms/$vmid";
1770 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1772 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1774 # Note: VNC ports are acessible from outside, so we do not gain any
1775 # security if we verify that $param->{port} belongs to VM $vmid. This
1776 # check is done by verifying the VNC ticket (inside VNC protocol).
1778 my $port = $param->{port
};
1780 return { port
=> $port };
1783 __PACKAGE__-
>register_method({
1784 name
=> 'spiceproxy',
1785 path
=> '{vmid}/spiceproxy',
1790 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1792 description
=> "Returns a SPICE configuration to connect to the VM.",
1794 additionalProperties
=> 0,
1796 node
=> get_standard_option
('pve-node'),
1797 vmid
=> get_standard_option
('pve-vmid'),
1798 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1801 returns
=> get_standard_option
('remote-viewer-config'),
1805 my $rpcenv = PVE
::RPCEnvironment
::get
();
1807 my $authuser = $rpcenv->get_user();
1809 my $vmid = $param->{vmid
};
1810 my $node = $param->{node
};
1811 my $proxy = $param->{proxy
};
1813 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1814 my $title = "VM $vmid";
1815 $title .= " - ". $conf->{name
} if $conf->{name
};
1817 my $port = PVE
::QemuServer
::spice_port
($vmid);
1819 my ($ticket, undef, $remote_viewer_config) =
1820 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1822 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1823 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1825 return $remote_viewer_config;
1828 __PACKAGE__-
>register_method({
1830 path
=> '{vmid}/status',
1833 description
=> "Directory index",
1838 additionalProperties
=> 0,
1840 node
=> get_standard_option
('pve-node'),
1841 vmid
=> get_standard_option
('pve-vmid'),
1849 subdir
=> { type
=> 'string' },
1852 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1858 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1861 { subdir
=> 'current' },
1862 { subdir
=> 'start' },
1863 { subdir
=> 'stop' },
1864 { subdir
=> 'reset' },
1865 { subdir
=> 'shutdown' },
1866 { subdir
=> 'suspend' },
1867 { subdir
=> 'reboot' },
1873 __PACKAGE__-
>register_method({
1874 name
=> 'vm_status',
1875 path
=> '{vmid}/status/current',
1878 protected
=> 1, # qemu pid files are only readable by root
1879 description
=> "Get virtual machine status.",
1881 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1884 additionalProperties
=> 0,
1886 node
=> get_standard_option
('pve-node'),
1887 vmid
=> get_standard_option
('pve-vmid'),
1893 %$PVE::QemuServer
::vmstatus_return_properties
,
1895 description
=> "HA manager service status.",
1899 description
=> "Qemu VGA configuration supports spice.",
1904 description
=> "Qemu GuestAgent enabled in config.",
1914 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1916 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1917 my $status = $vmstatus->{$param->{vmid
}};
1919 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1921 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1922 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1927 __PACKAGE__-
>register_method({
1929 path
=> '{vmid}/status/start',
1933 description
=> "Start virtual machine.",
1935 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1938 additionalProperties
=> 0,
1940 node
=> get_standard_option
('pve-node'),
1941 vmid
=> get_standard_option
('pve-vmid',
1942 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1943 skiplock
=> get_standard_option
('skiplock'),
1944 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1945 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1948 enum
=> ['secure', 'insecure'],
1949 description
=> "Migration traffic is encrypted using an SSH " .
1950 "tunnel by default. On secure, completely private networks " .
1951 "this can be disabled to increase performance.",
1954 migration_network
=> {
1955 type
=> 'string', format
=> 'CIDR',
1956 description
=> "CIDR of the (sub) network that is used for migration.",
1959 machine
=> get_standard_option
('pve-qm-machine'),
1961 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1973 my $rpcenv = PVE
::RPCEnvironment
::get
();
1974 my $authuser = $rpcenv->get_user();
1976 my $node = extract_param
($param, 'node');
1977 my $vmid = extract_param
($param, 'vmid');
1979 my $machine = extract_param
($param, 'machine');
1981 my $get_root_param = sub {
1982 my $value = extract_param
($param, $_[0]);
1983 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
1984 if $value && $authuser ne 'root@pam';
1988 my $stateuri = $get_root_param->('stateuri');
1989 my $skiplock = $get_root_param->('skiplock');
1990 my $migratedfrom = $get_root_param->('migratedfrom');
1991 my $migration_type = $get_root_param->('migration_type');
1992 my $migration_network = $get_root_param->('migration_network');
1993 my $targetstorage = $get_root_param->('targetstorage');
1995 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1996 if $targetstorage && !$migratedfrom;
1998 # read spice ticket from STDIN
2000 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2001 if (defined(my $line = <STDIN
>)) {
2003 $spice_ticket = $line;
2007 PVE
::Cluster
::check_cfs_quorum
();
2009 my $storecfg = PVE
::Storage
::config
();
2011 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2015 print "Requesting HA start for VM $vmid\n";
2017 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2018 PVE
::Tools
::run_command
($cmd);
2022 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2029 syslog
('info', "start VM $vmid: $upid\n");
2031 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2032 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
2036 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2040 __PACKAGE__-
>register_method({
2042 path
=> '{vmid}/status/stop',
2046 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2047 "is akin to pulling the power plug of a running computer and may damage the VM data",
2049 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2052 additionalProperties
=> 0,
2054 node
=> get_standard_option
('pve-node'),
2055 vmid
=> get_standard_option
('pve-vmid',
2056 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2057 skiplock
=> get_standard_option
('skiplock'),
2058 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2060 description
=> "Wait maximal timeout seconds.",
2066 description
=> "Do not deactivate storage volumes.",
2079 my $rpcenv = PVE
::RPCEnvironment
::get
();
2080 my $authuser = $rpcenv->get_user();
2082 my $node = extract_param
($param, 'node');
2083 my $vmid = extract_param
($param, 'vmid');
2085 my $skiplock = extract_param
($param, 'skiplock');
2086 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2087 if $skiplock && $authuser ne 'root@pam';
2089 my $keepActive = extract_param
($param, 'keepActive');
2090 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2091 if $keepActive && $authuser ne 'root@pam';
2093 my $migratedfrom = extract_param
($param, 'migratedfrom');
2094 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2095 if $migratedfrom && $authuser ne 'root@pam';
2098 my $storecfg = PVE
::Storage
::config
();
2100 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2105 print "Requesting HA stop for VM $vmid\n";
2107 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'stopped'];
2108 PVE
::Tools
::run_command
($cmd);
2112 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2118 syslog
('info', "stop VM $vmid: $upid\n");
2120 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2121 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2125 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2129 __PACKAGE__-
>register_method({
2131 path
=> '{vmid}/status/reset',
2135 description
=> "Reset virtual machine.",
2137 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2140 additionalProperties
=> 0,
2142 node
=> get_standard_option
('pve-node'),
2143 vmid
=> get_standard_option
('pve-vmid',
2144 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2145 skiplock
=> get_standard_option
('skiplock'),
2154 my $rpcenv = PVE
::RPCEnvironment
::get
();
2156 my $authuser = $rpcenv->get_user();
2158 my $node = extract_param
($param, 'node');
2160 my $vmid = extract_param
($param, 'vmid');
2162 my $skiplock = extract_param
($param, 'skiplock');
2163 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2164 if $skiplock && $authuser ne 'root@pam';
2166 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2171 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2176 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2179 __PACKAGE__-
>register_method({
2180 name
=> 'vm_shutdown',
2181 path
=> '{vmid}/status/shutdown',
2185 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2186 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2188 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2191 additionalProperties
=> 0,
2193 node
=> get_standard_option
('pve-node'),
2194 vmid
=> get_standard_option
('pve-vmid',
2195 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2196 skiplock
=> get_standard_option
('skiplock'),
2198 description
=> "Wait maximal timeout seconds.",
2204 description
=> "Make sure the VM stops.",
2210 description
=> "Do not deactivate storage volumes.",
2223 my $rpcenv = PVE
::RPCEnvironment
::get
();
2224 my $authuser = $rpcenv->get_user();
2226 my $node = extract_param
($param, 'node');
2227 my $vmid = extract_param
($param, 'vmid');
2229 my $skiplock = extract_param
($param, 'skiplock');
2230 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2231 if $skiplock && $authuser ne 'root@pam';
2233 my $keepActive = extract_param
($param, 'keepActive');
2234 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2235 if $keepActive && $authuser ne 'root@pam';
2237 my $storecfg = PVE
::Storage
::config
();
2241 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2242 # otherwise, we will infer a shutdown command, but run into the timeout,
2243 # then when the vm is resumed, it will instantly shutdown
2245 # checking the qmp status here to get feedback to the gui/cli/api
2246 # and the status query should not take too long
2247 my $qmpstatus = eval {
2248 PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2252 if (!$err && $qmpstatus->{status
} eq "paused") {
2253 if ($param->{forceStop
}) {
2254 warn "VM is paused - stop instead of shutdown\n";
2257 die "VM is paused - cannot shutdown\n";
2261 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2266 print "Requesting HA stop for VM $vmid\n";
2268 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'stopped'];
2269 PVE
::Tools
::run_command
($cmd);
2273 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2280 syslog
('info', "shutdown VM $vmid: $upid\n");
2282 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2283 $shutdown, $param->{forceStop
}, $keepActive);
2287 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2291 __PACKAGE__-
>register_method({
2292 name
=> 'vm_reboot',
2293 path
=> '{vmid}/status/reboot',
2297 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2299 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2302 additionalProperties
=> 0,
2304 node
=> get_standard_option
('pve-node'),
2305 vmid
=> get_standard_option
('pve-vmid',
2306 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2308 description
=> "Wait maximal timeout seconds for the shutdown.",
2321 my $rpcenv = PVE
::RPCEnvironment
::get
();
2322 my $authuser = $rpcenv->get_user();
2324 my $node = extract_param
($param, 'node');
2325 my $vmid = extract_param
($param, 'vmid');
2327 my $qmpstatus = eval {
2328 PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2332 if (!$err && $qmpstatus->{status
} eq "paused") {
2333 die "VM is paused - cannot shutdown\n";
2336 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2341 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2342 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2346 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2349 __PACKAGE__-
>register_method({
2350 name
=> 'vm_suspend',
2351 path
=> '{vmid}/status/suspend',
2355 description
=> "Suspend virtual machine.",
2357 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2360 additionalProperties
=> 0,
2362 node
=> get_standard_option
('pve-node'),
2363 vmid
=> get_standard_option
('pve-vmid',
2364 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2365 skiplock
=> get_standard_option
('skiplock'),
2370 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2372 statestorage
=> get_standard_option
('pve-storage-id', {
2373 description
=> "The storage for the VM state",
2374 requires
=> 'todisk',
2376 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2386 my $rpcenv = PVE
::RPCEnvironment
::get
();
2387 my $authuser = $rpcenv->get_user();
2389 my $node = extract_param
($param, 'node');
2390 my $vmid = extract_param
($param, 'vmid');
2392 my $todisk = extract_param
($param, 'todisk') // 0;
2394 my $statestorage = extract_param
($param, 'statestorage');
2396 my $skiplock = extract_param
($param, 'skiplock');
2397 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2398 if $skiplock && $authuser ne 'root@pam';
2400 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2402 die "Cannot suspend HA managed VM to disk\n"
2403 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2408 syslog
('info', "suspend VM $vmid: $upid\n");
2410 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2415 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2416 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2419 __PACKAGE__-
>register_method({
2420 name
=> 'vm_resume',
2421 path
=> '{vmid}/status/resume',
2425 description
=> "Resume virtual machine.",
2427 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2430 additionalProperties
=> 0,
2432 node
=> get_standard_option
('pve-node'),
2433 vmid
=> get_standard_option
('pve-vmid',
2434 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2435 skiplock
=> get_standard_option
('skiplock'),
2436 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2446 my $rpcenv = PVE
::RPCEnvironment
::get
();
2448 my $authuser = $rpcenv->get_user();
2450 my $node = extract_param
($param, 'node');
2452 my $vmid = extract_param
($param, 'vmid');
2454 my $skiplock = extract_param
($param, 'skiplock');
2455 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2456 if $skiplock && $authuser ne 'root@pam';
2458 my $nocheck = extract_param
($param, 'nocheck');
2460 my $to_disk_suspended;
2462 PVE
::QemuConfig-
>lock_config($vmid, sub {
2463 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2464 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2468 die "VM $vmid not running\n"
2469 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2474 syslog
('info', "resume VM $vmid: $upid\n");
2476 if (!$to_disk_suspended) {
2477 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2479 my $storecfg = PVE
::Storage
::config
();
2480 PVE
::QemuServer
::vm_start
($storecfg, $vmid, undef, $skiplock);
2486 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2489 __PACKAGE__-
>register_method({
2490 name
=> 'vm_sendkey',
2491 path
=> '{vmid}/sendkey',
2495 description
=> "Send key event to virtual machine.",
2497 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2500 additionalProperties
=> 0,
2502 node
=> get_standard_option
('pve-node'),
2503 vmid
=> get_standard_option
('pve-vmid',
2504 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2505 skiplock
=> get_standard_option
('skiplock'),
2507 description
=> "The key (qemu monitor encoding).",
2512 returns
=> { type
=> 'null'},
2516 my $rpcenv = PVE
::RPCEnvironment
::get
();
2518 my $authuser = $rpcenv->get_user();
2520 my $node = extract_param
($param, 'node');
2522 my $vmid = extract_param
($param, 'vmid');
2524 my $skiplock = extract_param
($param, 'skiplock');
2525 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2526 if $skiplock && $authuser ne 'root@pam';
2528 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2533 __PACKAGE__-
>register_method({
2534 name
=> 'vm_feature',
2535 path
=> '{vmid}/feature',
2539 description
=> "Check if feature for virtual machine is available.",
2541 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2544 additionalProperties
=> 0,
2546 node
=> get_standard_option
('pve-node'),
2547 vmid
=> get_standard_option
('pve-vmid'),
2549 description
=> "Feature to check.",
2551 enum
=> [ 'snapshot', 'clone', 'copy' ],
2553 snapname
=> get_standard_option
('pve-snapshot-name', {
2561 hasFeature
=> { type
=> 'boolean' },
2564 items
=> { type
=> 'string' },
2571 my $node = extract_param
($param, 'node');
2573 my $vmid = extract_param
($param, 'vmid');
2575 my $snapname = extract_param
($param, 'snapname');
2577 my $feature = extract_param
($param, 'feature');
2579 my $running = PVE
::QemuServer
::check_running
($vmid);
2581 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2584 my $snap = $conf->{snapshots
}->{$snapname};
2585 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2588 my $storecfg = PVE
::Storage
::config
();
2590 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2591 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2594 hasFeature
=> $hasFeature,
2595 nodes
=> [ keys %$nodelist ],
2599 __PACKAGE__-
>register_method({
2601 path
=> '{vmid}/clone',
2605 description
=> "Create a copy of virtual machine/template.",
2607 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2608 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2609 "'Datastore.AllocateSpace' on any used storage.",
2612 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2614 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2615 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2620 additionalProperties
=> 0,
2622 node
=> get_standard_option
('pve-node'),
2623 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2624 newid
=> get_standard_option
('pve-vmid', {
2625 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2626 description
=> 'VMID for the clone.' }),
2629 type
=> 'string', format
=> 'dns-name',
2630 description
=> "Set a name for the new VM.",
2635 description
=> "Description for the new VM.",
2639 type
=> 'string', format
=> 'pve-poolid',
2640 description
=> "Add the new VM to the specified pool.",
2642 snapname
=> get_standard_option
('pve-snapshot-name', {
2645 storage
=> get_standard_option
('pve-storage-id', {
2646 description
=> "Target storage for full clone.",
2650 description
=> "Target format for file storage. Only valid for full clone.",
2653 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2658 description
=> "Create a full copy of all disks. This is always done when " .
2659 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2661 target
=> get_standard_option
('pve-node', {
2662 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2666 description
=> "Override I/O bandwidth limit (in KiB/s).",
2670 default => 'clone limit from datacenter or storage config',
2680 my $rpcenv = PVE
::RPCEnvironment
::get
();
2682 my $authuser = $rpcenv->get_user();
2684 my $node = extract_param
($param, 'node');
2686 my $vmid = extract_param
($param, 'vmid');
2688 my $newid = extract_param
($param, 'newid');
2690 my $pool = extract_param
($param, 'pool');
2692 if (defined($pool)) {
2693 $rpcenv->check_pool_exist($pool);
2696 my $snapname = extract_param
($param, 'snapname');
2698 my $storage = extract_param
($param, 'storage');
2700 my $format = extract_param
($param, 'format');
2702 my $target = extract_param
($param, 'target');
2704 my $localnode = PVE
::INotify
::nodename
();
2706 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2708 PVE
::Cluster
::check_node_exists
($target) if $target;
2710 my $storecfg = PVE
::Storage
::config
();
2713 # check if storage is enabled on local node
2714 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2716 # check if storage is available on target node
2717 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2718 # clone only works if target storage is shared
2719 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2720 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2724 PVE
::Cluster
::check_cfs_quorum
();
2726 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2728 # exclusive lock if VM is running - else shared lock is enough;
2729 my $shared_lock = $running ?
0 : 1;
2733 # do all tests after lock
2734 # we also try to do all tests before we fork the worker
2736 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2738 PVE
::QemuConfig-
>check_lock($conf);
2740 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2742 die "unexpected state change\n" if $verify_running != $running;
2744 die "snapshot '$snapname' does not exist\n"
2745 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2747 my $full = extract_param
($param, 'full');
2748 if (!defined($full)) {
2749 $full = !PVE
::QemuConfig-
>is_template($conf);
2752 die "parameter 'storage' not allowed for linked clones\n"
2753 if defined($storage) && !$full;
2755 die "parameter 'format' not allowed for linked clones\n"
2756 if defined($format) && !$full;
2758 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2760 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2762 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2764 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2766 die "unable to create VM $newid: config file already exists\n"
2769 my $newconf = { lock => 'clone' };
2774 foreach my $opt (keys %$oldconf) {
2775 my $value = $oldconf->{$opt};
2777 # do not copy snapshot related info
2778 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2779 $opt eq 'vmstate' || $opt eq 'snapstate';
2781 # no need to copy unused images, because VMID(owner) changes anyways
2782 next if $opt =~ m/^unused\d+$/;
2784 # always change MAC! address
2785 if ($opt =~ m/^net(\d+)$/) {
2786 my $net = PVE
::QemuServer
::parse_net
($value);
2787 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2788 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2789 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2790 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2791 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2792 die "unable to parse drive options for '$opt'\n" if !$drive;
2793 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2794 $newconf->{$opt} = $value; # simply copy configuration
2797 die "Full clone feature is not supported for drive '$opt'\n"
2798 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2799 $fullclone->{$opt} = 1;
2801 # not full means clone instead of copy
2802 die "Linked clone feature is not supported for drive '$opt'\n"
2803 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2805 $drives->{$opt} = $drive;
2806 push @$vollist, $drive->{file
};
2809 # copy everything else
2810 $newconf->{$opt} = $value;
2814 # auto generate a new uuid
2815 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2816 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2817 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2819 # auto generate a new vmgenid if the option was set
2820 if ($newconf->{vmgenid
}) {
2821 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2824 delete $newconf->{template
};
2826 if ($param->{name
}) {
2827 $newconf->{name
} = $param->{name
};
2829 if ($oldconf->{name
}) {
2830 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2832 $newconf->{name
} = "Copy-of-VM-$vmid";
2836 if ($param->{description
}) {
2837 $newconf->{description
} = $param->{description
};
2840 # create empty/temp config - this fails if VM already exists on other node
2841 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2846 my $newvollist = [];
2853 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2855 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2857 my $bwlimit = extract_param
($param, 'bwlimit');
2859 my $total_jobs = scalar(keys %{$drives});
2862 foreach my $opt (keys %$drives) {
2863 my $drive = $drives->{$opt};
2864 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2866 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
2867 my $storage_list = [ $src_sid ];
2868 push @$storage_list, $storage if defined($storage);
2869 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
2871 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2872 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2873 $jobs, $skipcomplete, $oldconf->{agent
}, $clonelimit);
2875 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2877 PVE
::QemuConfig-
>write_config($newid, $newconf);
2881 delete $newconf->{lock};
2883 # do not write pending changes
2884 if (my @changes = keys %{$newconf->{pending
}}) {
2885 my $pending = join(',', @changes);
2886 warn "found pending changes for '$pending', discarding for clone\n";
2887 delete $newconf->{pending
};
2890 PVE
::QemuConfig-
>write_config($newid, $newconf);
2893 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2894 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2895 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2897 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2898 die "Failed to move config to node '$target' - rename failed: $!\n"
2899 if !rename($conffile, $newconffile);
2902 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2907 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2909 sleep 1; # some storage like rbd need to wait before release volume - really?
2911 foreach my $volid (@$newvollist) {
2912 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2915 die "clone failed: $err";
2921 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2923 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2926 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2927 # Aquire exclusive lock lock for $newid
2928 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2933 __PACKAGE__-
>register_method({
2934 name
=> 'move_vm_disk',
2935 path
=> '{vmid}/move_disk',
2939 description
=> "Move volume to different storage.",
2941 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2943 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2944 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2948 additionalProperties
=> 0,
2950 node
=> get_standard_option
('pve-node'),
2951 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2954 description
=> "The disk you want to move.",
2955 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2957 storage
=> get_standard_option
('pve-storage-id', {
2958 description
=> "Target storage.",
2959 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2963 description
=> "Target Format.",
2964 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2969 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2975 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2980 description
=> "Override I/O bandwidth limit (in KiB/s).",
2984 default => 'move limit from datacenter or storage config',
2990 description
=> "the task ID.",
2995 my $rpcenv = PVE
::RPCEnvironment
::get
();
2997 my $authuser = $rpcenv->get_user();
2999 my $node = extract_param
($param, 'node');
3001 my $vmid = extract_param
($param, 'vmid');
3003 my $digest = extract_param
($param, 'digest');
3005 my $disk = extract_param
($param, 'disk');
3007 my $storeid = extract_param
($param, 'storage');
3009 my $format = extract_param
($param, 'format');
3011 my $storecfg = PVE
::Storage
::config
();
3013 my $updatefn = sub {
3015 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3017 PVE
::QemuConfig-
>check_lock($conf);
3019 die "checksum missmatch (file change by other user?)\n"
3020 if $digest && $digest ne $conf->{digest
};
3022 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3024 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3026 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
3028 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3031 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3032 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3036 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
3037 (!$format || !$oldfmt || $oldfmt eq $format);
3039 # this only checks snapshots because $disk is passed!
3040 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3041 die "you can't move a disk with snapshots and delete the source\n"
3042 if $snapshotted && $param->{delete};
3044 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3046 my $running = PVE
::QemuServer
::check_running
($vmid);
3048 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3052 my $newvollist = [];
3058 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3060 warn "moving disk with snapshots, snapshots will not be moved!\n"
3063 my $bwlimit = extract_param
($param, 'bwlimit');
3064 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3066 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3067 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit);
3069 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
3071 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3073 # convert moved disk to base if part of template
3074 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3075 if PVE
::QemuConfig-
>is_template($conf);
3077 PVE
::QemuConfig-
>write_config($vmid, $conf);
3079 if ($running && PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
} && PVE
::QemuServer
::qga_check_running
($vmid)) {
3080 eval { PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-fstrim"); };
3084 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3085 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3092 foreach my $volid (@$newvollist) {
3093 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3096 die "storage migration failed: $err";
3099 if ($param->{delete}) {
3101 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3102 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3108 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3111 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3114 my $check_vm_disks_local = sub {
3115 my ($storecfg, $vmconf, $vmid) = @_;
3117 my $local_disks = {};
3119 # add some more information to the disks e.g. cdrom
3120 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3121 my ($volid, $attr) = @_;
3123 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3125 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3126 return if $scfg->{shared
};
3128 # The shared attr here is just a special case where the vdisk
3129 # is marked as shared manually
3130 return if $attr->{shared
};
3131 return if $attr->{cdrom
} and $volid eq "none";
3133 if (exists $local_disks->{$volid}) {
3134 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3136 $local_disks->{$volid} = $attr;
3137 # ensure volid is present in case it's needed
3138 $local_disks->{$volid}->{volid
} = $volid;
3142 return $local_disks;
3145 __PACKAGE__-
>register_method({
3146 name
=> 'migrate_vm_precondition',
3147 path
=> '{vmid}/migrate',
3151 description
=> "Get preconditions for migration.",
3153 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3156 additionalProperties
=> 0,
3158 node
=> get_standard_option
('pve-node'),
3159 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3160 target
=> get_standard_option
('pve-node', {
3161 description
=> "Target node.",
3162 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3170 running
=> { type
=> 'boolean' },
3174 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3176 not_allowed_nodes
=> {
3179 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3183 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3185 local_resources
=> {
3187 description
=> "List local resources e.g. pci, usb"
3194 my $rpcenv = PVE
::RPCEnvironment
::get
();
3196 my $authuser = $rpcenv->get_user();
3198 PVE
::Cluster
::check_cfs_quorum
();
3202 my $vmid = extract_param
($param, 'vmid');
3203 my $target = extract_param
($param, 'target');
3204 my $localnode = PVE
::INotify
::nodename
();
3208 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3209 my $storecfg = PVE
::Storage
::config
();
3212 # try to detect errors early
3213 PVE
::QemuConfig-
>check_lock($vmconf);
3215 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3217 # if vm is not running, return target nodes where local storage is available
3218 # for offline migration
3219 if (!$res->{running
}) {
3220 $res->{allowed_nodes
} = [];
3221 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3222 delete $checked_nodes->{$localnode};
3224 foreach my $node (keys %$checked_nodes) {
3225 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3226 push @{$res->{allowed_nodes
}}, $node;
3230 $res->{not_allowed_nodes
} = $checked_nodes;
3234 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3235 $res->{local_disks
} = [ values %$local_disks ];;
3237 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3239 $res->{local_resources
} = $local_resources;
3246 __PACKAGE__-
>register_method({
3247 name
=> 'migrate_vm',
3248 path
=> '{vmid}/migrate',
3252 description
=> "Migrate virtual machine. Creates a new migration task.",
3254 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3257 additionalProperties
=> 0,
3259 node
=> get_standard_option
('pve-node'),
3260 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3261 target
=> get_standard_option
('pve-node', {
3262 description
=> "Target node.",
3263 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3267 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3272 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3277 enum
=> ['secure', 'insecure'],
3278 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3281 migration_network
=> {
3282 type
=> 'string', format
=> 'CIDR',
3283 description
=> "CIDR of the (sub) network that is used for migration.",
3286 "with-local-disks" => {
3288 description
=> "Enable live storage migration for local disk",
3291 targetstorage
=> get_standard_option
('pve-storage-id', {
3292 description
=> "Default target storage.",
3294 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3297 description
=> "Override I/O bandwidth limit (in KiB/s).",
3301 default => 'migrate limit from datacenter or storage config',
3307 description
=> "the task ID.",
3312 my $rpcenv = PVE
::RPCEnvironment
::get
();
3313 my $authuser = $rpcenv->get_user();
3315 my $target = extract_param
($param, 'target');
3317 my $localnode = PVE
::INotify
::nodename
();
3318 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3320 PVE
::Cluster
::check_cfs_quorum
();
3322 PVE
::Cluster
::check_node_exists
($target);
3324 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3326 my $vmid = extract_param
($param, 'vmid');
3328 raise_param_exc
({ force
=> "Only root may use this option." })
3329 if $param->{force
} && $authuser ne 'root@pam';
3331 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3332 if $param->{migration_type
} && $authuser ne 'root@pam';
3334 # allow root only until better network permissions are available
3335 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3336 if $param->{migration_network
} && $authuser ne 'root@pam';
3339 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3341 # try to detect errors early
3343 PVE
::QemuConfig-
>check_lock($conf);
3345 if (PVE
::QemuServer
::check_running
($vmid)) {
3346 die "can't migrate running VM without --online\n" if !$param->{online
};
3348 warn "VM isn't running. Doing offline migration instead\n." if $param->{online
};
3349 $param->{online
} = 0;
3352 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3353 if !$param->{online
} && $param->{targetstorage
};
3355 my $storecfg = PVE
::Storage
::config
();
3357 if( $param->{targetstorage
}) {
3358 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3360 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3363 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3368 print "Requesting HA migration for VM $vmid to node $target\n";
3370 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3371 PVE
::Tools
::run_command
($cmd);
3375 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3380 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3384 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3387 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3392 __PACKAGE__-
>register_method({
3394 path
=> '{vmid}/monitor',
3398 description
=> "Execute Qemu monitor commands.",
3400 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3401 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3404 additionalProperties
=> 0,
3406 node
=> get_standard_option
('pve-node'),
3407 vmid
=> get_standard_option
('pve-vmid'),
3410 description
=> "The monitor command.",
3414 returns
=> { type
=> 'string'},
3418 my $rpcenv = PVE
::RPCEnvironment
::get
();
3419 my $authuser = $rpcenv->get_user();
3422 my $command = shift;
3423 return $command =~ m/^\s*info(\s+|$)/
3424 || $command =~ m/^\s*help\s*$/;
3427 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3428 if !&$is_ro($param->{command
});
3430 my $vmid = $param->{vmid
};
3432 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3436 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3438 $res = "ERROR: $@" if $@;
3443 __PACKAGE__-
>register_method({
3444 name
=> 'resize_vm',
3445 path
=> '{vmid}/resize',
3449 description
=> "Extend volume size.",
3451 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3454 additionalProperties
=> 0,
3456 node
=> get_standard_option
('pve-node'),
3457 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3458 skiplock
=> get_standard_option
('skiplock'),
3461 description
=> "The disk you want to resize.",
3462 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3466 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3467 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.",
3471 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3477 returns
=> { type
=> 'null'},
3481 my $rpcenv = PVE
::RPCEnvironment
::get
();
3483 my $authuser = $rpcenv->get_user();
3485 my $node = extract_param
($param, 'node');
3487 my $vmid = extract_param
($param, 'vmid');
3489 my $digest = extract_param
($param, 'digest');
3491 my $disk = extract_param
($param, 'disk');
3493 my $sizestr = extract_param
($param, 'size');
3495 my $skiplock = extract_param
($param, 'skiplock');
3496 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3497 if $skiplock && $authuser ne 'root@pam';
3499 my $storecfg = PVE
::Storage
::config
();
3501 my $updatefn = sub {
3503 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3505 die "checksum missmatch (file change by other user?)\n"
3506 if $digest && $digest ne $conf->{digest
};
3507 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3509 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3511 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3513 my (undef, undef, undef, undef, undef, undef, $format) =
3514 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3516 die "can't resize volume: $disk if snapshot exists\n"
3517 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3519 my $volid = $drive->{file
};
3521 die "disk '$disk' has no associated volume\n" if !$volid;
3523 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3525 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3527 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3529 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3530 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3532 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3534 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3535 my ($ext, $newsize, $unit) = ($1, $2, $4);
3538 $newsize = $newsize * 1024;
3539 } elsif ($unit eq 'M') {
3540 $newsize = $newsize * 1024 * 1024;
3541 } elsif ($unit eq 'G') {
3542 $newsize = $newsize * 1024 * 1024 * 1024;
3543 } elsif ($unit eq 'T') {
3544 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3547 $newsize += $size if $ext;
3548 $newsize = int($newsize);
3550 die "shrinking disks is not supported\n" if $newsize < $size;
3552 return if $size == $newsize;
3554 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3556 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3558 $drive->{size
} = $newsize;
3559 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3561 PVE
::QemuConfig-
>write_config($vmid, $conf);
3564 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3568 __PACKAGE__-
>register_method({
3569 name
=> 'snapshot_list',
3570 path
=> '{vmid}/snapshot',
3572 description
=> "List all snapshots.",
3574 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3577 protected
=> 1, # qemu pid files are only readable by root
3579 additionalProperties
=> 0,
3581 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3582 node
=> get_standard_option
('pve-node'),
3591 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3595 description
=> "Snapshot includes RAM.",
3600 description
=> "Snapshot description.",
3604 description
=> "Snapshot creation time",
3606 renderer
=> 'timestamp',
3610 description
=> "Parent snapshot identifier.",
3616 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3621 my $vmid = $param->{vmid
};
3623 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3624 my $snaphash = $conf->{snapshots
} || {};
3628 foreach my $name (keys %$snaphash) {
3629 my $d = $snaphash->{$name};
3632 snaptime
=> $d->{snaptime
} || 0,
3633 vmstate
=> $d->{vmstate
} ?
1 : 0,
3634 description
=> $d->{description
} || '',
3636 $item->{parent
} = $d->{parent
} if $d->{parent
};
3637 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3641 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3644 digest
=> $conf->{digest
},
3645 running
=> $running,
3646 description
=> "You are here!",
3648 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3650 push @$res, $current;
3655 __PACKAGE__-
>register_method({
3657 path
=> '{vmid}/snapshot',
3661 description
=> "Snapshot a VM.",
3663 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3666 additionalProperties
=> 0,
3668 node
=> get_standard_option
('pve-node'),
3669 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3670 snapname
=> get_standard_option
('pve-snapshot-name'),
3674 description
=> "Save the vmstate",
3679 description
=> "A textual description or comment.",
3685 description
=> "the task ID.",
3690 my $rpcenv = PVE
::RPCEnvironment
::get
();
3692 my $authuser = $rpcenv->get_user();
3694 my $node = extract_param
($param, 'node');
3696 my $vmid = extract_param
($param, 'vmid');
3698 my $snapname = extract_param
($param, 'snapname');
3700 die "unable to use snapshot name 'current' (reserved name)\n"
3701 if $snapname eq 'current';
3704 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3705 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3706 $param->{description
});
3709 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3712 __PACKAGE__-
>register_method({
3713 name
=> 'snapshot_cmd_idx',
3714 path
=> '{vmid}/snapshot/{snapname}',
3721 additionalProperties
=> 0,
3723 vmid
=> get_standard_option
('pve-vmid'),
3724 node
=> get_standard_option
('pve-node'),
3725 snapname
=> get_standard_option
('pve-snapshot-name'),
3734 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3741 push @$res, { cmd
=> 'rollback' };
3742 push @$res, { cmd
=> 'config' };
3747 __PACKAGE__-
>register_method({
3748 name
=> 'update_snapshot_config',
3749 path
=> '{vmid}/snapshot/{snapname}/config',
3753 description
=> "Update snapshot metadata.",
3755 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3758 additionalProperties
=> 0,
3760 node
=> get_standard_option
('pve-node'),
3761 vmid
=> get_standard_option
('pve-vmid'),
3762 snapname
=> get_standard_option
('pve-snapshot-name'),
3766 description
=> "A textual description or comment.",
3770 returns
=> { type
=> 'null' },
3774 my $rpcenv = PVE
::RPCEnvironment
::get
();
3776 my $authuser = $rpcenv->get_user();
3778 my $vmid = extract_param
($param, 'vmid');
3780 my $snapname = extract_param
($param, 'snapname');
3782 return undef if !defined($param->{description
});
3784 my $updatefn = sub {
3786 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3788 PVE
::QemuConfig-
>check_lock($conf);
3790 my $snap = $conf->{snapshots
}->{$snapname};
3792 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3794 $snap->{description
} = $param->{description
} if defined($param->{description
});
3796 PVE
::QemuConfig-
>write_config($vmid, $conf);
3799 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3804 __PACKAGE__-
>register_method({
3805 name
=> 'get_snapshot_config',
3806 path
=> '{vmid}/snapshot/{snapname}/config',
3809 description
=> "Get snapshot configuration",
3811 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
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'),
3821 returns
=> { type
=> "object" },
3825 my $rpcenv = PVE
::RPCEnvironment
::get
();
3827 my $authuser = $rpcenv->get_user();
3829 my $vmid = extract_param
($param, 'vmid');
3831 my $snapname = extract_param
($param, 'snapname');
3833 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3835 my $snap = $conf->{snapshots
}->{$snapname};
3837 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3842 __PACKAGE__-
>register_method({
3844 path
=> '{vmid}/snapshot/{snapname}/rollback',
3848 description
=> "Rollback VM state to specified snapshot.",
3850 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3853 additionalProperties
=> 0,
3855 node
=> get_standard_option
('pve-node'),
3856 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3857 snapname
=> get_standard_option
('pve-snapshot-name'),
3862 description
=> "the task ID.",
3867 my $rpcenv = PVE
::RPCEnvironment
::get
();
3869 my $authuser = $rpcenv->get_user();
3871 my $node = extract_param
($param, 'node');
3873 my $vmid = extract_param
($param, 'vmid');
3875 my $snapname = extract_param
($param, 'snapname');
3878 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3879 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3883 # hold migration lock, this makes sure that nobody create replication snapshots
3884 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3887 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3890 __PACKAGE__-
>register_method({
3891 name
=> 'delsnapshot',
3892 path
=> '{vmid}/snapshot/{snapname}',
3896 description
=> "Delete a VM snapshot.",
3898 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3901 additionalProperties
=> 0,
3903 node
=> get_standard_option
('pve-node'),
3904 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3905 snapname
=> get_standard_option
('pve-snapshot-name'),
3909 description
=> "For removal from config file, even if removing disk snapshots fails.",
3915 description
=> "the task ID.",
3920 my $rpcenv = PVE
::RPCEnvironment
::get
();
3922 my $authuser = $rpcenv->get_user();
3924 my $node = extract_param
($param, 'node');
3926 my $vmid = extract_param
($param, 'vmid');
3928 my $snapname = extract_param
($param, 'snapname');
3931 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3932 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3935 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3938 __PACKAGE__-
>register_method({
3940 path
=> '{vmid}/template',
3944 description
=> "Create a Template.",
3946 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3947 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3950 additionalProperties
=> 0,
3952 node
=> get_standard_option
('pve-node'),
3953 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3957 description
=> "If you want to convert only 1 disk to base image.",
3958 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3963 returns
=> { type
=> 'null'},
3967 my $rpcenv = PVE
::RPCEnvironment
::get
();
3969 my $authuser = $rpcenv->get_user();
3971 my $node = extract_param
($param, 'node');
3973 my $vmid = extract_param
($param, 'vmid');
3975 my $disk = extract_param
($param, 'disk');
3977 my $updatefn = sub {
3979 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3981 PVE
::QemuConfig-
>check_lock($conf);
3983 die "unable to create template, because VM contains snapshots\n"
3984 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3986 die "you can't convert a template to a template\n"
3987 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3989 die "you can't convert a VM to template if VM is running\n"
3990 if PVE
::QemuServer
::check_running
($vmid);
3993 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3996 $conf->{template
} = 1;
3997 PVE
::QemuConfig-
>write_config($vmid, $conf);
3999 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4002 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4006 __PACKAGE__-
>register_method({
4007 name
=> 'cloudinit_generated_config_dump',
4008 path
=> '{vmid}/cloudinit/dump',
4011 description
=> "Get automatically generated cloudinit config.",
4013 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4016 additionalProperties
=> 0,
4018 node
=> get_standard_option
('pve-node'),
4019 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4021 description
=> 'Config type.',
4023 enum
=> ['user', 'network', 'meta'],
4033 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4035 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});