1 package PVE
::API2
::Qemu
;
12 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
14 use PVE
::Tools
qw(extract_param);
15 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
17 use PVE
::JSONSchema
qw(get_standard_option);
19 use PVE
::ReplicationConfig
;
20 use PVE
::GuestHelpers
;
24 use PVE
::RPCEnvironment
;
25 use PVE
::AccessControl
;
29 use PVE
::API2
::Firewall
::VM
;
30 use PVE
::API2
::Qemu
::Agent
;
33 if (!$ENV{PVE_GENERATING_DOCS
}) {
34 require PVE
::HA
::Env
::PVE2
;
35 import PVE
::HA
::Env
::PVE2
;
36 require PVE
::HA
::Config
;
37 import PVE
::HA
::Config
;
41 use Data
::Dumper
; # fixme: remove
43 use base
qw(PVE::RESTHandler);
45 my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal.";
47 my $resolve_cdrom_alias = sub {
50 if (my $value = $param->{cdrom
}) {
51 $value .= ",media=cdrom" if $value !~ m/media=/;
52 $param->{ide2
} = $value;
53 delete $param->{cdrom
};
57 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
58 my $check_storage_access = sub {
59 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
61 PVE
::QemuServer
::foreach_drive
($settings, sub {
62 my ($ds, $drive) = @_;
64 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
66 my $volid = $drive->{file
};
68 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit')) {
70 } elsif ($volid =~ m/^(([^:\s]+):)?(cloudinit)$/) {
72 } elsif ($isCDROM && ($volid eq 'cdrom')) {
73 $rpcenv->check($authuser, "/", ['Sys.Console']);
74 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
75 my ($storeid, $size) = ($2 || $default_storage, $3);
76 die "no storage ID specified (and no default storage)\n" if !$storeid;
77 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
78 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
79 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
80 if !$scfg->{content
}->{images
};
82 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
86 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
87 if defined($settings->{vmstatestorage
});
90 my $check_storage_access_clone = sub {
91 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
95 PVE
::QemuServer
::foreach_drive
($conf, sub {
96 my ($ds, $drive) = @_;
98 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
100 my $volid = $drive->{file
};
102 return if !$volid || $volid eq 'none';
105 if ($volid eq 'cdrom') {
106 $rpcenv->check($authuser, "/", ['Sys.Console']);
108 # we simply allow access
109 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
110 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
111 $sharedvm = 0 if !$scfg->{shared
};
115 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
116 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
117 $sharedvm = 0 if !$scfg->{shared
};
119 $sid = $storage if $storage;
120 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
124 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
125 if defined($conf->{vmstatestorage
});
130 # Note: $pool is only needed when creating a VM, because pool permissions
131 # are automatically inherited if VM already exists inside a pool.
132 my $create_disks = sub {
133 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
140 my ($ds, $disk) = @_;
142 my $volid = $disk->{file
};
144 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
145 delete $disk->{size
};
146 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
147 } elsif ($volid =~ m!^(?:([^/:\s]+):)?cloudinit$!) {
148 my $storeid = $1 || $default_storage;
149 die "no storage ID specified (and no default storage)\n" if !$storeid;
150 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
151 my $name = "vm-$vmid-cloudinit";
155 $fmt = $disk->{format
} // "qcow2";
158 $fmt = $disk->{format
} // "raw";
161 # Initial disk created with 4 MB and aligned to 4MB on regeneration
162 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
163 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
164 $disk->{file
} = $volid;
165 $disk->{media
} = 'cdrom';
166 push @$vollist, $volid;
167 delete $disk->{format
}; # no longer needed
168 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
169 } elsif ($volid =~ $NEW_DISK_RE) {
170 my ($storeid, $size) = ($2 || $default_storage, $3);
171 die "no storage ID specified (and no default storage)\n" if !$storeid;
172 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
173 my $fmt = $disk->{format
} || $defformat;
175 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
178 if ($ds eq 'efidisk0') {
179 ($volid, $size) = PVE
::QemuServer
::create_efidisk
($storecfg, $storeid, $vmid, $fmt, $arch);
181 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
183 push @$vollist, $volid;
184 $disk->{file
} = $volid;
185 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
186 delete $disk->{format
}; # no longer needed
187 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
190 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
192 my $volid_is_new = 1;
195 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
196 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
201 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
203 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
205 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
207 die "volume $volid does not exists\n" if !$size;
209 $disk->{size
} = $size;
212 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
216 eval { PVE
::QemuServer
::foreach_drive
($settings, $code); };
218 # free allocated images on error
220 syslog
('err', "VM $vmid creating disks failed");
221 foreach my $volid (@$vollist) {
222 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
228 # modify vm config if everything went well
229 foreach my $ds (keys %$res) {
230 $conf->{$ds} = $res->{$ds};
247 my $memoryoptions = {
253 my $hwtypeoptions = {
266 my $generaloptions = {
273 'migrate_downtime' => 1,
274 'migrate_speed' => 1,
286 my $vmpoweroptions = {
293 'vmstatestorage' => 1,
296 my $cloudinitoptions = {
306 my $check_vm_modify_config_perm = sub {
307 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
309 return 1 if $authuser eq 'root@pam';
311 foreach my $opt (@$key_list) {
312 # some checks (e.g., disk, serial port, usb) need to be done somewhere
313 # else, as there the permission can be value dependend
314 next if PVE
::QemuServer
::is_valid_drivename
($opt);
315 next if $opt eq 'cdrom';
316 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
319 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
320 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
321 } elsif ($memoryoptions->{$opt}) {
322 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
323 } elsif ($hwtypeoptions->{$opt}) {
324 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
325 } elsif ($generaloptions->{$opt}) {
326 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
327 # special case for startup since it changes host behaviour
328 if ($opt eq 'startup') {
329 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
331 } elsif ($vmpoweroptions->{$opt}) {
332 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
333 } elsif ($diskoptions->{$opt}) {
334 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
335 } elsif ($cloudinitoptions->{$opt} || ($opt =~ m/^(?:net|ipconfig)\d+$/)) {
336 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
338 # catches hostpci\d+, args, lock, etc.
339 # new options will be checked here
340 die "only root can set '$opt' config\n";
347 __PACKAGE__-
>register_method({
351 description
=> "Virtual machine index (per node).",
353 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
357 protected
=> 1, # qemu pid files are only readable by root
359 additionalProperties
=> 0,
361 node
=> get_standard_option
('pve-node'),
365 description
=> "Determine the full status of active VMs.",
373 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
375 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
380 my $rpcenv = PVE
::RPCEnvironment
::get
();
381 my $authuser = $rpcenv->get_user();
383 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
386 foreach my $vmid (keys %$vmstatus) {
387 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
389 my $data = $vmstatus->{$vmid};
398 __PACKAGE__-
>register_method({
402 description
=> "Create or restore a virtual machine.",
404 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
405 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
406 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
407 user
=> 'all', # check inside
412 additionalProperties
=> 0,
413 properties
=> PVE
::QemuServer
::json_config_properties
(
415 node
=> get_standard_option
('pve-node'),
416 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
418 description
=> "The backup file.",
422 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
424 storage
=> get_standard_option
('pve-storage-id', {
425 description
=> "Default storage.",
427 completion
=> \
&PVE
::QemuServer
::complete_storage
,
432 description
=> "Allow to overwrite existing VM.",
433 requires
=> 'archive',
438 description
=> "Assign a unique random ethernet address.",
439 requires
=> 'archive',
443 type
=> 'string', format
=> 'pve-poolid',
444 description
=> "Add the VM to the specified pool.",
447 description
=> "Override I/O bandwidth limit (in KiB/s).",
451 default => 'restore limit from datacenter or storage config',
457 description
=> "Start VM after it was created successfully.",
467 my $rpcenv = PVE
::RPCEnvironment
::get
();
469 my $authuser = $rpcenv->get_user();
471 my $node = extract_param
($param, 'node');
473 my $vmid = extract_param
($param, 'vmid');
475 my $archive = extract_param
($param, 'archive');
476 my $is_restore = !!$archive;
478 my $storage = extract_param
($param, 'storage');
480 my $force = extract_param
($param, 'force');
482 my $unique = extract_param
($param, 'unique');
484 my $pool = extract_param
($param, 'pool');
486 my $bwlimit = extract_param
($param, 'bwlimit');
488 my $start_after_create = extract_param
($param, 'start');
490 my $filename = PVE
::QemuConfig-
>config_file($vmid);
492 my $storecfg = PVE
::Storage
::config
();
494 if (defined(my $ssh_keys = $param->{sshkeys
})) {
495 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
496 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
499 PVE
::Cluster
::check_cfs_quorum
();
501 if (defined($pool)) {
502 $rpcenv->check_pool_exist($pool);
505 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
506 if defined($storage);
508 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
510 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
512 } elsif ($archive && $force && (-f
$filename) &&
513 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
514 # OK: user has VM.Backup permissions, and want to restore an existing VM
520 &$resolve_cdrom_alias($param);
522 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
524 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
526 foreach my $opt (keys %$param) {
527 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
528 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
529 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
531 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
532 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
536 PVE
::QemuServer
::add_random_macs
($param);
538 my $keystr = join(' ', keys %$param);
539 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
541 if ($archive eq '-') {
542 die "pipe requires cli environment\n"
543 if $rpcenv->{type
} ne 'cli';
545 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
546 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
550 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
552 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
553 die "$emsg $@" if $@;
555 my $restorefn = sub {
556 my $conf = PVE
::QemuConfig-
>load_config($vmid);
558 PVE
::QemuConfig-
>check_protection($conf, $emsg);
560 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
563 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
569 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
570 # Convert restored VM to template if backup was VM template
571 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
572 warn "Convert to template.\n";
573 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
577 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
579 if ($start_after_create) {
580 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
585 # ensure no old replication state are exists
586 PVE
::ReplicationState
::delete_guest_states
($vmid);
588 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
592 # ensure no old replication state are exists
593 PVE
::ReplicationState
::delete_guest_states
($vmid);
601 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
605 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
607 if (!$conf->{bootdisk
}) {
608 my $firstdisk = PVE
::QemuServer
::resolve_first_disk
($conf);
609 $conf->{bootdisk
} = $firstdisk if $firstdisk;
612 # auto generate uuid if user did not specify smbios1 option
613 if (!$conf->{smbios1
}) {
614 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
617 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
618 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
621 PVE
::QemuConfig-
>write_config($vmid, $conf);
627 foreach my $volid (@$vollist) {
628 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
634 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
637 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
639 if ($start_after_create) {
640 print "Execute autostart\n";
641 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
646 my ($code, $worker_name);
648 $worker_name = 'qmrestore';
650 eval { $restorefn->() };
652 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
658 $worker_name = 'qmcreate';
660 eval { $createfn->() };
663 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
664 unlink($conffile) or die "failed to remove config file: $!\n";
672 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
675 __PACKAGE__-
>register_method({
680 description
=> "Directory index",
685 additionalProperties
=> 0,
687 node
=> get_standard_option
('pve-node'),
688 vmid
=> get_standard_option
('pve-vmid'),
696 subdir
=> { type
=> 'string' },
699 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
705 { subdir
=> 'config' },
706 { subdir
=> 'pending' },
707 { subdir
=> 'status' },
708 { subdir
=> 'unlink' },
709 { subdir
=> 'vncproxy' },
710 { subdir
=> 'termproxy' },
711 { subdir
=> 'migrate' },
712 { subdir
=> 'resize' },
713 { subdir
=> 'move' },
715 { subdir
=> 'rrddata' },
716 { subdir
=> 'monitor' },
717 { subdir
=> 'agent' },
718 { subdir
=> 'snapshot' },
719 { subdir
=> 'spiceproxy' },
720 { subdir
=> 'sendkey' },
721 { subdir
=> 'firewall' },
727 __PACKAGE__-
>register_method ({
728 subclass
=> "PVE::API2::Firewall::VM",
729 path
=> '{vmid}/firewall',
732 __PACKAGE__-
>register_method ({
733 subclass
=> "PVE::API2::Qemu::Agent",
734 path
=> '{vmid}/agent',
737 __PACKAGE__-
>register_method({
739 path
=> '{vmid}/rrd',
741 protected
=> 1, # fixme: can we avoid that?
743 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
745 description
=> "Read VM RRD statistics (returns PNG)",
747 additionalProperties
=> 0,
749 node
=> get_standard_option
('pve-node'),
750 vmid
=> get_standard_option
('pve-vmid'),
752 description
=> "Specify the time frame you are interested in.",
754 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
757 description
=> "The list of datasources you want to display.",
758 type
=> 'string', format
=> 'pve-configid-list',
761 description
=> "The RRD consolidation function",
763 enum
=> [ 'AVERAGE', 'MAX' ],
771 filename
=> { type
=> 'string' },
777 return PVE
::Cluster
::create_rrd_graph
(
778 "pve2-vm/$param->{vmid}", $param->{timeframe
},
779 $param->{ds
}, $param->{cf
});
783 __PACKAGE__-
>register_method({
785 path
=> '{vmid}/rrddata',
787 protected
=> 1, # fixme: can we avoid that?
789 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
791 description
=> "Read VM RRD statistics",
793 additionalProperties
=> 0,
795 node
=> get_standard_option
('pve-node'),
796 vmid
=> get_standard_option
('pve-vmid'),
798 description
=> "Specify the time frame you are interested in.",
800 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
803 description
=> "The RRD consolidation function",
805 enum
=> [ 'AVERAGE', 'MAX' ],
820 return PVE
::Cluster
::create_rrd_data
(
821 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
825 __PACKAGE__-
>register_method({
827 path
=> '{vmid}/config',
830 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
832 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
835 additionalProperties
=> 0,
837 node
=> get_standard_option
('pve-node'),
838 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
840 description
=> "Get current values (instead of pending values).",
845 snapshot
=> get_standard_option
('pve-snapshot-name', {
846 description
=> "Fetch config values from given snapshot.",
849 my ($cmd, $pname, $cur, $args) = @_;
850 PVE
::QemuConfig-
>snapshot_list($args->[0]);
856 description
=> "The current VM configuration.",
858 properties
=> PVE
::QemuServer
::json_config_properties
({
861 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
868 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
870 if (my $snapname = $param->{snapshot
}) {
871 my $snapshot = $conf->{snapshots
}->{$snapname};
872 die "snapshot '$snapname' does not exist\n" if !defined($snapshot);
874 $snapshot->{digest
} = $conf->{digest
}; # keep file digest for API
879 delete $conf->{snapshots
};
881 if (!$param->{current
}) {
882 foreach my $opt (keys %{$conf->{pending
}}) {
883 next if $opt eq 'delete';
884 my $value = $conf->{pending
}->{$opt};
885 next if ref($value); # just to be sure
886 $conf->{$opt} = $value;
888 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
889 foreach my $opt (keys %$pending_delete_hash) {
890 delete $conf->{$opt} if $conf->{$opt};
894 delete $conf->{pending
};
896 # hide cloudinit password
897 if ($conf->{cipassword
}) {
898 $conf->{cipassword
} = '**********';
904 __PACKAGE__-
>register_method({
905 name
=> 'vm_pending',
906 path
=> '{vmid}/pending',
909 description
=> "Get virtual machine configuration, including pending changes.",
911 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
914 additionalProperties
=> 0,
916 node
=> get_standard_option
('pve-node'),
917 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
926 description
=> "Configuration option name.",
930 description
=> "Current value.",
935 description
=> "Pending value.",
940 description
=> "Indicates a pending delete request if present and not 0. " .
941 "The value 2 indicates a force-delete request.",
953 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
955 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
959 foreach my $opt (keys %$conf) {
960 next if ref($conf->{$opt});
961 my $item = { key
=> $opt };
962 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
963 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
964 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
966 # hide cloudinit password
967 if ($opt eq 'cipassword') {
968 $item->{value
} = '**********' if defined($item->{value
});
969 # the trailing space so that the pending string is different
970 $item->{pending
} = '********** ' if defined($item->{pending
});
975 foreach my $opt (keys %{$conf->{pending
}}) {
976 next if $opt eq 'delete';
977 next if ref($conf->{pending
}->{$opt}); # just to be sure
978 next if defined($conf->{$opt});
979 my $item = { key
=> $opt };
980 $item->{pending
} = $conf->{pending
}->{$opt};
982 # hide cloudinit password
983 if ($opt eq 'cipassword') {
984 $item->{pending
} = '**********' if defined($item->{pending
});
989 while (my ($opt, $force) = each %$pending_delete_hash) {
990 next if $conf->{pending
}->{$opt}; # just to be sure
991 next if $conf->{$opt};
992 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
999 # POST/PUT {vmid}/config implementation
1001 # The original API used PUT (idempotent) an we assumed that all operations
1002 # are fast. But it turned out that almost any configuration change can
1003 # involve hot-plug actions, or disk alloc/free. Such actions can take long
1004 # time to complete and have side effects (not idempotent).
1006 # The new implementation uses POST and forks a worker process. We added
1007 # a new option 'background_delay'. If specified we wait up to
1008 # 'background_delay' second for the worker task to complete. It returns null
1009 # if the task is finished within that time, else we return the UPID.
1011 my $update_vm_api = sub {
1012 my ($param, $sync) = @_;
1014 my $rpcenv = PVE
::RPCEnvironment
::get
();
1016 my $authuser = $rpcenv->get_user();
1018 my $node = extract_param
($param, 'node');
1020 my $vmid = extract_param
($param, 'vmid');
1022 my $digest = extract_param
($param, 'digest');
1024 my $background_delay = extract_param
($param, 'background_delay');
1026 if (defined(my $cipassword = $param->{cipassword
})) {
1027 # Same logic as in cloud-init (but with the regex fixed...)
1028 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
1029 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1032 my @paramarr = (); # used for log message
1033 foreach my $key (sort keys %$param) {
1034 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1035 push @paramarr, "-$key", $value;
1038 my $skiplock = extract_param
($param, 'skiplock');
1039 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1040 if $skiplock && $authuser ne 'root@pam';
1042 my $delete_str = extract_param
($param, 'delete');
1044 my $revert_str = extract_param
($param, 'revert');
1046 my $force = extract_param
($param, 'force');
1048 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1049 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1050 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1053 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1055 my $storecfg = PVE
::Storage
::config
();
1057 my $defaults = PVE
::QemuServer
::load_defaults
();
1059 &$resolve_cdrom_alias($param);
1061 # now try to verify all parameters
1064 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1065 if (!PVE
::QemuServer
::option_exists
($opt)) {
1066 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1069 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1070 "-revert $opt' at the same time" })
1071 if defined($param->{$opt});
1073 $revert->{$opt} = 1;
1077 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1078 $opt = 'ide2' if $opt eq 'cdrom';
1080 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1081 "-delete $opt' at the same time" })
1082 if defined($param->{$opt});
1084 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1085 "-revert $opt' at the same time" })
1088 if (!PVE
::QemuServer
::option_exists
($opt)) {
1089 raise_param_exc
({ delete => "unknown option '$opt'" });
1095 my $repl_conf = PVE
::ReplicationConfig-
>new();
1096 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1097 my $check_replication = sub {
1099 return if !$is_replicated;
1100 my $volid = $drive->{file
};
1101 return if !$volid || !($drive->{replicate
}//1);
1102 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1103 my ($storeid, $format);
1104 if ($volid =~ $NEW_DISK_RE) {
1106 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1108 ($storeid, undef) = PVE
::Storage
::parse_volume_id
($volid, 1);
1109 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1111 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1112 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1113 return if $scfg->{shared
};
1114 die "cannot add non-replicatable volume to a replicated VM\n";
1117 foreach my $opt (keys %$param) {
1118 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1119 # cleanup drive path
1120 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1121 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1122 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1123 $check_replication->($drive);
1124 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1125 } elsif ($opt =~ m/^net(\d+)$/) {
1127 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1128 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1129 } elsif ($opt eq 'vmgenid') {
1130 if ($param->{$opt} eq '1') {
1131 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1133 } elsif ($opt eq 'hookscript') {
1134 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1135 raise_param_exc
({ $opt => $@ }) if $@;
1139 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1141 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1143 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1145 my $updatefn = sub {
1147 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1149 die "checksum missmatch (file change by other user?)\n"
1150 if $digest && $digest ne $conf->{digest
};
1152 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1154 foreach my $opt (keys %$revert) {
1155 if (defined($conf->{$opt})) {
1156 $param->{$opt} = $conf->{$opt};
1157 } elsif (defined($conf->{pending
}->{$opt})) {
1162 if ($param->{memory
} || defined($param->{balloon
})) {
1163 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1164 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1166 die "balloon value too large (must be smaller than assigned memory)\n"
1167 if $balloon && $balloon > $maxmem;
1170 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1174 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1176 # write updates to pending section
1178 my $modified = {}; # record what $option we modify
1180 foreach my $opt (@delete) {
1181 $modified->{$opt} = 1;
1182 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1183 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1184 warn "cannot delete '$opt' - not set in current configuration!\n";
1185 $modified->{$opt} = 0;
1189 if ($opt =~ m/^unused/) {
1190 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1191 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1192 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1193 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1194 delete $conf->{$opt};
1195 PVE
::QemuConfig-
>write_config($vmid, $conf);
1197 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1198 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1199 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1200 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1201 if defined($conf->{pending
}->{$opt});
1202 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1203 PVE
::QemuConfig-
>write_config($vmid, $conf);
1204 } elsif ($opt =~ m/^serial\d+$/) {
1205 if ($conf->{$opt} eq 'socket') {
1206 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1207 } elsif ($authuser ne 'root@pam') {
1208 die "only root can delete '$opt' config for real devices\n";
1210 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1211 PVE
::QemuConfig-
>write_config($vmid, $conf);
1212 } elsif ($opt =~ m/^usb\d+$/) {
1213 if ($conf->{$opt} =~ m/spice/) {
1214 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1215 } elsif ($authuser ne 'root@pam') {
1216 die "only root can delete '$opt' config for real devices\n";
1218 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1219 PVE
::QemuConfig-
>write_config($vmid, $conf);
1221 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1222 PVE
::QemuConfig-
>write_config($vmid, $conf);
1226 foreach my $opt (keys %$param) { # add/change
1227 $modified->{$opt} = 1;
1228 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1229 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1231 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
1233 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1234 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1235 # FIXME: cloudinit: CDROM or Disk?
1236 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1237 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1239 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1241 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1242 if defined($conf->{pending
}->{$opt});
1244 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1245 } elsif ($opt =~ m/^serial\d+/) {
1246 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1247 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1248 } elsif ($authuser ne 'root@pam') {
1249 die "only root can modify '$opt' config for real devices\n";
1251 $conf->{pending
}->{$opt} = $param->{$opt};
1252 } elsif ($opt =~ m/^usb\d+/) {
1253 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1254 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1255 } elsif ($authuser ne 'root@pam') {
1256 die "only root can modify '$opt' config for real devices\n";
1258 $conf->{pending
}->{$opt} = $param->{$opt};
1260 $conf->{pending
}->{$opt} = $param->{$opt};
1262 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1263 PVE
::QemuConfig-
>write_config($vmid, $conf);
1266 # remove pending changes when nothing changed
1267 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1268 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1269 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1271 return if !scalar(keys %{$conf->{pending
}});
1273 my $running = PVE
::QemuServer
::check_running
($vmid);
1275 # apply pending changes
1277 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1281 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1282 raise_param_exc
($errors) if scalar(keys %$errors);
1284 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1294 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1296 if ($background_delay) {
1298 # Note: It would be better to do that in the Event based HTTPServer
1299 # to avoid blocking call to sleep.
1301 my $end_time = time() + $background_delay;
1303 my $task = PVE
::Tools
::upid_decode
($upid);
1306 while (time() < $end_time) {
1307 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1309 sleep(1); # this gets interrupted when child process ends
1313 my $status = PVE
::Tools
::upid_read_status
($upid);
1314 return undef if $status eq 'OK';
1323 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1326 my $vm_config_perm_list = [
1331 'VM.Config.Network',
1333 'VM.Config.Options',
1336 __PACKAGE__-
>register_method({
1337 name
=> 'update_vm_async',
1338 path
=> '{vmid}/config',
1342 description
=> "Set virtual machine options (asynchrounous API).",
1344 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1347 additionalProperties
=> 0,
1348 properties
=> PVE
::QemuServer
::json_config_properties
(
1350 node
=> get_standard_option
('pve-node'),
1351 vmid
=> get_standard_option
('pve-vmid'),
1352 skiplock
=> get_standard_option
('skiplock'),
1354 type
=> 'string', format
=> 'pve-configid-list',
1355 description
=> "A list of settings you want to delete.",
1359 type
=> 'string', format
=> 'pve-configid-list',
1360 description
=> "Revert a pending change.",
1365 description
=> $opt_force_description,
1367 requires
=> 'delete',
1371 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1375 background_delay
=> {
1377 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1388 code
=> $update_vm_api,
1391 __PACKAGE__-
>register_method({
1392 name
=> 'update_vm',
1393 path
=> '{vmid}/config',
1397 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1399 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1402 additionalProperties
=> 0,
1403 properties
=> PVE
::QemuServer
::json_config_properties
(
1405 node
=> get_standard_option
('pve-node'),
1406 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1407 skiplock
=> get_standard_option
('skiplock'),
1409 type
=> 'string', format
=> 'pve-configid-list',
1410 description
=> "A list of settings you want to delete.",
1414 type
=> 'string', format
=> 'pve-configid-list',
1415 description
=> "Revert a pending change.",
1420 description
=> $opt_force_description,
1422 requires
=> 'delete',
1426 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1432 returns
=> { type
=> 'null' },
1435 &$update_vm_api($param, 1);
1441 __PACKAGE__-
>register_method({
1442 name
=> 'destroy_vm',
1447 description
=> "Destroy the vm (also delete all used/owned volumes).",
1449 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1452 additionalProperties
=> 0,
1454 node
=> get_standard_option
('pve-node'),
1455 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1456 skiplock
=> get_standard_option
('skiplock'),
1465 my $rpcenv = PVE
::RPCEnvironment
::get
();
1467 my $authuser = $rpcenv->get_user();
1469 my $vmid = $param->{vmid
};
1471 my $skiplock = $param->{skiplock
};
1472 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1473 if $skiplock && $authuser ne 'root@pam';
1476 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1478 my $storecfg = PVE
::Storage
::config
();
1480 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1482 die "unable to remove VM $vmid - used in HA resources\n"
1483 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1485 # do not allow destroy if there are replication jobs
1486 my $repl_conf = PVE
::ReplicationConfig-
>new();
1487 $repl_conf->check_for_existing_jobs($vmid);
1489 # early tests (repeat after locking)
1490 die "VM $vmid is running - destroy failed\n"
1491 if PVE
::QemuServer
::check_running
($vmid);
1496 syslog
('info', "destroy VM $vmid: $upid\n");
1498 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1500 PVE
::AccessControl
::remove_vm_access
($vmid);
1502 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1505 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1508 __PACKAGE__-
>register_method({
1510 path
=> '{vmid}/unlink',
1514 description
=> "Unlink/delete disk images.",
1516 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1519 additionalProperties
=> 0,
1521 node
=> get_standard_option
('pve-node'),
1522 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1524 type
=> 'string', format
=> 'pve-configid-list',
1525 description
=> "A list of disk IDs you want to delete.",
1529 description
=> $opt_force_description,
1534 returns
=> { type
=> 'null'},
1538 $param->{delete} = extract_param
($param, 'idlist');
1540 __PACKAGE__-
>update_vm($param);
1547 __PACKAGE__-
>register_method({
1549 path
=> '{vmid}/vncproxy',
1553 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1555 description
=> "Creates a TCP VNC proxy connections.",
1557 additionalProperties
=> 0,
1559 node
=> get_standard_option
('pve-node'),
1560 vmid
=> get_standard_option
('pve-vmid'),
1564 description
=> "starts websockify instead of vncproxy",
1569 additionalProperties
=> 0,
1571 user
=> { type
=> 'string' },
1572 ticket
=> { type
=> 'string' },
1573 cert
=> { type
=> 'string' },
1574 port
=> { type
=> 'integer' },
1575 upid
=> { type
=> 'string' },
1581 my $rpcenv = PVE
::RPCEnvironment
::get
();
1583 my $authuser = $rpcenv->get_user();
1585 my $vmid = $param->{vmid
};
1586 my $node = $param->{node
};
1587 my $websocket = $param->{websocket
};
1589 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1590 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1592 my $authpath = "/vms/$vmid";
1594 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1596 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1602 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1603 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1604 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1605 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1606 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1608 $family = PVE
::Tools
::get_host_address_family
($node);
1611 my $port = PVE
::Tools
::next_vnc_port
($family);
1618 syslog
('info', "starting vnc proxy $upid\n");
1624 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1626 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1627 '-timeout', $timeout, '-authpath', $authpath,
1628 '-perm', 'Sys.Console'];
1630 if ($param->{websocket
}) {
1631 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1632 push @$cmd, '-notls', '-listen', 'localhost';
1635 push @$cmd, '-c', @$remcmd, @$termcmd;
1637 PVE
::Tools
::run_command
($cmd);
1641 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1643 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1645 my $sock = IO
::Socket
::IP-
>new(
1650 GetAddrInfoFlags
=> 0,
1651 ) or die "failed to create socket: $!\n";
1652 # Inside the worker we shouldn't have any previous alarms
1653 # running anyway...:
1655 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1657 accept(my $cli, $sock) or die "connection failed: $!\n";
1660 if (PVE
::Tools
::run_command
($cmd,
1661 output
=> '>&'.fileno($cli),
1662 input
=> '<&'.fileno($cli),
1665 die "Failed to run vncproxy.\n";
1672 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1674 PVE
::Tools
::wait_for_vnc_port
($port);
1685 __PACKAGE__-
>register_method({
1686 name
=> 'termproxy',
1687 path
=> '{vmid}/termproxy',
1691 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1693 description
=> "Creates a TCP proxy connections.",
1695 additionalProperties
=> 0,
1697 node
=> get_standard_option
('pve-node'),
1698 vmid
=> get_standard_option
('pve-vmid'),
1702 enum
=> [qw(serial0 serial1 serial2 serial3)],
1703 description
=> "opens a serial terminal (defaults to display)",
1708 additionalProperties
=> 0,
1710 user
=> { type
=> 'string' },
1711 ticket
=> { type
=> 'string' },
1712 port
=> { type
=> 'integer' },
1713 upid
=> { type
=> 'string' },
1719 my $rpcenv = PVE
::RPCEnvironment
::get
();
1721 my $authuser = $rpcenv->get_user();
1723 my $vmid = $param->{vmid
};
1724 my $node = $param->{node
};
1725 my $serial = $param->{serial
};
1727 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1729 if (!defined($serial)) {
1730 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1731 $serial = $conf->{vga
};
1735 my $authpath = "/vms/$vmid";
1737 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1742 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1743 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1744 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1745 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, '-t');
1746 push @$remcmd, '--';
1748 $family = PVE
::Tools
::get_host_address_family
($node);
1751 my $port = PVE
::Tools
::next_vnc_port
($family);
1753 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1754 push @$termcmd, '-iface', $serial if $serial;
1759 syslog
('info', "starting qemu termproxy $upid\n");
1761 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1762 '--perm', 'VM.Console', '--'];
1763 push @$cmd, @$remcmd, @$termcmd;
1765 PVE
::Tools
::run_command
($cmd);
1768 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1770 PVE
::Tools
::wait_for_vnc_port
($port);
1780 __PACKAGE__-
>register_method({
1781 name
=> 'vncwebsocket',
1782 path
=> '{vmid}/vncwebsocket',
1785 description
=> "You also need to pass a valid ticket (vncticket).",
1786 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1788 description
=> "Opens a weksocket for VNC traffic.",
1790 additionalProperties
=> 0,
1792 node
=> get_standard_option
('pve-node'),
1793 vmid
=> get_standard_option
('pve-vmid'),
1795 description
=> "Ticket from previous call to vncproxy.",
1800 description
=> "Port number returned by previous vncproxy call.",
1810 port
=> { type
=> 'string' },
1816 my $rpcenv = PVE
::RPCEnvironment
::get
();
1818 my $authuser = $rpcenv->get_user();
1820 my $vmid = $param->{vmid
};
1821 my $node = $param->{node
};
1823 my $authpath = "/vms/$vmid";
1825 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1827 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1829 # Note: VNC ports are acessible from outside, so we do not gain any
1830 # security if we verify that $param->{port} belongs to VM $vmid. This
1831 # check is done by verifying the VNC ticket (inside VNC protocol).
1833 my $port = $param->{port
};
1835 return { port
=> $port };
1838 __PACKAGE__-
>register_method({
1839 name
=> 'spiceproxy',
1840 path
=> '{vmid}/spiceproxy',
1845 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1847 description
=> "Returns a SPICE configuration to connect to the VM.",
1849 additionalProperties
=> 0,
1851 node
=> get_standard_option
('pve-node'),
1852 vmid
=> get_standard_option
('pve-vmid'),
1853 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1856 returns
=> get_standard_option
('remote-viewer-config'),
1860 my $rpcenv = PVE
::RPCEnvironment
::get
();
1862 my $authuser = $rpcenv->get_user();
1864 my $vmid = $param->{vmid
};
1865 my $node = $param->{node
};
1866 my $proxy = $param->{proxy
};
1868 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1869 my $title = "VM $vmid";
1870 $title .= " - ". $conf->{name
} if $conf->{name
};
1872 my $port = PVE
::QemuServer
::spice_port
($vmid);
1874 my ($ticket, undef, $remote_viewer_config) =
1875 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1877 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1878 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1880 return $remote_viewer_config;
1883 __PACKAGE__-
>register_method({
1885 path
=> '{vmid}/status',
1888 description
=> "Directory index",
1893 additionalProperties
=> 0,
1895 node
=> get_standard_option
('pve-node'),
1896 vmid
=> get_standard_option
('pve-vmid'),
1904 subdir
=> { type
=> 'string' },
1907 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1913 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1916 { subdir
=> 'current' },
1917 { subdir
=> 'start' },
1918 { subdir
=> 'stop' },
1924 __PACKAGE__-
>register_method({
1925 name
=> 'vm_status',
1926 path
=> '{vmid}/status/current',
1929 protected
=> 1, # qemu pid files are only readable by root
1930 description
=> "Get virtual machine status.",
1932 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1935 additionalProperties
=> 0,
1937 node
=> get_standard_option
('pve-node'),
1938 vmid
=> get_standard_option
('pve-vmid'),
1944 %$PVE::QemuServer
::vmstatus_return_properties
,
1946 description
=> "HA manager service status.",
1950 description
=> "Qemu VGA configuration supports spice.",
1955 description
=> "Qemu GuestAgent enabled in config.",
1965 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1967 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1968 my $status = $vmstatus->{$param->{vmid
}};
1970 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1972 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1973 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1978 __PACKAGE__-
>register_method({
1980 path
=> '{vmid}/status/start',
1984 description
=> "Start virtual machine.",
1986 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1989 additionalProperties
=> 0,
1991 node
=> get_standard_option
('pve-node'),
1992 vmid
=> get_standard_option
('pve-vmid',
1993 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1994 skiplock
=> get_standard_option
('skiplock'),
1995 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1996 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1999 enum
=> ['secure', 'insecure'],
2000 description
=> "Migration traffic is encrypted using an SSH " .
2001 "tunnel by default. On secure, completely private networks " .
2002 "this can be disabled to increase performance.",
2005 migration_network
=> {
2006 type
=> 'string', format
=> 'CIDR',
2007 description
=> "CIDR of the (sub) network that is used for migration.",
2010 machine
=> get_standard_option
('pve-qm-machine'),
2012 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
2024 my $rpcenv = PVE
::RPCEnvironment
::get
();
2025 my $authuser = $rpcenv->get_user();
2027 my $node = extract_param
($param, 'node');
2028 my $vmid = extract_param
($param, 'vmid');
2030 my $machine = extract_param
($param, 'machine');
2032 my $get_root_param = sub {
2033 my $value = extract_param
($param, $_[0]);
2034 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2035 if $value && $authuser ne 'root@pam';
2039 my $stateuri = $get_root_param->('stateuri');
2040 my $skiplock = $get_root_param->('skiplock');
2041 my $migratedfrom = $get_root_param->('migratedfrom');
2042 my $migration_type = $get_root_param->('migration_type');
2043 my $migration_network = $get_root_param->('migration_network');
2044 my $targetstorage = $get_root_param->('targetstorage');
2046 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2047 if $targetstorage && !$migratedfrom;
2049 # read spice ticket from STDIN
2051 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2052 if (defined(my $line = <STDIN
>)) {
2054 $spice_ticket = $line;
2058 PVE
::Cluster
::check_cfs_quorum
();
2060 my $storecfg = PVE
::Storage
::config
();
2062 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2066 print "Requesting HA start for VM $vmid\n";
2068 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2069 PVE
::Tools
::run_command
($cmd);
2073 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2080 syslog
('info', "start VM $vmid: $upid\n");
2082 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2083 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
2087 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2091 __PACKAGE__-
>register_method({
2093 path
=> '{vmid}/status/stop',
2097 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2098 "is akin to pulling the power plug of a running computer and may damage the VM data",
2100 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2103 additionalProperties
=> 0,
2105 node
=> get_standard_option
('pve-node'),
2106 vmid
=> get_standard_option
('pve-vmid',
2107 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2108 skiplock
=> get_standard_option
('skiplock'),
2109 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2111 description
=> "Wait maximal timeout seconds.",
2117 description
=> "Do not deactivate storage volumes.",
2130 my $rpcenv = PVE
::RPCEnvironment
::get
();
2131 my $authuser = $rpcenv->get_user();
2133 my $node = extract_param
($param, 'node');
2134 my $vmid = extract_param
($param, 'vmid');
2136 my $skiplock = extract_param
($param, 'skiplock');
2137 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2138 if $skiplock && $authuser ne 'root@pam';
2140 my $keepActive = extract_param
($param, 'keepActive');
2141 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2142 if $keepActive && $authuser ne 'root@pam';
2144 my $migratedfrom = extract_param
($param, 'migratedfrom');
2145 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2146 if $migratedfrom && $authuser ne 'root@pam';
2149 my $storecfg = PVE
::Storage
::config
();
2151 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2156 print "Requesting HA stop for VM $vmid\n";
2158 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'stopped'];
2159 PVE
::Tools
::run_command
($cmd);
2163 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2169 syslog
('info', "stop VM $vmid: $upid\n");
2171 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2172 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2176 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2180 __PACKAGE__-
>register_method({
2182 path
=> '{vmid}/status/reset',
2186 description
=> "Reset virtual machine.",
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'),
2205 my $rpcenv = PVE
::RPCEnvironment
::get
();
2207 my $authuser = $rpcenv->get_user();
2209 my $node = extract_param
($param, 'node');
2211 my $vmid = extract_param
($param, 'vmid');
2213 my $skiplock = extract_param
($param, 'skiplock');
2214 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2215 if $skiplock && $authuser ne 'root@pam';
2217 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2222 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2227 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2230 __PACKAGE__-
>register_method({
2231 name
=> 'vm_shutdown',
2232 path
=> '{vmid}/status/shutdown',
2236 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2237 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2239 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2242 additionalProperties
=> 0,
2244 node
=> get_standard_option
('pve-node'),
2245 vmid
=> get_standard_option
('pve-vmid',
2246 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2247 skiplock
=> get_standard_option
('skiplock'),
2249 description
=> "Wait maximal timeout seconds.",
2255 description
=> "Make sure the VM stops.",
2261 description
=> "Do not deactivate storage volumes.",
2274 my $rpcenv = PVE
::RPCEnvironment
::get
();
2275 my $authuser = $rpcenv->get_user();
2277 my $node = extract_param
($param, 'node');
2278 my $vmid = extract_param
($param, 'vmid');
2280 my $skiplock = extract_param
($param, 'skiplock');
2281 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2282 if $skiplock && $authuser ne 'root@pam';
2284 my $keepActive = extract_param
($param, 'keepActive');
2285 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2286 if $keepActive && $authuser ne 'root@pam';
2288 my $storecfg = PVE
::Storage
::config
();
2292 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2293 # otherwise, we will infer a shutdown command, but run into the timeout,
2294 # then when the vm is resumed, it will instantly shutdown
2296 # checking the qmp status here to get feedback to the gui/cli/api
2297 # and the status query should not take too long
2298 my $qmpstatus = eval {
2299 PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2303 if (!$err && $qmpstatus->{status
} eq "paused") {
2304 if ($param->{forceStop
}) {
2305 warn "VM is paused - stop instead of shutdown\n";
2308 die "VM is paused - cannot shutdown\n";
2312 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2317 print "Requesting HA stop for VM $vmid\n";
2319 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'stopped'];
2320 PVE
::Tools
::run_command
($cmd);
2324 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2331 syslog
('info', "shutdown VM $vmid: $upid\n");
2333 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2334 $shutdown, $param->{forceStop
}, $keepActive);
2338 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2342 __PACKAGE__-
>register_method({
2343 name
=> 'vm_suspend',
2344 path
=> '{vmid}/status/suspend',
2348 description
=> "Suspend virtual machine.",
2350 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2353 additionalProperties
=> 0,
2355 node
=> get_standard_option
('pve-node'),
2356 vmid
=> get_standard_option
('pve-vmid',
2357 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2358 skiplock
=> get_standard_option
('skiplock'),
2363 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2365 statestorage
=> get_standard_option
('pve-storage-id', {
2366 description
=> "The storage for the VM state",
2367 requires
=> 'todisk',
2369 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2379 my $rpcenv = PVE
::RPCEnvironment
::get
();
2380 my $authuser = $rpcenv->get_user();
2382 my $node = extract_param
($param, 'node');
2383 my $vmid = extract_param
($param, 'vmid');
2385 my $todisk = extract_param
($param, 'todisk') // 0;
2387 my $statestorage = extract_param
($param, 'statestorage');
2389 my $skiplock = extract_param
($param, 'skiplock');
2390 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2391 if $skiplock && $authuser ne 'root@pam';
2393 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2395 die "Cannot suspend HA managed VM to disk\n"
2396 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2401 syslog
('info', "suspend VM $vmid: $upid\n");
2403 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2408 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2409 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2412 __PACKAGE__-
>register_method({
2413 name
=> 'vm_resume',
2414 path
=> '{vmid}/status/resume',
2418 description
=> "Resume virtual machine.",
2420 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2423 additionalProperties
=> 0,
2425 node
=> get_standard_option
('pve-node'),
2426 vmid
=> get_standard_option
('pve-vmid',
2427 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2428 skiplock
=> get_standard_option
('skiplock'),
2429 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2439 my $rpcenv = PVE
::RPCEnvironment
::get
();
2441 my $authuser = $rpcenv->get_user();
2443 my $node = extract_param
($param, 'node');
2445 my $vmid = extract_param
($param, 'vmid');
2447 my $skiplock = extract_param
($param, 'skiplock');
2448 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2449 if $skiplock && $authuser ne 'root@pam';
2451 my $nocheck = extract_param
($param, 'nocheck');
2453 my $to_disk_suspended;
2455 PVE
::QemuConfig-
>lock_config($vmid, sub {
2456 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2457 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2461 die "VM $vmid not running\n"
2462 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2467 syslog
('info', "resume VM $vmid: $upid\n");
2469 if (!$to_disk_suspended) {
2470 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2472 my $storecfg = PVE
::Storage
::config
();
2473 PVE
::QemuServer
::vm_start
($storecfg, $vmid, undef, $skiplock);
2479 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2482 __PACKAGE__-
>register_method({
2483 name
=> 'vm_sendkey',
2484 path
=> '{vmid}/sendkey',
2488 description
=> "Send key event to virtual machine.",
2490 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2493 additionalProperties
=> 0,
2495 node
=> get_standard_option
('pve-node'),
2496 vmid
=> get_standard_option
('pve-vmid',
2497 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2498 skiplock
=> get_standard_option
('skiplock'),
2500 description
=> "The key (qemu monitor encoding).",
2505 returns
=> { type
=> 'null'},
2509 my $rpcenv = PVE
::RPCEnvironment
::get
();
2511 my $authuser = $rpcenv->get_user();
2513 my $node = extract_param
($param, 'node');
2515 my $vmid = extract_param
($param, 'vmid');
2517 my $skiplock = extract_param
($param, 'skiplock');
2518 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2519 if $skiplock && $authuser ne 'root@pam';
2521 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2526 __PACKAGE__-
>register_method({
2527 name
=> 'vm_feature',
2528 path
=> '{vmid}/feature',
2532 description
=> "Check if feature for virtual machine is available.",
2534 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2537 additionalProperties
=> 0,
2539 node
=> get_standard_option
('pve-node'),
2540 vmid
=> get_standard_option
('pve-vmid'),
2542 description
=> "Feature to check.",
2544 enum
=> [ 'snapshot', 'clone', 'copy' ],
2546 snapname
=> get_standard_option
('pve-snapshot-name', {
2554 hasFeature
=> { type
=> 'boolean' },
2557 items
=> { type
=> 'string' },
2564 my $node = extract_param
($param, 'node');
2566 my $vmid = extract_param
($param, 'vmid');
2568 my $snapname = extract_param
($param, 'snapname');
2570 my $feature = extract_param
($param, 'feature');
2572 my $running = PVE
::QemuServer
::check_running
($vmid);
2574 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2577 my $snap = $conf->{snapshots
}->{$snapname};
2578 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2581 my $storecfg = PVE
::Storage
::config
();
2583 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2584 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2587 hasFeature
=> $hasFeature,
2588 nodes
=> [ keys %$nodelist ],
2592 __PACKAGE__-
>register_method({
2594 path
=> '{vmid}/clone',
2598 description
=> "Create a copy of virtual machine/template.",
2600 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2601 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2602 "'Datastore.AllocateSpace' on any used storage.",
2605 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2607 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2608 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2613 additionalProperties
=> 0,
2615 node
=> get_standard_option
('pve-node'),
2616 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2617 newid
=> get_standard_option
('pve-vmid', {
2618 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2619 description
=> 'VMID for the clone.' }),
2622 type
=> 'string', format
=> 'dns-name',
2623 description
=> "Set a name for the new VM.",
2628 description
=> "Description for the new VM.",
2632 type
=> 'string', format
=> 'pve-poolid',
2633 description
=> "Add the new VM to the specified pool.",
2635 snapname
=> get_standard_option
('pve-snapshot-name', {
2638 storage
=> get_standard_option
('pve-storage-id', {
2639 description
=> "Target storage for full clone.",
2643 description
=> "Target format for file storage. Only valid for full clone.",
2646 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2651 description
=> "Create a full copy of all disks. This is always done when " .
2652 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2654 target
=> get_standard_option
('pve-node', {
2655 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2659 description
=> "Override I/O bandwidth limit (in KiB/s).",
2663 default => 'clone limit from datacenter or storage config',
2673 my $rpcenv = PVE
::RPCEnvironment
::get
();
2675 my $authuser = $rpcenv->get_user();
2677 my $node = extract_param
($param, 'node');
2679 my $vmid = extract_param
($param, 'vmid');
2681 my $newid = extract_param
($param, 'newid');
2683 my $pool = extract_param
($param, 'pool');
2685 if (defined($pool)) {
2686 $rpcenv->check_pool_exist($pool);
2689 my $snapname = extract_param
($param, 'snapname');
2691 my $storage = extract_param
($param, 'storage');
2693 my $format = extract_param
($param, 'format');
2695 my $target = extract_param
($param, 'target');
2697 my $localnode = PVE
::INotify
::nodename
();
2699 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2701 PVE
::Cluster
::check_node_exists
($target) if $target;
2703 my $storecfg = PVE
::Storage
::config
();
2706 # check if storage is enabled on local node
2707 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2709 # check if storage is available on target node
2710 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2711 # clone only works if target storage is shared
2712 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2713 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2717 PVE
::Cluster
::check_cfs_quorum
();
2719 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2721 # exclusive lock if VM is running - else shared lock is enough;
2722 my $shared_lock = $running ?
0 : 1;
2726 # do all tests after lock
2727 # we also try to do all tests before we fork the worker
2729 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2731 PVE
::QemuConfig-
>check_lock($conf);
2733 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2735 die "unexpected state change\n" if $verify_running != $running;
2737 die "snapshot '$snapname' does not exist\n"
2738 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2740 my $full = extract_param
($param, 'full');
2741 if (!defined($full)) {
2742 $full = !PVE
::QemuConfig-
>is_template($conf);
2745 die "parameter 'storage' not allowed for linked clones\n"
2746 if defined($storage) && !$full;
2748 die "parameter 'format' not allowed for linked clones\n"
2749 if defined($format) && !$full;
2751 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2753 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2755 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2757 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2759 die "unable to create VM $newid: config file already exists\n"
2762 my $newconf = { lock => 'clone' };
2767 foreach my $opt (keys %$oldconf) {
2768 my $value = $oldconf->{$opt};
2770 # do not copy snapshot related info
2771 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2772 $opt eq 'vmstate' || $opt eq 'snapstate';
2774 # no need to copy unused images, because VMID(owner) changes anyways
2775 next if $opt =~ m/^unused\d+$/;
2777 # always change MAC! address
2778 if ($opt =~ m/^net(\d+)$/) {
2779 my $net = PVE
::QemuServer
::parse_net
($value);
2780 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2781 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2782 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2783 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2784 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2785 die "unable to parse drive options for '$opt'\n" if !$drive;
2786 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2787 $newconf->{$opt} = $value; # simply copy configuration
2789 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2790 die "Full clone feature is not supported for drive '$opt'\n"
2791 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2792 $fullclone->{$opt} = 1;
2794 # not full means clone instead of copy
2795 die "Linked clone feature is not supported for drive '$opt'\n"
2796 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2798 $drives->{$opt} = $drive;
2799 push @$vollist, $drive->{file
};
2802 # copy everything else
2803 $newconf->{$opt} = $value;
2807 # auto generate a new uuid
2808 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2809 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2810 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2812 # auto generate a new vmgenid if the option was set
2813 if ($newconf->{vmgenid
}) {
2814 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2817 delete $newconf->{template
};
2819 if ($param->{name
}) {
2820 $newconf->{name
} = $param->{name
};
2822 if ($oldconf->{name
}) {
2823 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2825 $newconf->{name
} = "Copy-of-VM-$vmid";
2829 if ($param->{description
}) {
2830 $newconf->{description
} = $param->{description
};
2833 # create empty/temp config - this fails if VM already exists on other node
2834 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2839 my $newvollist = [];
2846 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2848 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2850 my $bwlimit = extract_param
($param, 'bwlimit');
2852 my $total_jobs = scalar(keys %{$drives});
2855 foreach my $opt (keys %$drives) {
2856 my $drive = $drives->{$opt};
2857 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2859 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
2860 my $storage_list = [ $src_sid ];
2861 push @$storage_list, $storage if defined($storage);
2862 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
2864 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2865 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2866 $jobs, $skipcomplete, $oldconf->{agent
}, $clonelimit);
2868 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2870 PVE
::QemuConfig-
>write_config($newid, $newconf);
2874 delete $newconf->{lock};
2876 # do not write pending changes
2877 if (my @changes = keys %{$newconf->{pending
}}) {
2878 my $pending = join(',', @changes);
2879 warn "found pending changes for '$pending', discarding for clone\n";
2880 delete $newconf->{pending
};
2883 PVE
::QemuConfig-
>write_config($newid, $newconf);
2886 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2887 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2888 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2890 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2891 die "Failed to move config to node '$target' - rename failed: $!\n"
2892 if !rename($conffile, $newconffile);
2895 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2900 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2902 sleep 1; # some storage like rbd need to wait before release volume - really?
2904 foreach my $volid (@$newvollist) {
2905 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2908 die "clone failed: $err";
2914 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2916 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2919 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2920 # Aquire exclusive lock lock for $newid
2921 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2926 __PACKAGE__-
>register_method({
2927 name
=> 'move_vm_disk',
2928 path
=> '{vmid}/move_disk',
2932 description
=> "Move volume to different storage.",
2934 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2936 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2937 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2941 additionalProperties
=> 0,
2943 node
=> get_standard_option
('pve-node'),
2944 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2947 description
=> "The disk you want to move.",
2948 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2950 storage
=> get_standard_option
('pve-storage-id', {
2951 description
=> "Target storage.",
2952 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2956 description
=> "Target Format.",
2957 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2962 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2968 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2973 description
=> "Override I/O bandwidth limit (in KiB/s).",
2977 default => 'move limit from datacenter or storage config',
2983 description
=> "the task ID.",
2988 my $rpcenv = PVE
::RPCEnvironment
::get
();
2990 my $authuser = $rpcenv->get_user();
2992 my $node = extract_param
($param, 'node');
2994 my $vmid = extract_param
($param, 'vmid');
2996 my $digest = extract_param
($param, 'digest');
2998 my $disk = extract_param
($param, 'disk');
3000 my $storeid = extract_param
($param, 'storage');
3002 my $format = extract_param
($param, 'format');
3004 my $storecfg = PVE
::Storage
::config
();
3006 my $updatefn = sub {
3008 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3010 PVE
::QemuConfig-
>check_lock($conf);
3012 die "checksum missmatch (file change by other user?)\n"
3013 if $digest && $digest ne $conf->{digest
};
3015 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3017 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3019 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
3021 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3024 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3025 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3029 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
3030 (!$format || !$oldfmt || $oldfmt eq $format);
3032 # this only checks snapshots because $disk is passed!
3033 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3034 die "you can't move a disk with snapshots and delete the source\n"
3035 if $snapshotted && $param->{delete};
3037 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3039 my $running = PVE
::QemuServer
::check_running
($vmid);
3041 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3045 my $newvollist = [];
3051 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3053 warn "moving disk with snapshots, snapshots will not be moved!\n"
3056 my $bwlimit = extract_param
($param, 'bwlimit');
3057 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3059 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3060 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit);
3062 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
3064 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3066 # convert moved disk to base if part of template
3067 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3068 if PVE
::QemuConfig-
>is_template($conf);
3070 PVE
::QemuConfig-
>write_config($vmid, $conf);
3072 if ($running && PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
} && PVE
::QemuServer
::qga_check_running
($vmid)) {
3073 eval { PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-fstrim"); };
3077 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3078 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3085 foreach my $volid (@$newvollist) {
3086 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3089 die "storage migration failed: $err";
3092 if ($param->{delete}) {
3094 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3095 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3101 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3104 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3107 my $check_vm_disks_local = sub {
3108 my ($storecfg, $vmconf, $vmid) = @_;
3110 my $local_disks = {};
3112 # add some more information to the disks e.g. cdrom
3113 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3114 my ($volid, $attr) = @_;
3116 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3118 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3119 return if $scfg->{shared
};
3121 # The shared attr here is just a special case where the vdisk
3122 # is marked as shared manually
3123 return if $attr->{shared
};
3124 return if $attr->{cdrom
} and $volid eq "none";
3126 if (exists $local_disks->{$volid}) {
3127 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3129 $local_disks->{$volid} = $attr;
3130 # ensure volid is present in case it's needed
3131 $local_disks->{$volid}->{volid
} = $volid;
3135 return $local_disks;
3138 __PACKAGE__-
>register_method({
3139 name
=> 'migrate_vm_precondition',
3140 path
=> '{vmid}/migrate',
3144 description
=> "Get preconditions for migration.",
3146 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3149 additionalProperties
=> 0,
3151 node
=> get_standard_option
('pve-node'),
3152 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3153 target
=> get_standard_option
('pve-node', {
3154 description
=> "Target node.",
3155 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3163 running
=> { type
=> 'boolean' },
3167 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3169 not_allowed_nodes
=> {
3172 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3176 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3178 local_resources
=> {
3180 description
=> "List local resources e.g. pci, usb"
3187 my $rpcenv = PVE
::RPCEnvironment
::get
();
3189 my $authuser = $rpcenv->get_user();
3191 PVE
::Cluster
::check_cfs_quorum
();
3195 my $vmid = extract_param
($param, 'vmid');
3196 my $target = extract_param
($param, 'target');
3197 my $localnode = PVE
::INotify
::nodename
();
3201 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3202 my $storecfg = PVE
::Storage
::config
();
3205 # try to detect errors early
3206 PVE
::QemuConfig-
>check_lock($vmconf);
3208 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3210 # if vm is not running, return target nodes where local storage is available
3211 # for offline migration
3212 if (!$res->{running
}) {
3213 $res->{allowed_nodes
} = [];
3214 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3215 delete $checked_nodes->{$localnode};
3217 foreach my $node (keys %$checked_nodes) {
3218 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3219 push @{$res->{allowed_nodes
}}, $node;
3223 $res->{not_allowed_nodes
} = $checked_nodes;
3227 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3228 $res->{local_disks
} = [ values %$local_disks ];;
3230 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3232 $res->{local_resources
} = $local_resources;
3239 __PACKAGE__-
>register_method({
3240 name
=> 'migrate_vm',
3241 path
=> '{vmid}/migrate',
3245 description
=> "Migrate virtual machine. Creates a new migration task.",
3247 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3250 additionalProperties
=> 0,
3252 node
=> get_standard_option
('pve-node'),
3253 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3254 target
=> get_standard_option
('pve-node', {
3255 description
=> "Target node.",
3256 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3260 description
=> "Use online/live migration.",
3265 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3270 enum
=> ['secure', 'insecure'],
3271 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3274 migration_network
=> {
3275 type
=> 'string', format
=> 'CIDR',
3276 description
=> "CIDR of the (sub) network that is used for migration.",
3279 "with-local-disks" => {
3281 description
=> "Enable live storage migration for local disk",
3284 targetstorage
=> get_standard_option
('pve-storage-id', {
3285 description
=> "Default target storage.",
3287 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3290 description
=> "Override I/O bandwidth limit (in KiB/s).",
3294 default => 'migrate limit from datacenter or storage config',
3300 description
=> "the task ID.",
3305 my $rpcenv = PVE
::RPCEnvironment
::get
();
3306 my $authuser = $rpcenv->get_user();
3308 my $target = extract_param
($param, 'target');
3310 my $localnode = PVE
::INotify
::nodename
();
3311 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3313 PVE
::Cluster
::check_cfs_quorum
();
3315 PVE
::Cluster
::check_node_exists
($target);
3317 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3319 my $vmid = extract_param
($param, 'vmid');
3321 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3322 if !$param->{online
} && $param->{targetstorage
};
3324 raise_param_exc
({ force
=> "Only root may use this option." })
3325 if $param->{force
} && $authuser ne 'root@pam';
3327 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3328 if $param->{migration_type
} && $authuser ne 'root@pam';
3330 # allow root only until better network permissions are available
3331 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3332 if $param->{migration_network
} && $authuser ne 'root@pam';
3335 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3337 # try to detect errors early
3339 PVE
::QemuConfig-
>check_lock($conf);
3341 if (PVE
::QemuServer
::check_running
($vmid)) {
3342 die "cant migrate running VM without --online\n"
3343 if !$param->{online
};
3346 my $storecfg = PVE
::Storage
::config
();
3348 if( $param->{targetstorage
}) {
3349 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3351 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3354 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3359 print "Requesting HA migration for VM $vmid to node $target\n";
3361 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3362 PVE
::Tools
::run_command
($cmd);
3366 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3371 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3375 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3378 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3383 __PACKAGE__-
>register_method({
3385 path
=> '{vmid}/monitor',
3389 description
=> "Execute Qemu monitor commands.",
3391 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3392 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3395 additionalProperties
=> 0,
3397 node
=> get_standard_option
('pve-node'),
3398 vmid
=> get_standard_option
('pve-vmid'),
3401 description
=> "The monitor command.",
3405 returns
=> { type
=> 'string'},
3409 my $rpcenv = PVE
::RPCEnvironment
::get
();
3410 my $authuser = $rpcenv->get_user();
3413 my $command = shift;
3414 return $command =~ m/^\s*info(\s+|$)/
3415 || $command =~ m/^\s*help\s*$/;
3418 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3419 if !&$is_ro($param->{command
});
3421 my $vmid = $param->{vmid
};
3423 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3427 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3429 $res = "ERROR: $@" if $@;
3434 __PACKAGE__-
>register_method({
3435 name
=> 'resize_vm',
3436 path
=> '{vmid}/resize',
3440 description
=> "Extend volume size.",
3442 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3445 additionalProperties
=> 0,
3447 node
=> get_standard_option
('pve-node'),
3448 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3449 skiplock
=> get_standard_option
('skiplock'),
3452 description
=> "The disk you want to resize.",
3453 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3457 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3458 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.",
3462 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3468 returns
=> { type
=> 'null'},
3472 my $rpcenv = PVE
::RPCEnvironment
::get
();
3474 my $authuser = $rpcenv->get_user();
3476 my $node = extract_param
($param, 'node');
3478 my $vmid = extract_param
($param, 'vmid');
3480 my $digest = extract_param
($param, 'digest');
3482 my $disk = extract_param
($param, 'disk');
3484 my $sizestr = extract_param
($param, 'size');
3486 my $skiplock = extract_param
($param, 'skiplock');
3487 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3488 if $skiplock && $authuser ne 'root@pam';
3490 my $storecfg = PVE
::Storage
::config
();
3492 my $updatefn = sub {
3494 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3496 die "checksum missmatch (file change by other user?)\n"
3497 if $digest && $digest ne $conf->{digest
};
3498 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3500 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3502 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3504 my (undef, undef, undef, undef, undef, undef, $format) =
3505 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3507 die "can't resize volume: $disk if snapshot exists\n"
3508 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3510 my $volid = $drive->{file
};
3512 die "disk '$disk' has no associated volume\n" if !$volid;
3514 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3516 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3518 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3520 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3521 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3523 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3524 my ($ext, $newsize, $unit) = ($1, $2, $4);
3527 $newsize = $newsize * 1024;
3528 } elsif ($unit eq 'M') {
3529 $newsize = $newsize * 1024 * 1024;
3530 } elsif ($unit eq 'G') {
3531 $newsize = $newsize * 1024 * 1024 * 1024;
3532 } elsif ($unit eq 'T') {
3533 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3536 $newsize += $size if $ext;
3537 $newsize = int($newsize);
3539 die "shrinking disks is not supported\n" if $newsize < $size;
3541 return if $size == $newsize;
3543 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3545 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3547 $drive->{size
} = $newsize;
3548 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3550 PVE
::QemuConfig-
>write_config($vmid, $conf);
3553 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3557 __PACKAGE__-
>register_method({
3558 name
=> 'snapshot_list',
3559 path
=> '{vmid}/snapshot',
3561 description
=> "List all snapshots.",
3563 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3566 protected
=> 1, # qemu pid files are only readable by root
3568 additionalProperties
=> 0,
3570 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3571 node
=> get_standard_option
('pve-node'),
3580 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3584 description
=> "Snapshot includes RAM.",
3589 description
=> "Snapshot description.",
3593 description
=> "Snapshot creation time",
3595 renderer
=> 'timestamp',
3599 description
=> "Parent snapshot identifier.",
3605 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3610 my $vmid = $param->{vmid
};
3612 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3613 my $snaphash = $conf->{snapshots
} || {};
3617 foreach my $name (keys %$snaphash) {
3618 my $d = $snaphash->{$name};
3621 snaptime
=> $d->{snaptime
} || 0,
3622 vmstate
=> $d->{vmstate
} ?
1 : 0,
3623 description
=> $d->{description
} || '',
3625 $item->{parent
} = $d->{parent
} if $d->{parent
};
3626 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3630 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3633 digest
=> $conf->{digest
},
3634 running
=> $running,
3635 description
=> "You are here!",
3637 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3639 push @$res, $current;
3644 __PACKAGE__-
>register_method({
3646 path
=> '{vmid}/snapshot',
3650 description
=> "Snapshot a VM.",
3652 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3655 additionalProperties
=> 0,
3657 node
=> get_standard_option
('pve-node'),
3658 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3659 snapname
=> get_standard_option
('pve-snapshot-name'),
3663 description
=> "Save the vmstate",
3668 description
=> "A textual description or comment.",
3674 description
=> "the task ID.",
3679 my $rpcenv = PVE
::RPCEnvironment
::get
();
3681 my $authuser = $rpcenv->get_user();
3683 my $node = extract_param
($param, 'node');
3685 my $vmid = extract_param
($param, 'vmid');
3687 my $snapname = extract_param
($param, 'snapname');
3689 die "unable to use snapshot name 'current' (reserved name)\n"
3690 if $snapname eq 'current';
3693 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3694 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3695 $param->{description
});
3698 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3701 __PACKAGE__-
>register_method({
3702 name
=> 'snapshot_cmd_idx',
3703 path
=> '{vmid}/snapshot/{snapname}',
3710 additionalProperties
=> 0,
3712 vmid
=> get_standard_option
('pve-vmid'),
3713 node
=> get_standard_option
('pve-node'),
3714 snapname
=> get_standard_option
('pve-snapshot-name'),
3723 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3730 push @$res, { cmd
=> 'rollback' };
3731 push @$res, { cmd
=> 'config' };
3736 __PACKAGE__-
>register_method({
3737 name
=> 'update_snapshot_config',
3738 path
=> '{vmid}/snapshot/{snapname}/config',
3742 description
=> "Update snapshot metadata.",
3744 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3747 additionalProperties
=> 0,
3749 node
=> get_standard_option
('pve-node'),
3750 vmid
=> get_standard_option
('pve-vmid'),
3751 snapname
=> get_standard_option
('pve-snapshot-name'),
3755 description
=> "A textual description or comment.",
3759 returns
=> { type
=> 'null' },
3763 my $rpcenv = PVE
::RPCEnvironment
::get
();
3765 my $authuser = $rpcenv->get_user();
3767 my $vmid = extract_param
($param, 'vmid');
3769 my $snapname = extract_param
($param, 'snapname');
3771 return undef if !defined($param->{description
});
3773 my $updatefn = sub {
3775 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3777 PVE
::QemuConfig-
>check_lock($conf);
3779 my $snap = $conf->{snapshots
}->{$snapname};
3781 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3783 $snap->{description
} = $param->{description
} if defined($param->{description
});
3785 PVE
::QemuConfig-
>write_config($vmid, $conf);
3788 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3793 __PACKAGE__-
>register_method({
3794 name
=> 'get_snapshot_config',
3795 path
=> '{vmid}/snapshot/{snapname}/config',
3798 description
=> "Get snapshot configuration",
3800 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3803 additionalProperties
=> 0,
3805 node
=> get_standard_option
('pve-node'),
3806 vmid
=> get_standard_option
('pve-vmid'),
3807 snapname
=> get_standard_option
('pve-snapshot-name'),
3810 returns
=> { type
=> "object" },
3814 my $rpcenv = PVE
::RPCEnvironment
::get
();
3816 my $authuser = $rpcenv->get_user();
3818 my $vmid = extract_param
($param, 'vmid');
3820 my $snapname = extract_param
($param, 'snapname');
3822 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3824 my $snap = $conf->{snapshots
}->{$snapname};
3826 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3831 __PACKAGE__-
>register_method({
3833 path
=> '{vmid}/snapshot/{snapname}/rollback',
3837 description
=> "Rollback VM state to specified snapshot.",
3839 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3842 additionalProperties
=> 0,
3844 node
=> get_standard_option
('pve-node'),
3845 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3846 snapname
=> get_standard_option
('pve-snapshot-name'),
3851 description
=> "the task ID.",
3856 my $rpcenv = PVE
::RPCEnvironment
::get
();
3858 my $authuser = $rpcenv->get_user();
3860 my $node = extract_param
($param, 'node');
3862 my $vmid = extract_param
($param, 'vmid');
3864 my $snapname = extract_param
($param, 'snapname');
3867 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3868 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3872 # hold migration lock, this makes sure that nobody create replication snapshots
3873 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3876 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3879 __PACKAGE__-
>register_method({
3880 name
=> 'delsnapshot',
3881 path
=> '{vmid}/snapshot/{snapname}',
3885 description
=> "Delete a VM snapshot.",
3887 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3890 additionalProperties
=> 0,
3892 node
=> get_standard_option
('pve-node'),
3893 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3894 snapname
=> get_standard_option
('pve-snapshot-name'),
3898 description
=> "For removal from config file, even if removing disk snapshots fails.",
3904 description
=> "the task ID.",
3909 my $rpcenv = PVE
::RPCEnvironment
::get
();
3911 my $authuser = $rpcenv->get_user();
3913 my $node = extract_param
($param, 'node');
3915 my $vmid = extract_param
($param, 'vmid');
3917 my $snapname = extract_param
($param, 'snapname');
3920 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3921 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3924 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3927 __PACKAGE__-
>register_method({
3929 path
=> '{vmid}/template',
3933 description
=> "Create a Template.",
3935 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3936 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3939 additionalProperties
=> 0,
3941 node
=> get_standard_option
('pve-node'),
3942 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3946 description
=> "If you want to convert only 1 disk to base image.",
3947 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3952 returns
=> { type
=> 'null'},
3956 my $rpcenv = PVE
::RPCEnvironment
::get
();
3958 my $authuser = $rpcenv->get_user();
3960 my $node = extract_param
($param, 'node');
3962 my $vmid = extract_param
($param, 'vmid');
3964 my $disk = extract_param
($param, 'disk');
3966 my $updatefn = sub {
3968 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3970 PVE
::QemuConfig-
>check_lock($conf);
3972 die "unable to create template, because VM contains snapshots\n"
3973 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3975 die "you can't convert a template to a template\n"
3976 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3978 die "you can't convert a VM to template if VM is running\n"
3979 if PVE
::QemuServer
::check_running
($vmid);
3982 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3985 $conf->{template
} = 1;
3986 PVE
::QemuConfig-
>write_config($vmid, $conf);
3988 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3991 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3995 __PACKAGE__-
>register_method({
3996 name
=> 'cloudinit_generated_config_dump',
3997 path
=> '{vmid}/cloudinit/dump',
4000 description
=> "Get automatically generated cloudinit config.",
4002 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4005 additionalProperties
=> 0,
4007 node
=> get_standard_option
('pve-node'),
4008 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4010 description
=> 'Config type.',
4012 enum
=> ['user', 'network', 'meta'],
4022 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4024 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});