1 package PVE
::API2
::Qemu
;
11 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
13 use PVE
::Tools
qw(extract_param);
14 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
16 use PVE
::JSONSchema
qw(get_standard_option);
18 use PVE
::ReplicationConfig
;
19 use PVE
::GuestHelpers
;
23 use PVE
::RPCEnvironment
;
24 use PVE
::AccessControl
;
28 use PVE
::API2
::Firewall
::VM
;
29 use PVE
::API2
::Qemu
::Agent
;
30 use PVE
::VZDump
::Plugin
;
31 use PVE
::DataCenterConfig
;
34 if (!$ENV{PVE_GENERATING_DOCS
}) {
35 require PVE
::HA
::Env
::PVE2
;
36 import PVE
::HA
::Env
::PVE2
;
37 require PVE
::HA
::Config
;
38 import PVE
::HA
::Config
;
42 use Data
::Dumper
; # fixme: remove
44 use base
qw(PVE::RESTHandler);
46 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.";
48 my $resolve_cdrom_alias = sub {
51 if (my $value = $param->{cdrom
}) {
52 $value .= ",media=cdrom" if $value !~ m/media=/;
53 $param->{ide2
} = $value;
54 delete $param->{cdrom
};
58 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
59 my $check_storage_access = sub {
60 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
62 PVE
::QemuServer
::foreach_drive
($settings, sub {
63 my ($ds, $drive) = @_;
65 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
67 my $volid = $drive->{file
};
68 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
70 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit' || $volname eq '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
};
143 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
145 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
146 delete $disk->{size
};
147 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
148 } elsif ($volname eq 'cloudinit') {
149 $storeid = $storeid // $default_storage;
150 die "no storage ID specified (and no default storage)\n" if !$storeid;
151 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
152 my $name = "vm-$vmid-cloudinit";
156 $fmt = $disk->{format
} // "qcow2";
159 $fmt = $disk->{format
} // "raw";
162 # Initial disk created with 4 MB and aligned to 4MB on regeneration
163 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
164 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
165 $disk->{file
} = $volid;
166 $disk->{media
} = 'cdrom';
167 push @$vollist, $volid;
168 delete $disk->{format
}; # no longer needed
169 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
170 } elsif ($volid =~ $NEW_DISK_RE) {
171 my ($storeid, $size) = ($2 || $default_storage, $3);
172 die "no storage ID specified (and no default storage)\n" if !$storeid;
173 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
174 my $fmt = $disk->{format
} || $defformat;
176 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
179 if ($ds eq 'efidisk0') {
180 ($volid, $size) = PVE
::QemuServer
::create_efidisk
($storecfg, $storeid, $vmid, $fmt, $arch);
182 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
184 push @$vollist, $volid;
185 $disk->{file
} = $volid;
186 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
187 delete $disk->{format
}; # no longer needed
188 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
191 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
193 my $volid_is_new = 1;
196 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
197 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
202 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
204 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
206 die "volume $volid does not exists\n" if !$size;
208 $disk->{size
} = $size;
211 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
215 eval { PVE
::QemuServer
::foreach_drive
($settings, $code); };
217 # free allocated images on error
219 syslog
('err', "VM $vmid creating disks failed");
220 foreach my $volid (@$vollist) {
221 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
227 # modify vm config if everything went well
228 foreach my $ds (keys %$res) {
229 $conf->{$ds} = $res->{$ds};
246 my $memoryoptions = {
252 my $hwtypeoptions = {
265 my $generaloptions = {
272 'migrate_downtime' => 1,
273 'migrate_speed' => 1,
285 my $vmpoweroptions = {
292 'vmstatestorage' => 1,
295 my $cloudinitoptions = {
305 my $check_vm_modify_config_perm = sub {
306 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
308 return 1 if $authuser eq 'root@pam';
310 foreach my $opt (@$key_list) {
311 # some checks (e.g., disk, serial port, usb) need to be done somewhere
312 # else, as there the permission can be value dependend
313 next if PVE
::QemuServer
::is_valid_drivename
($opt);
314 next if $opt eq 'cdrom';
315 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
318 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
319 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
320 } elsif ($memoryoptions->{$opt}) {
321 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
322 } elsif ($hwtypeoptions->{$opt}) {
323 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
324 } elsif ($generaloptions->{$opt}) {
325 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
326 # special case for startup since it changes host behaviour
327 if ($opt eq 'startup') {
328 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
330 } elsif ($vmpoweroptions->{$opt}) {
331 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
332 } elsif ($diskoptions->{$opt}) {
333 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
334 } elsif ($cloudinitoptions->{$opt} || ($opt =~ m/^(?:net|ipconfig)\d+$/)) {
335 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
337 # catches hostpci\d+, args, lock, etc.
338 # new options will be checked here
339 die "only root can set '$opt' config\n";
346 __PACKAGE__-
>register_method({
350 description
=> "Virtual machine index (per node).",
352 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
356 protected
=> 1, # qemu pid files are only readable by root
358 additionalProperties
=> 0,
360 node
=> get_standard_option
('pve-node'),
364 description
=> "Determine the full status of active VMs.",
372 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
374 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
379 my $rpcenv = PVE
::RPCEnvironment
::get
();
380 my $authuser = $rpcenv->get_user();
382 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
385 foreach my $vmid (keys %$vmstatus) {
386 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
388 my $data = $vmstatus->{$vmid};
397 __PACKAGE__-
>register_method({
401 description
=> "Create or restore a virtual machine.",
403 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
404 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
405 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
406 user
=> 'all', # check inside
411 additionalProperties
=> 0,
412 properties
=> PVE
::QemuServer
::json_config_properties
(
414 node
=> get_standard_option
('pve-node'),
415 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
417 description
=> "The backup file.",
421 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
423 storage
=> get_standard_option
('pve-storage-id', {
424 description
=> "Default storage.",
426 completion
=> \
&PVE
::QemuServer
::complete_storage
,
431 description
=> "Allow to overwrite existing VM.",
432 requires
=> 'archive',
437 description
=> "Assign a unique random ethernet address.",
438 requires
=> 'archive',
442 type
=> 'string', format
=> 'pve-poolid',
443 description
=> "Add the VM to the specified pool.",
446 description
=> "Override I/O bandwidth limit (in KiB/s).",
450 default => 'restore limit from datacenter or storage config',
456 description
=> "Start VM after it was created successfully.",
466 my $rpcenv = PVE
::RPCEnvironment
::get
();
467 my $authuser = $rpcenv->get_user();
469 my $node = extract_param
($param, 'node');
470 my $vmid = extract_param
($param, 'vmid');
472 my $archive = extract_param
($param, 'archive');
473 my $is_restore = !!$archive;
475 my $bwlimit = extract_param
($param, 'bwlimit');
476 my $force = extract_param
($param, 'force');
477 my $pool = extract_param
($param, 'pool');
478 my $start_after_create = extract_param
($param, 'start');
479 my $storage = extract_param
($param, 'storage');
480 my $unique = extract_param
($param, 'unique');
482 if (defined(my $ssh_keys = $param->{sshkeys
})) {
483 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
484 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
487 PVE
::Cluster
::check_cfs_quorum
();
489 my $filename = PVE
::QemuConfig-
>config_file($vmid);
490 my $storecfg = PVE
::Storage
::config
();
492 if (defined($pool)) {
493 $rpcenv->check_pool_exist($pool);
496 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
497 if defined($storage);
499 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
501 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
503 } elsif ($archive && $force && (-f
$filename) &&
504 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
505 # OK: user has VM.Backup permissions, and want to restore an existing VM
511 &$resolve_cdrom_alias($param);
513 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
515 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
517 foreach my $opt (keys %$param) {
518 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
519 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
520 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
522 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
523 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
527 PVE
::QemuServer
::add_random_macs
($param);
529 my $keystr = join(' ', keys %$param);
530 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
532 if ($archive eq '-') {
533 die "pipe requires cli environment\n"
534 if $rpcenv->{type
} ne 'cli';
536 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
537 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
541 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
543 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
544 die "$emsg $@" if $@;
546 my $restorefn = sub {
547 my $conf = PVE
::QemuConfig-
>load_config($vmid);
549 PVE
::QemuConfig-
>check_protection($conf, $emsg);
551 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
554 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
560 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
561 # Convert restored VM to template if backup was VM template
562 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
563 warn "Convert to template.\n";
564 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
568 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
570 if ($start_after_create) {
571 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
576 # ensure no old replication state are exists
577 PVE
::ReplicationState
::delete_guest_states
($vmid);
579 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
583 # ensure no old replication state are exists
584 PVE
::ReplicationState
::delete_guest_states
($vmid);
592 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
596 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
598 if (!$conf->{bootdisk
}) {
599 my $firstdisk = PVE
::QemuServer
::resolve_first_disk
($conf);
600 $conf->{bootdisk
} = $firstdisk if $firstdisk;
603 # auto generate uuid if user did not specify smbios1 option
604 if (!$conf->{smbios1
}) {
605 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
608 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
609 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
612 PVE
::QemuConfig-
>write_config($vmid, $conf);
618 foreach my $volid (@$vollist) {
619 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
625 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
628 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
630 if ($start_after_create) {
631 print "Execute autostart\n";
632 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
637 my ($code, $worker_name);
639 $worker_name = 'qmrestore';
641 eval { $restorefn->() };
643 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
649 $worker_name = 'qmcreate';
651 eval { $createfn->() };
654 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
655 unlink($conffile) or die "failed to remove config file: $!\n";
663 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
666 __PACKAGE__-
>register_method({
671 description
=> "Directory index",
676 additionalProperties
=> 0,
678 node
=> get_standard_option
('pve-node'),
679 vmid
=> get_standard_option
('pve-vmid'),
687 subdir
=> { type
=> 'string' },
690 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
696 { subdir
=> 'config' },
697 { subdir
=> 'pending' },
698 { subdir
=> 'status' },
699 { subdir
=> 'unlink' },
700 { subdir
=> 'vncproxy' },
701 { subdir
=> 'termproxy' },
702 { subdir
=> 'migrate' },
703 { subdir
=> 'resize' },
704 { subdir
=> 'move' },
706 { subdir
=> 'rrddata' },
707 { subdir
=> 'monitor' },
708 { subdir
=> 'agent' },
709 { subdir
=> 'snapshot' },
710 { subdir
=> 'spiceproxy' },
711 { subdir
=> 'sendkey' },
712 { subdir
=> 'firewall' },
718 __PACKAGE__-
>register_method ({
719 subclass
=> "PVE::API2::Firewall::VM",
720 path
=> '{vmid}/firewall',
723 __PACKAGE__-
>register_method ({
724 subclass
=> "PVE::API2::Qemu::Agent",
725 path
=> '{vmid}/agent',
728 __PACKAGE__-
>register_method({
730 path
=> '{vmid}/rrd',
732 protected
=> 1, # fixme: can we avoid that?
734 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
736 description
=> "Read VM RRD statistics (returns PNG)",
738 additionalProperties
=> 0,
740 node
=> get_standard_option
('pve-node'),
741 vmid
=> get_standard_option
('pve-vmid'),
743 description
=> "Specify the time frame you are interested in.",
745 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
748 description
=> "The list of datasources you want to display.",
749 type
=> 'string', format
=> 'pve-configid-list',
752 description
=> "The RRD consolidation function",
754 enum
=> [ 'AVERAGE', 'MAX' ],
762 filename
=> { type
=> 'string' },
768 return PVE
::Cluster
::create_rrd_graph
(
769 "pve2-vm/$param->{vmid}", $param->{timeframe
},
770 $param->{ds
}, $param->{cf
});
774 __PACKAGE__-
>register_method({
776 path
=> '{vmid}/rrddata',
778 protected
=> 1, # fixme: can we avoid that?
780 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
782 description
=> "Read VM RRD statistics",
784 additionalProperties
=> 0,
786 node
=> get_standard_option
('pve-node'),
787 vmid
=> get_standard_option
('pve-vmid'),
789 description
=> "Specify the time frame you are interested in.",
791 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
794 description
=> "The RRD consolidation function",
796 enum
=> [ 'AVERAGE', 'MAX' ],
811 return PVE
::Cluster
::create_rrd_data
(
812 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
816 __PACKAGE__-
>register_method({
818 path
=> '{vmid}/config',
821 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
823 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
826 additionalProperties
=> 0,
828 node
=> get_standard_option
('pve-node'),
829 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
831 description
=> "Get current values (instead of pending values).",
836 snapshot
=> get_standard_option
('pve-snapshot-name', {
837 description
=> "Fetch config values from given snapshot.",
840 my ($cmd, $pname, $cur, $args) = @_;
841 PVE
::QemuConfig-
>snapshot_list($args->[0]);
847 description
=> "The current VM configuration.",
849 properties
=> PVE
::QemuServer
::json_config_properties
({
852 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
859 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
860 current
=> "cannot use 'snapshot' parameter with 'current'"})
861 if ($param->{snapshot
} && $param->{current
});
864 if ($param->{snapshot
}) {
865 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
867 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
869 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
874 __PACKAGE__-
>register_method({
875 name
=> 'vm_pending',
876 path
=> '{vmid}/pending',
879 description
=> "Get virtual machine configuration, including pending changes.",
881 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
884 additionalProperties
=> 0,
886 node
=> get_standard_option
('pve-node'),
887 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
896 description
=> "Configuration option name.",
900 description
=> "Current value.",
905 description
=> "Pending value.",
910 description
=> "Indicates a pending delete request if present and not 0. " .
911 "The value 2 indicates a force-delete request.",
923 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
925 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
927 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
928 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
930 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
933 # POST/PUT {vmid}/config implementation
935 # The original API used PUT (idempotent) an we assumed that all operations
936 # are fast. But it turned out that almost any configuration change can
937 # involve hot-plug actions, or disk alloc/free. Such actions can take long
938 # time to complete and have side effects (not idempotent).
940 # The new implementation uses POST and forks a worker process. We added
941 # a new option 'background_delay'. If specified we wait up to
942 # 'background_delay' second for the worker task to complete. It returns null
943 # if the task is finished within that time, else we return the UPID.
945 my $update_vm_api = sub {
946 my ($param, $sync) = @_;
948 my $rpcenv = PVE
::RPCEnvironment
::get
();
950 my $authuser = $rpcenv->get_user();
952 my $node = extract_param
($param, 'node');
954 my $vmid = extract_param
($param, 'vmid');
956 my $digest = extract_param
($param, 'digest');
958 my $background_delay = extract_param
($param, 'background_delay');
960 if (defined(my $cipassword = $param->{cipassword
})) {
961 # Same logic as in cloud-init (but with the regex fixed...)
962 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
963 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
966 my @paramarr = (); # used for log message
967 foreach my $key (sort keys %$param) {
968 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
969 push @paramarr, "-$key", $value;
972 my $skiplock = extract_param
($param, 'skiplock');
973 raise_param_exc
({ skiplock
=> "Only root may use this option." })
974 if $skiplock && $authuser ne 'root@pam';
976 my $delete_str = extract_param
($param, 'delete');
978 my $revert_str = extract_param
($param, 'revert');
980 my $force = extract_param
($param, 'force');
982 if (defined(my $ssh_keys = $param->{sshkeys
})) {
983 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
984 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
987 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
989 my $storecfg = PVE
::Storage
::config
();
991 my $defaults = PVE
::QemuServer
::load_defaults
();
993 &$resolve_cdrom_alias($param);
995 # now try to verify all parameters
998 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
999 if (!PVE
::QemuServer
::option_exists
($opt)) {
1000 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1003 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1004 "-revert $opt' at the same time" })
1005 if defined($param->{$opt});
1007 $revert->{$opt} = 1;
1011 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1012 $opt = 'ide2' if $opt eq 'cdrom';
1014 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1015 "-delete $opt' at the same time" })
1016 if defined($param->{$opt});
1018 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1019 "-revert $opt' at the same time" })
1022 if (!PVE
::QemuServer
::option_exists
($opt)) {
1023 raise_param_exc
({ delete => "unknown option '$opt'" });
1029 my $repl_conf = PVE
::ReplicationConfig-
>new();
1030 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1031 my $check_replication = sub {
1033 return if !$is_replicated;
1034 my $volid = $drive->{file
};
1035 return if !$volid || !($drive->{replicate
}//1);
1036 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1038 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1039 return if $volname eq 'cloudinit';
1042 if ($volid =~ $NEW_DISK_RE) {
1044 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1046 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1048 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1049 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1050 return if $scfg->{shared
};
1051 die "cannot add non-replicatable volume to a replicated VM\n";
1054 foreach my $opt (keys %$param) {
1055 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1056 # cleanup drive path
1057 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1058 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1059 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1060 $check_replication->($drive);
1061 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1062 } elsif ($opt =~ m/^net(\d+)$/) {
1064 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1065 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1066 } elsif ($opt eq 'vmgenid') {
1067 if ($param->{$opt} eq '1') {
1068 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1070 } elsif ($opt eq 'hookscript') {
1071 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1072 raise_param_exc
({ $opt => $@ }) if $@;
1076 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1078 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1080 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1082 my $updatefn = sub {
1084 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1086 die "checksum missmatch (file change by other user?)\n"
1087 if $digest && $digest ne $conf->{digest
};
1089 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1091 foreach my $opt (keys %$revert) {
1092 if (defined($conf->{$opt})) {
1093 $param->{$opt} = $conf->{$opt};
1094 } elsif (defined($conf->{pending
}->{$opt})) {
1099 if ($param->{memory
} || defined($param->{balloon
})) {
1100 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1101 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1103 die "balloon value too large (must be smaller than assigned memory)\n"
1104 if $balloon && $balloon > $maxmem;
1107 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1111 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1113 # write updates to pending section
1115 my $modified = {}; # record what $option we modify
1117 foreach my $opt (@delete) {
1118 $modified->{$opt} = 1;
1119 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1121 # value of what we want to delete, independent if pending or not
1122 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1123 if (!defined($val)) {
1124 warn "cannot delete '$opt' - not set in current configuration!\n";
1125 $modified->{$opt} = 0;
1128 my $is_pending_val = defined($conf->{pending
}->{$opt});
1129 delete $conf->{pending
}->{$opt};
1131 if ($opt =~ m/^unused/) {
1132 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1133 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1134 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1135 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1136 delete $conf->{$opt};
1137 PVE
::QemuConfig-
>write_config($vmid, $conf);
1139 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1140 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1141 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1142 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1144 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1145 PVE
::QemuConfig-
>write_config($vmid, $conf);
1146 } elsif ($opt =~ m/^serial\d+$/) {
1147 if ($val eq 'socket') {
1148 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1149 } elsif ($authuser ne 'root@pam') {
1150 die "only root can delete '$opt' config for real devices\n";
1152 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1153 PVE
::QemuConfig-
>write_config($vmid, $conf);
1154 } elsif ($opt =~ m/^usb\d+$/) {
1155 if ($val =~ m/spice/) {
1156 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1157 } elsif ($authuser ne 'root@pam') {
1158 die "only root can delete '$opt' config for real devices\n";
1160 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1161 PVE
::QemuConfig-
>write_config($vmid, $conf);
1163 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1164 PVE
::QemuConfig-
>write_config($vmid, $conf);
1168 foreach my $opt (keys %$param) { # add/change
1169 $modified->{$opt} = 1;
1170 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1171 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1173 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
1175 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1176 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1177 # FIXME: cloudinit: CDROM or Disk?
1178 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1179 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1181 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1183 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1184 if defined($conf->{pending
}->{$opt});
1186 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1187 } elsif ($opt =~ m/^serial\d+/) {
1188 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1189 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1190 } elsif ($authuser ne 'root@pam') {
1191 die "only root can modify '$opt' config for real devices\n";
1193 $conf->{pending
}->{$opt} = $param->{$opt};
1194 } elsif ($opt =~ m/^usb\d+/) {
1195 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1196 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1197 } elsif ($authuser ne 'root@pam') {
1198 die "only root can modify '$opt' config for real devices\n";
1200 $conf->{pending
}->{$opt} = $param->{$opt};
1202 $conf->{pending
}->{$opt} = $param->{$opt};
1204 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1205 PVE
::QemuConfig-
>write_config($vmid, $conf);
1208 # remove pending changes when nothing changed
1209 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1210 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1211 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1213 return if !scalar(keys %{$conf->{pending
}});
1215 my $running = PVE
::QemuServer
::check_running
($vmid);
1217 # apply pending changes
1219 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1223 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1224 raise_param_exc
($errors) if scalar(keys %$errors);
1226 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1236 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1238 if ($background_delay) {
1240 # Note: It would be better to do that in the Event based HTTPServer
1241 # to avoid blocking call to sleep.
1243 my $end_time = time() + $background_delay;
1245 my $task = PVE
::Tools
::upid_decode
($upid);
1248 while (time() < $end_time) {
1249 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1251 sleep(1); # this gets interrupted when child process ends
1255 my $status = PVE
::Tools
::upid_read_status
($upid);
1256 return undef if $status eq 'OK';
1265 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1268 my $vm_config_perm_list = [
1273 'VM.Config.Network',
1275 'VM.Config.Options',
1278 __PACKAGE__-
>register_method({
1279 name
=> 'update_vm_async',
1280 path
=> '{vmid}/config',
1284 description
=> "Set virtual machine options (asynchrounous API).",
1286 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1289 additionalProperties
=> 0,
1290 properties
=> PVE
::QemuServer
::json_config_properties
(
1292 node
=> get_standard_option
('pve-node'),
1293 vmid
=> get_standard_option
('pve-vmid'),
1294 skiplock
=> get_standard_option
('skiplock'),
1296 type
=> 'string', format
=> 'pve-configid-list',
1297 description
=> "A list of settings you want to delete.",
1301 type
=> 'string', format
=> 'pve-configid-list',
1302 description
=> "Revert a pending change.",
1307 description
=> $opt_force_description,
1309 requires
=> 'delete',
1313 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1317 background_delay
=> {
1319 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1330 code
=> $update_vm_api,
1333 __PACKAGE__-
>register_method({
1334 name
=> 'update_vm',
1335 path
=> '{vmid}/config',
1339 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1341 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1344 additionalProperties
=> 0,
1345 properties
=> PVE
::QemuServer
::json_config_properties
(
1347 node
=> get_standard_option
('pve-node'),
1348 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1349 skiplock
=> get_standard_option
('skiplock'),
1351 type
=> 'string', format
=> 'pve-configid-list',
1352 description
=> "A list of settings you want to delete.",
1356 type
=> 'string', format
=> 'pve-configid-list',
1357 description
=> "Revert a pending change.",
1362 description
=> $opt_force_description,
1364 requires
=> 'delete',
1368 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1374 returns
=> { type
=> 'null' },
1377 &$update_vm_api($param, 1);
1382 __PACKAGE__-
>register_method({
1383 name
=> 'destroy_vm',
1388 description
=> "Destroy the vm (also delete all used/owned volumes).",
1390 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1393 additionalProperties
=> 0,
1395 node
=> get_standard_option
('pve-node'),
1396 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1397 skiplock
=> get_standard_option
('skiplock'),
1400 description
=> "Remove vmid from backup cron jobs.",
1411 my $rpcenv = PVE
::RPCEnvironment
::get
();
1412 my $authuser = $rpcenv->get_user();
1413 my $vmid = $param->{vmid
};
1415 my $skiplock = $param->{skiplock
};
1416 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1417 if $skiplock && $authuser ne 'root@pam';
1420 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1421 my $storecfg = PVE
::Storage
::config
();
1422 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1423 die "unable to remove VM $vmid - used in HA resources\n"
1424 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1426 if (!$param->{purge
}) {
1427 # don't allow destroy if with replication jobs but no purge param
1428 my $repl_conf = PVE
::ReplicationConfig-
>new();
1429 $repl_conf->check_for_existing_jobs($vmid);
1432 # early tests (repeat after locking)
1433 die "VM $vmid is running - destroy failed\n"
1434 if PVE
::QemuServer
::check_running
($vmid);
1439 syslog
('info', "destroy VM $vmid: $upid\n");
1440 PVE
::QemuConfig-
>lock_config($vmid, sub {
1441 die "VM $vmid is running - destroy failed\n"
1442 if (PVE
::QemuServer
::check_running
($vmid));
1444 PVE
::QemuServer
::destroy_vm
($storecfg, $vmid, $skiplock, { lock => 'destroyed' });
1446 PVE
::AccessControl
::remove_vm_access
($vmid);
1447 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1448 if ($param->{purge
}) {
1449 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1450 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1453 # only now remove the zombie config, else we can have reuse race
1454 PVE
::QemuConfig-
>destroy_config($vmid);
1458 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1461 __PACKAGE__-
>register_method({
1463 path
=> '{vmid}/unlink',
1467 description
=> "Unlink/delete disk images.",
1469 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1472 additionalProperties
=> 0,
1474 node
=> get_standard_option
('pve-node'),
1475 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1477 type
=> 'string', format
=> 'pve-configid-list',
1478 description
=> "A list of disk IDs you want to delete.",
1482 description
=> $opt_force_description,
1487 returns
=> { type
=> 'null'},
1491 $param->{delete} = extract_param
($param, 'idlist');
1493 __PACKAGE__-
>update_vm($param);
1500 __PACKAGE__-
>register_method({
1502 path
=> '{vmid}/vncproxy',
1506 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1508 description
=> "Creates a TCP VNC proxy connections.",
1510 additionalProperties
=> 0,
1512 node
=> get_standard_option
('pve-node'),
1513 vmid
=> get_standard_option
('pve-vmid'),
1517 description
=> "starts websockify instead of vncproxy",
1522 additionalProperties
=> 0,
1524 user
=> { type
=> 'string' },
1525 ticket
=> { type
=> 'string' },
1526 cert
=> { type
=> 'string' },
1527 port
=> { type
=> 'integer' },
1528 upid
=> { type
=> 'string' },
1534 my $rpcenv = PVE
::RPCEnvironment
::get
();
1536 my $authuser = $rpcenv->get_user();
1538 my $vmid = $param->{vmid
};
1539 my $node = $param->{node
};
1540 my $websocket = $param->{websocket
};
1542 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1543 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1545 my $authpath = "/vms/$vmid";
1547 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1549 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1555 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1556 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1557 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1558 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1559 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1561 $family = PVE
::Tools
::get_host_address_family
($node);
1564 my $port = PVE
::Tools
::next_vnc_port
($family);
1571 syslog
('info', "starting vnc proxy $upid\n");
1577 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1579 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1580 '-timeout', $timeout, '-authpath', $authpath,
1581 '-perm', 'Sys.Console'];
1583 if ($param->{websocket
}) {
1584 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1585 push @$cmd, '-notls', '-listen', 'localhost';
1588 push @$cmd, '-c', @$remcmd, @$termcmd;
1590 PVE
::Tools
::run_command
($cmd);
1594 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1596 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1598 my $sock = IO
::Socket
::IP-
>new(
1603 GetAddrInfoFlags
=> 0,
1604 ) or die "failed to create socket: $!\n";
1605 # Inside the worker we shouldn't have any previous alarms
1606 # running anyway...:
1608 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1610 accept(my $cli, $sock) or die "connection failed: $!\n";
1613 if (PVE
::Tools
::run_command
($cmd,
1614 output
=> '>&'.fileno($cli),
1615 input
=> '<&'.fileno($cli),
1618 die "Failed to run vncproxy.\n";
1625 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1627 PVE
::Tools
::wait_for_vnc_port
($port);
1638 __PACKAGE__-
>register_method({
1639 name
=> 'termproxy',
1640 path
=> '{vmid}/termproxy',
1644 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1646 description
=> "Creates a TCP proxy connections.",
1648 additionalProperties
=> 0,
1650 node
=> get_standard_option
('pve-node'),
1651 vmid
=> get_standard_option
('pve-vmid'),
1655 enum
=> [qw(serial0 serial1 serial2 serial3)],
1656 description
=> "opens a serial terminal (defaults to display)",
1661 additionalProperties
=> 0,
1663 user
=> { type
=> 'string' },
1664 ticket
=> { type
=> 'string' },
1665 port
=> { type
=> 'integer' },
1666 upid
=> { type
=> 'string' },
1672 my $rpcenv = PVE
::RPCEnvironment
::get
();
1674 my $authuser = $rpcenv->get_user();
1676 my $vmid = $param->{vmid
};
1677 my $node = $param->{node
};
1678 my $serial = $param->{serial
};
1680 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1682 if (!defined($serial)) {
1683 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1684 $serial = $conf->{vga
};
1688 my $authpath = "/vms/$vmid";
1690 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1695 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1696 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1697 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1698 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, '-t');
1699 push @$remcmd, '--';
1701 $family = PVE
::Tools
::get_host_address_family
($node);
1704 my $port = PVE
::Tools
::next_vnc_port
($family);
1706 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1707 push @$termcmd, '-iface', $serial if $serial;
1712 syslog
('info', "starting qemu termproxy $upid\n");
1714 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1715 '--perm', 'VM.Console', '--'];
1716 push @$cmd, @$remcmd, @$termcmd;
1718 PVE
::Tools
::run_command
($cmd);
1721 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1723 PVE
::Tools
::wait_for_vnc_port
($port);
1733 __PACKAGE__-
>register_method({
1734 name
=> 'vncwebsocket',
1735 path
=> '{vmid}/vncwebsocket',
1738 description
=> "You also need to pass a valid ticket (vncticket).",
1739 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1741 description
=> "Opens a weksocket for VNC traffic.",
1743 additionalProperties
=> 0,
1745 node
=> get_standard_option
('pve-node'),
1746 vmid
=> get_standard_option
('pve-vmid'),
1748 description
=> "Ticket from previous call to vncproxy.",
1753 description
=> "Port number returned by previous vncproxy call.",
1763 port
=> { type
=> 'string' },
1769 my $rpcenv = PVE
::RPCEnvironment
::get
();
1771 my $authuser = $rpcenv->get_user();
1773 my $vmid = $param->{vmid
};
1774 my $node = $param->{node
};
1776 my $authpath = "/vms/$vmid";
1778 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1780 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1782 # Note: VNC ports are acessible from outside, so we do not gain any
1783 # security if we verify that $param->{port} belongs to VM $vmid. This
1784 # check is done by verifying the VNC ticket (inside VNC protocol).
1786 my $port = $param->{port
};
1788 return { port
=> $port };
1791 __PACKAGE__-
>register_method({
1792 name
=> 'spiceproxy',
1793 path
=> '{vmid}/spiceproxy',
1798 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1800 description
=> "Returns a SPICE configuration to connect to the VM.",
1802 additionalProperties
=> 0,
1804 node
=> get_standard_option
('pve-node'),
1805 vmid
=> get_standard_option
('pve-vmid'),
1806 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1809 returns
=> get_standard_option
('remote-viewer-config'),
1813 my $rpcenv = PVE
::RPCEnvironment
::get
();
1815 my $authuser = $rpcenv->get_user();
1817 my $vmid = $param->{vmid
};
1818 my $node = $param->{node
};
1819 my $proxy = $param->{proxy
};
1821 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1822 my $title = "VM $vmid";
1823 $title .= " - ". $conf->{name
} if $conf->{name
};
1825 my $port = PVE
::QemuServer
::spice_port
($vmid);
1827 my ($ticket, undef, $remote_viewer_config) =
1828 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1830 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1831 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1833 return $remote_viewer_config;
1836 __PACKAGE__-
>register_method({
1838 path
=> '{vmid}/status',
1841 description
=> "Directory index",
1846 additionalProperties
=> 0,
1848 node
=> get_standard_option
('pve-node'),
1849 vmid
=> get_standard_option
('pve-vmid'),
1857 subdir
=> { type
=> 'string' },
1860 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1866 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1869 { subdir
=> 'current' },
1870 { subdir
=> 'start' },
1871 { subdir
=> 'stop' },
1872 { subdir
=> 'reset' },
1873 { subdir
=> 'shutdown' },
1874 { subdir
=> 'suspend' },
1875 { subdir
=> 'reboot' },
1881 __PACKAGE__-
>register_method({
1882 name
=> 'vm_status',
1883 path
=> '{vmid}/status/current',
1886 protected
=> 1, # qemu pid files are only readable by root
1887 description
=> "Get virtual machine status.",
1889 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1892 additionalProperties
=> 0,
1894 node
=> get_standard_option
('pve-node'),
1895 vmid
=> get_standard_option
('pve-vmid'),
1901 %$PVE::QemuServer
::vmstatus_return_properties
,
1903 description
=> "HA manager service status.",
1907 description
=> "Qemu VGA configuration supports spice.",
1912 description
=> "Qemu GuestAgent enabled in config.",
1922 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1924 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1925 my $status = $vmstatus->{$param->{vmid
}};
1927 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1929 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1930 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1935 __PACKAGE__-
>register_method({
1937 path
=> '{vmid}/status/start',
1941 description
=> "Start virtual machine.",
1943 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1946 additionalProperties
=> 0,
1948 node
=> get_standard_option
('pve-node'),
1949 vmid
=> get_standard_option
('pve-vmid',
1950 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1951 skiplock
=> get_standard_option
('skiplock'),
1952 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1953 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1956 enum
=> ['secure', 'insecure'],
1957 description
=> "Migration traffic is encrypted using an SSH " .
1958 "tunnel by default. On secure, completely private networks " .
1959 "this can be disabled to increase performance.",
1962 migration_network
=> {
1963 type
=> 'string', format
=> 'CIDR',
1964 description
=> "CIDR of the (sub) network that is used for migration.",
1967 machine
=> get_standard_option
('pve-qm-machine'),
1969 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1981 my $rpcenv = PVE
::RPCEnvironment
::get
();
1982 my $authuser = $rpcenv->get_user();
1984 my $node = extract_param
($param, 'node');
1985 my $vmid = extract_param
($param, 'vmid');
1987 my $machine = extract_param
($param, 'machine');
1989 my $get_root_param = sub {
1990 my $value = extract_param
($param, $_[0]);
1991 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
1992 if $value && $authuser ne 'root@pam';
1996 my $stateuri = $get_root_param->('stateuri');
1997 my $skiplock = $get_root_param->('skiplock');
1998 my $migratedfrom = $get_root_param->('migratedfrom');
1999 my $migration_type = $get_root_param->('migration_type');
2000 my $migration_network = $get_root_param->('migration_network');
2001 my $targetstorage = $get_root_param->('targetstorage');
2003 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2004 if $targetstorage && !$migratedfrom;
2006 # read spice ticket from STDIN
2008 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2009 if (defined(my $line = <STDIN
>)) {
2011 $spice_ticket = $line;
2015 PVE
::Cluster
::check_cfs_quorum
();
2017 my $storecfg = PVE
::Storage
::config
();
2019 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2023 print "Requesting HA start for VM $vmid\n";
2025 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2026 PVE
::Tools
::run_command
($cmd);
2030 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2037 syslog
('info', "start VM $vmid: $upid\n");
2039 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2040 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
2044 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2048 __PACKAGE__-
>register_method({
2050 path
=> '{vmid}/status/stop',
2054 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2055 "is akin to pulling the power plug of a running computer and may damage the VM data",
2057 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2060 additionalProperties
=> 0,
2062 node
=> get_standard_option
('pve-node'),
2063 vmid
=> get_standard_option
('pve-vmid',
2064 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2065 skiplock
=> get_standard_option
('skiplock'),
2066 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2068 description
=> "Wait maximal timeout seconds.",
2074 description
=> "Do not deactivate storage volumes.",
2087 my $rpcenv = PVE
::RPCEnvironment
::get
();
2088 my $authuser = $rpcenv->get_user();
2090 my $node = extract_param
($param, 'node');
2091 my $vmid = extract_param
($param, 'vmid');
2093 my $skiplock = extract_param
($param, 'skiplock');
2094 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2095 if $skiplock && $authuser ne 'root@pam';
2097 my $keepActive = extract_param
($param, 'keepActive');
2098 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2099 if $keepActive && $authuser ne 'root@pam';
2101 my $migratedfrom = extract_param
($param, 'migratedfrom');
2102 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2103 if $migratedfrom && $authuser ne 'root@pam';
2106 my $storecfg = PVE
::Storage
::config
();
2108 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2113 print "Requesting HA stop for VM $vmid\n";
2115 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2116 PVE
::Tools
::run_command
($cmd);
2120 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2126 syslog
('info', "stop VM $vmid: $upid\n");
2128 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2129 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2133 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2137 __PACKAGE__-
>register_method({
2139 path
=> '{vmid}/status/reset',
2143 description
=> "Reset virtual machine.",
2145 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2148 additionalProperties
=> 0,
2150 node
=> get_standard_option
('pve-node'),
2151 vmid
=> get_standard_option
('pve-vmid',
2152 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2153 skiplock
=> get_standard_option
('skiplock'),
2162 my $rpcenv = PVE
::RPCEnvironment
::get
();
2164 my $authuser = $rpcenv->get_user();
2166 my $node = extract_param
($param, 'node');
2168 my $vmid = extract_param
($param, 'vmid');
2170 my $skiplock = extract_param
($param, 'skiplock');
2171 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2172 if $skiplock && $authuser ne 'root@pam';
2174 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2179 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2184 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2187 __PACKAGE__-
>register_method({
2188 name
=> 'vm_shutdown',
2189 path
=> '{vmid}/status/shutdown',
2193 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2194 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2196 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2199 additionalProperties
=> 0,
2201 node
=> get_standard_option
('pve-node'),
2202 vmid
=> get_standard_option
('pve-vmid',
2203 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2204 skiplock
=> get_standard_option
('skiplock'),
2206 description
=> "Wait maximal timeout seconds.",
2212 description
=> "Make sure the VM stops.",
2218 description
=> "Do not deactivate storage volumes.",
2231 my $rpcenv = PVE
::RPCEnvironment
::get
();
2232 my $authuser = $rpcenv->get_user();
2234 my $node = extract_param
($param, 'node');
2235 my $vmid = extract_param
($param, 'vmid');
2237 my $skiplock = extract_param
($param, 'skiplock');
2238 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2239 if $skiplock && $authuser ne 'root@pam';
2241 my $keepActive = extract_param
($param, 'keepActive');
2242 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2243 if $keepActive && $authuser ne 'root@pam';
2245 my $storecfg = PVE
::Storage
::config
();
2249 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2250 # otherwise, we will infer a shutdown command, but run into the timeout,
2251 # then when the vm is resumed, it will instantly shutdown
2253 # checking the qmp status here to get feedback to the gui/cli/api
2254 # and the status query should not take too long
2255 my $qmpstatus = eval {
2256 PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2260 if (!$err && $qmpstatus->{status
} eq "paused") {
2261 if ($param->{forceStop
}) {
2262 warn "VM is paused - stop instead of shutdown\n";
2265 die "VM is paused - cannot shutdown\n";
2269 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2271 my $timeout = $param->{timeout
} // 60;
2275 print "Requesting HA stop for VM $vmid\n";
2277 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2278 PVE
::Tools
::run_command
($cmd);
2282 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2289 syslog
('info', "shutdown VM $vmid: $upid\n");
2291 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2292 $shutdown, $param->{forceStop
}, $keepActive);
2296 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2300 __PACKAGE__-
>register_method({
2301 name
=> 'vm_reboot',
2302 path
=> '{vmid}/status/reboot',
2306 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2308 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2311 additionalProperties
=> 0,
2313 node
=> get_standard_option
('pve-node'),
2314 vmid
=> get_standard_option
('pve-vmid',
2315 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2317 description
=> "Wait maximal timeout seconds for the shutdown.",
2330 my $rpcenv = PVE
::RPCEnvironment
::get
();
2331 my $authuser = $rpcenv->get_user();
2333 my $node = extract_param
($param, 'node');
2334 my $vmid = extract_param
($param, 'vmid');
2336 my $qmpstatus = eval {
2337 PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2341 if (!$err && $qmpstatus->{status
} eq "paused") {
2342 die "VM is paused - cannot shutdown\n";
2345 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2350 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2351 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2355 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2358 __PACKAGE__-
>register_method({
2359 name
=> 'vm_suspend',
2360 path
=> '{vmid}/status/suspend',
2364 description
=> "Suspend virtual machine.",
2366 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2369 additionalProperties
=> 0,
2371 node
=> get_standard_option
('pve-node'),
2372 vmid
=> get_standard_option
('pve-vmid',
2373 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2374 skiplock
=> get_standard_option
('skiplock'),
2379 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2381 statestorage
=> get_standard_option
('pve-storage-id', {
2382 description
=> "The storage for the VM state",
2383 requires
=> 'todisk',
2385 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2395 my $rpcenv = PVE
::RPCEnvironment
::get
();
2396 my $authuser = $rpcenv->get_user();
2398 my $node = extract_param
($param, 'node');
2399 my $vmid = extract_param
($param, 'vmid');
2401 my $todisk = extract_param
($param, 'todisk') // 0;
2403 my $statestorage = extract_param
($param, 'statestorage');
2405 my $skiplock = extract_param
($param, 'skiplock');
2406 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2407 if $skiplock && $authuser ne 'root@pam';
2409 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2411 die "Cannot suspend HA managed VM to disk\n"
2412 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2417 syslog
('info', "suspend VM $vmid: $upid\n");
2419 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2424 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2425 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2428 __PACKAGE__-
>register_method({
2429 name
=> 'vm_resume',
2430 path
=> '{vmid}/status/resume',
2434 description
=> "Resume virtual machine.",
2436 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2439 additionalProperties
=> 0,
2441 node
=> get_standard_option
('pve-node'),
2442 vmid
=> get_standard_option
('pve-vmid',
2443 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2444 skiplock
=> get_standard_option
('skiplock'),
2445 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2455 my $rpcenv = PVE
::RPCEnvironment
::get
();
2457 my $authuser = $rpcenv->get_user();
2459 my $node = extract_param
($param, 'node');
2461 my $vmid = extract_param
($param, 'vmid');
2463 my $skiplock = extract_param
($param, 'skiplock');
2464 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2465 if $skiplock && $authuser ne 'root@pam';
2467 my $nocheck = extract_param
($param, 'nocheck');
2469 my $to_disk_suspended;
2471 PVE
::QemuConfig-
>lock_config($vmid, sub {
2472 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2473 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2477 die "VM $vmid not running\n"
2478 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2483 syslog
('info', "resume VM $vmid: $upid\n");
2485 if (!$to_disk_suspended) {
2486 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2488 my $storecfg = PVE
::Storage
::config
();
2489 PVE
::QemuServer
::vm_start
($storecfg, $vmid, undef, $skiplock);
2495 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2498 __PACKAGE__-
>register_method({
2499 name
=> 'vm_sendkey',
2500 path
=> '{vmid}/sendkey',
2504 description
=> "Send key event to virtual machine.",
2506 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2509 additionalProperties
=> 0,
2511 node
=> get_standard_option
('pve-node'),
2512 vmid
=> get_standard_option
('pve-vmid',
2513 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2514 skiplock
=> get_standard_option
('skiplock'),
2516 description
=> "The key (qemu monitor encoding).",
2521 returns
=> { type
=> 'null'},
2525 my $rpcenv = PVE
::RPCEnvironment
::get
();
2527 my $authuser = $rpcenv->get_user();
2529 my $node = extract_param
($param, 'node');
2531 my $vmid = extract_param
($param, 'vmid');
2533 my $skiplock = extract_param
($param, 'skiplock');
2534 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2535 if $skiplock && $authuser ne 'root@pam';
2537 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2542 __PACKAGE__-
>register_method({
2543 name
=> 'vm_feature',
2544 path
=> '{vmid}/feature',
2548 description
=> "Check if feature for virtual machine is available.",
2550 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2553 additionalProperties
=> 0,
2555 node
=> get_standard_option
('pve-node'),
2556 vmid
=> get_standard_option
('pve-vmid'),
2558 description
=> "Feature to check.",
2560 enum
=> [ 'snapshot', 'clone', 'copy' ],
2562 snapname
=> get_standard_option
('pve-snapshot-name', {
2570 hasFeature
=> { type
=> 'boolean' },
2573 items
=> { type
=> 'string' },
2580 my $node = extract_param
($param, 'node');
2582 my $vmid = extract_param
($param, 'vmid');
2584 my $snapname = extract_param
($param, 'snapname');
2586 my $feature = extract_param
($param, 'feature');
2588 my $running = PVE
::QemuServer
::check_running
($vmid);
2590 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2593 my $snap = $conf->{snapshots
}->{$snapname};
2594 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2597 my $storecfg = PVE
::Storage
::config
();
2599 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2600 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2603 hasFeature
=> $hasFeature,
2604 nodes
=> [ keys %$nodelist ],
2608 __PACKAGE__-
>register_method({
2610 path
=> '{vmid}/clone',
2614 description
=> "Create a copy of virtual machine/template.",
2616 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2617 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2618 "'Datastore.AllocateSpace' on any used storage.",
2621 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2623 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2624 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2629 additionalProperties
=> 0,
2631 node
=> get_standard_option
('pve-node'),
2632 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2633 newid
=> get_standard_option
('pve-vmid', {
2634 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2635 description
=> 'VMID for the clone.' }),
2638 type
=> 'string', format
=> 'dns-name',
2639 description
=> "Set a name for the new VM.",
2644 description
=> "Description for the new VM.",
2648 type
=> 'string', format
=> 'pve-poolid',
2649 description
=> "Add the new VM to the specified pool.",
2651 snapname
=> get_standard_option
('pve-snapshot-name', {
2654 storage
=> get_standard_option
('pve-storage-id', {
2655 description
=> "Target storage for full clone.",
2659 description
=> "Target format for file storage. Only valid for full clone.",
2662 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2667 description
=> "Create a full copy of all disks. This is always done when " .
2668 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2670 target
=> get_standard_option
('pve-node', {
2671 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2675 description
=> "Override I/O bandwidth limit (in KiB/s).",
2679 default => 'clone limit from datacenter or storage config',
2689 my $rpcenv = PVE
::RPCEnvironment
::get
();
2691 my $authuser = $rpcenv->get_user();
2693 my $node = extract_param
($param, 'node');
2695 my $vmid = extract_param
($param, 'vmid');
2697 my $newid = extract_param
($param, 'newid');
2699 my $pool = extract_param
($param, 'pool');
2701 if (defined($pool)) {
2702 $rpcenv->check_pool_exist($pool);
2705 my $snapname = extract_param
($param, 'snapname');
2707 my $storage = extract_param
($param, 'storage');
2709 my $format = extract_param
($param, 'format');
2711 my $target = extract_param
($param, 'target');
2713 my $localnode = PVE
::INotify
::nodename
();
2715 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2717 PVE
::Cluster
::check_node_exists
($target) if $target;
2719 my $storecfg = PVE
::Storage
::config
();
2722 # check if storage is enabled on local node
2723 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2725 # check if storage is available on target node
2726 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2727 # clone only works if target storage is shared
2728 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2729 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2733 PVE
::Cluster
::check_cfs_quorum
();
2735 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2737 # exclusive lock if VM is running - else shared lock is enough;
2738 my $shared_lock = $running ?
0 : 1;
2742 # do all tests after lock
2743 # we also try to do all tests before we fork the worker
2745 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2747 PVE
::QemuConfig-
>check_lock($conf);
2749 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2751 die "unexpected state change\n" if $verify_running != $running;
2753 die "snapshot '$snapname' does not exist\n"
2754 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2756 my $full = extract_param
($param, 'full');
2757 if (!defined($full)) {
2758 $full = !PVE
::QemuConfig-
>is_template($conf);
2761 die "parameter 'storage' not allowed for linked clones\n"
2762 if defined($storage) && !$full;
2764 die "parameter 'format' not allowed for linked clones\n"
2765 if defined($format) && !$full;
2767 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2769 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2771 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2773 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2775 die "unable to create VM $newid: config file already exists\n"
2778 my $newconf = { lock => 'clone' };
2783 foreach my $opt (keys %$oldconf) {
2784 my $value = $oldconf->{$opt};
2786 # do not copy snapshot related info
2787 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2788 $opt eq 'vmstate' || $opt eq 'snapstate';
2790 # no need to copy unused images, because VMID(owner) changes anyways
2791 next if $opt =~ m/^unused\d+$/;
2793 # always change MAC! address
2794 if ($opt =~ m/^net(\d+)$/) {
2795 my $net = PVE
::QemuServer
::parse_net
($value);
2796 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2797 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2798 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2799 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2800 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2801 die "unable to parse drive options for '$opt'\n" if !$drive;
2802 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2803 $newconf->{$opt} = $value; # simply copy configuration
2806 die "Full clone feature is not supported for drive '$opt'\n"
2807 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2808 $fullclone->{$opt} = 1;
2810 # not full means clone instead of copy
2811 die "Linked clone feature is not supported for drive '$opt'\n"
2812 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2814 $drives->{$opt} = $drive;
2815 push @$vollist, $drive->{file
};
2818 # copy everything else
2819 $newconf->{$opt} = $value;
2823 # auto generate a new uuid
2824 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2825 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2826 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2828 # auto generate a new vmgenid if the option was set
2829 if ($newconf->{vmgenid
}) {
2830 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2833 delete $newconf->{template
};
2835 if ($param->{name
}) {
2836 $newconf->{name
} = $param->{name
};
2838 if ($oldconf->{name
}) {
2839 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2841 $newconf->{name
} = "Copy-of-VM-$vmid";
2845 if ($param->{description
}) {
2846 $newconf->{description
} = $param->{description
};
2849 # create empty/temp config - this fails if VM already exists on other node
2850 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2855 my $newvollist = [];
2862 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2864 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2866 my $bwlimit = extract_param
($param, 'bwlimit');
2868 my $total_jobs = scalar(keys %{$drives});
2871 foreach my $opt (keys %$drives) {
2872 my $drive = $drives->{$opt};
2873 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2875 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
2876 my $storage_list = [ $src_sid ];
2877 push @$storage_list, $storage if defined($storage);
2878 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
2880 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2881 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2882 $jobs, $skipcomplete, $oldconf->{agent
}, $clonelimit);
2884 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2886 PVE
::QemuConfig-
>write_config($newid, $newconf);
2890 delete $newconf->{lock};
2892 # do not write pending changes
2893 if (my @changes = keys %{$newconf->{pending
}}) {
2894 my $pending = join(',', @changes);
2895 warn "found pending changes for '$pending', discarding for clone\n";
2896 delete $newconf->{pending
};
2899 PVE
::QemuConfig-
>write_config($newid, $newconf);
2902 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2903 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2904 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2906 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2907 die "Failed to move config to node '$target' - rename failed: $!\n"
2908 if !rename($conffile, $newconffile);
2911 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2916 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2918 sleep 1; # some storage like rbd need to wait before release volume - really?
2920 foreach my $volid (@$newvollist) {
2921 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2924 die "clone failed: $err";
2930 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2932 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2935 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2936 # Aquire exclusive lock lock for $newid
2937 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2942 __PACKAGE__-
>register_method({
2943 name
=> 'move_vm_disk',
2944 path
=> '{vmid}/move_disk',
2948 description
=> "Move volume to different storage.",
2950 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2952 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2953 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2957 additionalProperties
=> 0,
2959 node
=> get_standard_option
('pve-node'),
2960 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2963 description
=> "The disk you want to move.",
2964 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2966 storage
=> get_standard_option
('pve-storage-id', {
2967 description
=> "Target storage.",
2968 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2972 description
=> "Target Format.",
2973 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2978 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2984 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2989 description
=> "Override I/O bandwidth limit (in KiB/s).",
2993 default => 'move limit from datacenter or storage config',
2999 description
=> "the task ID.",
3004 my $rpcenv = PVE
::RPCEnvironment
::get
();
3006 my $authuser = $rpcenv->get_user();
3008 my $node = extract_param
($param, 'node');
3010 my $vmid = extract_param
($param, 'vmid');
3012 my $digest = extract_param
($param, 'digest');
3014 my $disk = extract_param
($param, 'disk');
3016 my $storeid = extract_param
($param, 'storage');
3018 my $format = extract_param
($param, 'format');
3020 my $storecfg = PVE
::Storage
::config
();
3022 my $updatefn = sub {
3024 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3026 PVE
::QemuConfig-
>check_lock($conf);
3028 die "checksum missmatch (file change by other user?)\n"
3029 if $digest && $digest ne $conf->{digest
};
3031 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3033 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3035 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
3037 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3040 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3041 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3045 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
3046 (!$format || !$oldfmt || $oldfmt eq $format);
3048 # this only checks snapshots because $disk is passed!
3049 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3050 die "you can't move a disk with snapshots and delete the source\n"
3051 if $snapshotted && $param->{delete};
3053 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3055 my $running = PVE
::QemuServer
::check_running
($vmid);
3057 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3061 my $newvollist = [];
3067 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3069 warn "moving disk with snapshots, snapshots will not be moved!\n"
3072 my $bwlimit = extract_param
($param, 'bwlimit');
3073 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3075 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3076 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit);
3078 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
3080 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3082 # convert moved disk to base if part of template
3083 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3084 if PVE
::QemuConfig-
>is_template($conf);
3086 PVE
::QemuConfig-
>write_config($vmid, $conf);
3088 if ($running && PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
} && PVE
::QemuServer
::qga_check_running
($vmid)) {
3089 eval { PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-fstrim"); };
3093 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3094 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3101 foreach my $volid (@$newvollist) {
3102 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3105 die "storage migration failed: $err";
3108 if ($param->{delete}) {
3110 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3111 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3117 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3120 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3123 my $check_vm_disks_local = sub {
3124 my ($storecfg, $vmconf, $vmid) = @_;
3126 my $local_disks = {};
3128 # add some more information to the disks e.g. cdrom
3129 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3130 my ($volid, $attr) = @_;
3132 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3134 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3135 return if $scfg->{shared
};
3137 # The shared attr here is just a special case where the vdisk
3138 # is marked as shared manually
3139 return if $attr->{shared
};
3140 return if $attr->{cdrom
} and $volid eq "none";
3142 if (exists $local_disks->{$volid}) {
3143 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3145 $local_disks->{$volid} = $attr;
3146 # ensure volid is present in case it's needed
3147 $local_disks->{$volid}->{volid
} = $volid;
3151 return $local_disks;
3154 __PACKAGE__-
>register_method({
3155 name
=> 'migrate_vm_precondition',
3156 path
=> '{vmid}/migrate',
3160 description
=> "Get preconditions for migration.",
3162 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3165 additionalProperties
=> 0,
3167 node
=> get_standard_option
('pve-node'),
3168 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3169 target
=> get_standard_option
('pve-node', {
3170 description
=> "Target node.",
3171 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3179 running
=> { type
=> 'boolean' },
3183 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3185 not_allowed_nodes
=> {
3188 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3192 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3194 local_resources
=> {
3196 description
=> "List local resources e.g. pci, usb"
3203 my $rpcenv = PVE
::RPCEnvironment
::get
();
3205 my $authuser = $rpcenv->get_user();
3207 PVE
::Cluster
::check_cfs_quorum
();
3211 my $vmid = extract_param
($param, 'vmid');
3212 my $target = extract_param
($param, 'target');
3213 my $localnode = PVE
::INotify
::nodename
();
3217 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3218 my $storecfg = PVE
::Storage
::config
();
3221 # try to detect errors early
3222 PVE
::QemuConfig-
>check_lock($vmconf);
3224 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3226 # if vm is not running, return target nodes where local storage is available
3227 # for offline migration
3228 if (!$res->{running
}) {
3229 $res->{allowed_nodes
} = [];
3230 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3231 delete $checked_nodes->{$localnode};
3233 foreach my $node (keys %$checked_nodes) {
3234 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3235 push @{$res->{allowed_nodes
}}, $node;
3239 $res->{not_allowed_nodes
} = $checked_nodes;
3243 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3244 $res->{local_disks
} = [ values %$local_disks ];;
3246 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3248 $res->{local_resources
} = $local_resources;
3255 __PACKAGE__-
>register_method({
3256 name
=> 'migrate_vm',
3257 path
=> '{vmid}/migrate',
3261 description
=> "Migrate virtual machine. Creates a new migration task.",
3263 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3266 additionalProperties
=> 0,
3268 node
=> get_standard_option
('pve-node'),
3269 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3270 target
=> get_standard_option
('pve-node', {
3271 description
=> "Target node.",
3272 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3276 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3281 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3286 enum
=> ['secure', 'insecure'],
3287 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3290 migration_network
=> {
3291 type
=> 'string', format
=> 'CIDR',
3292 description
=> "CIDR of the (sub) network that is used for migration.",
3295 "with-local-disks" => {
3297 description
=> "Enable live storage migration for local disk",
3300 targetstorage
=> get_standard_option
('pve-storage-id', {
3301 description
=> "Default target storage.",
3303 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3306 description
=> "Override I/O bandwidth limit (in KiB/s).",
3310 default => 'migrate limit from datacenter or storage config',
3316 description
=> "the task ID.",
3321 my $rpcenv = PVE
::RPCEnvironment
::get
();
3322 my $authuser = $rpcenv->get_user();
3324 my $target = extract_param
($param, 'target');
3326 my $localnode = PVE
::INotify
::nodename
();
3327 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3329 PVE
::Cluster
::check_cfs_quorum
();
3331 PVE
::Cluster
::check_node_exists
($target);
3333 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3335 my $vmid = extract_param
($param, 'vmid');
3337 raise_param_exc
({ force
=> "Only root may use this option." })
3338 if $param->{force
} && $authuser ne 'root@pam';
3340 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3341 if $param->{migration_type
} && $authuser ne 'root@pam';
3343 # allow root only until better network permissions are available
3344 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3345 if $param->{migration_network
} && $authuser ne 'root@pam';
3348 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3350 # try to detect errors early
3352 PVE
::QemuConfig-
>check_lock($conf);
3354 if (PVE
::QemuServer
::check_running
($vmid)) {
3355 die "can't migrate running VM without --online\n" if !$param->{online
};
3357 warn "VM isn't running. Doing offline migration instead\n." if $param->{online
};
3358 $param->{online
} = 0;
3361 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3362 if !$param->{online
} && $param->{targetstorage
};
3364 my $storecfg = PVE
::Storage
::config
();
3366 if( $param->{targetstorage
}) {
3367 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3369 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3372 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3377 print "Requesting HA migration for VM $vmid to node $target\n";
3379 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3380 PVE
::Tools
::run_command
($cmd);
3384 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3389 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3393 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3396 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3401 __PACKAGE__-
>register_method({
3403 path
=> '{vmid}/monitor',
3407 description
=> "Execute Qemu monitor commands.",
3409 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3410 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3413 additionalProperties
=> 0,
3415 node
=> get_standard_option
('pve-node'),
3416 vmid
=> get_standard_option
('pve-vmid'),
3419 description
=> "The monitor command.",
3423 returns
=> { type
=> 'string'},
3427 my $rpcenv = PVE
::RPCEnvironment
::get
();
3428 my $authuser = $rpcenv->get_user();
3431 my $command = shift;
3432 return $command =~ m/^\s*info(\s+|$)/
3433 || $command =~ m/^\s*help\s*$/;
3436 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3437 if !&$is_ro($param->{command
});
3439 my $vmid = $param->{vmid
};
3441 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3445 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3447 $res = "ERROR: $@" if $@;
3452 __PACKAGE__-
>register_method({
3453 name
=> 'resize_vm',
3454 path
=> '{vmid}/resize',
3458 description
=> "Extend volume size.",
3460 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3463 additionalProperties
=> 0,
3465 node
=> get_standard_option
('pve-node'),
3466 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3467 skiplock
=> get_standard_option
('skiplock'),
3470 description
=> "The disk you want to resize.",
3471 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3475 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3476 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.",
3480 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3486 returns
=> { type
=> 'null'},
3490 my $rpcenv = PVE
::RPCEnvironment
::get
();
3492 my $authuser = $rpcenv->get_user();
3494 my $node = extract_param
($param, 'node');
3496 my $vmid = extract_param
($param, 'vmid');
3498 my $digest = extract_param
($param, 'digest');
3500 my $disk = extract_param
($param, 'disk');
3502 my $sizestr = extract_param
($param, 'size');
3504 my $skiplock = extract_param
($param, 'skiplock');
3505 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3506 if $skiplock && $authuser ne 'root@pam';
3508 my $storecfg = PVE
::Storage
::config
();
3510 my $updatefn = sub {
3512 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3514 die "checksum missmatch (file change by other user?)\n"
3515 if $digest && $digest ne $conf->{digest
};
3516 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3518 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3520 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3522 my (undef, undef, undef, undef, undef, undef, $format) =
3523 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3525 die "can't resize volume: $disk if snapshot exists\n"
3526 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3528 my $volid = $drive->{file
};
3530 die "disk '$disk' has no associated volume\n" if !$volid;
3532 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3534 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3536 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3538 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3539 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3541 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3543 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3544 my ($ext, $newsize, $unit) = ($1, $2, $4);
3547 $newsize = $newsize * 1024;
3548 } elsif ($unit eq 'M') {
3549 $newsize = $newsize * 1024 * 1024;
3550 } elsif ($unit eq 'G') {
3551 $newsize = $newsize * 1024 * 1024 * 1024;
3552 } elsif ($unit eq 'T') {
3553 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3556 $newsize += $size if $ext;
3557 $newsize = int($newsize);
3559 die "shrinking disks is not supported\n" if $newsize < $size;
3561 return if $size == $newsize;
3563 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3565 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3567 $drive->{size
} = $newsize;
3568 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3570 PVE
::QemuConfig-
>write_config($vmid, $conf);
3573 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3577 __PACKAGE__-
>register_method({
3578 name
=> 'snapshot_list',
3579 path
=> '{vmid}/snapshot',
3581 description
=> "List all snapshots.",
3583 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3586 protected
=> 1, # qemu pid files are only readable by root
3588 additionalProperties
=> 0,
3590 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3591 node
=> get_standard_option
('pve-node'),
3600 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3604 description
=> "Snapshot includes RAM.",
3609 description
=> "Snapshot description.",
3613 description
=> "Snapshot creation time",
3615 renderer
=> 'timestamp',
3619 description
=> "Parent snapshot identifier.",
3625 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3630 my $vmid = $param->{vmid
};
3632 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3633 my $snaphash = $conf->{snapshots
} || {};
3637 foreach my $name (keys %$snaphash) {
3638 my $d = $snaphash->{$name};
3641 snaptime
=> $d->{snaptime
} || 0,
3642 vmstate
=> $d->{vmstate
} ?
1 : 0,
3643 description
=> $d->{description
} || '',
3645 $item->{parent
} = $d->{parent
} if $d->{parent
};
3646 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3650 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3653 digest
=> $conf->{digest
},
3654 running
=> $running,
3655 description
=> "You are here!",
3657 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3659 push @$res, $current;
3664 __PACKAGE__-
>register_method({
3666 path
=> '{vmid}/snapshot',
3670 description
=> "Snapshot a VM.",
3672 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3675 additionalProperties
=> 0,
3677 node
=> get_standard_option
('pve-node'),
3678 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3679 snapname
=> get_standard_option
('pve-snapshot-name'),
3683 description
=> "Save the vmstate",
3688 description
=> "A textual description or comment.",
3694 description
=> "the task ID.",
3699 my $rpcenv = PVE
::RPCEnvironment
::get
();
3701 my $authuser = $rpcenv->get_user();
3703 my $node = extract_param
($param, 'node');
3705 my $vmid = extract_param
($param, 'vmid');
3707 my $snapname = extract_param
($param, 'snapname');
3709 die "unable to use snapshot name 'current' (reserved name)\n"
3710 if $snapname eq 'current';
3713 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3714 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3715 $param->{description
});
3718 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3721 __PACKAGE__-
>register_method({
3722 name
=> 'snapshot_cmd_idx',
3723 path
=> '{vmid}/snapshot/{snapname}',
3730 additionalProperties
=> 0,
3732 vmid
=> get_standard_option
('pve-vmid'),
3733 node
=> get_standard_option
('pve-node'),
3734 snapname
=> get_standard_option
('pve-snapshot-name'),
3743 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3750 push @$res, { cmd
=> 'rollback' };
3751 push @$res, { cmd
=> 'config' };
3756 __PACKAGE__-
>register_method({
3757 name
=> 'update_snapshot_config',
3758 path
=> '{vmid}/snapshot/{snapname}/config',
3762 description
=> "Update snapshot metadata.",
3764 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3767 additionalProperties
=> 0,
3769 node
=> get_standard_option
('pve-node'),
3770 vmid
=> get_standard_option
('pve-vmid'),
3771 snapname
=> get_standard_option
('pve-snapshot-name'),
3775 description
=> "A textual description or comment.",
3779 returns
=> { type
=> 'null' },
3783 my $rpcenv = PVE
::RPCEnvironment
::get
();
3785 my $authuser = $rpcenv->get_user();
3787 my $vmid = extract_param
($param, 'vmid');
3789 my $snapname = extract_param
($param, 'snapname');
3791 return undef if !defined($param->{description
});
3793 my $updatefn = sub {
3795 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3797 PVE
::QemuConfig-
>check_lock($conf);
3799 my $snap = $conf->{snapshots
}->{$snapname};
3801 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3803 $snap->{description
} = $param->{description
} if defined($param->{description
});
3805 PVE
::QemuConfig-
>write_config($vmid, $conf);
3808 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3813 __PACKAGE__-
>register_method({
3814 name
=> 'get_snapshot_config',
3815 path
=> '{vmid}/snapshot/{snapname}/config',
3818 description
=> "Get snapshot configuration",
3820 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3823 additionalProperties
=> 0,
3825 node
=> get_standard_option
('pve-node'),
3826 vmid
=> get_standard_option
('pve-vmid'),
3827 snapname
=> get_standard_option
('pve-snapshot-name'),
3830 returns
=> { type
=> "object" },
3834 my $rpcenv = PVE
::RPCEnvironment
::get
();
3836 my $authuser = $rpcenv->get_user();
3838 my $vmid = extract_param
($param, 'vmid');
3840 my $snapname = extract_param
($param, 'snapname');
3842 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3844 my $snap = $conf->{snapshots
}->{$snapname};
3846 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3851 __PACKAGE__-
>register_method({
3853 path
=> '{vmid}/snapshot/{snapname}/rollback',
3857 description
=> "Rollback VM state to specified snapshot.",
3859 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3862 additionalProperties
=> 0,
3864 node
=> get_standard_option
('pve-node'),
3865 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3866 snapname
=> get_standard_option
('pve-snapshot-name'),
3871 description
=> "the task ID.",
3876 my $rpcenv = PVE
::RPCEnvironment
::get
();
3878 my $authuser = $rpcenv->get_user();
3880 my $node = extract_param
($param, 'node');
3882 my $vmid = extract_param
($param, 'vmid');
3884 my $snapname = extract_param
($param, 'snapname');
3887 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3888 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3892 # hold migration lock, this makes sure that nobody create replication snapshots
3893 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3896 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3899 __PACKAGE__-
>register_method({
3900 name
=> 'delsnapshot',
3901 path
=> '{vmid}/snapshot/{snapname}',
3905 description
=> "Delete a VM snapshot.",
3907 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3910 additionalProperties
=> 0,
3912 node
=> get_standard_option
('pve-node'),
3913 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3914 snapname
=> get_standard_option
('pve-snapshot-name'),
3918 description
=> "For removal from config file, even if removing disk snapshots fails.",
3924 description
=> "the task ID.",
3929 my $rpcenv = PVE
::RPCEnvironment
::get
();
3931 my $authuser = $rpcenv->get_user();
3933 my $node = extract_param
($param, 'node');
3935 my $vmid = extract_param
($param, 'vmid');
3937 my $snapname = extract_param
($param, 'snapname');
3940 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3941 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3944 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3947 __PACKAGE__-
>register_method({
3949 path
=> '{vmid}/template',
3953 description
=> "Create a Template.",
3955 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3956 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3959 additionalProperties
=> 0,
3961 node
=> get_standard_option
('pve-node'),
3962 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3966 description
=> "If you want to convert only 1 disk to base image.",
3967 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3972 returns
=> { type
=> 'null'},
3976 my $rpcenv = PVE
::RPCEnvironment
::get
();
3978 my $authuser = $rpcenv->get_user();
3980 my $node = extract_param
($param, 'node');
3982 my $vmid = extract_param
($param, 'vmid');
3984 my $disk = extract_param
($param, 'disk');
3986 my $updatefn = sub {
3988 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3990 PVE
::QemuConfig-
>check_lock($conf);
3992 die "unable to create template, because VM contains snapshots\n"
3993 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3995 die "you can't convert a template to a template\n"
3996 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3998 die "you can't convert a VM to template if VM is running\n"
3999 if PVE
::QemuServer
::check_running
($vmid);
4002 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4005 $conf->{template
} = 1;
4006 PVE
::QemuConfig-
>write_config($vmid, $conf);
4008 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4011 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4015 __PACKAGE__-
>register_method({
4016 name
=> 'cloudinit_generated_config_dump',
4017 path
=> '{vmid}/cloudinit/dump',
4020 description
=> "Get automatically generated cloudinit config.",
4022 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4025 additionalProperties
=> 0,
4027 node
=> get_standard_option
('pve-node'),
4028 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4030 description
=> 'Config type.',
4032 enum
=> ['user', 'network', 'meta'],
4042 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4044 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});