1 package PVE
::API2
::Qemu
;
12 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
14 use PVE
::Tools
qw(extract_param);
15 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
17 use PVE
::JSONSchema
qw(get_standard_option);
19 use PVE
::ReplicationConfig
;
20 use PVE
::GuestHelpers
;
24 use PVE
::RPCEnvironment
;
25 use PVE
::AccessControl
;
29 use PVE
::API2
::Firewall
::VM
;
30 use PVE
::API2
::Qemu
::Agent
;
33 if (!$ENV{PVE_GENERATING_DOCS
}) {
34 require PVE
::HA
::Env
::PVE2
;
35 import PVE
::HA
::Env
::PVE2
;
36 require PVE
::HA
::Config
;
37 import PVE
::HA
::Config
;
41 use Data
::Dumper
; # fixme: remove
43 use base
qw(PVE::RESTHandler);
45 my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal.";
47 my $resolve_cdrom_alias = sub {
50 if (my $value = $param->{cdrom
}) {
51 $value .= ",media=cdrom" if $value !~ m/media=/;
52 $param->{ide2
} = $value;
53 delete $param->{cdrom
};
57 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
58 my $check_storage_access = sub {
59 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
61 PVE
::QemuServer
::foreach_drive
($settings, sub {
62 my ($ds, $drive) = @_;
64 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
66 my $volid = $drive->{file
};
68 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit')) {
70 } elsif ($volid =~ m/^(([^:\s]+):)?(cloudinit)$/) {
72 } elsif ($isCDROM && ($volid eq 'cdrom')) {
73 $rpcenv->check($authuser, "/", ['Sys.Console']);
74 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
75 my ($storeid, $size) = ($2 || $default_storage, $3);
76 die "no storage ID specified (and no default storage)\n" if !$storeid;
77 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
78 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
79 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
80 if !$scfg->{content
}->{images
};
82 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
86 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
87 if defined($settings->{vmstatestorage
});
90 my $check_storage_access_clone = sub {
91 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
95 PVE
::QemuServer
::foreach_drive
($conf, sub {
96 my ($ds, $drive) = @_;
98 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
100 my $volid = $drive->{file
};
102 return if !$volid || $volid eq 'none';
105 if ($volid eq 'cdrom') {
106 $rpcenv->check($authuser, "/", ['Sys.Console']);
108 # we simply allow access
109 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
110 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
111 $sharedvm = 0 if !$scfg->{shared
};
115 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
116 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
117 $sharedvm = 0 if !$scfg->{shared
};
119 $sid = $storage if $storage;
120 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
124 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
125 if defined($conf->{vmstatestorage
});
130 # Note: $pool is only needed when creating a VM, because pool permissions
131 # are automatically inherited if VM already exists inside a pool.
132 my $create_disks = sub {
133 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
140 my ($ds, $disk) = @_;
142 my $volid = $disk->{file
};
144 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
145 delete $disk->{size
};
146 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
147 } elsif ($volid =~ m!^(?:([^/:\s]+):)?cloudinit$!) {
148 my $storeid = $1 || $default_storage;
149 die "no storage ID specified (and no default storage)\n" if !$storeid;
150 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
151 my $name = "vm-$vmid-cloudinit";
155 $fmt = $disk->{format
} // "qcow2";
158 $fmt = $disk->{format
} // "raw";
161 # Initial disk created with 4 MB and aligned to 4MB on regeneration
162 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
163 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
164 $disk->{file
} = $volid;
165 $disk->{media
} = 'cdrom';
166 push @$vollist, $volid;
167 delete $disk->{format
}; # no longer needed
168 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
169 } elsif ($volid =~ $NEW_DISK_RE) {
170 my ($storeid, $size) = ($2 || $default_storage, $3);
171 die "no storage ID specified (and no default storage)\n" if !$storeid;
172 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
173 my $fmt = $disk->{format
} || $defformat;
175 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
178 if ($ds eq 'efidisk0') {
179 ($volid, $size) = PVE
::QemuServer
::create_efidisk
($storecfg, $storeid, $vmid, $fmt, $arch);
181 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
183 push @$vollist, $volid;
184 $disk->{file
} = $volid;
185 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
186 delete $disk->{format
}; # no longer needed
187 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
190 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
192 my $volid_is_new = 1;
195 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
196 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
201 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
203 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
205 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
207 die "volume $volid does not exists\n" if !$size;
209 $disk->{size
} = $size;
212 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
216 eval { PVE
::QemuServer
::foreach_drive
($settings, $code); };
218 # free allocated images on error
220 syslog
('err', "VM $vmid creating disks failed");
221 foreach my $volid (@$vollist) {
222 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
228 # modify vm config if everything went well
229 foreach my $ds (keys %$res) {
230 $conf->{$ds} = $res->{$ds};
247 my $memoryoptions = {
253 my $hwtypeoptions = {
266 my $generaloptions = {
273 'migrate_downtime' => 1,
274 'migrate_speed' => 1,
286 my $vmpoweroptions = {
293 'vmstatestorage' => 1,
296 my $cloudinitoptions = {
306 my $check_vm_modify_config_perm = sub {
307 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
309 return 1 if $authuser eq 'root@pam';
311 foreach my $opt (@$key_list) {
312 # some checks (e.g., disk, serial port, usb) need to be done somewhere
313 # else, as there the permission can be value dependend
314 next if PVE
::QemuServer
::is_valid_drivename
($opt);
315 next if $opt eq 'cdrom';
316 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
319 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
320 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
321 } elsif ($memoryoptions->{$opt}) {
322 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
323 } elsif ($hwtypeoptions->{$opt}) {
324 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
325 } elsif ($generaloptions->{$opt}) {
326 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
327 # special case for startup since it changes host behaviour
328 if ($opt eq 'startup') {
329 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
331 } elsif ($vmpoweroptions->{$opt}) {
332 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
333 } elsif ($diskoptions->{$opt}) {
334 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
335 } elsif ($cloudinitoptions->{$opt} || ($opt =~ m/^(?:net|ipconfig)\d+$/)) {
336 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
338 # catches hostpci\d+, args, lock, etc.
339 # new options will be checked here
340 die "only root can set '$opt' config\n";
347 __PACKAGE__-
>register_method({
351 description
=> "Virtual machine index (per node).",
353 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
357 protected
=> 1, # qemu pid files are only readable by root
359 additionalProperties
=> 0,
361 node
=> get_standard_option
('pve-node'),
365 description
=> "Determine the full status of active VMs.",
373 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
375 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
380 my $rpcenv = PVE
::RPCEnvironment
::get
();
381 my $authuser = $rpcenv->get_user();
383 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
386 foreach my $vmid (keys %$vmstatus) {
387 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
389 my $data = $vmstatus->{$vmid};
398 __PACKAGE__-
>register_method({
402 description
=> "Create or restore a virtual machine.",
404 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
405 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
406 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
407 user
=> 'all', # check inside
412 additionalProperties
=> 0,
413 properties
=> PVE
::QemuServer
::json_config_properties
(
415 node
=> get_standard_option
('pve-node'),
416 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
418 description
=> "The backup file.",
422 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
424 storage
=> get_standard_option
('pve-storage-id', {
425 description
=> "Default storage.",
427 completion
=> \
&PVE
::QemuServer
::complete_storage
,
432 description
=> "Allow to overwrite existing VM.",
433 requires
=> 'archive',
438 description
=> "Assign a unique random ethernet address.",
439 requires
=> 'archive',
443 type
=> 'string', format
=> 'pve-poolid',
444 description
=> "Add the VM to the specified pool.",
447 description
=> "Override I/O bandwidth limit (in KiB/s).",
451 default => 'restore limit from datacenter or storage config',
457 description
=> "Start VM after it was created successfully.",
467 my $rpcenv = PVE
::RPCEnvironment
::get
();
469 my $authuser = $rpcenv->get_user();
471 my $node = extract_param
($param, 'node');
473 my $vmid = extract_param
($param, 'vmid');
475 my $archive = extract_param
($param, 'archive');
476 my $is_restore = !!$archive;
478 my $storage = extract_param
($param, 'storage');
480 my $force = extract_param
($param, 'force');
482 my $unique = extract_param
($param, 'unique');
484 my $pool = extract_param
($param, 'pool');
486 my $bwlimit = extract_param
($param, 'bwlimit');
488 my $start_after_create = extract_param
($param, 'start');
490 my $filename = PVE
::QemuConfig-
>config_file($vmid);
492 my $storecfg = PVE
::Storage
::config
();
494 if (defined(my $ssh_keys = $param->{sshkeys
})) {
495 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
496 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
499 PVE
::Cluster
::check_cfs_quorum
();
501 if (defined($pool)) {
502 $rpcenv->check_pool_exist($pool);
505 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
506 if defined($storage);
508 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
510 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
512 } elsif ($archive && $force && (-f
$filename) &&
513 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
514 # OK: user has VM.Backup permissions, and want to restore an existing VM
520 &$resolve_cdrom_alias($param);
522 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
524 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
526 foreach my $opt (keys %$param) {
527 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
528 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
529 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
531 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
532 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
536 PVE
::QemuServer
::add_random_macs
($param);
538 my $keystr = join(' ', keys %$param);
539 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
541 if ($archive eq '-') {
542 die "pipe requires cli environment\n"
543 if $rpcenv->{type
} ne 'cli';
545 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
546 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
550 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
552 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
553 die "$emsg $@" if $@;
555 my $restorefn = sub {
556 my $conf = PVE
::QemuConfig-
>load_config($vmid);
558 PVE
::QemuConfig-
>check_protection($conf, $emsg);
560 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
563 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
569 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
570 # Convert restored VM to template if backup was VM template
571 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
572 warn "Convert to template.\n";
573 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
577 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
579 if ($start_after_create) {
580 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
585 # ensure no old replication state are exists
586 PVE
::ReplicationState
::delete_guest_states
($vmid);
588 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
592 # ensure no old replication state are exists
593 PVE
::ReplicationState
::delete_guest_states
($vmid);
601 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
605 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
607 if (!$conf->{bootdisk
}) {
608 my $firstdisk = PVE
::QemuServer
::resolve_first_disk
($conf);
609 $conf->{bootdisk
} = $firstdisk if $firstdisk;
612 # auto generate uuid if user did not specify smbios1 option
613 if (!$conf->{smbios1
}) {
614 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
617 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
618 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
621 PVE
::QemuConfig-
>write_config($vmid, $conf);
627 foreach my $volid (@$vollist) {
628 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
634 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
637 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
639 if ($start_after_create) {
640 print "Execute autostart\n";
641 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
646 my ($code, $worker_name);
648 $worker_name = 'qmrestore';
650 eval { $restorefn->() };
652 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
658 $worker_name = 'qmcreate';
660 eval { $createfn->() };
663 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
664 unlink($conffile) or die "failed to remove config file: $!\n";
672 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
675 __PACKAGE__-
>register_method({
680 description
=> "Directory index",
685 additionalProperties
=> 0,
687 node
=> get_standard_option
('pve-node'),
688 vmid
=> get_standard_option
('pve-vmid'),
696 subdir
=> { type
=> 'string' },
699 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
705 { subdir
=> 'config' },
706 { subdir
=> 'pending' },
707 { subdir
=> 'status' },
708 { subdir
=> 'unlink' },
709 { subdir
=> 'vncproxy' },
710 { subdir
=> 'termproxy' },
711 { subdir
=> 'migrate' },
712 { subdir
=> 'resize' },
713 { subdir
=> 'move' },
715 { subdir
=> 'rrddata' },
716 { subdir
=> 'monitor' },
717 { subdir
=> 'agent' },
718 { subdir
=> 'snapshot' },
719 { subdir
=> 'spiceproxy' },
720 { subdir
=> 'sendkey' },
721 { subdir
=> 'firewall' },
727 __PACKAGE__-
>register_method ({
728 subclass
=> "PVE::API2::Firewall::VM",
729 path
=> '{vmid}/firewall',
732 __PACKAGE__-
>register_method ({
733 subclass
=> "PVE::API2::Qemu::Agent",
734 path
=> '{vmid}/agent',
737 __PACKAGE__-
>register_method({
739 path
=> '{vmid}/rrd',
741 protected
=> 1, # fixme: can we avoid that?
743 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
745 description
=> "Read VM RRD statistics (returns PNG)",
747 additionalProperties
=> 0,
749 node
=> get_standard_option
('pve-node'),
750 vmid
=> get_standard_option
('pve-vmid'),
752 description
=> "Specify the time frame you are interested in.",
754 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
757 description
=> "The list of datasources you want to display.",
758 type
=> 'string', format
=> 'pve-configid-list',
761 description
=> "The RRD consolidation function",
763 enum
=> [ 'AVERAGE', 'MAX' ],
771 filename
=> { type
=> 'string' },
777 return PVE
::Cluster
::create_rrd_graph
(
778 "pve2-vm/$param->{vmid}", $param->{timeframe
},
779 $param->{ds
}, $param->{cf
});
783 __PACKAGE__-
>register_method({
785 path
=> '{vmid}/rrddata',
787 protected
=> 1, # fixme: can we avoid that?
789 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
791 description
=> "Read VM RRD statistics",
793 additionalProperties
=> 0,
795 node
=> get_standard_option
('pve-node'),
796 vmid
=> get_standard_option
('pve-vmid'),
798 description
=> "Specify the time frame you are interested in.",
800 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
803 description
=> "The RRD consolidation function",
805 enum
=> [ 'AVERAGE', 'MAX' ],
820 return PVE
::Cluster
::create_rrd_data
(
821 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
825 __PACKAGE__-
>register_method({
827 path
=> '{vmid}/config',
830 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
832 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
835 additionalProperties
=> 0,
837 node
=> get_standard_option
('pve-node'),
838 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
840 description
=> "Get current values (instead of pending values).",
845 snapshot
=> get_standard_option
('pve-snapshot-name', {
846 description
=> "Fetch config values from given snapshot.",
849 my ($cmd, $pname, $cur, $args) = @_;
850 PVE
::QemuConfig-
>snapshot_list($args->[0]);
856 description
=> "The current VM configuration.",
858 properties
=> PVE
::QemuServer
::json_config_properties
({
861 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
868 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
870 if (my $snapname = $param->{snapshot
}) {
871 my $snapshot = $conf->{snapshots
}->{$snapname};
872 die "snapshot '$snapname' does not exist\n" if !defined($snapshot);
874 $snapshot->{digest
} = $conf->{digest
}; # keep file digest for API
879 delete $conf->{snapshots
};
881 if (!$param->{current
}) {
882 foreach my $opt (keys %{$conf->{pending
}}) {
883 next if $opt eq 'delete';
884 my $value = $conf->{pending
}->{$opt};
885 next if ref($value); # just to be sure
886 $conf->{$opt} = $value;
888 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
889 foreach my $opt (keys %$pending_delete_hash) {
890 delete $conf->{$opt} if $conf->{$opt};
894 delete $conf->{pending
};
896 # hide cloudinit password
897 if ($conf->{cipassword
}) {
898 $conf->{cipassword
} = '**********';
904 __PACKAGE__-
>register_method({
905 name
=> 'vm_pending',
906 path
=> '{vmid}/pending',
909 description
=> "Get virtual machine configuration, including pending changes.",
911 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
914 additionalProperties
=> 0,
916 node
=> get_standard_option
('pve-node'),
917 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
926 description
=> "Configuration option name.",
930 description
=> "Current value.",
935 description
=> "Pending value.",
940 description
=> "Indicates a pending delete request if present and not 0. " .
941 "The value 2 indicates a force-delete request.",
953 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
955 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
959 foreach my $opt (keys %$conf) {
960 next if ref($conf->{$opt});
961 my $item = { key
=> $opt };
962 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
963 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
964 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
966 # hide cloudinit password
967 if ($opt eq 'cipassword') {
968 $item->{value
} = '**********' if defined($item->{value
});
969 # the trailing space so that the pending string is different
970 $item->{pending
} = '********** ' if defined($item->{pending
});
975 foreach my $opt (keys %{$conf->{pending
}}) {
976 next if $opt eq 'delete';
977 next if ref($conf->{pending
}->{$opt}); # just to be sure
978 next if defined($conf->{$opt});
979 my $item = { key
=> $opt };
980 $item->{pending
} = $conf->{pending
}->{$opt};
982 # hide cloudinit password
983 if ($opt eq 'cipassword') {
984 $item->{pending
} = '**********' if defined($item->{pending
});
989 while (my ($opt, $force) = each %$pending_delete_hash) {
990 next if $conf->{pending
}->{$opt}; # just to be sure
991 next if $conf->{$opt};
992 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
999 # POST/PUT {vmid}/config implementation
1001 # The original API used PUT (idempotent) an we assumed that all operations
1002 # are fast. But it turned out that almost any configuration change can
1003 # involve hot-plug actions, or disk alloc/free. Such actions can take long
1004 # time to complete and have side effects (not idempotent).
1006 # The new implementation uses POST and forks a worker process. We added
1007 # a new option 'background_delay'. If specified we wait up to
1008 # 'background_delay' second for the worker task to complete. It returns null
1009 # if the task is finished within that time, else we return the UPID.
1011 my $update_vm_api = sub {
1012 my ($param, $sync) = @_;
1014 my $rpcenv = PVE
::RPCEnvironment
::get
();
1016 my $authuser = $rpcenv->get_user();
1018 my $node = extract_param
($param, 'node');
1020 my $vmid = extract_param
($param, 'vmid');
1022 my $digest = extract_param
($param, 'digest');
1024 my $background_delay = extract_param
($param, 'background_delay');
1026 if (defined(my $cipassword = $param->{cipassword
})) {
1027 # Same logic as in cloud-init (but with the regex fixed...)
1028 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
1029 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1032 my @paramarr = (); # used for log message
1033 foreach my $key (sort keys %$param) {
1034 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1035 push @paramarr, "-$key", $value;
1038 my $skiplock = extract_param
($param, 'skiplock');
1039 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1040 if $skiplock && $authuser ne 'root@pam';
1042 my $delete_str = extract_param
($param, 'delete');
1044 my $revert_str = extract_param
($param, 'revert');
1046 my $force = extract_param
($param, 'force');
1048 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1049 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1050 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1053 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1055 my $storecfg = PVE
::Storage
::config
();
1057 my $defaults = PVE
::QemuServer
::load_defaults
();
1059 &$resolve_cdrom_alias($param);
1061 # now try to verify all parameters
1064 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1065 if (!PVE
::QemuServer
::option_exists
($opt)) {
1066 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1069 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1070 "-revert $opt' at the same time" })
1071 if defined($param->{$opt});
1073 $revert->{$opt} = 1;
1077 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1078 $opt = 'ide2' if $opt eq 'cdrom';
1080 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1081 "-delete $opt' at the same time" })
1082 if defined($param->{$opt});
1084 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1085 "-revert $opt' at the same time" })
1088 if (!PVE
::QemuServer
::option_exists
($opt)) {
1089 raise_param_exc
({ delete => "unknown option '$opt'" });
1095 my $repl_conf = PVE
::ReplicationConfig-
>new();
1096 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1097 my $check_replication = sub {
1099 return if !$is_replicated;
1100 my $volid = $drive->{file
};
1101 return if !$volid || !($drive->{replicate
}//1);
1102 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1103 my ($storeid, $format);
1104 if ($volid =~ $NEW_DISK_RE) {
1106 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1108 ($storeid, undef) = PVE
::Storage
::parse_volume_id
($volid, 1);
1109 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1111 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1112 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1113 return if $scfg->{shared
};
1114 die "cannot add non-replicatable volume to a replicated VM\n";
1117 foreach my $opt (keys %$param) {
1118 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1119 # cleanup drive path
1120 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1121 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1122 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1123 $check_replication->($drive);
1124 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1125 } elsif ($opt =~ m/^net(\d+)$/) {
1127 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1128 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1129 } elsif ($opt eq 'vmgenid') {
1130 if ($param->{$opt} eq '1') {
1131 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1133 } elsif ($opt eq 'hookscript') {
1134 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1135 raise_param_exc
({ $opt => $@ }) if $@;
1139 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1141 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1143 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1145 my $updatefn = sub {
1147 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1149 die "checksum missmatch (file change by other user?)\n"
1150 if $digest && $digest ne $conf->{digest
};
1152 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1154 foreach my $opt (keys %$revert) {
1155 if (defined($conf->{$opt})) {
1156 $param->{$opt} = $conf->{$opt};
1157 } elsif (defined($conf->{pending
}->{$opt})) {
1162 if ($param->{memory
} || defined($param->{balloon
})) {
1163 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1164 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1166 die "balloon value too large (must be smaller than assigned memory)\n"
1167 if $balloon && $balloon > $maxmem;
1170 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1174 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1176 # write updates to pending section
1178 my $modified = {}; # record what $option we modify
1180 foreach my $opt (@delete) {
1181 $modified->{$opt} = 1;
1182 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1184 # value of what we want to delete, independent if pending or not
1185 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1186 if (!defined($val)) {
1187 warn "cannot delete '$opt' - not set in current configuration!\n";
1188 $modified->{$opt} = 0;
1191 my $is_pending_val = defined($conf->{pending
}->{$opt});
1193 if ($opt =~ m/^unused/) {
1194 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1195 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1196 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1197 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1198 delete $conf->{$opt};
1199 PVE
::QemuConfig-
>write_config($vmid, $conf);
1201 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1202 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1203 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1204 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1206 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1207 PVE
::QemuConfig-
>write_config($vmid, $conf);
1208 } elsif ($opt =~ m/^serial\d+$/) {
1209 if ($val eq 'socket') {
1210 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1211 } elsif ($authuser ne 'root@pam') {
1212 die "only root can delete '$opt' config for real devices\n";
1214 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1215 PVE
::QemuConfig-
>write_config($vmid, $conf);
1216 } elsif ($opt =~ m/^usb\d+$/) {
1217 if ($val =~ m/spice/) {
1218 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1219 } elsif ($authuser ne 'root@pam') {
1220 die "only root can delete '$opt' config for real devices\n";
1222 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1223 PVE
::QemuConfig-
>write_config($vmid, $conf);
1225 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1226 PVE
::QemuConfig-
>write_config($vmid, $conf);
1230 foreach my $opt (keys %$param) { # add/change
1231 $modified->{$opt} = 1;
1232 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1233 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1235 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
1237 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1238 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1239 # FIXME: cloudinit: CDROM or Disk?
1240 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1241 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1243 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1245 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1246 if defined($conf->{pending
}->{$opt});
1248 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1249 } elsif ($opt =~ m/^serial\d+/) {
1250 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1251 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1252 } elsif ($authuser ne 'root@pam') {
1253 die "only root can modify '$opt' config for real devices\n";
1255 $conf->{pending
}->{$opt} = $param->{$opt};
1256 } elsif ($opt =~ m/^usb\d+/) {
1257 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1258 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1259 } elsif ($authuser ne 'root@pam') {
1260 die "only root can modify '$opt' config for real devices\n";
1262 $conf->{pending
}->{$opt} = $param->{$opt};
1264 $conf->{pending
}->{$opt} = $param->{$opt};
1266 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1267 PVE
::QemuConfig-
>write_config($vmid, $conf);
1270 # remove pending changes when nothing changed
1271 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1272 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1273 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1275 return if !scalar(keys %{$conf->{pending
}});
1277 my $running = PVE
::QemuServer
::check_running
($vmid);
1279 # apply pending changes
1281 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1285 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1286 raise_param_exc
($errors) if scalar(keys %$errors);
1288 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1298 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1300 if ($background_delay) {
1302 # Note: It would be better to do that in the Event based HTTPServer
1303 # to avoid blocking call to sleep.
1305 my $end_time = time() + $background_delay;
1307 my $task = PVE
::Tools
::upid_decode
($upid);
1310 while (time() < $end_time) {
1311 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1313 sleep(1); # this gets interrupted when child process ends
1317 my $status = PVE
::Tools
::upid_read_status
($upid);
1318 return undef if $status eq 'OK';
1327 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1330 my $vm_config_perm_list = [
1335 'VM.Config.Network',
1337 'VM.Config.Options',
1340 __PACKAGE__-
>register_method({
1341 name
=> 'update_vm_async',
1342 path
=> '{vmid}/config',
1346 description
=> "Set virtual machine options (asynchrounous API).",
1348 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1351 additionalProperties
=> 0,
1352 properties
=> PVE
::QemuServer
::json_config_properties
(
1354 node
=> get_standard_option
('pve-node'),
1355 vmid
=> get_standard_option
('pve-vmid'),
1356 skiplock
=> get_standard_option
('skiplock'),
1358 type
=> 'string', format
=> 'pve-configid-list',
1359 description
=> "A list of settings you want to delete.",
1363 type
=> 'string', format
=> 'pve-configid-list',
1364 description
=> "Revert a pending change.",
1369 description
=> $opt_force_description,
1371 requires
=> 'delete',
1375 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1379 background_delay
=> {
1381 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1392 code
=> $update_vm_api,
1395 __PACKAGE__-
>register_method({
1396 name
=> 'update_vm',
1397 path
=> '{vmid}/config',
1401 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1403 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1406 additionalProperties
=> 0,
1407 properties
=> PVE
::QemuServer
::json_config_properties
(
1409 node
=> get_standard_option
('pve-node'),
1410 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1411 skiplock
=> get_standard_option
('skiplock'),
1413 type
=> 'string', format
=> 'pve-configid-list',
1414 description
=> "A list of settings you want to delete.",
1418 type
=> 'string', format
=> 'pve-configid-list',
1419 description
=> "Revert a pending change.",
1424 description
=> $opt_force_description,
1426 requires
=> 'delete',
1430 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1436 returns
=> { type
=> 'null' },
1439 &$update_vm_api($param, 1);
1444 __PACKAGE__-
>register_method({
1445 name
=> 'destroy_vm',
1450 description
=> "Destroy the vm (also delete all used/owned volumes).",
1452 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1455 additionalProperties
=> 0,
1457 node
=> get_standard_option
('pve-node'),
1458 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1459 skiplock
=> get_standard_option
('skiplock'),
1468 my $rpcenv = PVE
::RPCEnvironment
::get
();
1469 my $authuser = $rpcenv->get_user();
1470 my $vmid = $param->{vmid
};
1472 my $skiplock = $param->{skiplock
};
1473 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1474 if $skiplock && $authuser ne 'root@pam';
1477 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1478 my $storecfg = PVE
::Storage
::config
();
1479 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1480 die "unable to remove VM $vmid - used in HA resources\n"
1481 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1483 # do not allow destroy if there are replication jobs
1484 my $repl_conf = PVE
::ReplicationConfig-
>new();
1485 $repl_conf->check_for_existing_jobs($vmid);
1487 # early tests (repeat after locking)
1488 die "VM $vmid is running - destroy failed\n"
1489 if PVE
::QemuServer
::check_running
($vmid);
1494 syslog
('info', "destroy VM $vmid: $upid\n");
1495 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1496 PVE
::AccessControl
::remove_vm_access
($vmid);
1497 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1500 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1503 __PACKAGE__-
>register_method({
1505 path
=> '{vmid}/unlink',
1509 description
=> "Unlink/delete disk images.",
1511 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1514 additionalProperties
=> 0,
1516 node
=> get_standard_option
('pve-node'),
1517 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1519 type
=> 'string', format
=> 'pve-configid-list',
1520 description
=> "A list of disk IDs you want to delete.",
1524 description
=> $opt_force_description,
1529 returns
=> { type
=> 'null'},
1533 $param->{delete} = extract_param
($param, 'idlist');
1535 __PACKAGE__-
>update_vm($param);
1542 __PACKAGE__-
>register_method({
1544 path
=> '{vmid}/vncproxy',
1548 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1550 description
=> "Creates a TCP VNC proxy connections.",
1552 additionalProperties
=> 0,
1554 node
=> get_standard_option
('pve-node'),
1555 vmid
=> get_standard_option
('pve-vmid'),
1559 description
=> "starts websockify instead of vncproxy",
1564 additionalProperties
=> 0,
1566 user
=> { type
=> 'string' },
1567 ticket
=> { type
=> 'string' },
1568 cert
=> { type
=> 'string' },
1569 port
=> { type
=> 'integer' },
1570 upid
=> { type
=> 'string' },
1576 my $rpcenv = PVE
::RPCEnvironment
::get
();
1578 my $authuser = $rpcenv->get_user();
1580 my $vmid = $param->{vmid
};
1581 my $node = $param->{node
};
1582 my $websocket = $param->{websocket
};
1584 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1585 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1587 my $authpath = "/vms/$vmid";
1589 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1591 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1597 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1598 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1599 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1600 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1601 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1603 $family = PVE
::Tools
::get_host_address_family
($node);
1606 my $port = PVE
::Tools
::next_vnc_port
($family);
1613 syslog
('info', "starting vnc proxy $upid\n");
1619 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1621 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1622 '-timeout', $timeout, '-authpath', $authpath,
1623 '-perm', 'Sys.Console'];
1625 if ($param->{websocket
}) {
1626 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1627 push @$cmd, '-notls', '-listen', 'localhost';
1630 push @$cmd, '-c', @$remcmd, @$termcmd;
1632 PVE
::Tools
::run_command
($cmd);
1636 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1638 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1640 my $sock = IO
::Socket
::IP-
>new(
1645 GetAddrInfoFlags
=> 0,
1646 ) or die "failed to create socket: $!\n";
1647 # Inside the worker we shouldn't have any previous alarms
1648 # running anyway...:
1650 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1652 accept(my $cli, $sock) or die "connection failed: $!\n";
1655 if (PVE
::Tools
::run_command
($cmd,
1656 output
=> '>&'.fileno($cli),
1657 input
=> '<&'.fileno($cli),
1660 die "Failed to run vncproxy.\n";
1667 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1669 PVE
::Tools
::wait_for_vnc_port
($port);
1680 __PACKAGE__-
>register_method({
1681 name
=> 'termproxy',
1682 path
=> '{vmid}/termproxy',
1686 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1688 description
=> "Creates a TCP proxy connections.",
1690 additionalProperties
=> 0,
1692 node
=> get_standard_option
('pve-node'),
1693 vmid
=> get_standard_option
('pve-vmid'),
1697 enum
=> [qw(serial0 serial1 serial2 serial3)],
1698 description
=> "opens a serial terminal (defaults to display)",
1703 additionalProperties
=> 0,
1705 user
=> { type
=> 'string' },
1706 ticket
=> { type
=> 'string' },
1707 port
=> { type
=> 'integer' },
1708 upid
=> { type
=> 'string' },
1714 my $rpcenv = PVE
::RPCEnvironment
::get
();
1716 my $authuser = $rpcenv->get_user();
1718 my $vmid = $param->{vmid
};
1719 my $node = $param->{node
};
1720 my $serial = $param->{serial
};
1722 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1724 if (!defined($serial)) {
1725 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1726 $serial = $conf->{vga
};
1730 my $authpath = "/vms/$vmid";
1732 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1737 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1738 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1739 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1740 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, '-t');
1741 push @$remcmd, '--';
1743 $family = PVE
::Tools
::get_host_address_family
($node);
1746 my $port = PVE
::Tools
::next_vnc_port
($family);
1748 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1749 push @$termcmd, '-iface', $serial if $serial;
1754 syslog
('info', "starting qemu termproxy $upid\n");
1756 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1757 '--perm', 'VM.Console', '--'];
1758 push @$cmd, @$remcmd, @$termcmd;
1760 PVE
::Tools
::run_command
($cmd);
1763 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1765 PVE
::Tools
::wait_for_vnc_port
($port);
1775 __PACKAGE__-
>register_method({
1776 name
=> 'vncwebsocket',
1777 path
=> '{vmid}/vncwebsocket',
1780 description
=> "You also need to pass a valid ticket (vncticket).",
1781 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1783 description
=> "Opens a weksocket for VNC traffic.",
1785 additionalProperties
=> 0,
1787 node
=> get_standard_option
('pve-node'),
1788 vmid
=> get_standard_option
('pve-vmid'),
1790 description
=> "Ticket from previous call to vncproxy.",
1795 description
=> "Port number returned by previous vncproxy call.",
1805 port
=> { type
=> 'string' },
1811 my $rpcenv = PVE
::RPCEnvironment
::get
();
1813 my $authuser = $rpcenv->get_user();
1815 my $vmid = $param->{vmid
};
1816 my $node = $param->{node
};
1818 my $authpath = "/vms/$vmid";
1820 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1822 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1824 # Note: VNC ports are acessible from outside, so we do not gain any
1825 # security if we verify that $param->{port} belongs to VM $vmid. This
1826 # check is done by verifying the VNC ticket (inside VNC protocol).
1828 my $port = $param->{port
};
1830 return { port
=> $port };
1833 __PACKAGE__-
>register_method({
1834 name
=> 'spiceproxy',
1835 path
=> '{vmid}/spiceproxy',
1840 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1842 description
=> "Returns a SPICE configuration to connect to the VM.",
1844 additionalProperties
=> 0,
1846 node
=> get_standard_option
('pve-node'),
1847 vmid
=> get_standard_option
('pve-vmid'),
1848 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1851 returns
=> get_standard_option
('remote-viewer-config'),
1855 my $rpcenv = PVE
::RPCEnvironment
::get
();
1857 my $authuser = $rpcenv->get_user();
1859 my $vmid = $param->{vmid
};
1860 my $node = $param->{node
};
1861 my $proxy = $param->{proxy
};
1863 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1864 my $title = "VM $vmid";
1865 $title .= " - ". $conf->{name
} if $conf->{name
};
1867 my $port = PVE
::QemuServer
::spice_port
($vmid);
1869 my ($ticket, undef, $remote_viewer_config) =
1870 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1872 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1873 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1875 return $remote_viewer_config;
1878 __PACKAGE__-
>register_method({
1880 path
=> '{vmid}/status',
1883 description
=> "Directory index",
1888 additionalProperties
=> 0,
1890 node
=> get_standard_option
('pve-node'),
1891 vmid
=> get_standard_option
('pve-vmid'),
1899 subdir
=> { type
=> 'string' },
1902 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1908 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1911 { subdir
=> 'current' },
1912 { subdir
=> 'start' },
1913 { subdir
=> 'stop' },
1914 { subdir
=> 'reset' },
1915 { subdir
=> 'shutdown' },
1916 { subdir
=> 'suspend' },
1917 { subdir
=> 'reboot' },
1923 __PACKAGE__-
>register_method({
1924 name
=> 'vm_status',
1925 path
=> '{vmid}/status/current',
1928 protected
=> 1, # qemu pid files are only readable by root
1929 description
=> "Get virtual machine status.",
1931 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1934 additionalProperties
=> 0,
1936 node
=> get_standard_option
('pve-node'),
1937 vmid
=> get_standard_option
('pve-vmid'),
1943 %$PVE::QemuServer
::vmstatus_return_properties
,
1945 description
=> "HA manager service status.",
1949 description
=> "Qemu VGA configuration supports spice.",
1954 description
=> "Qemu GuestAgent enabled in config.",
1964 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1966 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1967 my $status = $vmstatus->{$param->{vmid
}};
1969 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1971 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1972 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1977 __PACKAGE__-
>register_method({
1979 path
=> '{vmid}/status/start',
1983 description
=> "Start virtual machine.",
1985 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1988 additionalProperties
=> 0,
1990 node
=> get_standard_option
('pve-node'),
1991 vmid
=> get_standard_option
('pve-vmid',
1992 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1993 skiplock
=> get_standard_option
('skiplock'),
1994 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1995 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1998 enum
=> ['secure', 'insecure'],
1999 description
=> "Migration traffic is encrypted using an SSH " .
2000 "tunnel by default. On secure, completely private networks " .
2001 "this can be disabled to increase performance.",
2004 migration_network
=> {
2005 type
=> 'string', format
=> 'CIDR',
2006 description
=> "CIDR of the (sub) network that is used for migration.",
2009 machine
=> get_standard_option
('pve-qm-machine'),
2011 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
2023 my $rpcenv = PVE
::RPCEnvironment
::get
();
2024 my $authuser = $rpcenv->get_user();
2026 my $node = extract_param
($param, 'node');
2027 my $vmid = extract_param
($param, 'vmid');
2029 my $machine = extract_param
($param, 'machine');
2031 my $get_root_param = sub {
2032 my $value = extract_param
($param, $_[0]);
2033 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2034 if $value && $authuser ne 'root@pam';
2038 my $stateuri = $get_root_param->('stateuri');
2039 my $skiplock = $get_root_param->('skiplock');
2040 my $migratedfrom = $get_root_param->('migratedfrom');
2041 my $migration_type = $get_root_param->('migration_type');
2042 my $migration_network = $get_root_param->('migration_network');
2043 my $targetstorage = $get_root_param->('targetstorage');
2045 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2046 if $targetstorage && !$migratedfrom;
2048 # read spice ticket from STDIN
2050 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2051 if (defined(my $line = <STDIN
>)) {
2053 $spice_ticket = $line;
2057 PVE
::Cluster
::check_cfs_quorum
();
2059 my $storecfg = PVE
::Storage
::config
();
2061 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2065 print "Requesting HA start for VM $vmid\n";
2067 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2068 PVE
::Tools
::run_command
($cmd);
2072 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2079 syslog
('info', "start VM $vmid: $upid\n");
2081 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2082 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
2086 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2090 __PACKAGE__-
>register_method({
2092 path
=> '{vmid}/status/stop',
2096 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2097 "is akin to pulling the power plug of a running computer and may damage the VM data",
2099 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2102 additionalProperties
=> 0,
2104 node
=> get_standard_option
('pve-node'),
2105 vmid
=> get_standard_option
('pve-vmid',
2106 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2107 skiplock
=> get_standard_option
('skiplock'),
2108 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2110 description
=> "Wait maximal timeout seconds.",
2116 description
=> "Do not deactivate storage volumes.",
2129 my $rpcenv = PVE
::RPCEnvironment
::get
();
2130 my $authuser = $rpcenv->get_user();
2132 my $node = extract_param
($param, 'node');
2133 my $vmid = extract_param
($param, 'vmid');
2135 my $skiplock = extract_param
($param, 'skiplock');
2136 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2137 if $skiplock && $authuser ne 'root@pam';
2139 my $keepActive = extract_param
($param, 'keepActive');
2140 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2141 if $keepActive && $authuser ne 'root@pam';
2143 my $migratedfrom = extract_param
($param, 'migratedfrom');
2144 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2145 if $migratedfrom && $authuser ne 'root@pam';
2148 my $storecfg = PVE
::Storage
::config
();
2150 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2155 print "Requesting HA stop for VM $vmid\n";
2157 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'stopped'];
2158 PVE
::Tools
::run_command
($cmd);
2162 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2168 syslog
('info', "stop VM $vmid: $upid\n");
2170 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2171 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2175 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2179 __PACKAGE__-
>register_method({
2181 path
=> '{vmid}/status/reset',
2185 description
=> "Reset virtual machine.",
2187 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2190 additionalProperties
=> 0,
2192 node
=> get_standard_option
('pve-node'),
2193 vmid
=> get_standard_option
('pve-vmid',
2194 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2195 skiplock
=> get_standard_option
('skiplock'),
2204 my $rpcenv = PVE
::RPCEnvironment
::get
();
2206 my $authuser = $rpcenv->get_user();
2208 my $node = extract_param
($param, 'node');
2210 my $vmid = extract_param
($param, 'vmid');
2212 my $skiplock = extract_param
($param, 'skiplock');
2213 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2214 if $skiplock && $authuser ne 'root@pam';
2216 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2221 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2226 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2229 __PACKAGE__-
>register_method({
2230 name
=> 'vm_shutdown',
2231 path
=> '{vmid}/status/shutdown',
2235 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2236 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2238 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2241 additionalProperties
=> 0,
2243 node
=> get_standard_option
('pve-node'),
2244 vmid
=> get_standard_option
('pve-vmid',
2245 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2246 skiplock
=> get_standard_option
('skiplock'),
2248 description
=> "Wait maximal timeout seconds.",
2254 description
=> "Make sure the VM stops.",
2260 description
=> "Do not deactivate storage volumes.",
2273 my $rpcenv = PVE
::RPCEnvironment
::get
();
2274 my $authuser = $rpcenv->get_user();
2276 my $node = extract_param
($param, 'node');
2277 my $vmid = extract_param
($param, 'vmid');
2279 my $skiplock = extract_param
($param, 'skiplock');
2280 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2281 if $skiplock && $authuser ne 'root@pam';
2283 my $keepActive = extract_param
($param, 'keepActive');
2284 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2285 if $keepActive && $authuser ne 'root@pam';
2287 my $storecfg = PVE
::Storage
::config
();
2291 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2292 # otherwise, we will infer a shutdown command, but run into the timeout,
2293 # then when the vm is resumed, it will instantly shutdown
2295 # checking the qmp status here to get feedback to the gui/cli/api
2296 # and the status query should not take too long
2297 my $qmpstatus = eval {
2298 PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2302 if (!$err && $qmpstatus->{status
} eq "paused") {
2303 if ($param->{forceStop
}) {
2304 warn "VM is paused - stop instead of shutdown\n";
2307 die "VM is paused - cannot shutdown\n";
2311 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2316 print "Requesting HA stop for VM $vmid\n";
2318 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'stopped'];
2319 PVE
::Tools
::run_command
($cmd);
2323 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2330 syslog
('info', "shutdown VM $vmid: $upid\n");
2332 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2333 $shutdown, $param->{forceStop
}, $keepActive);
2337 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2341 __PACKAGE__-
>register_method({
2342 name
=> 'vm_reboot',
2343 path
=> '{vmid}/status/reboot',
2347 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2349 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2352 additionalProperties
=> 0,
2354 node
=> get_standard_option
('pve-node'),
2355 vmid
=> get_standard_option
('pve-vmid',
2356 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2358 description
=> "Wait maximal timeout seconds for the shutdown.",
2371 my $rpcenv = PVE
::RPCEnvironment
::get
();
2372 my $authuser = $rpcenv->get_user();
2374 my $node = extract_param
($param, 'node');
2375 my $vmid = extract_param
($param, 'vmid');
2377 my $qmpstatus = eval {
2378 PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2382 if (!$err && $qmpstatus->{status
} eq "paused") {
2383 die "VM is paused - cannot shutdown\n";
2386 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2391 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2392 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2396 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2399 __PACKAGE__-
>register_method({
2400 name
=> 'vm_suspend',
2401 path
=> '{vmid}/status/suspend',
2405 description
=> "Suspend virtual machine.",
2407 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2410 additionalProperties
=> 0,
2412 node
=> get_standard_option
('pve-node'),
2413 vmid
=> get_standard_option
('pve-vmid',
2414 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2415 skiplock
=> get_standard_option
('skiplock'),
2420 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2422 statestorage
=> get_standard_option
('pve-storage-id', {
2423 description
=> "The storage for the VM state",
2424 requires
=> 'todisk',
2426 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2436 my $rpcenv = PVE
::RPCEnvironment
::get
();
2437 my $authuser = $rpcenv->get_user();
2439 my $node = extract_param
($param, 'node');
2440 my $vmid = extract_param
($param, 'vmid');
2442 my $todisk = extract_param
($param, 'todisk') // 0;
2444 my $statestorage = extract_param
($param, 'statestorage');
2446 my $skiplock = extract_param
($param, 'skiplock');
2447 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2448 if $skiplock && $authuser ne 'root@pam';
2450 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2452 die "Cannot suspend HA managed VM to disk\n"
2453 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2458 syslog
('info', "suspend VM $vmid: $upid\n");
2460 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2465 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2466 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2469 __PACKAGE__-
>register_method({
2470 name
=> 'vm_resume',
2471 path
=> '{vmid}/status/resume',
2475 description
=> "Resume virtual machine.",
2477 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2480 additionalProperties
=> 0,
2482 node
=> get_standard_option
('pve-node'),
2483 vmid
=> get_standard_option
('pve-vmid',
2484 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2485 skiplock
=> get_standard_option
('skiplock'),
2486 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2496 my $rpcenv = PVE
::RPCEnvironment
::get
();
2498 my $authuser = $rpcenv->get_user();
2500 my $node = extract_param
($param, 'node');
2502 my $vmid = extract_param
($param, 'vmid');
2504 my $skiplock = extract_param
($param, 'skiplock');
2505 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2506 if $skiplock && $authuser ne 'root@pam';
2508 my $nocheck = extract_param
($param, 'nocheck');
2510 my $to_disk_suspended;
2512 PVE
::QemuConfig-
>lock_config($vmid, sub {
2513 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2514 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2518 die "VM $vmid not running\n"
2519 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2524 syslog
('info', "resume VM $vmid: $upid\n");
2526 if (!$to_disk_suspended) {
2527 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2529 my $storecfg = PVE
::Storage
::config
();
2530 PVE
::QemuServer
::vm_start
($storecfg, $vmid, undef, $skiplock);
2536 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2539 __PACKAGE__-
>register_method({
2540 name
=> 'vm_sendkey',
2541 path
=> '{vmid}/sendkey',
2545 description
=> "Send key event to virtual machine.",
2547 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2550 additionalProperties
=> 0,
2552 node
=> get_standard_option
('pve-node'),
2553 vmid
=> get_standard_option
('pve-vmid',
2554 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2555 skiplock
=> get_standard_option
('skiplock'),
2557 description
=> "The key (qemu monitor encoding).",
2562 returns
=> { type
=> 'null'},
2566 my $rpcenv = PVE
::RPCEnvironment
::get
();
2568 my $authuser = $rpcenv->get_user();
2570 my $node = extract_param
($param, 'node');
2572 my $vmid = extract_param
($param, 'vmid');
2574 my $skiplock = extract_param
($param, 'skiplock');
2575 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2576 if $skiplock && $authuser ne 'root@pam';
2578 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2583 __PACKAGE__-
>register_method({
2584 name
=> 'vm_feature',
2585 path
=> '{vmid}/feature',
2589 description
=> "Check if feature for virtual machine is available.",
2591 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2594 additionalProperties
=> 0,
2596 node
=> get_standard_option
('pve-node'),
2597 vmid
=> get_standard_option
('pve-vmid'),
2599 description
=> "Feature to check.",
2601 enum
=> [ 'snapshot', 'clone', 'copy' ],
2603 snapname
=> get_standard_option
('pve-snapshot-name', {
2611 hasFeature
=> { type
=> 'boolean' },
2614 items
=> { type
=> 'string' },
2621 my $node = extract_param
($param, 'node');
2623 my $vmid = extract_param
($param, 'vmid');
2625 my $snapname = extract_param
($param, 'snapname');
2627 my $feature = extract_param
($param, 'feature');
2629 my $running = PVE
::QemuServer
::check_running
($vmid);
2631 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2634 my $snap = $conf->{snapshots
}->{$snapname};
2635 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2638 my $storecfg = PVE
::Storage
::config
();
2640 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2641 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2644 hasFeature
=> $hasFeature,
2645 nodes
=> [ keys %$nodelist ],
2649 __PACKAGE__-
>register_method({
2651 path
=> '{vmid}/clone',
2655 description
=> "Create a copy of virtual machine/template.",
2657 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2658 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2659 "'Datastore.AllocateSpace' on any used storage.",
2662 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2664 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2665 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2670 additionalProperties
=> 0,
2672 node
=> get_standard_option
('pve-node'),
2673 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2674 newid
=> get_standard_option
('pve-vmid', {
2675 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2676 description
=> 'VMID for the clone.' }),
2679 type
=> 'string', format
=> 'dns-name',
2680 description
=> "Set a name for the new VM.",
2685 description
=> "Description for the new VM.",
2689 type
=> 'string', format
=> 'pve-poolid',
2690 description
=> "Add the new VM to the specified pool.",
2692 snapname
=> get_standard_option
('pve-snapshot-name', {
2695 storage
=> get_standard_option
('pve-storage-id', {
2696 description
=> "Target storage for full clone.",
2700 description
=> "Target format for file storage. Only valid for full clone.",
2703 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2708 description
=> "Create a full copy of all disks. This is always done when " .
2709 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2711 target
=> get_standard_option
('pve-node', {
2712 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2716 description
=> "Override I/O bandwidth limit (in KiB/s).",
2720 default => 'clone limit from datacenter or storage config',
2730 my $rpcenv = PVE
::RPCEnvironment
::get
();
2732 my $authuser = $rpcenv->get_user();
2734 my $node = extract_param
($param, 'node');
2736 my $vmid = extract_param
($param, 'vmid');
2738 my $newid = extract_param
($param, 'newid');
2740 my $pool = extract_param
($param, 'pool');
2742 if (defined($pool)) {
2743 $rpcenv->check_pool_exist($pool);
2746 my $snapname = extract_param
($param, 'snapname');
2748 my $storage = extract_param
($param, 'storage');
2750 my $format = extract_param
($param, 'format');
2752 my $target = extract_param
($param, 'target');
2754 my $localnode = PVE
::INotify
::nodename
();
2756 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2758 PVE
::Cluster
::check_node_exists
($target) if $target;
2760 my $storecfg = PVE
::Storage
::config
();
2763 # check if storage is enabled on local node
2764 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2766 # check if storage is available on target node
2767 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2768 # clone only works if target storage is shared
2769 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2770 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2774 PVE
::Cluster
::check_cfs_quorum
();
2776 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2778 # exclusive lock if VM is running - else shared lock is enough;
2779 my $shared_lock = $running ?
0 : 1;
2783 # do all tests after lock
2784 # we also try to do all tests before we fork the worker
2786 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2788 PVE
::QemuConfig-
>check_lock($conf);
2790 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2792 die "unexpected state change\n" if $verify_running != $running;
2794 die "snapshot '$snapname' does not exist\n"
2795 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2797 my $full = extract_param
($param, 'full');
2798 if (!defined($full)) {
2799 $full = !PVE
::QemuConfig-
>is_template($conf);
2802 die "parameter 'storage' not allowed for linked clones\n"
2803 if defined($storage) && !$full;
2805 die "parameter 'format' not allowed for linked clones\n"
2806 if defined($format) && !$full;
2808 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2810 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2812 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2814 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2816 die "unable to create VM $newid: config file already exists\n"
2819 my $newconf = { lock => 'clone' };
2824 foreach my $opt (keys %$oldconf) {
2825 my $value = $oldconf->{$opt};
2827 # do not copy snapshot related info
2828 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2829 $opt eq 'vmstate' || $opt eq 'snapstate';
2831 # no need to copy unused images, because VMID(owner) changes anyways
2832 next if $opt =~ m/^unused\d+$/;
2834 # always change MAC! address
2835 if ($opt =~ m/^net(\d+)$/) {
2836 my $net = PVE
::QemuServer
::parse_net
($value);
2837 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2838 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2839 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2840 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2841 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2842 die "unable to parse drive options for '$opt'\n" if !$drive;
2843 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2844 $newconf->{$opt} = $value; # simply copy configuration
2846 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2847 die "Full clone feature is not supported for drive '$opt'\n"
2848 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2849 $fullclone->{$opt} = 1;
2851 # not full means clone instead of copy
2852 die "Linked clone feature is not supported for drive '$opt'\n"
2853 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2855 $drives->{$opt} = $drive;
2856 push @$vollist, $drive->{file
};
2859 # copy everything else
2860 $newconf->{$opt} = $value;
2864 # auto generate a new uuid
2865 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2866 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2867 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2869 # auto generate a new vmgenid if the option was set
2870 if ($newconf->{vmgenid
}) {
2871 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2874 delete $newconf->{template
};
2876 if ($param->{name
}) {
2877 $newconf->{name
} = $param->{name
};
2879 if ($oldconf->{name
}) {
2880 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2882 $newconf->{name
} = "Copy-of-VM-$vmid";
2886 if ($param->{description
}) {
2887 $newconf->{description
} = $param->{description
};
2890 # create empty/temp config - this fails if VM already exists on other node
2891 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2896 my $newvollist = [];
2903 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2905 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2907 my $bwlimit = extract_param
($param, 'bwlimit');
2909 my $total_jobs = scalar(keys %{$drives});
2912 foreach my $opt (keys %$drives) {
2913 my $drive = $drives->{$opt};
2914 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2916 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
2917 my $storage_list = [ $src_sid ];
2918 push @$storage_list, $storage if defined($storage);
2919 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
2921 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2922 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2923 $jobs, $skipcomplete, $oldconf->{agent
}, $clonelimit);
2925 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2927 PVE
::QemuConfig-
>write_config($newid, $newconf);
2931 delete $newconf->{lock};
2933 # do not write pending changes
2934 if (my @changes = keys %{$newconf->{pending
}}) {
2935 my $pending = join(',', @changes);
2936 warn "found pending changes for '$pending', discarding for clone\n";
2937 delete $newconf->{pending
};
2940 PVE
::QemuConfig-
>write_config($newid, $newconf);
2943 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2944 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2945 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2947 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2948 die "Failed to move config to node '$target' - rename failed: $!\n"
2949 if !rename($conffile, $newconffile);
2952 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2957 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2959 sleep 1; # some storage like rbd need to wait before release volume - really?
2961 foreach my $volid (@$newvollist) {
2962 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2965 die "clone failed: $err";
2971 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2973 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2976 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2977 # Aquire exclusive lock lock for $newid
2978 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2983 __PACKAGE__-
>register_method({
2984 name
=> 'move_vm_disk',
2985 path
=> '{vmid}/move_disk',
2989 description
=> "Move volume to different storage.",
2991 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2993 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2994 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2998 additionalProperties
=> 0,
3000 node
=> get_standard_option
('pve-node'),
3001 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3004 description
=> "The disk you want to move.",
3005 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
3007 storage
=> get_standard_option
('pve-storage-id', {
3008 description
=> "Target storage.",
3009 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3013 description
=> "Target Format.",
3014 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3019 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3025 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3030 description
=> "Override I/O bandwidth limit (in KiB/s).",
3034 default => 'move limit from datacenter or storage config',
3040 description
=> "the task ID.",
3045 my $rpcenv = PVE
::RPCEnvironment
::get
();
3047 my $authuser = $rpcenv->get_user();
3049 my $node = extract_param
($param, 'node');
3051 my $vmid = extract_param
($param, 'vmid');
3053 my $digest = extract_param
($param, 'digest');
3055 my $disk = extract_param
($param, 'disk');
3057 my $storeid = extract_param
($param, 'storage');
3059 my $format = extract_param
($param, 'format');
3061 my $storecfg = PVE
::Storage
::config
();
3063 my $updatefn = sub {
3065 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3067 PVE
::QemuConfig-
>check_lock($conf);
3069 die "checksum missmatch (file change by other user?)\n"
3070 if $digest && $digest ne $conf->{digest
};
3072 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3074 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3076 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
3078 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3081 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3082 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3086 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
3087 (!$format || !$oldfmt || $oldfmt eq $format);
3089 # this only checks snapshots because $disk is passed!
3090 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3091 die "you can't move a disk with snapshots and delete the source\n"
3092 if $snapshotted && $param->{delete};
3094 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3096 my $running = PVE
::QemuServer
::check_running
($vmid);
3098 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3102 my $newvollist = [];
3108 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3110 warn "moving disk with snapshots, snapshots will not be moved!\n"
3113 my $bwlimit = extract_param
($param, 'bwlimit');
3114 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3116 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3117 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit);
3119 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
3121 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3123 # convert moved disk to base if part of template
3124 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3125 if PVE
::QemuConfig-
>is_template($conf);
3127 PVE
::QemuConfig-
>write_config($vmid, $conf);
3129 if ($running && PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
} && PVE
::QemuServer
::qga_check_running
($vmid)) {
3130 eval { PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-fstrim"); };
3134 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3135 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3142 foreach my $volid (@$newvollist) {
3143 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3146 die "storage migration failed: $err";
3149 if ($param->{delete}) {
3151 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3152 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3158 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3161 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3164 my $check_vm_disks_local = sub {
3165 my ($storecfg, $vmconf, $vmid) = @_;
3167 my $local_disks = {};
3169 # add some more information to the disks e.g. cdrom
3170 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3171 my ($volid, $attr) = @_;
3173 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3175 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3176 return if $scfg->{shared
};
3178 # The shared attr here is just a special case where the vdisk
3179 # is marked as shared manually
3180 return if $attr->{shared
};
3181 return if $attr->{cdrom
} and $volid eq "none";
3183 if (exists $local_disks->{$volid}) {
3184 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3186 $local_disks->{$volid} = $attr;
3187 # ensure volid is present in case it's needed
3188 $local_disks->{$volid}->{volid
} = $volid;
3192 return $local_disks;
3195 __PACKAGE__-
>register_method({
3196 name
=> 'migrate_vm_precondition',
3197 path
=> '{vmid}/migrate',
3201 description
=> "Get preconditions for migration.",
3203 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3206 additionalProperties
=> 0,
3208 node
=> get_standard_option
('pve-node'),
3209 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3210 target
=> get_standard_option
('pve-node', {
3211 description
=> "Target node.",
3212 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3220 running
=> { type
=> 'boolean' },
3224 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3226 not_allowed_nodes
=> {
3229 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3233 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3235 local_resources
=> {
3237 description
=> "List local resources e.g. pci, usb"
3244 my $rpcenv = PVE
::RPCEnvironment
::get
();
3246 my $authuser = $rpcenv->get_user();
3248 PVE
::Cluster
::check_cfs_quorum
();
3252 my $vmid = extract_param
($param, 'vmid');
3253 my $target = extract_param
($param, 'target');
3254 my $localnode = PVE
::INotify
::nodename
();
3258 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3259 my $storecfg = PVE
::Storage
::config
();
3262 # try to detect errors early
3263 PVE
::QemuConfig-
>check_lock($vmconf);
3265 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3267 # if vm is not running, return target nodes where local storage is available
3268 # for offline migration
3269 if (!$res->{running
}) {
3270 $res->{allowed_nodes
} = [];
3271 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3272 delete $checked_nodes->{$localnode};
3274 foreach my $node (keys %$checked_nodes) {
3275 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3276 push @{$res->{allowed_nodes
}}, $node;
3280 $res->{not_allowed_nodes
} = $checked_nodes;
3284 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3285 $res->{local_disks
} = [ values %$local_disks ];;
3287 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3289 $res->{local_resources
} = $local_resources;
3296 __PACKAGE__-
>register_method({
3297 name
=> 'migrate_vm',
3298 path
=> '{vmid}/migrate',
3302 description
=> "Migrate virtual machine. Creates a new migration task.",
3304 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3307 additionalProperties
=> 0,
3309 node
=> get_standard_option
('pve-node'),
3310 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3311 target
=> get_standard_option
('pve-node', {
3312 description
=> "Target node.",
3313 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3317 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3322 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3327 enum
=> ['secure', 'insecure'],
3328 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3331 migration_network
=> {
3332 type
=> 'string', format
=> 'CIDR',
3333 description
=> "CIDR of the (sub) network that is used for migration.",
3336 "with-local-disks" => {
3338 description
=> "Enable live storage migration for local disk",
3341 targetstorage
=> get_standard_option
('pve-storage-id', {
3342 description
=> "Default target storage.",
3344 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3347 description
=> "Override I/O bandwidth limit (in KiB/s).",
3351 default => 'migrate limit from datacenter or storage config',
3357 description
=> "the task ID.",
3362 my $rpcenv = PVE
::RPCEnvironment
::get
();
3363 my $authuser = $rpcenv->get_user();
3365 my $target = extract_param
($param, 'target');
3367 my $localnode = PVE
::INotify
::nodename
();
3368 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3370 PVE
::Cluster
::check_cfs_quorum
();
3372 PVE
::Cluster
::check_node_exists
($target);
3374 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3376 my $vmid = extract_param
($param, 'vmid');
3378 raise_param_exc
({ force
=> "Only root may use this option." })
3379 if $param->{force
} && $authuser ne 'root@pam';
3381 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3382 if $param->{migration_type
} && $authuser ne 'root@pam';
3384 # allow root only until better network permissions are available
3385 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3386 if $param->{migration_network
} && $authuser ne 'root@pam';
3389 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3391 # try to detect errors early
3393 PVE
::QemuConfig-
>check_lock($conf);
3395 if (PVE
::QemuServer
::check_running
($vmid)) {
3396 die "can't migrate running VM without --online\n" if !$param->{online
};
3398 warn "VM isn't running. Doing offline migration instead\n." if $param->{online
};
3399 $param->{online
} = 0;
3402 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3403 if !$param->{online
} && $param->{targetstorage
};
3405 my $storecfg = PVE
::Storage
::config
();
3407 if( $param->{targetstorage
}) {
3408 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3410 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3413 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3418 print "Requesting HA migration for VM $vmid to node $target\n";
3420 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3421 PVE
::Tools
::run_command
($cmd);
3425 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3430 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3434 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3437 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3442 __PACKAGE__-
>register_method({
3444 path
=> '{vmid}/monitor',
3448 description
=> "Execute Qemu monitor commands.",
3450 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3451 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3454 additionalProperties
=> 0,
3456 node
=> get_standard_option
('pve-node'),
3457 vmid
=> get_standard_option
('pve-vmid'),
3460 description
=> "The monitor command.",
3464 returns
=> { type
=> 'string'},
3468 my $rpcenv = PVE
::RPCEnvironment
::get
();
3469 my $authuser = $rpcenv->get_user();
3472 my $command = shift;
3473 return $command =~ m/^\s*info(\s+|$)/
3474 || $command =~ m/^\s*help\s*$/;
3477 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3478 if !&$is_ro($param->{command
});
3480 my $vmid = $param->{vmid
};
3482 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3486 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3488 $res = "ERROR: $@" if $@;
3493 __PACKAGE__-
>register_method({
3494 name
=> 'resize_vm',
3495 path
=> '{vmid}/resize',
3499 description
=> "Extend volume size.",
3501 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3504 additionalProperties
=> 0,
3506 node
=> get_standard_option
('pve-node'),
3507 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3508 skiplock
=> get_standard_option
('skiplock'),
3511 description
=> "The disk you want to resize.",
3512 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3516 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3517 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.",
3521 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3527 returns
=> { type
=> 'null'},
3531 my $rpcenv = PVE
::RPCEnvironment
::get
();
3533 my $authuser = $rpcenv->get_user();
3535 my $node = extract_param
($param, 'node');
3537 my $vmid = extract_param
($param, 'vmid');
3539 my $digest = extract_param
($param, 'digest');
3541 my $disk = extract_param
($param, 'disk');
3543 my $sizestr = extract_param
($param, 'size');
3545 my $skiplock = extract_param
($param, 'skiplock');
3546 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3547 if $skiplock && $authuser ne 'root@pam';
3549 my $storecfg = PVE
::Storage
::config
();
3551 my $updatefn = sub {
3553 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3555 die "checksum missmatch (file change by other user?)\n"
3556 if $digest && $digest ne $conf->{digest
};
3557 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3559 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3561 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3563 my (undef, undef, undef, undef, undef, undef, $format) =
3564 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3566 die "can't resize volume: $disk if snapshot exists\n"
3567 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3569 my $volid = $drive->{file
};
3571 die "disk '$disk' has no associated volume\n" if !$volid;
3573 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3575 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3577 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3579 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3580 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3582 die "Size of volume $volid couldn't be determined\n" if (!defined($size));
3584 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3585 my ($ext, $newsize, $unit) = ($1, $2, $4);
3588 $newsize = $newsize * 1024;
3589 } elsif ($unit eq 'M') {
3590 $newsize = $newsize * 1024 * 1024;
3591 } elsif ($unit eq 'G') {
3592 $newsize = $newsize * 1024 * 1024 * 1024;
3593 } elsif ($unit eq 'T') {
3594 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3597 $newsize += $size if $ext;
3598 $newsize = int($newsize);
3600 die "shrinking disks is not supported\n" if $newsize < $size;
3602 return if $size == $newsize;
3604 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3606 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3608 $drive->{size
} = $newsize;
3609 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3611 PVE
::QemuConfig-
>write_config($vmid, $conf);
3614 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3618 __PACKAGE__-
>register_method({
3619 name
=> 'snapshot_list',
3620 path
=> '{vmid}/snapshot',
3622 description
=> "List all snapshots.",
3624 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3627 protected
=> 1, # qemu pid files are only readable by root
3629 additionalProperties
=> 0,
3631 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3632 node
=> get_standard_option
('pve-node'),
3641 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3645 description
=> "Snapshot includes RAM.",
3650 description
=> "Snapshot description.",
3654 description
=> "Snapshot creation time",
3656 renderer
=> 'timestamp',
3660 description
=> "Parent snapshot identifier.",
3666 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3671 my $vmid = $param->{vmid
};
3673 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3674 my $snaphash = $conf->{snapshots
} || {};
3678 foreach my $name (keys %$snaphash) {
3679 my $d = $snaphash->{$name};
3682 snaptime
=> $d->{snaptime
} || 0,
3683 vmstate
=> $d->{vmstate
} ?
1 : 0,
3684 description
=> $d->{description
} || '',
3686 $item->{parent
} = $d->{parent
} if $d->{parent
};
3687 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3691 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3694 digest
=> $conf->{digest
},
3695 running
=> $running,
3696 description
=> "You are here!",
3698 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3700 push @$res, $current;
3705 __PACKAGE__-
>register_method({
3707 path
=> '{vmid}/snapshot',
3711 description
=> "Snapshot a VM.",
3713 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3716 additionalProperties
=> 0,
3718 node
=> get_standard_option
('pve-node'),
3719 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3720 snapname
=> get_standard_option
('pve-snapshot-name'),
3724 description
=> "Save the vmstate",
3729 description
=> "A textual description or comment.",
3735 description
=> "the task ID.",
3740 my $rpcenv = PVE
::RPCEnvironment
::get
();
3742 my $authuser = $rpcenv->get_user();
3744 my $node = extract_param
($param, 'node');
3746 my $vmid = extract_param
($param, 'vmid');
3748 my $snapname = extract_param
($param, 'snapname');
3750 die "unable to use snapshot name 'current' (reserved name)\n"
3751 if $snapname eq 'current';
3754 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3755 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3756 $param->{description
});
3759 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3762 __PACKAGE__-
>register_method({
3763 name
=> 'snapshot_cmd_idx',
3764 path
=> '{vmid}/snapshot/{snapname}',
3771 additionalProperties
=> 0,
3773 vmid
=> get_standard_option
('pve-vmid'),
3774 node
=> get_standard_option
('pve-node'),
3775 snapname
=> get_standard_option
('pve-snapshot-name'),
3784 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3791 push @$res, { cmd
=> 'rollback' };
3792 push @$res, { cmd
=> 'config' };
3797 __PACKAGE__-
>register_method({
3798 name
=> 'update_snapshot_config',
3799 path
=> '{vmid}/snapshot/{snapname}/config',
3803 description
=> "Update snapshot metadata.",
3805 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3808 additionalProperties
=> 0,
3810 node
=> get_standard_option
('pve-node'),
3811 vmid
=> get_standard_option
('pve-vmid'),
3812 snapname
=> get_standard_option
('pve-snapshot-name'),
3816 description
=> "A textual description or comment.",
3820 returns
=> { type
=> 'null' },
3824 my $rpcenv = PVE
::RPCEnvironment
::get
();
3826 my $authuser = $rpcenv->get_user();
3828 my $vmid = extract_param
($param, 'vmid');
3830 my $snapname = extract_param
($param, 'snapname');
3832 return undef if !defined($param->{description
});
3834 my $updatefn = sub {
3836 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3838 PVE
::QemuConfig-
>check_lock($conf);
3840 my $snap = $conf->{snapshots
}->{$snapname};
3842 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3844 $snap->{description
} = $param->{description
} if defined($param->{description
});
3846 PVE
::QemuConfig-
>write_config($vmid, $conf);
3849 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3854 __PACKAGE__-
>register_method({
3855 name
=> 'get_snapshot_config',
3856 path
=> '{vmid}/snapshot/{snapname}/config',
3859 description
=> "Get snapshot configuration",
3861 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3864 additionalProperties
=> 0,
3866 node
=> get_standard_option
('pve-node'),
3867 vmid
=> get_standard_option
('pve-vmid'),
3868 snapname
=> get_standard_option
('pve-snapshot-name'),
3871 returns
=> { type
=> "object" },
3875 my $rpcenv = PVE
::RPCEnvironment
::get
();
3877 my $authuser = $rpcenv->get_user();
3879 my $vmid = extract_param
($param, 'vmid');
3881 my $snapname = extract_param
($param, 'snapname');
3883 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3885 my $snap = $conf->{snapshots
}->{$snapname};
3887 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3892 __PACKAGE__-
>register_method({
3894 path
=> '{vmid}/snapshot/{snapname}/rollback',
3898 description
=> "Rollback VM state to specified snapshot.",
3900 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3903 additionalProperties
=> 0,
3905 node
=> get_standard_option
('pve-node'),
3906 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3907 snapname
=> get_standard_option
('pve-snapshot-name'),
3912 description
=> "the task ID.",
3917 my $rpcenv = PVE
::RPCEnvironment
::get
();
3919 my $authuser = $rpcenv->get_user();
3921 my $node = extract_param
($param, 'node');
3923 my $vmid = extract_param
($param, 'vmid');
3925 my $snapname = extract_param
($param, 'snapname');
3928 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3929 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3933 # hold migration lock, this makes sure that nobody create replication snapshots
3934 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3937 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3940 __PACKAGE__-
>register_method({
3941 name
=> 'delsnapshot',
3942 path
=> '{vmid}/snapshot/{snapname}',
3946 description
=> "Delete a VM snapshot.",
3948 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3951 additionalProperties
=> 0,
3953 node
=> get_standard_option
('pve-node'),
3954 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3955 snapname
=> get_standard_option
('pve-snapshot-name'),
3959 description
=> "For removal from config file, even if removing disk snapshots fails.",
3965 description
=> "the task ID.",
3970 my $rpcenv = PVE
::RPCEnvironment
::get
();
3972 my $authuser = $rpcenv->get_user();
3974 my $node = extract_param
($param, 'node');
3976 my $vmid = extract_param
($param, 'vmid');
3978 my $snapname = extract_param
($param, 'snapname');
3981 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3982 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3985 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3988 __PACKAGE__-
>register_method({
3990 path
=> '{vmid}/template',
3994 description
=> "Create a Template.",
3996 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3997 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4000 additionalProperties
=> 0,
4002 node
=> get_standard_option
('pve-node'),
4003 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4007 description
=> "If you want to convert only 1 disk to base image.",
4008 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
4013 returns
=> { type
=> 'null'},
4017 my $rpcenv = PVE
::RPCEnvironment
::get
();
4019 my $authuser = $rpcenv->get_user();
4021 my $node = extract_param
($param, 'node');
4023 my $vmid = extract_param
($param, 'vmid');
4025 my $disk = extract_param
($param, 'disk');
4027 my $updatefn = sub {
4029 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4031 PVE
::QemuConfig-
>check_lock($conf);
4033 die "unable to create template, because VM contains snapshots\n"
4034 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4036 die "you can't convert a template to a template\n"
4037 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4039 die "you can't convert a VM to template if VM is running\n"
4040 if PVE
::QemuServer
::check_running
($vmid);
4043 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4046 $conf->{template
} = 1;
4047 PVE
::QemuConfig-
>write_config($vmid, $conf);
4049 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4052 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4056 __PACKAGE__-
>register_method({
4057 name
=> 'cloudinit_generated_config_dump',
4058 path
=> '{vmid}/cloudinit/dump',
4061 description
=> "Get automatically generated cloudinit config.",
4063 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4066 additionalProperties
=> 0,
4068 node
=> get_standard_option
('pve-node'),
4069 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4071 description
=> 'Config type.',
4073 enum
=> ['user', 'network', 'meta'],
4083 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4085 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});