1 package PVE
::API2
::Qemu
;
12 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
14 use PVE
::Tools
qw(extract_param);
15 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
17 use PVE
::JSONSchema
qw(get_standard_option);
19 use PVE
::ReplicationConfig
;
20 use PVE
::GuestHelpers
;
24 use PVE
::RPCEnvironment
;
25 use PVE
::AccessControl
;
29 use PVE
::API2
::Firewall
::VM
;
30 use PVE
::API2
::Qemu
::Agent
;
33 if (!$ENV{PVE_GENERATING_DOCS
}) {
34 require PVE
::HA
::Env
::PVE2
;
35 import PVE
::HA
::Env
::PVE2
;
36 require PVE
::HA
::Config
;
37 import PVE
::HA
::Config
;
41 use Data
::Dumper
; # fixme: remove
43 use base
qw(PVE::RESTHandler);
45 my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal.";
47 my $resolve_cdrom_alias = sub {
50 if (my $value = $param->{cdrom
}) {
51 $value .= ",media=cdrom" if $value !~ m/media=/;
52 $param->{ide2
} = $value;
53 delete $param->{cdrom
};
57 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
58 my $check_storage_access = sub {
59 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
61 PVE
::QemuServer
::foreach_drive
($settings, sub {
62 my ($ds, $drive) = @_;
64 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
66 my $volid = $drive->{file
};
68 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit')) {
70 } elsif ($volid =~ m/^(([^:\s]+):)?(cloudinit)$/) {
72 } elsif ($isCDROM && ($volid eq 'cdrom')) {
73 $rpcenv->check($authuser, "/", ['Sys.Console']);
74 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
75 my ($storeid, $size) = ($2 || $default_storage, $3);
76 die "no storage ID specified (and no default storage)\n" if !$storeid;
77 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
78 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
79 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
80 if !$scfg->{content
}->{images
};
82 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
86 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
87 if defined($settings->{vmstatestorage
});
90 my $check_storage_access_clone = sub {
91 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
95 PVE
::QemuServer
::foreach_drive
($conf, sub {
96 my ($ds, $drive) = @_;
98 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
100 my $volid = $drive->{file
};
102 return if !$volid || $volid eq 'none';
105 if ($volid eq 'cdrom') {
106 $rpcenv->check($authuser, "/", ['Sys.Console']);
108 # we simply allow access
109 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
110 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
111 $sharedvm = 0 if !$scfg->{shared
};
115 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
116 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
117 $sharedvm = 0 if !$scfg->{shared
};
119 $sid = $storage if $storage;
120 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
124 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
125 if defined($conf->{vmstatestorage
});
130 # Note: $pool is only needed when creating a VM, because pool permissions
131 # are automatically inherited if VM already exists inside a pool.
132 my $create_disks = sub {
133 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
140 my ($ds, $disk) = @_;
142 my $volid = $disk->{file
};
144 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
145 delete $disk->{size
};
146 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
147 } elsif ($volid =~ m!^(?:([^/:\s]+):)?cloudinit$!) {
148 my $storeid = $1 || $default_storage;
149 die "no storage ID specified (and no default storage)\n" if !$storeid;
150 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
151 my $name = "vm-$vmid-cloudinit";
155 $fmt = $disk->{format
} // "qcow2";
158 $fmt = $disk->{format
} // "raw";
161 # Initial disk created with 4 MB and aligned to 4MB on regeneration
162 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
163 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
164 $disk->{file
} = $volid;
165 $disk->{media
} = 'cdrom';
166 push @$vollist, $volid;
167 delete $disk->{format
}; # no longer needed
168 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
169 } elsif ($volid =~ $NEW_DISK_RE) {
170 my ($storeid, $size) = ($2 || $default_storage, $3);
171 die "no storage ID specified (and no default storage)\n" if !$storeid;
172 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
173 my $fmt = $disk->{format
} || $defformat;
175 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
178 if ($ds eq 'efidisk0') {
179 ($volid, $size) = PVE
::QemuServer
::create_efidisk
($storecfg, $storeid, $vmid, $fmt, $arch);
181 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
183 push @$vollist, $volid;
184 $disk->{file
} = $volid;
185 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
186 delete $disk->{format
}; # no longer needed
187 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
190 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
192 my $volid_is_new = 1;
195 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
196 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
201 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
203 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
205 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
207 die "volume $volid does not exists\n" if !$size;
209 $disk->{size
} = $size;
212 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
216 eval { PVE
::QemuServer
::foreach_drive
($settings, $code); };
218 # free allocated images on error
220 syslog
('err', "VM $vmid creating disks failed");
221 foreach my $volid (@$vollist) {
222 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
228 # modify vm config if everything went well
229 foreach my $ds (keys %$res) {
230 $conf->{$ds} = $res->{$ds};
247 my $memoryoptions = {
253 my $hwtypeoptions = {
266 my $generaloptions = {
273 'migrate_downtime' => 1,
274 'migrate_speed' => 1,
286 my $vmpoweroptions = {
293 'vmstatestorage' => 1,
296 my $cloudinitoptions = {
306 my $check_vm_modify_config_perm = sub {
307 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
309 return 1 if $authuser eq 'root@pam';
311 foreach my $opt (@$key_list) {
312 # some checks (e.g., disk, serial port, usb) need to be done somewhere
313 # else, as there the permission can be value dependend
314 next if PVE
::QemuServer
::is_valid_drivename
($opt);
315 next if $opt eq 'cdrom';
316 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
319 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
320 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
321 } elsif ($memoryoptions->{$opt}) {
322 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
323 } elsif ($hwtypeoptions->{$opt}) {
324 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
325 } elsif ($generaloptions->{$opt}) {
326 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
327 # special case for startup since it changes host behaviour
328 if ($opt eq 'startup') {
329 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
331 } elsif ($vmpoweroptions->{$opt}) {
332 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
333 } elsif ($diskoptions->{$opt}) {
334 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
335 } elsif ($cloudinitoptions->{$opt} || ($opt =~ m/^(?:net|ipconfig)\d+$/)) {
336 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
338 # catches hostpci\d+, args, lock, etc.
339 # new options will be checked here
340 die "only root can set '$opt' config\n";
347 __PACKAGE__-
>register_method({
351 description
=> "Virtual machine index (per node).",
353 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
357 protected
=> 1, # qemu pid files are only readable by root
359 additionalProperties
=> 0,
361 node
=> get_standard_option
('pve-node'),
365 description
=> "Determine the full status of active VMs.",
373 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
375 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
380 my $rpcenv = PVE
::RPCEnvironment
::get
();
381 my $authuser = $rpcenv->get_user();
383 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
386 foreach my $vmid (keys %$vmstatus) {
387 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
389 my $data = $vmstatus->{$vmid};
398 __PACKAGE__-
>register_method({
402 description
=> "Create or restore a virtual machine.",
404 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
405 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
406 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
407 user
=> 'all', # check inside
412 additionalProperties
=> 0,
413 properties
=> PVE
::QemuServer
::json_config_properties
(
415 node
=> get_standard_option
('pve-node'),
416 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
418 description
=> "The backup file.",
422 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
424 storage
=> get_standard_option
('pve-storage-id', {
425 description
=> "Default storage.",
427 completion
=> \
&PVE
::QemuServer
::complete_storage
,
432 description
=> "Allow to overwrite existing VM.",
433 requires
=> 'archive',
438 description
=> "Assign a unique random ethernet address.",
439 requires
=> 'archive',
443 type
=> 'string', format
=> 'pve-poolid',
444 description
=> "Add the VM to the specified pool.",
447 description
=> "Override I/O bandwidth limit (in KiB/s).",
451 default => 'restore limit from datacenter or storage config',
457 description
=> "Start VM after it was created successfully.",
467 my $rpcenv = PVE
::RPCEnvironment
::get
();
469 my $authuser = $rpcenv->get_user();
471 my $node = extract_param
($param, 'node');
473 my $vmid = extract_param
($param, 'vmid');
475 my $archive = extract_param
($param, 'archive');
476 my $is_restore = !!$archive;
478 my $storage = extract_param
($param, 'storage');
480 my $force = extract_param
($param, 'force');
482 my $unique = extract_param
($param, 'unique');
484 my $pool = extract_param
($param, 'pool');
486 my $bwlimit = extract_param
($param, 'bwlimit');
488 my $start_after_create = extract_param
($param, 'start');
490 my $filename = PVE
::QemuConfig-
>config_file($vmid);
492 my $storecfg = PVE
::Storage
::config
();
494 if (defined(my $ssh_keys = $param->{sshkeys
})) {
495 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
496 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
499 PVE
::Cluster
::check_cfs_quorum
();
501 if (defined($pool)) {
502 $rpcenv->check_pool_exist($pool);
505 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
506 if defined($storage);
508 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
510 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
512 } elsif ($archive && $force && (-f
$filename) &&
513 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
514 # OK: user has VM.Backup permissions, and want to restore an existing VM
520 &$resolve_cdrom_alias($param);
522 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
524 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
526 foreach my $opt (keys %$param) {
527 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
528 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
529 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
531 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
532 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
536 PVE
::QemuServer
::add_random_macs
($param);
538 my $keystr = join(' ', keys %$param);
539 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
541 if ($archive eq '-') {
542 die "pipe requires cli environment\n"
543 if $rpcenv->{type
} ne 'cli';
545 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
546 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
550 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
552 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
553 die "$emsg $@" if $@;
555 my $restorefn = sub {
556 my $conf = PVE
::QemuConfig-
>load_config($vmid);
558 PVE
::QemuConfig-
>check_protection($conf, $emsg);
560 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
563 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
569 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
570 # Convert restored VM to template if backup was VM template
571 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
572 warn "Convert to template.\n";
573 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
577 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
579 if ($start_after_create) {
580 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
585 # ensure no old replication state are exists
586 PVE
::ReplicationState
::delete_guest_states
($vmid);
588 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
592 # ensure no old replication state are exists
593 PVE
::ReplicationState
::delete_guest_states
($vmid);
601 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
605 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
607 if (!$conf->{bootdisk
}) {
608 my $firstdisk = PVE
::QemuServer
::resolve_first_disk
($conf);
609 $conf->{bootdisk
} = $firstdisk if $firstdisk;
612 # auto generate uuid if user did not specify smbios1 option
613 if (!$conf->{smbios1
}) {
614 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
617 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
618 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
621 PVE
::QemuConfig-
>write_config($vmid, $conf);
627 foreach my $volid (@$vollist) {
628 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
634 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
637 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
639 if ($start_after_create) {
640 print "Execute autostart\n";
641 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
646 my ($code, $worker_name);
648 $worker_name = 'qmrestore';
650 eval { $restorefn->() };
652 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
658 $worker_name = 'qmcreate';
660 eval { $createfn->() };
663 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
664 unlink($conffile) or die "failed to remove config file: $!\n";
672 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
675 __PACKAGE__-
>register_method({
680 description
=> "Directory index",
685 additionalProperties
=> 0,
687 node
=> get_standard_option
('pve-node'),
688 vmid
=> get_standard_option
('pve-vmid'),
696 subdir
=> { type
=> 'string' },
699 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
705 { subdir
=> 'config' },
706 { subdir
=> 'pending' },
707 { subdir
=> 'status' },
708 { subdir
=> 'unlink' },
709 { subdir
=> 'vncproxy' },
710 { subdir
=> 'termproxy' },
711 { subdir
=> 'migrate' },
712 { subdir
=> 'resize' },
713 { subdir
=> 'move' },
715 { subdir
=> 'rrddata' },
716 { subdir
=> 'monitor' },
717 { subdir
=> 'agent' },
718 { subdir
=> 'snapshot' },
719 { subdir
=> 'spiceproxy' },
720 { subdir
=> 'sendkey' },
721 { subdir
=> 'firewall' },
727 __PACKAGE__-
>register_method ({
728 subclass
=> "PVE::API2::Firewall::VM",
729 path
=> '{vmid}/firewall',
732 __PACKAGE__-
>register_method ({
733 subclass
=> "PVE::API2::Qemu::Agent",
734 path
=> '{vmid}/agent',
737 __PACKAGE__-
>register_method({
739 path
=> '{vmid}/rrd',
741 protected
=> 1, # fixme: can we avoid that?
743 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
745 description
=> "Read VM RRD statistics (returns PNG)",
747 additionalProperties
=> 0,
749 node
=> get_standard_option
('pve-node'),
750 vmid
=> get_standard_option
('pve-vmid'),
752 description
=> "Specify the time frame you are interested in.",
754 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
757 description
=> "The list of datasources you want to display.",
758 type
=> 'string', format
=> 'pve-configid-list',
761 description
=> "The RRD consolidation function",
763 enum
=> [ 'AVERAGE', 'MAX' ],
771 filename
=> { type
=> 'string' },
777 return PVE
::Cluster
::create_rrd_graph
(
778 "pve2-vm/$param->{vmid}", $param->{timeframe
},
779 $param->{ds
}, $param->{cf
});
783 __PACKAGE__-
>register_method({
785 path
=> '{vmid}/rrddata',
787 protected
=> 1, # fixme: can we avoid that?
789 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
791 description
=> "Read VM RRD statistics",
793 additionalProperties
=> 0,
795 node
=> get_standard_option
('pve-node'),
796 vmid
=> get_standard_option
('pve-vmid'),
798 description
=> "Specify the time frame you are interested in.",
800 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
803 description
=> "The RRD consolidation function",
805 enum
=> [ 'AVERAGE', 'MAX' ],
820 return PVE
::Cluster
::create_rrd_data
(
821 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
825 __PACKAGE__-
>register_method({
827 path
=> '{vmid}/config',
830 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
832 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
835 additionalProperties
=> 0,
837 node
=> get_standard_option
('pve-node'),
838 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
840 description
=> "Get current values (instead of pending values).",
845 snapshot
=> get_standard_option
('pve-snapshot-name', {
846 description
=> "Fetch config values from given snapshot.",
849 my ($cmd, $pname, $cur, $args) = @_;
850 PVE
::QemuConfig-
>snapshot_list($args->[0]);
856 description
=> "The current VM configuration.",
858 properties
=> PVE
::QemuServer
::json_config_properties
({
861 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
868 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
870 if (my $snapname = $param->{snapshot
}) {
871 my $snapshot = $conf->{snapshots
}->{$snapname};
872 die "snapshot '$snapname' does not exist\n" if !defined($snapshot);
874 $snapshot->{digest
} = $conf->{digest
}; # keep file digest for API
879 delete $conf->{snapshots
};
881 if (!$param->{current
}) {
882 foreach my $opt (keys %{$conf->{pending
}}) {
883 next if $opt eq 'delete';
884 my $value = $conf->{pending
}->{$opt};
885 next if ref($value); # just to be sure
886 $conf->{$opt} = $value;
888 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
889 foreach my $opt (keys %$pending_delete_hash) {
890 delete $conf->{$opt} if $conf->{$opt};
894 delete $conf->{pending
};
896 # hide cloudinit password
897 if ($conf->{cipassword
}) {
898 $conf->{cipassword
} = '**********';
904 __PACKAGE__-
>register_method({
905 name
=> 'vm_pending',
906 path
=> '{vmid}/pending',
909 description
=> "Get virtual machine configuration, including pending changes.",
911 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
914 additionalProperties
=> 0,
916 node
=> get_standard_option
('pve-node'),
917 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
926 description
=> "Configuration option name.",
930 description
=> "Current value.",
935 description
=> "Pending value.",
940 description
=> "Indicates a pending delete request if present and not 0. " .
941 "The value 2 indicates a force-delete request.",
953 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
955 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
959 foreach my $opt (keys %$conf) {
960 next if ref($conf->{$opt});
961 my $item = { key
=> $opt };
962 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
963 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
964 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
966 # hide cloudinit password
967 if ($opt eq 'cipassword') {
968 $item->{value
} = '**********' if defined($item->{value
});
969 # the trailing space so that the pending string is different
970 $item->{pending
} = '********** ' if defined($item->{pending
});
975 foreach my $opt (keys %{$conf->{pending
}}) {
976 next if $opt eq 'delete';
977 next if ref($conf->{pending
}->{$opt}); # just to be sure
978 next if defined($conf->{$opt});
979 my $item = { key
=> $opt };
980 $item->{pending
} = $conf->{pending
}->{$opt};
982 # hide cloudinit password
983 if ($opt eq 'cipassword') {
984 $item->{pending
} = '**********' if defined($item->{pending
});
989 while (my ($opt, $force) = each %$pending_delete_hash) {
990 next if $conf->{pending
}->{$opt}; # just to be sure
991 next if $conf->{$opt};
992 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
999 # POST/PUT {vmid}/config implementation
1001 # The original API used PUT (idempotent) an we assumed that all operations
1002 # are fast. But it turned out that almost any configuration change can
1003 # involve hot-plug actions, or disk alloc/free. Such actions can take long
1004 # time to complete and have side effects (not idempotent).
1006 # The new implementation uses POST and forks a worker process. We added
1007 # a new option 'background_delay'. If specified we wait up to
1008 # 'background_delay' second for the worker task to complete. It returns null
1009 # if the task is finished within that time, else we return the UPID.
1011 my $update_vm_api = sub {
1012 my ($param, $sync) = @_;
1014 my $rpcenv = PVE
::RPCEnvironment
::get
();
1016 my $authuser = $rpcenv->get_user();
1018 my $node = extract_param
($param, 'node');
1020 my $vmid = extract_param
($param, 'vmid');
1022 my $digest = extract_param
($param, 'digest');
1024 my $background_delay = extract_param
($param, 'background_delay');
1026 if (defined(my $cipassword = $param->{cipassword
})) {
1027 # Same logic as in cloud-init (but with the regex fixed...)
1028 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
1029 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1032 my @paramarr = (); # used for log message
1033 foreach my $key (sort keys %$param) {
1034 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1035 push @paramarr, "-$key", $value;
1038 my $skiplock = extract_param
($param, 'skiplock');
1039 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1040 if $skiplock && $authuser ne 'root@pam';
1042 my $delete_str = extract_param
($param, 'delete');
1044 my $revert_str = extract_param
($param, 'revert');
1046 my $force = extract_param
($param, 'force');
1048 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1049 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1050 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1053 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1055 my $storecfg = PVE
::Storage
::config
();
1057 my $defaults = PVE
::QemuServer
::load_defaults
();
1059 &$resolve_cdrom_alias($param);
1061 # now try to verify all parameters
1064 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1065 if (!PVE
::QemuServer
::option_exists
($opt)) {
1066 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1069 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1070 "-revert $opt' at the same time" })
1071 if defined($param->{$opt});
1073 $revert->{$opt} = 1;
1077 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1078 $opt = 'ide2' if $opt eq 'cdrom';
1080 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1081 "-delete $opt' at the same time" })
1082 if defined($param->{$opt});
1084 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1085 "-revert $opt' at the same time" })
1088 if (!PVE
::QemuServer
::option_exists
($opt)) {
1089 raise_param_exc
({ delete => "unknown option '$opt'" });
1095 my $repl_conf = PVE
::ReplicationConfig-
>new();
1096 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1097 my $check_replication = sub {
1099 return if !$is_replicated;
1100 my $volid = $drive->{file
};
1101 return if !$volid || !($drive->{replicate
}//1);
1102 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1103 my ($storeid, $format);
1104 if ($volid =~ $NEW_DISK_RE) {
1106 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1108 ($storeid, undef) = PVE
::Storage
::parse_volume_id
($volid, 1);
1109 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1111 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1112 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1113 return if $scfg->{shared
};
1114 die "cannot add non-replicatable volume to a replicated VM\n";
1117 foreach my $opt (keys %$param) {
1118 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1119 # cleanup drive path
1120 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1121 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1122 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1123 $check_replication->($drive);
1124 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1125 } elsif ($opt =~ m/^net(\d+)$/) {
1127 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1128 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1129 } elsif ($opt eq 'vmgenid') {
1130 if ($param->{$opt} eq '1') {
1131 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1133 } elsif ($opt eq 'hookscript') {
1134 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1135 raise_param_exc
({ $opt => $@ }) if $@;
1139 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1141 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1143 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1145 my $updatefn = sub {
1147 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1149 die "checksum missmatch (file change by other user?)\n"
1150 if $digest && $digest ne $conf->{digest
};
1152 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1154 foreach my $opt (keys %$revert) {
1155 if (defined($conf->{$opt})) {
1156 $param->{$opt} = $conf->{$opt};
1157 } elsif (defined($conf->{pending
}->{$opt})) {
1162 if ($param->{memory
} || defined($param->{balloon
})) {
1163 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1164 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1166 die "balloon value too large (must be smaller than assigned memory)\n"
1167 if $balloon && $balloon > $maxmem;
1170 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1174 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1176 # write updates to pending section
1178 my $modified = {}; # record what $option we modify
1180 foreach my $opt (@delete) {
1181 $modified->{$opt} = 1;
1182 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1183 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1184 warn "cannot delete '$opt' - not set in current configuration!\n";
1185 $modified->{$opt} = 0;
1189 if ($opt =~ m/^unused/) {
1190 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1191 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1192 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1193 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1194 delete $conf->{$opt};
1195 PVE
::QemuConfig-
>write_config($vmid, $conf);
1197 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1198 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1199 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1200 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1201 if defined($conf->{pending
}->{$opt});
1202 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1203 PVE
::QemuConfig-
>write_config($vmid, $conf);
1204 } elsif ($opt =~ m/^serial\d+$/) {
1205 if ($conf->{$opt} eq 'socket') {
1206 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1207 } elsif ($authuser ne 'root@pam') {
1208 die "only root can delete '$opt' config for real devices\n";
1210 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1211 PVE
::QemuConfig-
>write_config($vmid, $conf);
1212 } elsif ($opt =~ m/^usb\d+$/) {
1213 if ($conf->{$opt} =~ m/spice/) {
1214 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1215 } elsif ($authuser ne 'root@pam') {
1216 die "only root can delete '$opt' config for real devices\n";
1218 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1219 PVE
::QemuConfig-
>write_config($vmid, $conf);
1221 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1222 PVE
::QemuConfig-
>write_config($vmid, $conf);
1226 foreach my $opt (keys %$param) { # add/change
1227 $modified->{$opt} = 1;
1228 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1229 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1231 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
1233 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1234 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1235 # FIXME: cloudinit: CDROM or Disk?
1236 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1237 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1239 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1241 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1242 if defined($conf->{pending
}->{$opt});
1244 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1245 } elsif ($opt =~ m/^serial\d+/) {
1246 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1247 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1248 } elsif ($authuser ne 'root@pam') {
1249 die "only root can modify '$opt' config for real devices\n";
1251 $conf->{pending
}->{$opt} = $param->{$opt};
1252 } elsif ($opt =~ m/^usb\d+/) {
1253 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1254 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1255 } elsif ($authuser ne 'root@pam') {
1256 die "only root can modify '$opt' config for real devices\n";
1258 $conf->{pending
}->{$opt} = $param->{$opt};
1260 $conf->{pending
}->{$opt} = $param->{$opt};
1262 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1263 PVE
::QemuConfig-
>write_config($vmid, $conf);
1266 # remove pending changes when nothing changed
1267 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1268 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1269 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1271 return if !scalar(keys %{$conf->{pending
}});
1273 my $running = PVE
::QemuServer
::check_running
($vmid);
1275 # apply pending changes
1277 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1281 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1282 raise_param_exc
($errors) if scalar(keys %$errors);
1284 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1294 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1296 if ($background_delay) {
1298 # Note: It would be better to do that in the Event based HTTPServer
1299 # to avoid blocking call to sleep.
1301 my $end_time = time() + $background_delay;
1303 my $task = PVE
::Tools
::upid_decode
($upid);
1306 while (time() < $end_time) {
1307 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1309 sleep(1); # this gets interrupted when child process ends
1313 my $status = PVE
::Tools
::upid_read_status
($upid);
1314 return undef if $status eq 'OK';
1323 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1326 my $vm_config_perm_list = [
1331 'VM.Config.Network',
1333 'VM.Config.Options',
1336 __PACKAGE__-
>register_method({
1337 name
=> 'update_vm_async',
1338 path
=> '{vmid}/config',
1342 description
=> "Set virtual machine options (asynchrounous API).",
1344 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1347 additionalProperties
=> 0,
1348 properties
=> PVE
::QemuServer
::json_config_properties
(
1350 node
=> get_standard_option
('pve-node'),
1351 vmid
=> get_standard_option
('pve-vmid'),
1352 skiplock
=> get_standard_option
('skiplock'),
1354 type
=> 'string', format
=> 'pve-configid-list',
1355 description
=> "A list of settings you want to delete.",
1359 type
=> 'string', format
=> 'pve-configid-list',
1360 description
=> "Revert a pending change.",
1365 description
=> $opt_force_description,
1367 requires
=> 'delete',
1371 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1375 background_delay
=> {
1377 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1388 code
=> $update_vm_api,
1391 __PACKAGE__-
>register_method({
1392 name
=> 'update_vm',
1393 path
=> '{vmid}/config',
1397 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1399 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1402 additionalProperties
=> 0,
1403 properties
=> PVE
::QemuServer
::json_config_properties
(
1405 node
=> get_standard_option
('pve-node'),
1406 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1407 skiplock
=> get_standard_option
('skiplock'),
1409 type
=> 'string', format
=> 'pve-configid-list',
1410 description
=> "A list of settings you want to delete.",
1414 type
=> 'string', format
=> 'pve-configid-list',
1415 description
=> "Revert a pending change.",
1420 description
=> $opt_force_description,
1422 requires
=> 'delete',
1426 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1432 returns
=> { type
=> 'null' },
1435 &$update_vm_api($param, 1);
1440 __PACKAGE__-
>register_method({
1441 name
=> 'destroy_vm',
1446 description
=> "Destroy the vm (also delete all used/owned volumes).",
1448 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1451 additionalProperties
=> 0,
1453 node
=> get_standard_option
('pve-node'),
1454 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1455 skiplock
=> get_standard_option
('skiplock'),
1464 my $rpcenv = PVE
::RPCEnvironment
::get
();
1465 my $authuser = $rpcenv->get_user();
1466 my $vmid = $param->{vmid
};
1468 my $skiplock = $param->{skiplock
};
1469 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1470 if $skiplock && $authuser ne 'root@pam';
1473 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1474 my $storecfg = PVE
::Storage
::config
();
1475 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1476 die "unable to remove VM $vmid - used in HA resources\n"
1477 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1479 # do not allow destroy if there are replication jobs
1480 my $repl_conf = PVE
::ReplicationConfig-
>new();
1481 $repl_conf->check_for_existing_jobs($vmid);
1483 # early tests (repeat after locking)
1484 die "VM $vmid is running - destroy failed\n"
1485 if PVE
::QemuServer
::check_running
($vmid);
1490 syslog
('info', "destroy VM $vmid: $upid\n");
1491 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1492 PVE
::AccessControl
::remove_vm_access
($vmid);
1493 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1496 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1499 __PACKAGE__-
>register_method({
1501 path
=> '{vmid}/unlink',
1505 description
=> "Unlink/delete disk images.",
1507 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1510 additionalProperties
=> 0,
1512 node
=> get_standard_option
('pve-node'),
1513 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1515 type
=> 'string', format
=> 'pve-configid-list',
1516 description
=> "A list of disk IDs you want to delete.",
1520 description
=> $opt_force_description,
1525 returns
=> { type
=> 'null'},
1529 $param->{delete} = extract_param
($param, 'idlist');
1531 __PACKAGE__-
>update_vm($param);
1538 __PACKAGE__-
>register_method({
1540 path
=> '{vmid}/vncproxy',
1544 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1546 description
=> "Creates a TCP VNC proxy connections.",
1548 additionalProperties
=> 0,
1550 node
=> get_standard_option
('pve-node'),
1551 vmid
=> get_standard_option
('pve-vmid'),
1555 description
=> "starts websockify instead of vncproxy",
1560 additionalProperties
=> 0,
1562 user
=> { type
=> 'string' },
1563 ticket
=> { type
=> 'string' },
1564 cert
=> { type
=> 'string' },
1565 port
=> { type
=> 'integer' },
1566 upid
=> { type
=> 'string' },
1572 my $rpcenv = PVE
::RPCEnvironment
::get
();
1574 my $authuser = $rpcenv->get_user();
1576 my $vmid = $param->{vmid
};
1577 my $node = $param->{node
};
1578 my $websocket = $param->{websocket
};
1580 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1581 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1583 my $authpath = "/vms/$vmid";
1585 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1587 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1593 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1594 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1595 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1596 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1597 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1599 $family = PVE
::Tools
::get_host_address_family
($node);
1602 my $port = PVE
::Tools
::next_vnc_port
($family);
1609 syslog
('info', "starting vnc proxy $upid\n");
1615 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1617 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1618 '-timeout', $timeout, '-authpath', $authpath,
1619 '-perm', 'Sys.Console'];
1621 if ($param->{websocket
}) {
1622 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1623 push @$cmd, '-notls', '-listen', 'localhost';
1626 push @$cmd, '-c', @$remcmd, @$termcmd;
1628 PVE
::Tools
::run_command
($cmd);
1632 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1634 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1636 my $sock = IO
::Socket
::IP-
>new(
1641 GetAddrInfoFlags
=> 0,
1642 ) or die "failed to create socket: $!\n";
1643 # Inside the worker we shouldn't have any previous alarms
1644 # running anyway...:
1646 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1648 accept(my $cli, $sock) or die "connection failed: $!\n";
1651 if (PVE
::Tools
::run_command
($cmd,
1652 output
=> '>&'.fileno($cli),
1653 input
=> '<&'.fileno($cli),
1656 die "Failed to run vncproxy.\n";
1663 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1665 PVE
::Tools
::wait_for_vnc_port
($port);
1676 __PACKAGE__-
>register_method({
1677 name
=> 'termproxy',
1678 path
=> '{vmid}/termproxy',
1682 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1684 description
=> "Creates a TCP proxy connections.",
1686 additionalProperties
=> 0,
1688 node
=> get_standard_option
('pve-node'),
1689 vmid
=> get_standard_option
('pve-vmid'),
1693 enum
=> [qw(serial0 serial1 serial2 serial3)],
1694 description
=> "opens a serial terminal (defaults to display)",
1699 additionalProperties
=> 0,
1701 user
=> { type
=> 'string' },
1702 ticket
=> { type
=> 'string' },
1703 port
=> { type
=> 'integer' },
1704 upid
=> { type
=> 'string' },
1710 my $rpcenv = PVE
::RPCEnvironment
::get
();
1712 my $authuser = $rpcenv->get_user();
1714 my $vmid = $param->{vmid
};
1715 my $node = $param->{node
};
1716 my $serial = $param->{serial
};
1718 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1720 if (!defined($serial)) {
1721 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1722 $serial = $conf->{vga
};
1726 my $authpath = "/vms/$vmid";
1728 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1733 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1734 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1735 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1736 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, '-t');
1737 push @$remcmd, '--';
1739 $family = PVE
::Tools
::get_host_address_family
($node);
1742 my $port = PVE
::Tools
::next_vnc_port
($family);
1744 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1745 push @$termcmd, '-iface', $serial if $serial;
1750 syslog
('info', "starting qemu termproxy $upid\n");
1752 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1753 '--perm', 'VM.Console', '--'];
1754 push @$cmd, @$remcmd, @$termcmd;
1756 PVE
::Tools
::run_command
($cmd);
1759 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1761 PVE
::Tools
::wait_for_vnc_port
($port);
1771 __PACKAGE__-
>register_method({
1772 name
=> 'vncwebsocket',
1773 path
=> '{vmid}/vncwebsocket',
1776 description
=> "You also need to pass a valid ticket (vncticket).",
1777 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1779 description
=> "Opens a weksocket for VNC traffic.",
1781 additionalProperties
=> 0,
1783 node
=> get_standard_option
('pve-node'),
1784 vmid
=> get_standard_option
('pve-vmid'),
1786 description
=> "Ticket from previous call to vncproxy.",
1791 description
=> "Port number returned by previous vncproxy call.",
1801 port
=> { type
=> 'string' },
1807 my $rpcenv = PVE
::RPCEnvironment
::get
();
1809 my $authuser = $rpcenv->get_user();
1811 my $vmid = $param->{vmid
};
1812 my $node = $param->{node
};
1814 my $authpath = "/vms/$vmid";
1816 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1818 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1820 # Note: VNC ports are acessible from outside, so we do not gain any
1821 # security if we verify that $param->{port} belongs to VM $vmid. This
1822 # check is done by verifying the VNC ticket (inside VNC protocol).
1824 my $port = $param->{port
};
1826 return { port
=> $port };
1829 __PACKAGE__-
>register_method({
1830 name
=> 'spiceproxy',
1831 path
=> '{vmid}/spiceproxy',
1836 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1838 description
=> "Returns a SPICE configuration to connect to the VM.",
1840 additionalProperties
=> 0,
1842 node
=> get_standard_option
('pve-node'),
1843 vmid
=> get_standard_option
('pve-vmid'),
1844 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1847 returns
=> get_standard_option
('remote-viewer-config'),
1851 my $rpcenv = PVE
::RPCEnvironment
::get
();
1853 my $authuser = $rpcenv->get_user();
1855 my $vmid = $param->{vmid
};
1856 my $node = $param->{node
};
1857 my $proxy = $param->{proxy
};
1859 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1860 my $title = "VM $vmid";
1861 $title .= " - ". $conf->{name
} if $conf->{name
};
1863 my $port = PVE
::QemuServer
::spice_port
($vmid);
1865 my ($ticket, undef, $remote_viewer_config) =
1866 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1868 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1869 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1871 return $remote_viewer_config;
1874 __PACKAGE__-
>register_method({
1876 path
=> '{vmid}/status',
1879 description
=> "Directory index",
1884 additionalProperties
=> 0,
1886 node
=> get_standard_option
('pve-node'),
1887 vmid
=> get_standard_option
('pve-vmid'),
1895 subdir
=> { type
=> 'string' },
1898 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1904 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1907 { subdir
=> 'current' },
1908 { subdir
=> 'start' },
1909 { subdir
=> 'stop' },
1910 { subdir
=> 'reset' },
1911 { subdir
=> 'shutdown' },
1912 { subdir
=> 'suspend' },
1913 { subdir
=> 'reboot' },
1919 __PACKAGE__-
>register_method({
1920 name
=> 'vm_status',
1921 path
=> '{vmid}/status/current',
1924 protected
=> 1, # qemu pid files are only readable by root
1925 description
=> "Get virtual machine status.",
1927 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1930 additionalProperties
=> 0,
1932 node
=> get_standard_option
('pve-node'),
1933 vmid
=> get_standard_option
('pve-vmid'),
1939 %$PVE::QemuServer
::vmstatus_return_properties
,
1941 description
=> "HA manager service status.",
1945 description
=> "Qemu VGA configuration supports spice.",
1950 description
=> "Qemu GuestAgent enabled in config.",
1960 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1962 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1963 my $status = $vmstatus->{$param->{vmid
}};
1965 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1967 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1968 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1973 __PACKAGE__-
>register_method({
1975 path
=> '{vmid}/status/start',
1979 description
=> "Start virtual machine.",
1981 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1984 additionalProperties
=> 0,
1986 node
=> get_standard_option
('pve-node'),
1987 vmid
=> get_standard_option
('pve-vmid',
1988 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1989 skiplock
=> get_standard_option
('skiplock'),
1990 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1991 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1994 enum
=> ['secure', 'insecure'],
1995 description
=> "Migration traffic is encrypted using an SSH " .
1996 "tunnel by default. On secure, completely private networks " .
1997 "this can be disabled to increase performance.",
2000 migration_network
=> {
2001 type
=> 'string', format
=> 'CIDR',
2002 description
=> "CIDR of the (sub) network that is used for migration.",
2005 machine
=> get_standard_option
('pve-qm-machine'),
2007 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
2019 my $rpcenv = PVE
::RPCEnvironment
::get
();
2020 my $authuser = $rpcenv->get_user();
2022 my $node = extract_param
($param, 'node');
2023 my $vmid = extract_param
($param, 'vmid');
2025 my $machine = extract_param
($param, 'machine');
2027 my $get_root_param = sub {
2028 my $value = extract_param
($param, $_[0]);
2029 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2030 if $value && $authuser ne 'root@pam';
2034 my $stateuri = $get_root_param->('stateuri');
2035 my $skiplock = $get_root_param->('skiplock');
2036 my $migratedfrom = $get_root_param->('migratedfrom');
2037 my $migration_type = $get_root_param->('migration_type');
2038 my $migration_network = $get_root_param->('migration_network');
2039 my $targetstorage = $get_root_param->('targetstorage');
2041 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2042 if $targetstorage && !$migratedfrom;
2044 # read spice ticket from STDIN
2046 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2047 if (defined(my $line = <STDIN
>)) {
2049 $spice_ticket = $line;
2053 PVE
::Cluster
::check_cfs_quorum
();
2055 my $storecfg = PVE
::Storage
::config
();
2057 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2061 print "Requesting HA start for VM $vmid\n";
2063 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2064 PVE
::Tools
::run_command
($cmd);
2068 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2075 syslog
('info', "start VM $vmid: $upid\n");
2077 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2078 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
2082 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2086 __PACKAGE__-
>register_method({
2088 path
=> '{vmid}/status/stop',
2092 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2093 "is akin to pulling the power plug of a running computer and may damage the VM data",
2095 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2098 additionalProperties
=> 0,
2100 node
=> get_standard_option
('pve-node'),
2101 vmid
=> get_standard_option
('pve-vmid',
2102 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2103 skiplock
=> get_standard_option
('skiplock'),
2104 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2106 description
=> "Wait maximal timeout seconds.",
2112 description
=> "Do not deactivate storage volumes.",
2125 my $rpcenv = PVE
::RPCEnvironment
::get
();
2126 my $authuser = $rpcenv->get_user();
2128 my $node = extract_param
($param, 'node');
2129 my $vmid = extract_param
($param, 'vmid');
2131 my $skiplock = extract_param
($param, 'skiplock');
2132 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2133 if $skiplock && $authuser ne 'root@pam';
2135 my $keepActive = extract_param
($param, 'keepActive');
2136 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2137 if $keepActive && $authuser ne 'root@pam';
2139 my $migratedfrom = extract_param
($param, 'migratedfrom');
2140 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2141 if $migratedfrom && $authuser ne 'root@pam';
2144 my $storecfg = PVE
::Storage
::config
();
2146 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2151 print "Requesting HA stop for VM $vmid\n";
2153 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'stopped'];
2154 PVE
::Tools
::run_command
($cmd);
2158 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2164 syslog
('info', "stop VM $vmid: $upid\n");
2166 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2167 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2171 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2175 __PACKAGE__-
>register_method({
2177 path
=> '{vmid}/status/reset',
2181 description
=> "Reset virtual machine.",
2183 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2186 additionalProperties
=> 0,
2188 node
=> get_standard_option
('pve-node'),
2189 vmid
=> get_standard_option
('pve-vmid',
2190 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2191 skiplock
=> get_standard_option
('skiplock'),
2200 my $rpcenv = PVE
::RPCEnvironment
::get
();
2202 my $authuser = $rpcenv->get_user();
2204 my $node = extract_param
($param, 'node');
2206 my $vmid = extract_param
($param, 'vmid');
2208 my $skiplock = extract_param
($param, 'skiplock');
2209 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2210 if $skiplock && $authuser ne 'root@pam';
2212 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2217 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2222 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2225 __PACKAGE__-
>register_method({
2226 name
=> 'vm_shutdown',
2227 path
=> '{vmid}/status/shutdown',
2231 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2232 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2234 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2237 additionalProperties
=> 0,
2239 node
=> get_standard_option
('pve-node'),
2240 vmid
=> get_standard_option
('pve-vmid',
2241 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2242 skiplock
=> get_standard_option
('skiplock'),
2244 description
=> "Wait maximal timeout seconds.",
2250 description
=> "Make sure the VM stops.",
2256 description
=> "Do not deactivate storage volumes.",
2269 my $rpcenv = PVE
::RPCEnvironment
::get
();
2270 my $authuser = $rpcenv->get_user();
2272 my $node = extract_param
($param, 'node');
2273 my $vmid = extract_param
($param, 'vmid');
2275 my $skiplock = extract_param
($param, 'skiplock');
2276 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2277 if $skiplock && $authuser ne 'root@pam';
2279 my $keepActive = extract_param
($param, 'keepActive');
2280 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2281 if $keepActive && $authuser ne 'root@pam';
2283 my $storecfg = PVE
::Storage
::config
();
2287 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2288 # otherwise, we will infer a shutdown command, but run into the timeout,
2289 # then when the vm is resumed, it will instantly shutdown
2291 # checking the qmp status here to get feedback to the gui/cli/api
2292 # and the status query should not take too long
2293 my $qmpstatus = eval {
2294 PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2298 if (!$err && $qmpstatus->{status
} eq "paused") {
2299 if ($param->{forceStop
}) {
2300 warn "VM is paused - stop instead of shutdown\n";
2303 die "VM is paused - cannot shutdown\n";
2307 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2312 print "Requesting HA stop for VM $vmid\n";
2314 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'stopped'];
2315 PVE
::Tools
::run_command
($cmd);
2319 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2326 syslog
('info', "shutdown VM $vmid: $upid\n");
2328 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2329 $shutdown, $param->{forceStop
}, $keepActive);
2333 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2337 __PACKAGE__-
>register_method({
2338 name
=> 'vm_reboot',
2339 path
=> '{vmid}/status/reboot',
2343 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2345 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2348 additionalProperties
=> 0,
2350 node
=> get_standard_option
('pve-node'),
2351 vmid
=> get_standard_option
('pve-vmid',
2352 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2354 description
=> "Wait maximal timeout seconds for the shutdown.",
2367 my $rpcenv = PVE
::RPCEnvironment
::get
();
2368 my $authuser = $rpcenv->get_user();
2370 my $node = extract_param
($param, 'node');
2371 my $vmid = extract_param
($param, 'vmid');
2373 my $qmpstatus = eval {
2374 PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2378 if (!$err && $qmpstatus->{status
} eq "paused") {
2379 die "VM is paused - cannot shutdown\n";
2382 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2387 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2388 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2392 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2395 __PACKAGE__-
>register_method({
2396 name
=> 'vm_suspend',
2397 path
=> '{vmid}/status/suspend',
2401 description
=> "Suspend virtual machine.",
2403 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2406 additionalProperties
=> 0,
2408 node
=> get_standard_option
('pve-node'),
2409 vmid
=> get_standard_option
('pve-vmid',
2410 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2411 skiplock
=> get_standard_option
('skiplock'),
2416 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2418 statestorage
=> get_standard_option
('pve-storage-id', {
2419 description
=> "The storage for the VM state",
2420 requires
=> 'todisk',
2422 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2432 my $rpcenv = PVE
::RPCEnvironment
::get
();
2433 my $authuser = $rpcenv->get_user();
2435 my $node = extract_param
($param, 'node');
2436 my $vmid = extract_param
($param, 'vmid');
2438 my $todisk = extract_param
($param, 'todisk') // 0;
2440 my $statestorage = extract_param
($param, 'statestorage');
2442 my $skiplock = extract_param
($param, 'skiplock');
2443 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2444 if $skiplock && $authuser ne 'root@pam';
2446 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2448 die "Cannot suspend HA managed VM to disk\n"
2449 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2454 syslog
('info', "suspend VM $vmid: $upid\n");
2456 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2461 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2462 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2465 __PACKAGE__-
>register_method({
2466 name
=> 'vm_resume',
2467 path
=> '{vmid}/status/resume',
2471 description
=> "Resume virtual machine.",
2473 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2476 additionalProperties
=> 0,
2478 node
=> get_standard_option
('pve-node'),
2479 vmid
=> get_standard_option
('pve-vmid',
2480 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2481 skiplock
=> get_standard_option
('skiplock'),
2482 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2492 my $rpcenv = PVE
::RPCEnvironment
::get
();
2494 my $authuser = $rpcenv->get_user();
2496 my $node = extract_param
($param, 'node');
2498 my $vmid = extract_param
($param, 'vmid');
2500 my $skiplock = extract_param
($param, 'skiplock');
2501 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2502 if $skiplock && $authuser ne 'root@pam';
2504 my $nocheck = extract_param
($param, 'nocheck');
2506 my $to_disk_suspended;
2508 PVE
::QemuConfig-
>lock_config($vmid, sub {
2509 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2510 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2514 die "VM $vmid not running\n"
2515 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2520 syslog
('info', "resume VM $vmid: $upid\n");
2522 if (!$to_disk_suspended) {
2523 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2525 my $storecfg = PVE
::Storage
::config
();
2526 PVE
::QemuServer
::vm_start
($storecfg, $vmid, undef, $skiplock);
2532 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2535 __PACKAGE__-
>register_method({
2536 name
=> 'vm_sendkey',
2537 path
=> '{vmid}/sendkey',
2541 description
=> "Send key event to virtual machine.",
2543 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2546 additionalProperties
=> 0,
2548 node
=> get_standard_option
('pve-node'),
2549 vmid
=> get_standard_option
('pve-vmid',
2550 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2551 skiplock
=> get_standard_option
('skiplock'),
2553 description
=> "The key (qemu monitor encoding).",
2558 returns
=> { type
=> 'null'},
2562 my $rpcenv = PVE
::RPCEnvironment
::get
();
2564 my $authuser = $rpcenv->get_user();
2566 my $node = extract_param
($param, 'node');
2568 my $vmid = extract_param
($param, 'vmid');
2570 my $skiplock = extract_param
($param, 'skiplock');
2571 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2572 if $skiplock && $authuser ne 'root@pam';
2574 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2579 __PACKAGE__-
>register_method({
2580 name
=> 'vm_feature',
2581 path
=> '{vmid}/feature',
2585 description
=> "Check if feature for virtual machine is available.",
2587 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2590 additionalProperties
=> 0,
2592 node
=> get_standard_option
('pve-node'),
2593 vmid
=> get_standard_option
('pve-vmid'),
2595 description
=> "Feature to check.",
2597 enum
=> [ 'snapshot', 'clone', 'copy' ],
2599 snapname
=> get_standard_option
('pve-snapshot-name', {
2607 hasFeature
=> { type
=> 'boolean' },
2610 items
=> { type
=> 'string' },
2617 my $node = extract_param
($param, 'node');
2619 my $vmid = extract_param
($param, 'vmid');
2621 my $snapname = extract_param
($param, 'snapname');
2623 my $feature = extract_param
($param, 'feature');
2625 my $running = PVE
::QemuServer
::check_running
($vmid);
2627 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2630 my $snap = $conf->{snapshots
}->{$snapname};
2631 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2634 my $storecfg = PVE
::Storage
::config
();
2636 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2637 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2640 hasFeature
=> $hasFeature,
2641 nodes
=> [ keys %$nodelist ],
2645 __PACKAGE__-
>register_method({
2647 path
=> '{vmid}/clone',
2651 description
=> "Create a copy of virtual machine/template.",
2653 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2654 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2655 "'Datastore.AllocateSpace' on any used storage.",
2658 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2660 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2661 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2666 additionalProperties
=> 0,
2668 node
=> get_standard_option
('pve-node'),
2669 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2670 newid
=> get_standard_option
('pve-vmid', {
2671 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2672 description
=> 'VMID for the clone.' }),
2675 type
=> 'string', format
=> 'dns-name',
2676 description
=> "Set a name for the new VM.",
2681 description
=> "Description for the new VM.",
2685 type
=> 'string', format
=> 'pve-poolid',
2686 description
=> "Add the new VM to the specified pool.",
2688 snapname
=> get_standard_option
('pve-snapshot-name', {
2691 storage
=> get_standard_option
('pve-storage-id', {
2692 description
=> "Target storage for full clone.",
2696 description
=> "Target format for file storage. Only valid for full clone.",
2699 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2704 description
=> "Create a full copy of all disks. This is always done when " .
2705 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2707 target
=> get_standard_option
('pve-node', {
2708 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2712 description
=> "Override I/O bandwidth limit (in KiB/s).",
2716 default => 'clone limit from datacenter or storage config',
2726 my $rpcenv = PVE
::RPCEnvironment
::get
();
2728 my $authuser = $rpcenv->get_user();
2730 my $node = extract_param
($param, 'node');
2732 my $vmid = extract_param
($param, 'vmid');
2734 my $newid = extract_param
($param, 'newid');
2736 my $pool = extract_param
($param, 'pool');
2738 if (defined($pool)) {
2739 $rpcenv->check_pool_exist($pool);
2742 my $snapname = extract_param
($param, 'snapname');
2744 my $storage = extract_param
($param, 'storage');
2746 my $format = extract_param
($param, 'format');
2748 my $target = extract_param
($param, 'target');
2750 my $localnode = PVE
::INotify
::nodename
();
2752 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2754 PVE
::Cluster
::check_node_exists
($target) if $target;
2756 my $storecfg = PVE
::Storage
::config
();
2759 # check if storage is enabled on local node
2760 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2762 # check if storage is available on target node
2763 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2764 # clone only works if target storage is shared
2765 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2766 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2770 PVE
::Cluster
::check_cfs_quorum
();
2772 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2774 # exclusive lock if VM is running - else shared lock is enough;
2775 my $shared_lock = $running ?
0 : 1;
2779 # do all tests after lock
2780 # we also try to do all tests before we fork the worker
2782 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2784 PVE
::QemuConfig-
>check_lock($conf);
2786 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2788 die "unexpected state change\n" if $verify_running != $running;
2790 die "snapshot '$snapname' does not exist\n"
2791 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2793 my $full = extract_param
($param, 'full');
2794 if (!defined($full)) {
2795 $full = !PVE
::QemuConfig-
>is_template($conf);
2798 die "parameter 'storage' not allowed for linked clones\n"
2799 if defined($storage) && !$full;
2801 die "parameter 'format' not allowed for linked clones\n"
2802 if defined($format) && !$full;
2804 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2806 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2808 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2810 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2812 die "unable to create VM $newid: config file already exists\n"
2815 my $newconf = { lock => 'clone' };
2820 foreach my $opt (keys %$oldconf) {
2821 my $value = $oldconf->{$opt};
2823 # do not copy snapshot related info
2824 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2825 $opt eq 'vmstate' || $opt eq 'snapstate';
2827 # no need to copy unused images, because VMID(owner) changes anyways
2828 next if $opt =~ m/^unused\d+$/;
2830 # always change MAC! address
2831 if ($opt =~ m/^net(\d+)$/) {
2832 my $net = PVE
::QemuServer
::parse_net
($value);
2833 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2834 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2835 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2836 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2837 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2838 die "unable to parse drive options for '$opt'\n" if !$drive;
2839 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2840 $newconf->{$opt} = $value; # simply copy configuration
2842 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2843 die "Full clone feature is not supported for drive '$opt'\n"
2844 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2845 $fullclone->{$opt} = 1;
2847 # not full means clone instead of copy
2848 die "Linked clone feature is not supported for drive '$opt'\n"
2849 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2851 $drives->{$opt} = $drive;
2852 push @$vollist, $drive->{file
};
2855 # copy everything else
2856 $newconf->{$opt} = $value;
2860 # auto generate a new uuid
2861 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2862 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2863 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2865 # auto generate a new vmgenid if the option was set
2866 if ($newconf->{vmgenid
}) {
2867 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2870 delete $newconf->{template
};
2872 if ($param->{name
}) {
2873 $newconf->{name
} = $param->{name
};
2875 if ($oldconf->{name
}) {
2876 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2878 $newconf->{name
} = "Copy-of-VM-$vmid";
2882 if ($param->{description
}) {
2883 $newconf->{description
} = $param->{description
};
2886 # create empty/temp config - this fails if VM already exists on other node
2887 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2892 my $newvollist = [];
2899 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2901 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2903 my $bwlimit = extract_param
($param, 'bwlimit');
2905 my $total_jobs = scalar(keys %{$drives});
2908 foreach my $opt (keys %$drives) {
2909 my $drive = $drives->{$opt};
2910 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2912 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
2913 my $storage_list = [ $src_sid ];
2914 push @$storage_list, $storage if defined($storage);
2915 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
2917 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2918 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2919 $jobs, $skipcomplete, $oldconf->{agent
}, $clonelimit);
2921 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2923 PVE
::QemuConfig-
>write_config($newid, $newconf);
2927 delete $newconf->{lock};
2929 # do not write pending changes
2930 if (my @changes = keys %{$newconf->{pending
}}) {
2931 my $pending = join(',', @changes);
2932 warn "found pending changes for '$pending', discarding for clone\n";
2933 delete $newconf->{pending
};
2936 PVE
::QemuConfig-
>write_config($newid, $newconf);
2939 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2940 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2941 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2943 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2944 die "Failed to move config to node '$target' - rename failed: $!\n"
2945 if !rename($conffile, $newconffile);
2948 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2953 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2955 sleep 1; # some storage like rbd need to wait before release volume - really?
2957 foreach my $volid (@$newvollist) {
2958 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2961 die "clone failed: $err";
2967 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2969 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2972 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2973 # Aquire exclusive lock lock for $newid
2974 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2979 __PACKAGE__-
>register_method({
2980 name
=> 'move_vm_disk',
2981 path
=> '{vmid}/move_disk',
2985 description
=> "Move volume to different storage.",
2987 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2989 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2990 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2994 additionalProperties
=> 0,
2996 node
=> get_standard_option
('pve-node'),
2997 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3000 description
=> "The disk you want to move.",
3001 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
3003 storage
=> get_standard_option
('pve-storage-id', {
3004 description
=> "Target storage.",
3005 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3009 description
=> "Target Format.",
3010 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3015 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3021 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3026 description
=> "Override I/O bandwidth limit (in KiB/s).",
3030 default => 'move limit from datacenter or storage config',
3036 description
=> "the task ID.",
3041 my $rpcenv = PVE
::RPCEnvironment
::get
();
3043 my $authuser = $rpcenv->get_user();
3045 my $node = extract_param
($param, 'node');
3047 my $vmid = extract_param
($param, 'vmid');
3049 my $digest = extract_param
($param, 'digest');
3051 my $disk = extract_param
($param, 'disk');
3053 my $storeid = extract_param
($param, 'storage');
3055 my $format = extract_param
($param, 'format');
3057 my $storecfg = PVE
::Storage
::config
();
3059 my $updatefn = sub {
3061 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3063 PVE
::QemuConfig-
>check_lock($conf);
3065 die "checksum missmatch (file change by other user?)\n"
3066 if $digest && $digest ne $conf->{digest
};
3068 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3070 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3072 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
3074 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3077 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3078 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3082 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
3083 (!$format || !$oldfmt || $oldfmt eq $format);
3085 # this only checks snapshots because $disk is passed!
3086 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3087 die "you can't move a disk with snapshots and delete the source\n"
3088 if $snapshotted && $param->{delete};
3090 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3092 my $running = PVE
::QemuServer
::check_running
($vmid);
3094 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3098 my $newvollist = [];
3104 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3106 warn "moving disk with snapshots, snapshots will not be moved!\n"
3109 my $bwlimit = extract_param
($param, 'bwlimit');
3110 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3112 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3113 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit);
3115 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
3117 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3119 # convert moved disk to base if part of template
3120 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3121 if PVE
::QemuConfig-
>is_template($conf);
3123 PVE
::QemuConfig-
>write_config($vmid, $conf);
3125 if ($running && PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
} && PVE
::QemuServer
::qga_check_running
($vmid)) {
3126 eval { PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-fstrim"); };
3130 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3131 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3138 foreach my $volid (@$newvollist) {
3139 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3142 die "storage migration failed: $err";
3145 if ($param->{delete}) {
3147 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3148 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3154 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3157 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3160 my $check_vm_disks_local = sub {
3161 my ($storecfg, $vmconf, $vmid) = @_;
3163 my $local_disks = {};
3165 # add some more information to the disks e.g. cdrom
3166 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3167 my ($volid, $attr) = @_;
3169 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3171 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3172 return if $scfg->{shared
};
3174 # The shared attr here is just a special case where the vdisk
3175 # is marked as shared manually
3176 return if $attr->{shared
};
3177 return if $attr->{cdrom
} and $volid eq "none";
3179 if (exists $local_disks->{$volid}) {
3180 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3182 $local_disks->{$volid} = $attr;
3183 # ensure volid is present in case it's needed
3184 $local_disks->{$volid}->{volid
} = $volid;
3188 return $local_disks;
3191 __PACKAGE__-
>register_method({
3192 name
=> 'migrate_vm_precondition',
3193 path
=> '{vmid}/migrate',
3197 description
=> "Get preconditions for migration.",
3199 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3202 additionalProperties
=> 0,
3204 node
=> get_standard_option
('pve-node'),
3205 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3206 target
=> get_standard_option
('pve-node', {
3207 description
=> "Target node.",
3208 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3216 running
=> { type
=> 'boolean' },
3220 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3222 not_allowed_nodes
=> {
3225 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3229 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3231 local_resources
=> {
3233 description
=> "List local resources e.g. pci, usb"
3240 my $rpcenv = PVE
::RPCEnvironment
::get
();
3242 my $authuser = $rpcenv->get_user();
3244 PVE
::Cluster
::check_cfs_quorum
();
3248 my $vmid = extract_param
($param, 'vmid');
3249 my $target = extract_param
($param, 'target');
3250 my $localnode = PVE
::INotify
::nodename
();
3254 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3255 my $storecfg = PVE
::Storage
::config
();
3258 # try to detect errors early
3259 PVE
::QemuConfig-
>check_lock($vmconf);
3261 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3263 # if vm is not running, return target nodes where local storage is available
3264 # for offline migration
3265 if (!$res->{running
}) {
3266 $res->{allowed_nodes
} = [];
3267 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3268 delete $checked_nodes->{$localnode};
3270 foreach my $node (keys %$checked_nodes) {
3271 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3272 push @{$res->{allowed_nodes
}}, $node;
3276 $res->{not_allowed_nodes
} = $checked_nodes;
3280 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3281 $res->{local_disks
} = [ values %$local_disks ];;
3283 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3285 $res->{local_resources
} = $local_resources;
3292 __PACKAGE__-
>register_method({
3293 name
=> 'migrate_vm',
3294 path
=> '{vmid}/migrate',
3298 description
=> "Migrate virtual machine. Creates a new migration task.",
3300 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3303 additionalProperties
=> 0,
3305 node
=> get_standard_option
('pve-node'),
3306 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3307 target
=> get_standard_option
('pve-node', {
3308 description
=> "Target node.",
3309 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3313 description
=> "Use online/live migration.",
3318 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3323 enum
=> ['secure', 'insecure'],
3324 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3327 migration_network
=> {
3328 type
=> 'string', format
=> 'CIDR',
3329 description
=> "CIDR of the (sub) network that is used for migration.",
3332 "with-local-disks" => {
3334 description
=> "Enable live storage migration for local disk",
3337 targetstorage
=> get_standard_option
('pve-storage-id', {
3338 description
=> "Default target storage.",
3340 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3343 description
=> "Override I/O bandwidth limit (in KiB/s).",
3347 default => 'migrate limit from datacenter or storage config',
3353 description
=> "the task ID.",
3358 my $rpcenv = PVE
::RPCEnvironment
::get
();
3359 my $authuser = $rpcenv->get_user();
3361 my $target = extract_param
($param, 'target');
3363 my $localnode = PVE
::INotify
::nodename
();
3364 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3366 PVE
::Cluster
::check_cfs_quorum
();
3368 PVE
::Cluster
::check_node_exists
($target);
3370 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3372 my $vmid = extract_param
($param, 'vmid');
3374 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3375 if !$param->{online
} && $param->{targetstorage
};
3377 raise_param_exc
({ force
=> "Only root may use this option." })
3378 if $param->{force
} && $authuser ne 'root@pam';
3380 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3381 if $param->{migration_type
} && $authuser ne 'root@pam';
3383 # allow root only until better network permissions are available
3384 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3385 if $param->{migration_network
} && $authuser ne 'root@pam';
3388 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3390 # try to detect errors early
3392 PVE
::QemuConfig-
>check_lock($conf);
3394 if (PVE
::QemuServer
::check_running
($vmid)) {
3395 die "cant migrate running VM without --online\n"
3396 if !$param->{online
};
3399 my $storecfg = PVE
::Storage
::config
();
3401 if( $param->{targetstorage
}) {
3402 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3404 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3407 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3412 print "Requesting HA migration for VM $vmid to node $target\n";
3414 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3415 PVE
::Tools
::run_command
($cmd);
3419 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3424 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3428 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3431 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3436 __PACKAGE__-
>register_method({
3438 path
=> '{vmid}/monitor',
3442 description
=> "Execute Qemu monitor commands.",
3444 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3445 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3448 additionalProperties
=> 0,
3450 node
=> get_standard_option
('pve-node'),
3451 vmid
=> get_standard_option
('pve-vmid'),
3454 description
=> "The monitor command.",
3458 returns
=> { type
=> 'string'},
3462 my $rpcenv = PVE
::RPCEnvironment
::get
();
3463 my $authuser = $rpcenv->get_user();
3466 my $command = shift;
3467 return $command =~ m/^\s*info(\s+|$)/
3468 || $command =~ m/^\s*help\s*$/;
3471 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3472 if !&$is_ro($param->{command
});
3474 my $vmid = $param->{vmid
};
3476 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3480 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3482 $res = "ERROR: $@" if $@;
3487 __PACKAGE__-
>register_method({
3488 name
=> 'resize_vm',
3489 path
=> '{vmid}/resize',
3493 description
=> "Extend volume size.",
3495 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3498 additionalProperties
=> 0,
3500 node
=> get_standard_option
('pve-node'),
3501 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3502 skiplock
=> get_standard_option
('skiplock'),
3505 description
=> "The disk you want to resize.",
3506 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3510 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3511 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.",
3515 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3521 returns
=> { type
=> 'null'},
3525 my $rpcenv = PVE
::RPCEnvironment
::get
();
3527 my $authuser = $rpcenv->get_user();
3529 my $node = extract_param
($param, 'node');
3531 my $vmid = extract_param
($param, 'vmid');
3533 my $digest = extract_param
($param, 'digest');
3535 my $disk = extract_param
($param, 'disk');
3537 my $sizestr = extract_param
($param, 'size');
3539 my $skiplock = extract_param
($param, 'skiplock');
3540 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3541 if $skiplock && $authuser ne 'root@pam';
3543 my $storecfg = PVE
::Storage
::config
();
3545 my $updatefn = sub {
3547 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3549 die "checksum missmatch (file change by other user?)\n"
3550 if $digest && $digest ne $conf->{digest
};
3551 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3553 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3555 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3557 my (undef, undef, undef, undef, undef, undef, $format) =
3558 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3560 die "can't resize volume: $disk if snapshot exists\n"
3561 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3563 my $volid = $drive->{file
};
3565 die "disk '$disk' has no associated volume\n" if !$volid;
3567 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3569 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3571 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3573 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3574 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3576 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3577 my ($ext, $newsize, $unit) = ($1, $2, $4);
3580 $newsize = $newsize * 1024;
3581 } elsif ($unit eq 'M') {
3582 $newsize = $newsize * 1024 * 1024;
3583 } elsif ($unit eq 'G') {
3584 $newsize = $newsize * 1024 * 1024 * 1024;
3585 } elsif ($unit eq 'T') {
3586 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3589 $newsize += $size if $ext;
3590 $newsize = int($newsize);
3592 die "shrinking disks is not supported\n" if $newsize < $size;
3594 return if $size == $newsize;
3596 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3598 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3600 $drive->{size
} = $newsize;
3601 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3603 PVE
::QemuConfig-
>write_config($vmid, $conf);
3606 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3610 __PACKAGE__-
>register_method({
3611 name
=> 'snapshot_list',
3612 path
=> '{vmid}/snapshot',
3614 description
=> "List all snapshots.",
3616 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3619 protected
=> 1, # qemu pid files are only readable by root
3621 additionalProperties
=> 0,
3623 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3624 node
=> get_standard_option
('pve-node'),
3633 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3637 description
=> "Snapshot includes RAM.",
3642 description
=> "Snapshot description.",
3646 description
=> "Snapshot creation time",
3648 renderer
=> 'timestamp',
3652 description
=> "Parent snapshot identifier.",
3658 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3663 my $vmid = $param->{vmid
};
3665 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3666 my $snaphash = $conf->{snapshots
} || {};
3670 foreach my $name (keys %$snaphash) {
3671 my $d = $snaphash->{$name};
3674 snaptime
=> $d->{snaptime
} || 0,
3675 vmstate
=> $d->{vmstate
} ?
1 : 0,
3676 description
=> $d->{description
} || '',
3678 $item->{parent
} = $d->{parent
} if $d->{parent
};
3679 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3683 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3686 digest
=> $conf->{digest
},
3687 running
=> $running,
3688 description
=> "You are here!",
3690 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3692 push @$res, $current;
3697 __PACKAGE__-
>register_method({
3699 path
=> '{vmid}/snapshot',
3703 description
=> "Snapshot a VM.",
3705 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3708 additionalProperties
=> 0,
3710 node
=> get_standard_option
('pve-node'),
3711 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3712 snapname
=> get_standard_option
('pve-snapshot-name'),
3716 description
=> "Save the vmstate",
3721 description
=> "A textual description or comment.",
3727 description
=> "the task ID.",
3732 my $rpcenv = PVE
::RPCEnvironment
::get
();
3734 my $authuser = $rpcenv->get_user();
3736 my $node = extract_param
($param, 'node');
3738 my $vmid = extract_param
($param, 'vmid');
3740 my $snapname = extract_param
($param, 'snapname');
3742 die "unable to use snapshot name 'current' (reserved name)\n"
3743 if $snapname eq 'current';
3746 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3747 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3748 $param->{description
});
3751 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3754 __PACKAGE__-
>register_method({
3755 name
=> 'snapshot_cmd_idx',
3756 path
=> '{vmid}/snapshot/{snapname}',
3763 additionalProperties
=> 0,
3765 vmid
=> get_standard_option
('pve-vmid'),
3766 node
=> get_standard_option
('pve-node'),
3767 snapname
=> get_standard_option
('pve-snapshot-name'),
3776 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3783 push @$res, { cmd
=> 'rollback' };
3784 push @$res, { cmd
=> 'config' };
3789 __PACKAGE__-
>register_method({
3790 name
=> 'update_snapshot_config',
3791 path
=> '{vmid}/snapshot/{snapname}/config',
3795 description
=> "Update snapshot metadata.",
3797 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3800 additionalProperties
=> 0,
3802 node
=> get_standard_option
('pve-node'),
3803 vmid
=> get_standard_option
('pve-vmid'),
3804 snapname
=> get_standard_option
('pve-snapshot-name'),
3808 description
=> "A textual description or comment.",
3812 returns
=> { type
=> 'null' },
3816 my $rpcenv = PVE
::RPCEnvironment
::get
();
3818 my $authuser = $rpcenv->get_user();
3820 my $vmid = extract_param
($param, 'vmid');
3822 my $snapname = extract_param
($param, 'snapname');
3824 return undef if !defined($param->{description
});
3826 my $updatefn = sub {
3828 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3830 PVE
::QemuConfig-
>check_lock($conf);
3832 my $snap = $conf->{snapshots
}->{$snapname};
3834 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3836 $snap->{description
} = $param->{description
} if defined($param->{description
});
3838 PVE
::QemuConfig-
>write_config($vmid, $conf);
3841 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3846 __PACKAGE__-
>register_method({
3847 name
=> 'get_snapshot_config',
3848 path
=> '{vmid}/snapshot/{snapname}/config',
3851 description
=> "Get snapshot configuration",
3853 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3856 additionalProperties
=> 0,
3858 node
=> get_standard_option
('pve-node'),
3859 vmid
=> get_standard_option
('pve-vmid'),
3860 snapname
=> get_standard_option
('pve-snapshot-name'),
3863 returns
=> { type
=> "object" },
3867 my $rpcenv = PVE
::RPCEnvironment
::get
();
3869 my $authuser = $rpcenv->get_user();
3871 my $vmid = extract_param
($param, 'vmid');
3873 my $snapname = extract_param
($param, 'snapname');
3875 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3877 my $snap = $conf->{snapshots
}->{$snapname};
3879 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3884 __PACKAGE__-
>register_method({
3886 path
=> '{vmid}/snapshot/{snapname}/rollback',
3890 description
=> "Rollback VM state to specified snapshot.",
3892 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3895 additionalProperties
=> 0,
3897 node
=> get_standard_option
('pve-node'),
3898 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3899 snapname
=> get_standard_option
('pve-snapshot-name'),
3904 description
=> "the task ID.",
3909 my $rpcenv = PVE
::RPCEnvironment
::get
();
3911 my $authuser = $rpcenv->get_user();
3913 my $node = extract_param
($param, 'node');
3915 my $vmid = extract_param
($param, 'vmid');
3917 my $snapname = extract_param
($param, 'snapname');
3920 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3921 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3925 # hold migration lock, this makes sure that nobody create replication snapshots
3926 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3929 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3932 __PACKAGE__-
>register_method({
3933 name
=> 'delsnapshot',
3934 path
=> '{vmid}/snapshot/{snapname}',
3938 description
=> "Delete a VM snapshot.",
3940 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3943 additionalProperties
=> 0,
3945 node
=> get_standard_option
('pve-node'),
3946 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3947 snapname
=> get_standard_option
('pve-snapshot-name'),
3951 description
=> "For removal from config file, even if removing disk snapshots fails.",
3957 description
=> "the task ID.",
3962 my $rpcenv = PVE
::RPCEnvironment
::get
();
3964 my $authuser = $rpcenv->get_user();
3966 my $node = extract_param
($param, 'node');
3968 my $vmid = extract_param
($param, 'vmid');
3970 my $snapname = extract_param
($param, 'snapname');
3973 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3974 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3977 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3980 __PACKAGE__-
>register_method({
3982 path
=> '{vmid}/template',
3986 description
=> "Create a Template.",
3988 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3989 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3992 additionalProperties
=> 0,
3994 node
=> get_standard_option
('pve-node'),
3995 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3999 description
=> "If you want to convert only 1 disk to base image.",
4000 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
4005 returns
=> { type
=> 'null'},
4009 my $rpcenv = PVE
::RPCEnvironment
::get
();
4011 my $authuser = $rpcenv->get_user();
4013 my $node = extract_param
($param, 'node');
4015 my $vmid = extract_param
($param, 'vmid');
4017 my $disk = extract_param
($param, 'disk');
4019 my $updatefn = sub {
4021 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4023 PVE
::QemuConfig-
>check_lock($conf);
4025 die "unable to create template, because VM contains snapshots\n"
4026 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4028 die "you can't convert a template to a template\n"
4029 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4031 die "you can't convert a VM to template if VM is running\n"
4032 if PVE
::QemuServer
::check_running
($vmid);
4035 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4038 $conf->{template
} = 1;
4039 PVE
::QemuConfig-
>write_config($vmid, $conf);
4041 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4044 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4048 __PACKAGE__-
>register_method({
4049 name
=> 'cloudinit_generated_config_dump',
4050 path
=> '{vmid}/cloudinit/dump',
4053 description
=> "Get automatically generated cloudinit config.",
4055 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4058 additionalProperties
=> 0,
4060 node
=> get_standard_option
('pve-node'),
4061 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4063 description
=> 'Config type.',
4065 enum
=> ['user', 'network', 'meta'],
4075 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4077 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});