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);
1440 __PACKAGE__-
>register_method({
1441 name
=> 'destroy_vm',
1446 description
=> "Destroy the vm (also delete all used/owned volumes).",
1448 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1451 additionalProperties
=> 0,
1453 node
=> get_standard_option
('pve-node'),
1454 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1455 skiplock
=> get_standard_option
('skiplock'),
1464 my $rpcenv = PVE
::RPCEnvironment
::get
();
1465 my $authuser = $rpcenv->get_user();
1466 my $vmid = $param->{vmid
};
1468 my $skiplock = $param->{skiplock
};
1469 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1470 if $skiplock && $authuser ne 'root@pam';
1473 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1474 my $storecfg = PVE
::Storage
::config
();
1475 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1476 die "unable to remove VM $vmid - used in HA resources\n"
1477 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1479 # do not allow destroy if there are replication jobs
1480 my $repl_conf = PVE
::ReplicationConfig-
>new();
1481 $repl_conf->check_for_existing_jobs($vmid);
1483 # early tests (repeat after locking)
1484 die "VM $vmid is running - destroy failed\n"
1485 if PVE
::QemuServer
::check_running
($vmid);
1490 syslog
('info', "destroy VM $vmid: $upid\n");
1491 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1492 PVE
::AccessControl
::remove_vm_access
($vmid);
1493 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1496 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1499 __PACKAGE__-
>register_method({
1501 path
=> '{vmid}/unlink',
1505 description
=> "Unlink/delete disk images.",
1507 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1510 additionalProperties
=> 0,
1512 node
=> get_standard_option
('pve-node'),
1513 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1515 type
=> 'string', format
=> 'pve-configid-list',
1516 description
=> "A list of disk IDs you want to delete.",
1520 description
=> $opt_force_description,
1525 returns
=> { type
=> 'null'},
1529 $param->{delete} = extract_param
($param, 'idlist');
1531 __PACKAGE__-
>update_vm($param);
1538 __PACKAGE__-
>register_method({
1540 path
=> '{vmid}/vncproxy',
1544 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1546 description
=> "Creates a TCP VNC proxy connections.",
1548 additionalProperties
=> 0,
1550 node
=> get_standard_option
('pve-node'),
1551 vmid
=> get_standard_option
('pve-vmid'),
1555 description
=> "starts websockify instead of vncproxy",
1560 additionalProperties
=> 0,
1562 user
=> { type
=> 'string' },
1563 ticket
=> { type
=> 'string' },
1564 cert
=> { type
=> 'string' },
1565 port
=> { type
=> 'integer' },
1566 upid
=> { type
=> 'string' },
1572 my $rpcenv = PVE
::RPCEnvironment
::get
();
1574 my $authuser = $rpcenv->get_user();
1576 my $vmid = $param->{vmid
};
1577 my $node = $param->{node
};
1578 my $websocket = $param->{websocket
};
1580 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1581 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1583 my $authpath = "/vms/$vmid";
1585 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1587 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1593 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1594 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1595 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1596 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1597 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1599 $family = PVE
::Tools
::get_host_address_family
($node);
1602 my $port = PVE
::Tools
::next_vnc_port
($family);
1609 syslog
('info', "starting vnc proxy $upid\n");
1615 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1617 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1618 '-timeout', $timeout, '-authpath', $authpath,
1619 '-perm', 'Sys.Console'];
1621 if ($param->{websocket
}) {
1622 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1623 push @$cmd, '-notls', '-listen', 'localhost';
1626 push @$cmd, '-c', @$remcmd, @$termcmd;
1628 PVE
::Tools
::run_command
($cmd);
1632 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1634 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1636 my $sock = IO
::Socket
::IP-
>new(
1641 GetAddrInfoFlags
=> 0,
1642 ) or die "failed to create socket: $!\n";
1643 # Inside the worker we shouldn't have any previous alarms
1644 # running anyway...:
1646 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1648 accept(my $cli, $sock) or die "connection failed: $!\n";
1651 if (PVE
::Tools
::run_command
($cmd,
1652 output
=> '>&'.fileno($cli),
1653 input
=> '<&'.fileno($cli),
1656 die "Failed to run vncproxy.\n";
1663 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1665 PVE
::Tools
::wait_for_vnc_port
($port);
1676 __PACKAGE__-
>register_method({
1677 name
=> 'termproxy',
1678 path
=> '{vmid}/termproxy',
1682 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1684 description
=> "Creates a TCP proxy connections.",
1686 additionalProperties
=> 0,
1688 node
=> get_standard_option
('pve-node'),
1689 vmid
=> get_standard_option
('pve-vmid'),
1693 enum
=> [qw(serial0 serial1 serial2 serial3)],
1694 description
=> "opens a serial terminal (defaults to display)",
1699 additionalProperties
=> 0,
1701 user
=> { type
=> 'string' },
1702 ticket
=> { type
=> 'string' },
1703 port
=> { type
=> 'integer' },
1704 upid
=> { type
=> 'string' },
1710 my $rpcenv = PVE
::RPCEnvironment
::get
();
1712 my $authuser = $rpcenv->get_user();
1714 my $vmid = $param->{vmid
};
1715 my $node = $param->{node
};
1716 my $serial = $param->{serial
};
1718 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1720 if (!defined($serial)) {
1721 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1722 $serial = $conf->{vga
};
1726 my $authpath = "/vms/$vmid";
1728 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1733 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1734 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1735 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1736 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, '-t');
1737 push @$remcmd, '--';
1739 $family = PVE
::Tools
::get_host_address_family
($node);
1742 my $port = PVE
::Tools
::next_vnc_port
($family);
1744 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1745 push @$termcmd, '-iface', $serial if $serial;
1750 syslog
('info', "starting qemu termproxy $upid\n");
1752 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1753 '--perm', 'VM.Console', '--'];
1754 push @$cmd, @$remcmd, @$termcmd;
1756 PVE
::Tools
::run_command
($cmd);
1759 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1761 PVE
::Tools
::wait_for_vnc_port
($port);
1771 __PACKAGE__-
>register_method({
1772 name
=> 'vncwebsocket',
1773 path
=> '{vmid}/vncwebsocket',
1776 description
=> "You also need to pass a valid ticket (vncticket).",
1777 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1779 description
=> "Opens a weksocket for VNC traffic.",
1781 additionalProperties
=> 0,
1783 node
=> get_standard_option
('pve-node'),
1784 vmid
=> get_standard_option
('pve-vmid'),
1786 description
=> "Ticket from previous call to vncproxy.",
1791 description
=> "Port number returned by previous vncproxy call.",
1801 port
=> { type
=> 'string' },
1807 my $rpcenv = PVE
::RPCEnvironment
::get
();
1809 my $authuser = $rpcenv->get_user();
1811 my $vmid = $param->{vmid
};
1812 my $node = $param->{node
};
1814 my $authpath = "/vms/$vmid";
1816 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1818 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1820 # Note: VNC ports are acessible from outside, so we do not gain any
1821 # security if we verify that $param->{port} belongs to VM $vmid. This
1822 # check is done by verifying the VNC ticket (inside VNC protocol).
1824 my $port = $param->{port
};
1826 return { port
=> $port };
1829 __PACKAGE__-
>register_method({
1830 name
=> 'spiceproxy',
1831 path
=> '{vmid}/spiceproxy',
1836 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1838 description
=> "Returns a SPICE configuration to connect to the VM.",
1840 additionalProperties
=> 0,
1842 node
=> get_standard_option
('pve-node'),
1843 vmid
=> get_standard_option
('pve-vmid'),
1844 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1847 returns
=> get_standard_option
('remote-viewer-config'),
1851 my $rpcenv = PVE
::RPCEnvironment
::get
();
1853 my $authuser = $rpcenv->get_user();
1855 my $vmid = $param->{vmid
};
1856 my $node = $param->{node
};
1857 my $proxy = $param->{proxy
};
1859 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1860 my $title = "VM $vmid";
1861 $title .= " - ". $conf->{name
} if $conf->{name
};
1863 my $port = PVE
::QemuServer
::spice_port
($vmid);
1865 my ($ticket, undef, $remote_viewer_config) =
1866 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1868 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1869 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1871 return $remote_viewer_config;
1874 __PACKAGE__-
>register_method({
1876 path
=> '{vmid}/status',
1879 description
=> "Directory index",
1884 additionalProperties
=> 0,
1886 node
=> get_standard_option
('pve-node'),
1887 vmid
=> get_standard_option
('pve-vmid'),
1895 subdir
=> { type
=> 'string' },
1898 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1904 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1907 { subdir
=> 'current' },
1908 { subdir
=> 'start' },
1909 { subdir
=> 'stop' },
1910 { subdir
=> 'reset' },
1911 { subdir
=> 'shutdown' },
1912 { subdir
=> 'suspend' },
1918 __PACKAGE__-
>register_method({
1919 name
=> 'vm_status',
1920 path
=> '{vmid}/status/current',
1923 protected
=> 1, # qemu pid files are only readable by root
1924 description
=> "Get virtual machine status.",
1926 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1929 additionalProperties
=> 0,
1931 node
=> get_standard_option
('pve-node'),
1932 vmid
=> get_standard_option
('pve-vmid'),
1938 %$PVE::QemuServer
::vmstatus_return_properties
,
1940 description
=> "HA manager service status.",
1944 description
=> "Qemu VGA configuration supports spice.",
1949 description
=> "Qemu GuestAgent enabled in config.",
1959 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1961 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1962 my $status = $vmstatus->{$param->{vmid
}};
1964 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1966 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1967 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1972 __PACKAGE__-
>register_method({
1974 path
=> '{vmid}/status/start',
1978 description
=> "Start virtual machine.",
1980 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1983 additionalProperties
=> 0,
1985 node
=> get_standard_option
('pve-node'),
1986 vmid
=> get_standard_option
('pve-vmid',
1987 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1988 skiplock
=> get_standard_option
('skiplock'),
1989 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1990 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1993 enum
=> ['secure', 'insecure'],
1994 description
=> "Migration traffic is encrypted using an SSH " .
1995 "tunnel by default. On secure, completely private networks " .
1996 "this can be disabled to increase performance.",
1999 migration_network
=> {
2000 type
=> 'string', format
=> 'CIDR',
2001 description
=> "CIDR of the (sub) network that is used for migration.",
2004 machine
=> get_standard_option
('pve-qm-machine'),
2006 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
2018 my $rpcenv = PVE
::RPCEnvironment
::get
();
2019 my $authuser = $rpcenv->get_user();
2021 my $node = extract_param
($param, 'node');
2022 my $vmid = extract_param
($param, 'vmid');
2024 my $machine = extract_param
($param, 'machine');
2026 my $get_root_param = sub {
2027 my $value = extract_param
($param, $_[0]);
2028 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2029 if $value && $authuser ne 'root@pam';
2033 my $stateuri = $get_root_param->('stateuri');
2034 my $skiplock = $get_root_param->('skiplock');
2035 my $migratedfrom = $get_root_param->('migratedfrom');
2036 my $migration_type = $get_root_param->('migration_type');
2037 my $migration_network = $get_root_param->('migration_network');
2038 my $targetstorage = $get_root_param->('targetstorage');
2040 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2041 if $targetstorage && !$migratedfrom;
2043 # read spice ticket from STDIN
2045 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2046 if (defined(my $line = <STDIN
>)) {
2048 $spice_ticket = $line;
2052 PVE
::Cluster
::check_cfs_quorum
();
2054 my $storecfg = PVE
::Storage
::config
();
2056 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2060 print "Requesting HA start for VM $vmid\n";
2062 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2063 PVE
::Tools
::run_command
($cmd);
2067 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2074 syslog
('info', "start VM $vmid: $upid\n");
2076 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2077 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
2081 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2085 __PACKAGE__-
>register_method({
2087 path
=> '{vmid}/status/stop',
2091 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2092 "is akin to pulling the power plug of a running computer and may damage the VM data",
2094 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2097 additionalProperties
=> 0,
2099 node
=> get_standard_option
('pve-node'),
2100 vmid
=> get_standard_option
('pve-vmid',
2101 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2102 skiplock
=> get_standard_option
('skiplock'),
2103 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2105 description
=> "Wait maximal timeout seconds.",
2111 description
=> "Do not deactivate storage volumes.",
2124 my $rpcenv = PVE
::RPCEnvironment
::get
();
2125 my $authuser = $rpcenv->get_user();
2127 my $node = extract_param
($param, 'node');
2128 my $vmid = extract_param
($param, 'vmid');
2130 my $skiplock = extract_param
($param, 'skiplock');
2131 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2132 if $skiplock && $authuser ne 'root@pam';
2134 my $keepActive = extract_param
($param, 'keepActive');
2135 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2136 if $keepActive && $authuser ne 'root@pam';
2138 my $migratedfrom = extract_param
($param, 'migratedfrom');
2139 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2140 if $migratedfrom && $authuser ne 'root@pam';
2143 my $storecfg = PVE
::Storage
::config
();
2145 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2150 print "Requesting HA stop for VM $vmid\n";
2152 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'stopped'];
2153 PVE
::Tools
::run_command
($cmd);
2157 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2163 syslog
('info', "stop VM $vmid: $upid\n");
2165 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2166 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2170 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2174 __PACKAGE__-
>register_method({
2176 path
=> '{vmid}/status/reset',
2180 description
=> "Reset virtual machine.",
2182 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2185 additionalProperties
=> 0,
2187 node
=> get_standard_option
('pve-node'),
2188 vmid
=> get_standard_option
('pve-vmid',
2189 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2190 skiplock
=> get_standard_option
('skiplock'),
2199 my $rpcenv = PVE
::RPCEnvironment
::get
();
2201 my $authuser = $rpcenv->get_user();
2203 my $node = extract_param
($param, 'node');
2205 my $vmid = extract_param
($param, 'vmid');
2207 my $skiplock = extract_param
($param, 'skiplock');
2208 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2209 if $skiplock && $authuser ne 'root@pam';
2211 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2216 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2221 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2224 __PACKAGE__-
>register_method({
2225 name
=> 'vm_shutdown',
2226 path
=> '{vmid}/status/shutdown',
2230 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2231 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2233 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2236 additionalProperties
=> 0,
2238 node
=> get_standard_option
('pve-node'),
2239 vmid
=> get_standard_option
('pve-vmid',
2240 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2241 skiplock
=> get_standard_option
('skiplock'),
2243 description
=> "Wait maximal timeout seconds.",
2249 description
=> "Make sure the VM stops.",
2255 description
=> "Do not deactivate storage volumes.",
2268 my $rpcenv = PVE
::RPCEnvironment
::get
();
2269 my $authuser = $rpcenv->get_user();
2271 my $node = extract_param
($param, 'node');
2272 my $vmid = extract_param
($param, 'vmid');
2274 my $skiplock = extract_param
($param, 'skiplock');
2275 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2276 if $skiplock && $authuser ne 'root@pam';
2278 my $keepActive = extract_param
($param, 'keepActive');
2279 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2280 if $keepActive && $authuser ne 'root@pam';
2282 my $storecfg = PVE
::Storage
::config
();
2286 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2287 # otherwise, we will infer a shutdown command, but run into the timeout,
2288 # then when the vm is resumed, it will instantly shutdown
2290 # checking the qmp status here to get feedback to the gui/cli/api
2291 # and the status query should not take too long
2292 my $qmpstatus = eval {
2293 PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2297 if (!$err && $qmpstatus->{status
} eq "paused") {
2298 if ($param->{forceStop
}) {
2299 warn "VM is paused - stop instead of shutdown\n";
2302 die "VM is paused - cannot shutdown\n";
2306 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2311 print "Requesting HA stop for VM $vmid\n";
2313 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'stopped'];
2314 PVE
::Tools
::run_command
($cmd);
2318 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2325 syslog
('info', "shutdown VM $vmid: $upid\n");
2327 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2328 $shutdown, $param->{forceStop
}, $keepActive);
2332 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2336 __PACKAGE__-
>register_method({
2337 name
=> 'vm_suspend',
2338 path
=> '{vmid}/status/suspend',
2342 description
=> "Suspend virtual machine.",
2344 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2347 additionalProperties
=> 0,
2349 node
=> get_standard_option
('pve-node'),
2350 vmid
=> get_standard_option
('pve-vmid',
2351 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2352 skiplock
=> get_standard_option
('skiplock'),
2357 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2359 statestorage
=> get_standard_option
('pve-storage-id', {
2360 description
=> "The storage for the VM state",
2361 requires
=> 'todisk',
2363 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2373 my $rpcenv = PVE
::RPCEnvironment
::get
();
2374 my $authuser = $rpcenv->get_user();
2376 my $node = extract_param
($param, 'node');
2377 my $vmid = extract_param
($param, 'vmid');
2379 my $todisk = extract_param
($param, 'todisk') // 0;
2381 my $statestorage = extract_param
($param, 'statestorage');
2383 my $skiplock = extract_param
($param, 'skiplock');
2384 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2385 if $skiplock && $authuser ne 'root@pam';
2387 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2389 die "Cannot suspend HA managed VM to disk\n"
2390 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2395 syslog
('info', "suspend VM $vmid: $upid\n");
2397 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2402 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2403 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2406 __PACKAGE__-
>register_method({
2407 name
=> 'vm_resume',
2408 path
=> '{vmid}/status/resume',
2412 description
=> "Resume virtual machine.",
2414 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2417 additionalProperties
=> 0,
2419 node
=> get_standard_option
('pve-node'),
2420 vmid
=> get_standard_option
('pve-vmid',
2421 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2422 skiplock
=> get_standard_option
('skiplock'),
2423 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2433 my $rpcenv = PVE
::RPCEnvironment
::get
();
2435 my $authuser = $rpcenv->get_user();
2437 my $node = extract_param
($param, 'node');
2439 my $vmid = extract_param
($param, 'vmid');
2441 my $skiplock = extract_param
($param, 'skiplock');
2442 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2443 if $skiplock && $authuser ne 'root@pam';
2445 my $nocheck = extract_param
($param, 'nocheck');
2447 my $to_disk_suspended;
2449 PVE
::QemuConfig-
>lock_config($vmid, sub {
2450 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2451 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2455 die "VM $vmid not running\n"
2456 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2461 syslog
('info', "resume VM $vmid: $upid\n");
2463 if (!$to_disk_suspended) {
2464 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2466 my $storecfg = PVE
::Storage
::config
();
2467 PVE
::QemuServer
::vm_start
($storecfg, $vmid, undef, $skiplock);
2473 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2476 __PACKAGE__-
>register_method({
2477 name
=> 'vm_sendkey',
2478 path
=> '{vmid}/sendkey',
2482 description
=> "Send key event to virtual machine.",
2484 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2487 additionalProperties
=> 0,
2489 node
=> get_standard_option
('pve-node'),
2490 vmid
=> get_standard_option
('pve-vmid',
2491 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2492 skiplock
=> get_standard_option
('skiplock'),
2494 description
=> "The key (qemu monitor encoding).",
2499 returns
=> { type
=> 'null'},
2503 my $rpcenv = PVE
::RPCEnvironment
::get
();
2505 my $authuser = $rpcenv->get_user();
2507 my $node = extract_param
($param, 'node');
2509 my $vmid = extract_param
($param, 'vmid');
2511 my $skiplock = extract_param
($param, 'skiplock');
2512 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2513 if $skiplock && $authuser ne 'root@pam';
2515 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2520 __PACKAGE__-
>register_method({
2521 name
=> 'vm_feature',
2522 path
=> '{vmid}/feature',
2526 description
=> "Check if feature for virtual machine is available.",
2528 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2531 additionalProperties
=> 0,
2533 node
=> get_standard_option
('pve-node'),
2534 vmid
=> get_standard_option
('pve-vmid'),
2536 description
=> "Feature to check.",
2538 enum
=> [ 'snapshot', 'clone', 'copy' ],
2540 snapname
=> get_standard_option
('pve-snapshot-name', {
2548 hasFeature
=> { type
=> 'boolean' },
2551 items
=> { type
=> 'string' },
2558 my $node = extract_param
($param, 'node');
2560 my $vmid = extract_param
($param, 'vmid');
2562 my $snapname = extract_param
($param, 'snapname');
2564 my $feature = extract_param
($param, 'feature');
2566 my $running = PVE
::QemuServer
::check_running
($vmid);
2568 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2571 my $snap = $conf->{snapshots
}->{$snapname};
2572 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2575 my $storecfg = PVE
::Storage
::config
();
2577 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2578 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2581 hasFeature
=> $hasFeature,
2582 nodes
=> [ keys %$nodelist ],
2586 __PACKAGE__-
>register_method({
2588 path
=> '{vmid}/clone',
2592 description
=> "Create a copy of virtual machine/template.",
2594 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2595 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2596 "'Datastore.AllocateSpace' on any used storage.",
2599 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2601 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2602 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2607 additionalProperties
=> 0,
2609 node
=> get_standard_option
('pve-node'),
2610 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2611 newid
=> get_standard_option
('pve-vmid', {
2612 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2613 description
=> 'VMID for the clone.' }),
2616 type
=> 'string', format
=> 'dns-name',
2617 description
=> "Set a name for the new VM.",
2622 description
=> "Description for the new VM.",
2626 type
=> 'string', format
=> 'pve-poolid',
2627 description
=> "Add the new VM to the specified pool.",
2629 snapname
=> get_standard_option
('pve-snapshot-name', {
2632 storage
=> get_standard_option
('pve-storage-id', {
2633 description
=> "Target storage for full clone.",
2637 description
=> "Target format for file storage. Only valid for full clone.",
2640 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2645 description
=> "Create a full copy of all disks. This is always done when " .
2646 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2648 target
=> get_standard_option
('pve-node', {
2649 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2653 description
=> "Override I/O bandwidth limit (in KiB/s).",
2657 default => 'clone limit from datacenter or storage config',
2667 my $rpcenv = PVE
::RPCEnvironment
::get
();
2669 my $authuser = $rpcenv->get_user();
2671 my $node = extract_param
($param, 'node');
2673 my $vmid = extract_param
($param, 'vmid');
2675 my $newid = extract_param
($param, 'newid');
2677 my $pool = extract_param
($param, 'pool');
2679 if (defined($pool)) {
2680 $rpcenv->check_pool_exist($pool);
2683 my $snapname = extract_param
($param, 'snapname');
2685 my $storage = extract_param
($param, 'storage');
2687 my $format = extract_param
($param, 'format');
2689 my $target = extract_param
($param, 'target');
2691 my $localnode = PVE
::INotify
::nodename
();
2693 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2695 PVE
::Cluster
::check_node_exists
($target) if $target;
2697 my $storecfg = PVE
::Storage
::config
();
2700 # check if storage is enabled on local node
2701 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2703 # check if storage is available on target node
2704 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2705 # clone only works if target storage is shared
2706 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2707 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2711 PVE
::Cluster
::check_cfs_quorum
();
2713 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2715 # exclusive lock if VM is running - else shared lock is enough;
2716 my $shared_lock = $running ?
0 : 1;
2720 # do all tests after lock
2721 # we also try to do all tests before we fork the worker
2723 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2725 PVE
::QemuConfig-
>check_lock($conf);
2727 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2729 die "unexpected state change\n" if $verify_running != $running;
2731 die "snapshot '$snapname' does not exist\n"
2732 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2734 my $full = extract_param
($param, 'full');
2735 if (!defined($full)) {
2736 $full = !PVE
::QemuConfig-
>is_template($conf);
2739 die "parameter 'storage' not allowed for linked clones\n"
2740 if defined($storage) && !$full;
2742 die "parameter 'format' not allowed for linked clones\n"
2743 if defined($format) && !$full;
2745 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2747 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2749 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2751 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2753 die "unable to create VM $newid: config file already exists\n"
2756 my $newconf = { lock => 'clone' };
2761 foreach my $opt (keys %$oldconf) {
2762 my $value = $oldconf->{$opt};
2764 # do not copy snapshot related info
2765 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2766 $opt eq 'vmstate' || $opt eq 'snapstate';
2768 # no need to copy unused images, because VMID(owner) changes anyways
2769 next if $opt =~ m/^unused\d+$/;
2771 # always change MAC! address
2772 if ($opt =~ m/^net(\d+)$/) {
2773 my $net = PVE
::QemuServer
::parse_net
($value);
2774 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2775 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2776 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2777 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2778 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2779 die "unable to parse drive options for '$opt'\n" if !$drive;
2780 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2781 $newconf->{$opt} = $value; # simply copy configuration
2783 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2784 die "Full clone feature is not supported for drive '$opt'\n"
2785 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2786 $fullclone->{$opt} = 1;
2788 # not full means clone instead of copy
2789 die "Linked clone feature is not supported for drive '$opt'\n"
2790 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2792 $drives->{$opt} = $drive;
2793 push @$vollist, $drive->{file
};
2796 # copy everything else
2797 $newconf->{$opt} = $value;
2801 # auto generate a new uuid
2802 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2803 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2804 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2806 # auto generate a new vmgenid if the option was set
2807 if ($newconf->{vmgenid
}) {
2808 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2811 delete $newconf->{template
};
2813 if ($param->{name
}) {
2814 $newconf->{name
} = $param->{name
};
2816 if ($oldconf->{name
}) {
2817 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2819 $newconf->{name
} = "Copy-of-VM-$vmid";
2823 if ($param->{description
}) {
2824 $newconf->{description
} = $param->{description
};
2827 # create empty/temp config - this fails if VM already exists on other node
2828 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2833 my $newvollist = [];
2840 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2842 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2844 my $bwlimit = extract_param
($param, 'bwlimit');
2846 my $total_jobs = scalar(keys %{$drives});
2849 foreach my $opt (keys %$drives) {
2850 my $drive = $drives->{$opt};
2851 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2853 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
2854 my $storage_list = [ $src_sid ];
2855 push @$storage_list, $storage if defined($storage);
2856 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
2858 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2859 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2860 $jobs, $skipcomplete, $oldconf->{agent
}, $clonelimit);
2862 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2864 PVE
::QemuConfig-
>write_config($newid, $newconf);
2868 delete $newconf->{lock};
2870 # do not write pending changes
2871 if (my @changes = keys %{$newconf->{pending
}}) {
2872 my $pending = join(',', @changes);
2873 warn "found pending changes for '$pending', discarding for clone\n";
2874 delete $newconf->{pending
};
2877 PVE
::QemuConfig-
>write_config($newid, $newconf);
2880 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2881 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2882 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2884 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2885 die "Failed to move config to node '$target' - rename failed: $!\n"
2886 if !rename($conffile, $newconffile);
2889 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2894 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2896 sleep 1; # some storage like rbd need to wait before release volume - really?
2898 foreach my $volid (@$newvollist) {
2899 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2902 die "clone failed: $err";
2908 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2910 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2913 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2914 # Aquire exclusive lock lock for $newid
2915 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2920 __PACKAGE__-
>register_method({
2921 name
=> 'move_vm_disk',
2922 path
=> '{vmid}/move_disk',
2926 description
=> "Move volume to different storage.",
2928 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2930 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2931 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2935 additionalProperties
=> 0,
2937 node
=> get_standard_option
('pve-node'),
2938 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2941 description
=> "The disk you want to move.",
2942 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2944 storage
=> get_standard_option
('pve-storage-id', {
2945 description
=> "Target storage.",
2946 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2950 description
=> "Target Format.",
2951 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2956 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2962 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2967 description
=> "Override I/O bandwidth limit (in KiB/s).",
2971 default => 'move limit from datacenter or storage config',
2977 description
=> "the task ID.",
2982 my $rpcenv = PVE
::RPCEnvironment
::get
();
2984 my $authuser = $rpcenv->get_user();
2986 my $node = extract_param
($param, 'node');
2988 my $vmid = extract_param
($param, 'vmid');
2990 my $digest = extract_param
($param, 'digest');
2992 my $disk = extract_param
($param, 'disk');
2994 my $storeid = extract_param
($param, 'storage');
2996 my $format = extract_param
($param, 'format');
2998 my $storecfg = PVE
::Storage
::config
();
3000 my $updatefn = sub {
3002 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3004 PVE
::QemuConfig-
>check_lock($conf);
3006 die "checksum missmatch (file change by other user?)\n"
3007 if $digest && $digest ne $conf->{digest
};
3009 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3011 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3013 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
3015 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3018 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3019 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3023 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
3024 (!$format || !$oldfmt || $oldfmt eq $format);
3026 # this only checks snapshots because $disk is passed!
3027 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3028 die "you can't move a disk with snapshots and delete the source\n"
3029 if $snapshotted && $param->{delete};
3031 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3033 my $running = PVE
::QemuServer
::check_running
($vmid);
3035 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3039 my $newvollist = [];
3045 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3047 warn "moving disk with snapshots, snapshots will not be moved!\n"
3050 my $bwlimit = extract_param
($param, 'bwlimit');
3051 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3053 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3054 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit);
3056 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
3058 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3060 # convert moved disk to base if part of template
3061 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3062 if PVE
::QemuConfig-
>is_template($conf);
3064 PVE
::QemuConfig-
>write_config($vmid, $conf);
3066 if ($running && PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
} && PVE
::QemuServer
::qga_check_running
($vmid)) {
3067 eval { PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-fstrim"); };
3071 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3072 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3079 foreach my $volid (@$newvollist) {
3080 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3083 die "storage migration failed: $err";
3086 if ($param->{delete}) {
3088 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3089 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3095 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3098 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3101 my $check_vm_disks_local = sub {
3102 my ($storecfg, $vmconf, $vmid) = @_;
3104 my $local_disks = {};
3106 # add some more information to the disks e.g. cdrom
3107 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3108 my ($volid, $attr) = @_;
3110 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3112 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3113 return if $scfg->{shared
};
3115 # The shared attr here is just a special case where the vdisk
3116 # is marked as shared manually
3117 return if $attr->{shared
};
3118 return if $attr->{cdrom
} and $volid eq "none";
3120 if (exists $local_disks->{$volid}) {
3121 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3123 $local_disks->{$volid} = $attr;
3124 # ensure volid is present in case it's needed
3125 $local_disks->{$volid}->{volid
} = $volid;
3129 return $local_disks;
3132 __PACKAGE__-
>register_method({
3133 name
=> 'migrate_vm_precondition',
3134 path
=> '{vmid}/migrate',
3138 description
=> "Get preconditions for migration.",
3140 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3143 additionalProperties
=> 0,
3145 node
=> get_standard_option
('pve-node'),
3146 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3147 target
=> get_standard_option
('pve-node', {
3148 description
=> "Target node.",
3149 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3157 running
=> { type
=> 'boolean' },
3161 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3163 not_allowed_nodes
=> {
3166 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3170 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3172 local_resources
=> {
3174 description
=> "List local resources e.g. pci, usb"
3181 my $rpcenv = PVE
::RPCEnvironment
::get
();
3183 my $authuser = $rpcenv->get_user();
3185 PVE
::Cluster
::check_cfs_quorum
();
3189 my $vmid = extract_param
($param, 'vmid');
3190 my $target = extract_param
($param, 'target');
3191 my $localnode = PVE
::INotify
::nodename
();
3195 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3196 my $storecfg = PVE
::Storage
::config
();
3199 # try to detect errors early
3200 PVE
::QemuConfig-
>check_lock($vmconf);
3202 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3204 # if vm is not running, return target nodes where local storage is available
3205 # for offline migration
3206 if (!$res->{running
}) {
3207 $res->{allowed_nodes
} = [];
3208 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3209 delete $checked_nodes->{$localnode};
3211 foreach my $node (keys %$checked_nodes) {
3212 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3213 push @{$res->{allowed_nodes
}}, $node;
3217 $res->{not_allowed_nodes
} = $checked_nodes;
3221 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3222 $res->{local_disks
} = [ values %$local_disks ];;
3224 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3226 $res->{local_resources
} = $local_resources;
3233 __PACKAGE__-
>register_method({
3234 name
=> 'migrate_vm',
3235 path
=> '{vmid}/migrate',
3239 description
=> "Migrate virtual machine. Creates a new migration task.",
3241 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3244 additionalProperties
=> 0,
3246 node
=> get_standard_option
('pve-node'),
3247 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3248 target
=> get_standard_option
('pve-node', {
3249 description
=> "Target node.",
3250 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3254 description
=> "Use online/live migration.",
3259 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3264 enum
=> ['secure', 'insecure'],
3265 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3268 migration_network
=> {
3269 type
=> 'string', format
=> 'CIDR',
3270 description
=> "CIDR of the (sub) network that is used for migration.",
3273 "with-local-disks" => {
3275 description
=> "Enable live storage migration for local disk",
3278 targetstorage
=> get_standard_option
('pve-storage-id', {
3279 description
=> "Default target storage.",
3281 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3284 description
=> "Override I/O bandwidth limit (in KiB/s).",
3288 default => 'migrate limit from datacenter or storage config',
3294 description
=> "the task ID.",
3299 my $rpcenv = PVE
::RPCEnvironment
::get
();
3300 my $authuser = $rpcenv->get_user();
3302 my $target = extract_param
($param, 'target');
3304 my $localnode = PVE
::INotify
::nodename
();
3305 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3307 PVE
::Cluster
::check_cfs_quorum
();
3309 PVE
::Cluster
::check_node_exists
($target);
3311 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3313 my $vmid = extract_param
($param, 'vmid');
3315 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3316 if !$param->{online
} && $param->{targetstorage
};
3318 raise_param_exc
({ force
=> "Only root may use this option." })
3319 if $param->{force
} && $authuser ne 'root@pam';
3321 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3322 if $param->{migration_type
} && $authuser ne 'root@pam';
3324 # allow root only until better network permissions are available
3325 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3326 if $param->{migration_network
} && $authuser ne 'root@pam';
3329 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3331 # try to detect errors early
3333 PVE
::QemuConfig-
>check_lock($conf);
3335 if (PVE
::QemuServer
::check_running
($vmid)) {
3336 die "cant migrate running VM without --online\n"
3337 if !$param->{online
};
3340 my $storecfg = PVE
::Storage
::config
();
3342 if( $param->{targetstorage
}) {
3343 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3345 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3348 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3353 print "Requesting HA migration for VM $vmid to node $target\n";
3355 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3356 PVE
::Tools
::run_command
($cmd);
3360 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3365 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3369 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3372 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3377 __PACKAGE__-
>register_method({
3379 path
=> '{vmid}/monitor',
3383 description
=> "Execute Qemu monitor commands.",
3385 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3386 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3389 additionalProperties
=> 0,
3391 node
=> get_standard_option
('pve-node'),
3392 vmid
=> get_standard_option
('pve-vmid'),
3395 description
=> "The monitor command.",
3399 returns
=> { type
=> 'string'},
3403 my $rpcenv = PVE
::RPCEnvironment
::get
();
3404 my $authuser = $rpcenv->get_user();
3407 my $command = shift;
3408 return $command =~ m/^\s*info(\s+|$)/
3409 || $command =~ m/^\s*help\s*$/;
3412 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3413 if !&$is_ro($param->{command
});
3415 my $vmid = $param->{vmid
};
3417 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3421 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3423 $res = "ERROR: $@" if $@;
3428 __PACKAGE__-
>register_method({
3429 name
=> 'resize_vm',
3430 path
=> '{vmid}/resize',
3434 description
=> "Extend volume size.",
3436 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3439 additionalProperties
=> 0,
3441 node
=> get_standard_option
('pve-node'),
3442 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3443 skiplock
=> get_standard_option
('skiplock'),
3446 description
=> "The disk you want to resize.",
3447 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3451 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3452 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.",
3456 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3462 returns
=> { type
=> 'null'},
3466 my $rpcenv = PVE
::RPCEnvironment
::get
();
3468 my $authuser = $rpcenv->get_user();
3470 my $node = extract_param
($param, 'node');
3472 my $vmid = extract_param
($param, 'vmid');
3474 my $digest = extract_param
($param, 'digest');
3476 my $disk = extract_param
($param, 'disk');
3478 my $sizestr = extract_param
($param, 'size');
3480 my $skiplock = extract_param
($param, 'skiplock');
3481 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3482 if $skiplock && $authuser ne 'root@pam';
3484 my $storecfg = PVE
::Storage
::config
();
3486 my $updatefn = sub {
3488 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3490 die "checksum missmatch (file change by other user?)\n"
3491 if $digest && $digest ne $conf->{digest
};
3492 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3494 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3496 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3498 my (undef, undef, undef, undef, undef, undef, $format) =
3499 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3501 die "can't resize volume: $disk if snapshot exists\n"
3502 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3504 my $volid = $drive->{file
};
3506 die "disk '$disk' has no associated volume\n" if !$volid;
3508 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3510 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3512 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3514 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3515 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3517 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3518 my ($ext, $newsize, $unit) = ($1, $2, $4);
3521 $newsize = $newsize * 1024;
3522 } elsif ($unit eq 'M') {
3523 $newsize = $newsize * 1024 * 1024;
3524 } elsif ($unit eq 'G') {
3525 $newsize = $newsize * 1024 * 1024 * 1024;
3526 } elsif ($unit eq 'T') {
3527 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3530 $newsize += $size if $ext;
3531 $newsize = int($newsize);
3533 die "shrinking disks is not supported\n" if $newsize < $size;
3535 return if $size == $newsize;
3537 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3539 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3541 $drive->{size
} = $newsize;
3542 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3544 PVE
::QemuConfig-
>write_config($vmid, $conf);
3547 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3551 __PACKAGE__-
>register_method({
3552 name
=> 'snapshot_list',
3553 path
=> '{vmid}/snapshot',
3555 description
=> "List all snapshots.",
3557 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3560 protected
=> 1, # qemu pid files are only readable by root
3562 additionalProperties
=> 0,
3564 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3565 node
=> get_standard_option
('pve-node'),
3574 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3578 description
=> "Snapshot includes RAM.",
3583 description
=> "Snapshot description.",
3587 description
=> "Snapshot creation time",
3589 renderer
=> 'timestamp',
3593 description
=> "Parent snapshot identifier.",
3599 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3604 my $vmid = $param->{vmid
};
3606 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3607 my $snaphash = $conf->{snapshots
} || {};
3611 foreach my $name (keys %$snaphash) {
3612 my $d = $snaphash->{$name};
3615 snaptime
=> $d->{snaptime
} || 0,
3616 vmstate
=> $d->{vmstate
} ?
1 : 0,
3617 description
=> $d->{description
} || '',
3619 $item->{parent
} = $d->{parent
} if $d->{parent
};
3620 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3624 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3627 digest
=> $conf->{digest
},
3628 running
=> $running,
3629 description
=> "You are here!",
3631 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3633 push @$res, $current;
3638 __PACKAGE__-
>register_method({
3640 path
=> '{vmid}/snapshot',
3644 description
=> "Snapshot a VM.",
3646 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3649 additionalProperties
=> 0,
3651 node
=> get_standard_option
('pve-node'),
3652 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3653 snapname
=> get_standard_option
('pve-snapshot-name'),
3657 description
=> "Save the vmstate",
3662 description
=> "A textual description or comment.",
3668 description
=> "the task ID.",
3673 my $rpcenv = PVE
::RPCEnvironment
::get
();
3675 my $authuser = $rpcenv->get_user();
3677 my $node = extract_param
($param, 'node');
3679 my $vmid = extract_param
($param, 'vmid');
3681 my $snapname = extract_param
($param, 'snapname');
3683 die "unable to use snapshot name 'current' (reserved name)\n"
3684 if $snapname eq 'current';
3687 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3688 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3689 $param->{description
});
3692 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3695 __PACKAGE__-
>register_method({
3696 name
=> 'snapshot_cmd_idx',
3697 path
=> '{vmid}/snapshot/{snapname}',
3704 additionalProperties
=> 0,
3706 vmid
=> get_standard_option
('pve-vmid'),
3707 node
=> get_standard_option
('pve-node'),
3708 snapname
=> get_standard_option
('pve-snapshot-name'),
3717 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3724 push @$res, { cmd
=> 'rollback' };
3725 push @$res, { cmd
=> 'config' };
3730 __PACKAGE__-
>register_method({
3731 name
=> 'update_snapshot_config',
3732 path
=> '{vmid}/snapshot/{snapname}/config',
3736 description
=> "Update snapshot metadata.",
3738 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3741 additionalProperties
=> 0,
3743 node
=> get_standard_option
('pve-node'),
3744 vmid
=> get_standard_option
('pve-vmid'),
3745 snapname
=> get_standard_option
('pve-snapshot-name'),
3749 description
=> "A textual description or comment.",
3753 returns
=> { type
=> 'null' },
3757 my $rpcenv = PVE
::RPCEnvironment
::get
();
3759 my $authuser = $rpcenv->get_user();
3761 my $vmid = extract_param
($param, 'vmid');
3763 my $snapname = extract_param
($param, 'snapname');
3765 return undef if !defined($param->{description
});
3767 my $updatefn = sub {
3769 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3771 PVE
::QemuConfig-
>check_lock($conf);
3773 my $snap = $conf->{snapshots
}->{$snapname};
3775 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3777 $snap->{description
} = $param->{description
} if defined($param->{description
});
3779 PVE
::QemuConfig-
>write_config($vmid, $conf);
3782 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3787 __PACKAGE__-
>register_method({
3788 name
=> 'get_snapshot_config',
3789 path
=> '{vmid}/snapshot/{snapname}/config',
3792 description
=> "Get snapshot configuration",
3794 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3797 additionalProperties
=> 0,
3799 node
=> get_standard_option
('pve-node'),
3800 vmid
=> get_standard_option
('pve-vmid'),
3801 snapname
=> get_standard_option
('pve-snapshot-name'),
3804 returns
=> { type
=> "object" },
3808 my $rpcenv = PVE
::RPCEnvironment
::get
();
3810 my $authuser = $rpcenv->get_user();
3812 my $vmid = extract_param
($param, 'vmid');
3814 my $snapname = extract_param
($param, 'snapname');
3816 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3818 my $snap = $conf->{snapshots
}->{$snapname};
3820 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3825 __PACKAGE__-
>register_method({
3827 path
=> '{vmid}/snapshot/{snapname}/rollback',
3831 description
=> "Rollback VM state to specified snapshot.",
3833 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3836 additionalProperties
=> 0,
3838 node
=> get_standard_option
('pve-node'),
3839 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3840 snapname
=> get_standard_option
('pve-snapshot-name'),
3845 description
=> "the task ID.",
3850 my $rpcenv = PVE
::RPCEnvironment
::get
();
3852 my $authuser = $rpcenv->get_user();
3854 my $node = extract_param
($param, 'node');
3856 my $vmid = extract_param
($param, 'vmid');
3858 my $snapname = extract_param
($param, 'snapname');
3861 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3862 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3866 # hold migration lock, this makes sure that nobody create replication snapshots
3867 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3870 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3873 __PACKAGE__-
>register_method({
3874 name
=> 'delsnapshot',
3875 path
=> '{vmid}/snapshot/{snapname}',
3879 description
=> "Delete a VM snapshot.",
3881 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3884 additionalProperties
=> 0,
3886 node
=> get_standard_option
('pve-node'),
3887 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3888 snapname
=> get_standard_option
('pve-snapshot-name'),
3892 description
=> "For removal from config file, even if removing disk snapshots fails.",
3898 description
=> "the task ID.",
3903 my $rpcenv = PVE
::RPCEnvironment
::get
();
3905 my $authuser = $rpcenv->get_user();
3907 my $node = extract_param
($param, 'node');
3909 my $vmid = extract_param
($param, 'vmid');
3911 my $snapname = extract_param
($param, 'snapname');
3914 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3915 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3918 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3921 __PACKAGE__-
>register_method({
3923 path
=> '{vmid}/template',
3927 description
=> "Create a Template.",
3929 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3930 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3933 additionalProperties
=> 0,
3935 node
=> get_standard_option
('pve-node'),
3936 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3940 description
=> "If you want to convert only 1 disk to base image.",
3941 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3946 returns
=> { type
=> 'null'},
3950 my $rpcenv = PVE
::RPCEnvironment
::get
();
3952 my $authuser = $rpcenv->get_user();
3954 my $node = extract_param
($param, 'node');
3956 my $vmid = extract_param
($param, 'vmid');
3958 my $disk = extract_param
($param, 'disk');
3960 my $updatefn = sub {
3962 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3964 PVE
::QemuConfig-
>check_lock($conf);
3966 die "unable to create template, because VM contains snapshots\n"
3967 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3969 die "you can't convert a template to a template\n"
3970 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3972 die "you can't convert a VM to template if VM is running\n"
3973 if PVE
::QemuServer
::check_running
($vmid);
3976 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3979 $conf->{template
} = 1;
3980 PVE
::QemuConfig-
>write_config($vmid, $conf);
3982 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3985 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3989 __PACKAGE__-
>register_method({
3990 name
=> 'cloudinit_generated_config_dump',
3991 path
=> '{vmid}/cloudinit/dump',
3994 description
=> "Get automatically generated cloudinit config.",
3996 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3999 additionalProperties
=> 0,
4001 node
=> get_standard_option
('pve-node'),
4002 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4004 description
=> 'Config type.',
4006 enum
=> ['user', 'network', 'meta'],
4016 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4018 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});