1 package PVE
::API2
::Qemu
;
12 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
14 use PVE
::Tools
qw(extract_param);
15 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
17 use PVE
::JSONSchema
qw(get_standard_option);
19 use PVE
::ReplicationConfig
;
20 use PVE
::GuestHelpers
;
24 use PVE
::RPCEnvironment
;
25 use PVE
::AccessControl
;
29 use PVE
::API2
::Firewall
::VM
;
30 use PVE
::API2
::Qemu
::Agent
;
31 use PVE
::VZDump
::Plugin
;
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
();
468 my $authuser = $rpcenv->get_user();
470 my $node = extract_param
($param, 'node');
472 my $vmid = extract_param
($param, 'vmid');
474 my $archive = extract_param
($param, 'archive');
475 my $is_restore = !!$archive;
477 my $storage = extract_param
($param, 'storage');
479 my $force = extract_param
($param, 'force');
481 my $unique = extract_param
($param, 'unique');
483 my $pool = extract_param
($param, 'pool');
485 my $bwlimit = extract_param
($param, 'bwlimit');
487 my $start_after_create = extract_param
($param, 'start');
489 my $filename = PVE
::QemuConfig-
>config_file($vmid);
491 my $storecfg = PVE
::Storage
::config
();
493 if (defined(my $ssh_keys = $param->{sshkeys
})) {
494 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
495 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
498 PVE
::Cluster
::check_cfs_quorum
();
500 if (defined($pool)) {
501 $rpcenv->check_pool_exist($pool);
504 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
505 if defined($storage);
507 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
509 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
511 } elsif ($archive && $force && (-f
$filename) &&
512 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
513 # OK: user has VM.Backup permissions, and want to restore an existing VM
519 &$resolve_cdrom_alias($param);
521 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
523 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
525 foreach my $opt (keys %$param) {
526 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
527 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
528 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
530 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
531 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
535 PVE
::QemuServer
::add_random_macs
($param);
537 my $keystr = join(' ', keys %$param);
538 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
540 if ($archive eq '-') {
541 die "pipe requires cli environment\n"
542 if $rpcenv->{type
} ne 'cli';
544 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
545 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
549 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
551 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
552 die "$emsg $@" if $@;
554 my $restorefn = sub {
555 my $conf = PVE
::QemuConfig-
>load_config($vmid);
557 PVE
::QemuConfig-
>check_protection($conf, $emsg);
559 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
562 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
568 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
569 # Convert restored VM to template if backup was VM template
570 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
571 warn "Convert to template.\n";
572 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
576 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
578 if ($start_after_create) {
579 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
584 # ensure no old replication state are exists
585 PVE
::ReplicationState
::delete_guest_states
($vmid);
587 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
591 # ensure no old replication state are exists
592 PVE
::ReplicationState
::delete_guest_states
($vmid);
600 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
604 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
606 if (!$conf->{bootdisk
}) {
607 my $firstdisk = PVE
::QemuServer
::resolve_first_disk
($conf);
608 $conf->{bootdisk
} = $firstdisk if $firstdisk;
611 # auto generate uuid if user did not specify smbios1 option
612 if (!$conf->{smbios1
}) {
613 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
616 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
617 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
620 PVE
::QemuConfig-
>write_config($vmid, $conf);
626 foreach my $volid (@$vollist) {
627 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
633 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
636 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
638 if ($start_after_create) {
639 print "Execute autostart\n";
640 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
645 my ($code, $worker_name);
647 $worker_name = 'qmrestore';
649 eval { $restorefn->() };
651 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
657 $worker_name = 'qmcreate';
659 eval { $createfn->() };
662 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
663 unlink($conffile) or die "failed to remove config file: $!\n";
671 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
674 __PACKAGE__-
>register_method({
679 description
=> "Directory index",
684 additionalProperties
=> 0,
686 node
=> get_standard_option
('pve-node'),
687 vmid
=> get_standard_option
('pve-vmid'),
695 subdir
=> { type
=> 'string' },
698 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
704 { subdir
=> 'config' },
705 { subdir
=> 'pending' },
706 { subdir
=> 'status' },
707 { subdir
=> 'unlink' },
708 { subdir
=> 'vncproxy' },
709 { subdir
=> 'termproxy' },
710 { subdir
=> 'migrate' },
711 { subdir
=> 'resize' },
712 { subdir
=> 'move' },
714 { subdir
=> 'rrddata' },
715 { subdir
=> 'monitor' },
716 { subdir
=> 'agent' },
717 { subdir
=> 'snapshot' },
718 { subdir
=> 'spiceproxy' },
719 { subdir
=> 'sendkey' },
720 { subdir
=> 'firewall' },
726 __PACKAGE__-
>register_method ({
727 subclass
=> "PVE::API2::Firewall::VM",
728 path
=> '{vmid}/firewall',
731 __PACKAGE__-
>register_method ({
732 subclass
=> "PVE::API2::Qemu::Agent",
733 path
=> '{vmid}/agent',
736 __PACKAGE__-
>register_method({
738 path
=> '{vmid}/rrd',
740 protected
=> 1, # fixme: can we avoid that?
742 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
744 description
=> "Read VM RRD statistics (returns PNG)",
746 additionalProperties
=> 0,
748 node
=> get_standard_option
('pve-node'),
749 vmid
=> get_standard_option
('pve-vmid'),
751 description
=> "Specify the time frame you are interested in.",
753 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
756 description
=> "The list of datasources you want to display.",
757 type
=> 'string', format
=> 'pve-configid-list',
760 description
=> "The RRD consolidation function",
762 enum
=> [ 'AVERAGE', 'MAX' ],
770 filename
=> { type
=> 'string' },
776 return PVE
::Cluster
::create_rrd_graph
(
777 "pve2-vm/$param->{vmid}", $param->{timeframe
},
778 $param->{ds
}, $param->{cf
});
782 __PACKAGE__-
>register_method({
784 path
=> '{vmid}/rrddata',
786 protected
=> 1, # fixme: can we avoid that?
788 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
790 description
=> "Read VM RRD statistics",
792 additionalProperties
=> 0,
794 node
=> get_standard_option
('pve-node'),
795 vmid
=> get_standard_option
('pve-vmid'),
797 description
=> "Specify the time frame you are interested in.",
799 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
802 description
=> "The RRD consolidation function",
804 enum
=> [ 'AVERAGE', 'MAX' ],
819 return PVE
::Cluster
::create_rrd_data
(
820 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
824 __PACKAGE__-
>register_method({
826 path
=> '{vmid}/config',
829 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
831 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
834 additionalProperties
=> 0,
836 node
=> get_standard_option
('pve-node'),
837 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
839 description
=> "Get current values (instead of pending values).",
844 snapshot
=> get_standard_option
('pve-snapshot-name', {
845 description
=> "Fetch config values from given snapshot.",
848 my ($cmd, $pname, $cur, $args) = @_;
849 PVE
::QemuConfig-
>snapshot_list($args->[0]);
855 description
=> "The current VM configuration.",
857 properties
=> PVE
::QemuServer
::json_config_properties
({
860 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
867 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
868 current
=> "cannot use 'snapshot' parameter with 'current'"})
869 if ($param->{snapshot
} && $param->{current
});
872 if ($param->{snapshot
}) {
873 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
875 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
877 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
882 __PACKAGE__-
>register_method({
883 name
=> 'vm_pending',
884 path
=> '{vmid}/pending',
887 description
=> "Get virtual machine configuration, including pending changes.",
889 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
892 additionalProperties
=> 0,
894 node
=> get_standard_option
('pve-node'),
895 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
904 description
=> "Configuration option name.",
908 description
=> "Current value.",
913 description
=> "Pending value.",
918 description
=> "Indicates a pending delete request if present and not 0. " .
919 "The value 2 indicates a force-delete request.",
931 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
933 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
935 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
936 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
938 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
941 # POST/PUT {vmid}/config implementation
943 # The original API used PUT (idempotent) an we assumed that all operations
944 # are fast. But it turned out that almost any configuration change can
945 # involve hot-plug actions, or disk alloc/free. Such actions can take long
946 # time to complete and have side effects (not idempotent).
948 # The new implementation uses POST and forks a worker process. We added
949 # a new option 'background_delay'. If specified we wait up to
950 # 'background_delay' second for the worker task to complete. It returns null
951 # if the task is finished within that time, else we return the UPID.
953 my $update_vm_api = sub {
954 my ($param, $sync) = @_;
956 my $rpcenv = PVE
::RPCEnvironment
::get
();
958 my $authuser = $rpcenv->get_user();
960 my $node = extract_param
($param, 'node');
962 my $vmid = extract_param
($param, 'vmid');
964 my $digest = extract_param
($param, 'digest');
966 my $background_delay = extract_param
($param, 'background_delay');
968 if (defined(my $cipassword = $param->{cipassword
})) {
969 # Same logic as in cloud-init (but with the regex fixed...)
970 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
971 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
974 my @paramarr = (); # used for log message
975 foreach my $key (sort keys %$param) {
976 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
977 push @paramarr, "-$key", $value;
980 my $skiplock = extract_param
($param, 'skiplock');
981 raise_param_exc
({ skiplock
=> "Only root may use this option." })
982 if $skiplock && $authuser ne 'root@pam';
984 my $delete_str = extract_param
($param, 'delete');
986 my $revert_str = extract_param
($param, 'revert');
988 my $force = extract_param
($param, 'force');
990 if (defined(my $ssh_keys = $param->{sshkeys
})) {
991 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
992 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
995 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
997 my $storecfg = PVE
::Storage
::config
();
999 my $defaults = PVE
::QemuServer
::load_defaults
();
1001 &$resolve_cdrom_alias($param);
1003 # now try to verify all parameters
1006 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1007 if (!PVE
::QemuServer
::option_exists
($opt)) {
1008 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1011 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1012 "-revert $opt' at the same time" })
1013 if defined($param->{$opt});
1015 $revert->{$opt} = 1;
1019 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1020 $opt = 'ide2' if $opt eq 'cdrom';
1022 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1023 "-delete $opt' at the same time" })
1024 if defined($param->{$opt});
1026 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1027 "-revert $opt' at the same time" })
1030 if (!PVE
::QemuServer
::option_exists
($opt)) {
1031 raise_param_exc
({ delete => "unknown option '$opt'" });
1037 my $repl_conf = PVE
::ReplicationConfig-
>new();
1038 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1039 my $check_replication = sub {
1041 return if !$is_replicated;
1042 my $volid = $drive->{file
};
1043 return if !$volid || !($drive->{replicate
}//1);
1044 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1046 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1047 return if $volname eq 'cloudinit';
1050 if ($volid =~ $NEW_DISK_RE) {
1052 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1054 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1056 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1057 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1058 return if $scfg->{shared
};
1059 die "cannot add non-replicatable volume to a replicated VM\n";
1062 foreach my $opt (keys %$param) {
1063 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1064 # cleanup drive path
1065 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1066 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1067 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1068 $check_replication->($drive);
1069 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1070 } elsif ($opt =~ m/^net(\d+)$/) {
1072 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1073 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1074 } elsif ($opt eq 'vmgenid') {
1075 if ($param->{$opt} eq '1') {
1076 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1078 } elsif ($opt eq 'hookscript') {
1079 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1080 raise_param_exc
({ $opt => $@ }) if $@;
1084 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1086 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1088 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1090 my $updatefn = sub {
1092 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1094 die "checksum missmatch (file change by other user?)\n"
1095 if $digest && $digest ne $conf->{digest
};
1097 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1099 foreach my $opt (keys %$revert) {
1100 if (defined($conf->{$opt})) {
1101 $param->{$opt} = $conf->{$opt};
1102 } elsif (defined($conf->{pending
}->{$opt})) {
1107 if ($param->{memory
} || defined($param->{balloon
})) {
1108 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1109 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1111 die "balloon value too large (must be smaller than assigned memory)\n"
1112 if $balloon && $balloon > $maxmem;
1115 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1119 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1121 # write updates to pending section
1123 my $modified = {}; # record what $option we modify
1125 foreach my $opt (@delete) {
1126 $modified->{$opt} = 1;
1127 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1129 # value of what we want to delete, independent if pending or not
1130 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1131 if (!defined($val)) {
1132 warn "cannot delete '$opt' - not set in current configuration!\n";
1133 $modified->{$opt} = 0;
1136 my $is_pending_val = defined($conf->{pending
}->{$opt});
1137 delete $conf->{pending
}->{$opt};
1139 if ($opt =~ m/^unused/) {
1140 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1141 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1142 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1143 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1144 delete $conf->{$opt};
1145 PVE
::QemuConfig-
>write_config($vmid, $conf);
1147 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1148 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1149 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1150 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1152 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1153 PVE
::QemuConfig-
>write_config($vmid, $conf);
1154 } elsif ($opt =~ m/^serial\d+$/) {
1155 if ($val eq 'socket') {
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);
1162 } elsif ($opt =~ m/^usb\d+$/) {
1163 if ($val =~ m/spice/) {
1164 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1165 } elsif ($authuser ne 'root@pam') {
1166 die "only root can delete '$opt' config for real devices\n";
1168 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1169 PVE
::QemuConfig-
>write_config($vmid, $conf);
1171 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1172 PVE
::QemuConfig-
>write_config($vmid, $conf);
1176 foreach my $opt (keys %$param) { # add/change
1177 $modified->{$opt} = 1;
1178 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1179 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1181 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
1183 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1184 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1185 # FIXME: cloudinit: CDROM or Disk?
1186 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1187 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1189 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1191 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1192 if defined($conf->{pending
}->{$opt});
1194 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1195 } elsif ($opt =~ m/^serial\d+/) {
1196 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1197 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1198 } elsif ($authuser ne 'root@pam') {
1199 die "only root can modify '$opt' config for real devices\n";
1201 $conf->{pending
}->{$opt} = $param->{$opt};
1202 } elsif ($opt =~ m/^usb\d+/) {
1203 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1204 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1205 } elsif ($authuser ne 'root@pam') {
1206 die "only root can modify '$opt' config for real devices\n";
1208 $conf->{pending
}->{$opt} = $param->{$opt};
1210 $conf->{pending
}->{$opt} = $param->{$opt};
1212 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1213 PVE
::QemuConfig-
>write_config($vmid, $conf);
1216 # remove pending changes when nothing changed
1217 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1218 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1219 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1221 return if !scalar(keys %{$conf->{pending
}});
1223 my $running = PVE
::QemuServer
::check_running
($vmid);
1225 # apply pending changes
1227 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1231 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1232 raise_param_exc
($errors) if scalar(keys %$errors);
1234 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1244 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1246 if ($background_delay) {
1248 # Note: It would be better to do that in the Event based HTTPServer
1249 # to avoid blocking call to sleep.
1251 my $end_time = time() + $background_delay;
1253 my $task = PVE
::Tools
::upid_decode
($upid);
1256 while (time() < $end_time) {
1257 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1259 sleep(1); # this gets interrupted when child process ends
1263 my $status = PVE
::Tools
::upid_read_status
($upid);
1264 return undef if $status eq 'OK';
1273 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1276 my $vm_config_perm_list = [
1281 'VM.Config.Network',
1283 'VM.Config.Options',
1286 __PACKAGE__-
>register_method({
1287 name
=> 'update_vm_async',
1288 path
=> '{vmid}/config',
1292 description
=> "Set virtual machine options (asynchrounous API).",
1294 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1297 additionalProperties
=> 0,
1298 properties
=> PVE
::QemuServer
::json_config_properties
(
1300 node
=> get_standard_option
('pve-node'),
1301 vmid
=> get_standard_option
('pve-vmid'),
1302 skiplock
=> get_standard_option
('skiplock'),
1304 type
=> 'string', format
=> 'pve-configid-list',
1305 description
=> "A list of settings you want to delete.",
1309 type
=> 'string', format
=> 'pve-configid-list',
1310 description
=> "Revert a pending change.",
1315 description
=> $opt_force_description,
1317 requires
=> 'delete',
1321 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1325 background_delay
=> {
1327 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1338 code
=> $update_vm_api,
1341 __PACKAGE__-
>register_method({
1342 name
=> 'update_vm',
1343 path
=> '{vmid}/config',
1347 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1349 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1352 additionalProperties
=> 0,
1353 properties
=> PVE
::QemuServer
::json_config_properties
(
1355 node
=> get_standard_option
('pve-node'),
1356 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1357 skiplock
=> get_standard_option
('skiplock'),
1359 type
=> 'string', format
=> 'pve-configid-list',
1360 description
=> "A list of settings you want to delete.",
1364 type
=> 'string', format
=> 'pve-configid-list',
1365 description
=> "Revert a pending change.",
1370 description
=> $opt_force_description,
1372 requires
=> 'delete',
1376 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1382 returns
=> { type
=> 'null' },
1385 &$update_vm_api($param, 1);
1390 __PACKAGE__-
>register_method({
1391 name
=> 'destroy_vm',
1396 description
=> "Destroy the vm (also delete all used/owned volumes).",
1398 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1401 additionalProperties
=> 0,
1403 node
=> get_standard_option
('pve-node'),
1404 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1405 skiplock
=> get_standard_option
('skiplock'),
1408 description
=> "Remove vmid from backup cron jobs.",
1419 my $rpcenv = PVE
::RPCEnvironment
::get
();
1420 my $authuser = $rpcenv->get_user();
1421 my $vmid = $param->{vmid
};
1423 my $skiplock = $param->{skiplock
};
1424 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1425 if $skiplock && $authuser ne 'root@pam';
1428 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1429 my $storecfg = PVE
::Storage
::config
();
1430 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1431 die "unable to remove VM $vmid - used in HA resources\n"
1432 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1434 if (!$param->{purge
}) {
1435 # don't allow destroy if with replication jobs but no purge param
1436 my $repl_conf = PVE
::ReplicationConfig-
>new();
1437 $repl_conf->check_for_existing_jobs($vmid);
1440 # early tests (repeat after locking)
1441 die "VM $vmid is running - destroy failed\n"
1442 if PVE
::QemuServer
::check_running
($vmid);
1447 syslog
('info', "destroy VM $vmid: $upid\n");
1448 PVE
::QemuConfig-
>lock_config($vmid, sub {
1449 die "VM $vmid is running - destroy failed\n"
1450 if (PVE
::QemuServer
::check_running
($vmid));
1452 PVE
::QemuServer
::destroy_vm
($storecfg, $vmid, 1, $skiplock);
1454 PVE
::AccessControl
::remove_vm_access
($vmid);
1455 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1456 if ($param->{purge
}) {
1457 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1458 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1461 # only now remove the zombie config, else we can have reuse race
1462 PVE
::QemuConfig-
>destroy_config($vmid);
1466 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1469 __PACKAGE__-
>register_method({
1471 path
=> '{vmid}/unlink',
1475 description
=> "Unlink/delete disk images.",
1477 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1480 additionalProperties
=> 0,
1482 node
=> get_standard_option
('pve-node'),
1483 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1485 type
=> 'string', format
=> 'pve-configid-list',
1486 description
=> "A list of disk IDs you want to delete.",
1490 description
=> $opt_force_description,
1495 returns
=> { type
=> 'null'},
1499 $param->{delete} = extract_param
($param, 'idlist');
1501 __PACKAGE__-
>update_vm($param);
1508 __PACKAGE__-
>register_method({
1510 path
=> '{vmid}/vncproxy',
1514 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1516 description
=> "Creates a TCP VNC proxy connections.",
1518 additionalProperties
=> 0,
1520 node
=> get_standard_option
('pve-node'),
1521 vmid
=> get_standard_option
('pve-vmid'),
1525 description
=> "starts websockify instead of vncproxy",
1530 additionalProperties
=> 0,
1532 user
=> { type
=> 'string' },
1533 ticket
=> { type
=> 'string' },
1534 cert
=> { type
=> 'string' },
1535 port
=> { type
=> 'integer' },
1536 upid
=> { type
=> 'string' },
1542 my $rpcenv = PVE
::RPCEnvironment
::get
();
1544 my $authuser = $rpcenv->get_user();
1546 my $vmid = $param->{vmid
};
1547 my $node = $param->{node
};
1548 my $websocket = $param->{websocket
};
1550 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1551 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1553 my $authpath = "/vms/$vmid";
1555 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1557 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1563 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1564 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1565 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1566 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1567 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1569 $family = PVE
::Tools
::get_host_address_family
($node);
1572 my $port = PVE
::Tools
::next_vnc_port
($family);
1579 syslog
('info', "starting vnc proxy $upid\n");
1585 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1587 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1588 '-timeout', $timeout, '-authpath', $authpath,
1589 '-perm', 'Sys.Console'];
1591 if ($param->{websocket
}) {
1592 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1593 push @$cmd, '-notls', '-listen', 'localhost';
1596 push @$cmd, '-c', @$remcmd, @$termcmd;
1598 PVE
::Tools
::run_command
($cmd);
1602 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1604 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1606 my $sock = IO
::Socket
::IP-
>new(
1611 GetAddrInfoFlags
=> 0,
1612 ) or die "failed to create socket: $!\n";
1613 # Inside the worker we shouldn't have any previous alarms
1614 # running anyway...:
1616 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1618 accept(my $cli, $sock) or die "connection failed: $!\n";
1621 if (PVE
::Tools
::run_command
($cmd,
1622 output
=> '>&'.fileno($cli),
1623 input
=> '<&'.fileno($cli),
1626 die "Failed to run vncproxy.\n";
1633 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1635 PVE
::Tools
::wait_for_vnc_port
($port);
1646 __PACKAGE__-
>register_method({
1647 name
=> 'termproxy',
1648 path
=> '{vmid}/termproxy',
1652 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1654 description
=> "Creates a TCP proxy connections.",
1656 additionalProperties
=> 0,
1658 node
=> get_standard_option
('pve-node'),
1659 vmid
=> get_standard_option
('pve-vmid'),
1663 enum
=> [qw(serial0 serial1 serial2 serial3)],
1664 description
=> "opens a serial terminal (defaults to display)",
1669 additionalProperties
=> 0,
1671 user
=> { type
=> 'string' },
1672 ticket
=> { type
=> 'string' },
1673 port
=> { type
=> 'integer' },
1674 upid
=> { type
=> 'string' },
1680 my $rpcenv = PVE
::RPCEnvironment
::get
();
1682 my $authuser = $rpcenv->get_user();
1684 my $vmid = $param->{vmid
};
1685 my $node = $param->{node
};
1686 my $serial = $param->{serial
};
1688 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1690 if (!defined($serial)) {
1691 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1692 $serial = $conf->{vga
};
1696 my $authpath = "/vms/$vmid";
1698 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1703 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1704 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1705 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1706 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, '-t');
1707 push @$remcmd, '--';
1709 $family = PVE
::Tools
::get_host_address_family
($node);
1712 my $port = PVE
::Tools
::next_vnc_port
($family);
1714 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1715 push @$termcmd, '-iface', $serial if $serial;
1720 syslog
('info', "starting qemu termproxy $upid\n");
1722 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1723 '--perm', 'VM.Console', '--'];
1724 push @$cmd, @$remcmd, @$termcmd;
1726 PVE
::Tools
::run_command
($cmd);
1729 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1731 PVE
::Tools
::wait_for_vnc_port
($port);
1741 __PACKAGE__-
>register_method({
1742 name
=> 'vncwebsocket',
1743 path
=> '{vmid}/vncwebsocket',
1746 description
=> "You also need to pass a valid ticket (vncticket).",
1747 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1749 description
=> "Opens a weksocket for VNC traffic.",
1751 additionalProperties
=> 0,
1753 node
=> get_standard_option
('pve-node'),
1754 vmid
=> get_standard_option
('pve-vmid'),
1756 description
=> "Ticket from previous call to vncproxy.",
1761 description
=> "Port number returned by previous vncproxy call.",
1771 port
=> { type
=> 'string' },
1777 my $rpcenv = PVE
::RPCEnvironment
::get
();
1779 my $authuser = $rpcenv->get_user();
1781 my $vmid = $param->{vmid
};
1782 my $node = $param->{node
};
1784 my $authpath = "/vms/$vmid";
1786 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1788 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1790 # Note: VNC ports are acessible from outside, so we do not gain any
1791 # security if we verify that $param->{port} belongs to VM $vmid. This
1792 # check is done by verifying the VNC ticket (inside VNC protocol).
1794 my $port = $param->{port
};
1796 return { port
=> $port };
1799 __PACKAGE__-
>register_method({
1800 name
=> 'spiceproxy',
1801 path
=> '{vmid}/spiceproxy',
1806 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1808 description
=> "Returns a SPICE configuration to connect to the VM.",
1810 additionalProperties
=> 0,
1812 node
=> get_standard_option
('pve-node'),
1813 vmid
=> get_standard_option
('pve-vmid'),
1814 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1817 returns
=> get_standard_option
('remote-viewer-config'),
1821 my $rpcenv = PVE
::RPCEnvironment
::get
();
1823 my $authuser = $rpcenv->get_user();
1825 my $vmid = $param->{vmid
};
1826 my $node = $param->{node
};
1827 my $proxy = $param->{proxy
};
1829 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1830 my $title = "VM $vmid";
1831 $title .= " - ". $conf->{name
} if $conf->{name
};
1833 my $port = PVE
::QemuServer
::spice_port
($vmid);
1835 my ($ticket, undef, $remote_viewer_config) =
1836 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1838 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1839 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1841 return $remote_viewer_config;
1844 __PACKAGE__-
>register_method({
1846 path
=> '{vmid}/status',
1849 description
=> "Directory index",
1854 additionalProperties
=> 0,
1856 node
=> get_standard_option
('pve-node'),
1857 vmid
=> get_standard_option
('pve-vmid'),
1865 subdir
=> { type
=> 'string' },
1868 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1874 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1877 { subdir
=> 'current' },
1878 { subdir
=> 'start' },
1879 { subdir
=> 'stop' },
1880 { subdir
=> 'reset' },
1881 { subdir
=> 'shutdown' },
1882 { subdir
=> 'suspend' },
1883 { subdir
=> 'reboot' },
1889 __PACKAGE__-
>register_method({
1890 name
=> 'vm_status',
1891 path
=> '{vmid}/status/current',
1894 protected
=> 1, # qemu pid files are only readable by root
1895 description
=> "Get virtual machine status.",
1897 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1900 additionalProperties
=> 0,
1902 node
=> get_standard_option
('pve-node'),
1903 vmid
=> get_standard_option
('pve-vmid'),
1909 %$PVE::QemuServer
::vmstatus_return_properties
,
1911 description
=> "HA manager service status.",
1915 description
=> "Qemu VGA configuration supports spice.",
1920 description
=> "Qemu GuestAgent enabled in config.",
1930 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1932 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1933 my $status = $vmstatus->{$param->{vmid
}};
1935 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1937 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1938 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1943 __PACKAGE__-
>register_method({
1945 path
=> '{vmid}/status/start',
1949 description
=> "Start virtual machine.",
1951 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1954 additionalProperties
=> 0,
1956 node
=> get_standard_option
('pve-node'),
1957 vmid
=> get_standard_option
('pve-vmid',
1958 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1959 skiplock
=> get_standard_option
('skiplock'),
1960 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1961 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1964 enum
=> ['secure', 'insecure'],
1965 description
=> "Migration traffic is encrypted using an SSH " .
1966 "tunnel by default. On secure, completely private networks " .
1967 "this can be disabled to increase performance.",
1970 migration_network
=> {
1971 type
=> 'string', format
=> 'CIDR',
1972 description
=> "CIDR of the (sub) network that is used for migration.",
1975 machine
=> get_standard_option
('pve-qm-machine'),
1977 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1989 my $rpcenv = PVE
::RPCEnvironment
::get
();
1990 my $authuser = $rpcenv->get_user();
1992 my $node = extract_param
($param, 'node');
1993 my $vmid = extract_param
($param, 'vmid');
1995 my $machine = extract_param
($param, 'machine');
1997 my $get_root_param = sub {
1998 my $value = extract_param
($param, $_[0]);
1999 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2000 if $value && $authuser ne 'root@pam';
2004 my $stateuri = $get_root_param->('stateuri');
2005 my $skiplock = $get_root_param->('skiplock');
2006 my $migratedfrom = $get_root_param->('migratedfrom');
2007 my $migration_type = $get_root_param->('migration_type');
2008 my $migration_network = $get_root_param->('migration_network');
2009 my $targetstorage = $get_root_param->('targetstorage');
2011 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2012 if $targetstorage && !$migratedfrom;
2014 # read spice ticket from STDIN
2016 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2017 if (defined(my $line = <STDIN
>)) {
2019 $spice_ticket = $line;
2023 PVE
::Cluster
::check_cfs_quorum
();
2025 my $storecfg = PVE
::Storage
::config
();
2027 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2031 print "Requesting HA start for VM $vmid\n";
2033 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2034 PVE
::Tools
::run_command
($cmd);
2038 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2045 syslog
('info', "start VM $vmid: $upid\n");
2047 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2048 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
2052 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2056 __PACKAGE__-
>register_method({
2058 path
=> '{vmid}/status/stop',
2062 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2063 "is akin to pulling the power plug of a running computer and may damage the VM data",
2065 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2068 additionalProperties
=> 0,
2070 node
=> get_standard_option
('pve-node'),
2071 vmid
=> get_standard_option
('pve-vmid',
2072 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2073 skiplock
=> get_standard_option
('skiplock'),
2074 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2076 description
=> "Wait maximal timeout seconds.",
2082 description
=> "Do not deactivate storage volumes.",
2095 my $rpcenv = PVE
::RPCEnvironment
::get
();
2096 my $authuser = $rpcenv->get_user();
2098 my $node = extract_param
($param, 'node');
2099 my $vmid = extract_param
($param, 'vmid');
2101 my $skiplock = extract_param
($param, 'skiplock');
2102 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2103 if $skiplock && $authuser ne 'root@pam';
2105 my $keepActive = extract_param
($param, 'keepActive');
2106 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2107 if $keepActive && $authuser ne 'root@pam';
2109 my $migratedfrom = extract_param
($param, 'migratedfrom');
2110 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2111 if $migratedfrom && $authuser ne 'root@pam';
2114 my $storecfg = PVE
::Storage
::config
();
2116 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2121 print "Requesting HA stop for VM $vmid\n";
2123 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'stopped'];
2124 PVE
::Tools
::run_command
($cmd);
2128 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2134 syslog
('info', "stop VM $vmid: $upid\n");
2136 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2137 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2141 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2145 __PACKAGE__-
>register_method({
2147 path
=> '{vmid}/status/reset',
2151 description
=> "Reset virtual machine.",
2153 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2156 additionalProperties
=> 0,
2158 node
=> get_standard_option
('pve-node'),
2159 vmid
=> get_standard_option
('pve-vmid',
2160 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2161 skiplock
=> get_standard_option
('skiplock'),
2170 my $rpcenv = PVE
::RPCEnvironment
::get
();
2172 my $authuser = $rpcenv->get_user();
2174 my $node = extract_param
($param, 'node');
2176 my $vmid = extract_param
($param, 'vmid');
2178 my $skiplock = extract_param
($param, 'skiplock');
2179 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2180 if $skiplock && $authuser ne 'root@pam';
2182 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2187 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2192 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2195 __PACKAGE__-
>register_method({
2196 name
=> 'vm_shutdown',
2197 path
=> '{vmid}/status/shutdown',
2201 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2202 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2204 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2207 additionalProperties
=> 0,
2209 node
=> get_standard_option
('pve-node'),
2210 vmid
=> get_standard_option
('pve-vmid',
2211 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2212 skiplock
=> get_standard_option
('skiplock'),
2214 description
=> "Wait maximal timeout seconds.",
2220 description
=> "Make sure the VM stops.",
2226 description
=> "Do not deactivate storage volumes.",
2239 my $rpcenv = PVE
::RPCEnvironment
::get
();
2240 my $authuser = $rpcenv->get_user();
2242 my $node = extract_param
($param, 'node');
2243 my $vmid = extract_param
($param, 'vmid');
2245 my $skiplock = extract_param
($param, 'skiplock');
2246 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2247 if $skiplock && $authuser ne 'root@pam';
2249 my $keepActive = extract_param
($param, 'keepActive');
2250 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2251 if $keepActive && $authuser ne 'root@pam';
2253 my $storecfg = PVE
::Storage
::config
();
2257 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2258 # otherwise, we will infer a shutdown command, but run into the timeout,
2259 # then when the vm is resumed, it will instantly shutdown
2261 # checking the qmp status here to get feedback to the gui/cli/api
2262 # and the status query should not take too long
2263 my $qmpstatus = eval {
2264 PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2268 if (!$err && $qmpstatus->{status
} eq "paused") {
2269 if ($param->{forceStop
}) {
2270 warn "VM is paused - stop instead of shutdown\n";
2273 die "VM is paused - cannot shutdown\n";
2277 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2282 print "Requesting HA stop for VM $vmid\n";
2284 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'stopped'];
2285 PVE
::Tools
::run_command
($cmd);
2289 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2296 syslog
('info', "shutdown VM $vmid: $upid\n");
2298 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2299 $shutdown, $param->{forceStop
}, $keepActive);
2303 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2307 __PACKAGE__-
>register_method({
2308 name
=> 'vm_reboot',
2309 path
=> '{vmid}/status/reboot',
2313 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2315 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2318 additionalProperties
=> 0,
2320 node
=> get_standard_option
('pve-node'),
2321 vmid
=> get_standard_option
('pve-vmid',
2322 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2324 description
=> "Wait maximal timeout seconds for the shutdown.",
2337 my $rpcenv = PVE
::RPCEnvironment
::get
();
2338 my $authuser = $rpcenv->get_user();
2340 my $node = extract_param
($param, 'node');
2341 my $vmid = extract_param
($param, 'vmid');
2343 my $qmpstatus = eval {
2344 PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2348 if (!$err && $qmpstatus->{status
} eq "paused") {
2349 die "VM is paused - cannot shutdown\n";
2352 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2357 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2358 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2362 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2365 __PACKAGE__-
>register_method({
2366 name
=> 'vm_suspend',
2367 path
=> '{vmid}/status/suspend',
2371 description
=> "Suspend virtual machine.",
2373 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2376 additionalProperties
=> 0,
2378 node
=> get_standard_option
('pve-node'),
2379 vmid
=> get_standard_option
('pve-vmid',
2380 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2381 skiplock
=> get_standard_option
('skiplock'),
2386 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2388 statestorage
=> get_standard_option
('pve-storage-id', {
2389 description
=> "The storage for the VM state",
2390 requires
=> 'todisk',
2392 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2402 my $rpcenv = PVE
::RPCEnvironment
::get
();
2403 my $authuser = $rpcenv->get_user();
2405 my $node = extract_param
($param, 'node');
2406 my $vmid = extract_param
($param, 'vmid');
2408 my $todisk = extract_param
($param, 'todisk') // 0;
2410 my $statestorage = extract_param
($param, 'statestorage');
2412 my $skiplock = extract_param
($param, 'skiplock');
2413 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2414 if $skiplock && $authuser ne 'root@pam';
2416 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2418 die "Cannot suspend HA managed VM to disk\n"
2419 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2424 syslog
('info', "suspend VM $vmid: $upid\n");
2426 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2431 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2432 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2435 __PACKAGE__-
>register_method({
2436 name
=> 'vm_resume',
2437 path
=> '{vmid}/status/resume',
2441 description
=> "Resume virtual machine.",
2443 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2446 additionalProperties
=> 0,
2448 node
=> get_standard_option
('pve-node'),
2449 vmid
=> get_standard_option
('pve-vmid',
2450 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2451 skiplock
=> get_standard_option
('skiplock'),
2452 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2462 my $rpcenv = PVE
::RPCEnvironment
::get
();
2464 my $authuser = $rpcenv->get_user();
2466 my $node = extract_param
($param, 'node');
2468 my $vmid = extract_param
($param, 'vmid');
2470 my $skiplock = extract_param
($param, 'skiplock');
2471 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2472 if $skiplock && $authuser ne 'root@pam';
2474 my $nocheck = extract_param
($param, 'nocheck');
2476 my $to_disk_suspended;
2478 PVE
::QemuConfig-
>lock_config($vmid, sub {
2479 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2480 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2484 die "VM $vmid not running\n"
2485 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2490 syslog
('info', "resume VM $vmid: $upid\n");
2492 if (!$to_disk_suspended) {
2493 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2495 my $storecfg = PVE
::Storage
::config
();
2496 PVE
::QemuServer
::vm_start
($storecfg, $vmid, undef, $skiplock);
2502 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2505 __PACKAGE__-
>register_method({
2506 name
=> 'vm_sendkey',
2507 path
=> '{vmid}/sendkey',
2511 description
=> "Send key event to virtual machine.",
2513 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2516 additionalProperties
=> 0,
2518 node
=> get_standard_option
('pve-node'),
2519 vmid
=> get_standard_option
('pve-vmid',
2520 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2521 skiplock
=> get_standard_option
('skiplock'),
2523 description
=> "The key (qemu monitor encoding).",
2528 returns
=> { type
=> 'null'},
2532 my $rpcenv = PVE
::RPCEnvironment
::get
();
2534 my $authuser = $rpcenv->get_user();
2536 my $node = extract_param
($param, 'node');
2538 my $vmid = extract_param
($param, 'vmid');
2540 my $skiplock = extract_param
($param, 'skiplock');
2541 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2542 if $skiplock && $authuser ne 'root@pam';
2544 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2549 __PACKAGE__-
>register_method({
2550 name
=> 'vm_feature',
2551 path
=> '{vmid}/feature',
2555 description
=> "Check if feature for virtual machine is available.",
2557 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2560 additionalProperties
=> 0,
2562 node
=> get_standard_option
('pve-node'),
2563 vmid
=> get_standard_option
('pve-vmid'),
2565 description
=> "Feature to check.",
2567 enum
=> [ 'snapshot', 'clone', 'copy' ],
2569 snapname
=> get_standard_option
('pve-snapshot-name', {
2577 hasFeature
=> { type
=> 'boolean' },
2580 items
=> { type
=> 'string' },
2587 my $node = extract_param
($param, 'node');
2589 my $vmid = extract_param
($param, 'vmid');
2591 my $snapname = extract_param
($param, 'snapname');
2593 my $feature = extract_param
($param, 'feature');
2595 my $running = PVE
::QemuServer
::check_running
($vmid);
2597 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2600 my $snap = $conf->{snapshots
}->{$snapname};
2601 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2604 my $storecfg = PVE
::Storage
::config
();
2606 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2607 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2610 hasFeature
=> $hasFeature,
2611 nodes
=> [ keys %$nodelist ],
2615 __PACKAGE__-
>register_method({
2617 path
=> '{vmid}/clone',
2621 description
=> "Create a copy of virtual machine/template.",
2623 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2624 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2625 "'Datastore.AllocateSpace' on any used storage.",
2628 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2630 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2631 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2636 additionalProperties
=> 0,
2638 node
=> get_standard_option
('pve-node'),
2639 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2640 newid
=> get_standard_option
('pve-vmid', {
2641 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2642 description
=> 'VMID for the clone.' }),
2645 type
=> 'string', format
=> 'dns-name',
2646 description
=> "Set a name for the new VM.",
2651 description
=> "Description for the new VM.",
2655 type
=> 'string', format
=> 'pve-poolid',
2656 description
=> "Add the new VM to the specified pool.",
2658 snapname
=> get_standard_option
('pve-snapshot-name', {
2661 storage
=> get_standard_option
('pve-storage-id', {
2662 description
=> "Target storage for full clone.",
2666 description
=> "Target format for file storage. Only valid for full clone.",
2669 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2674 description
=> "Create a full copy of all disks. This is always done when " .
2675 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2677 target
=> get_standard_option
('pve-node', {
2678 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2682 description
=> "Override I/O bandwidth limit (in KiB/s).",
2686 default => 'clone limit from datacenter or storage config',
2696 my $rpcenv = PVE
::RPCEnvironment
::get
();
2698 my $authuser = $rpcenv->get_user();
2700 my $node = extract_param
($param, 'node');
2702 my $vmid = extract_param
($param, 'vmid');
2704 my $newid = extract_param
($param, 'newid');
2706 my $pool = extract_param
($param, 'pool');
2708 if (defined($pool)) {
2709 $rpcenv->check_pool_exist($pool);
2712 my $snapname = extract_param
($param, 'snapname');
2714 my $storage = extract_param
($param, 'storage');
2716 my $format = extract_param
($param, 'format');
2718 my $target = extract_param
($param, 'target');
2720 my $localnode = PVE
::INotify
::nodename
();
2722 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2724 PVE
::Cluster
::check_node_exists
($target) if $target;
2726 my $storecfg = PVE
::Storage
::config
();
2729 # check if storage is enabled on local node
2730 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2732 # check if storage is available on target node
2733 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2734 # clone only works if target storage is shared
2735 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2736 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2740 PVE
::Cluster
::check_cfs_quorum
();
2742 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2744 # exclusive lock if VM is running - else shared lock is enough;
2745 my $shared_lock = $running ?
0 : 1;
2749 # do all tests after lock
2750 # we also try to do all tests before we fork the worker
2752 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2754 PVE
::QemuConfig-
>check_lock($conf);
2756 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2758 die "unexpected state change\n" if $verify_running != $running;
2760 die "snapshot '$snapname' does not exist\n"
2761 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2763 my $full = extract_param
($param, 'full');
2764 if (!defined($full)) {
2765 $full = !PVE
::QemuConfig-
>is_template($conf);
2768 die "parameter 'storage' not allowed for linked clones\n"
2769 if defined($storage) && !$full;
2771 die "parameter 'format' not allowed for linked clones\n"
2772 if defined($format) && !$full;
2774 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2776 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2778 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2780 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2782 die "unable to create VM $newid: config file already exists\n"
2785 my $newconf = { lock => 'clone' };
2790 foreach my $opt (keys %$oldconf) {
2791 my $value = $oldconf->{$opt};
2793 # do not copy snapshot related info
2794 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2795 $opt eq 'vmstate' || $opt eq 'snapstate';
2797 # no need to copy unused images, because VMID(owner) changes anyways
2798 next if $opt =~ m/^unused\d+$/;
2800 # always change MAC! address
2801 if ($opt =~ m/^net(\d+)$/) {
2802 my $net = PVE
::QemuServer
::parse_net
($value);
2803 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2804 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2805 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2806 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2807 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2808 die "unable to parse drive options for '$opt'\n" if !$drive;
2809 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2810 $newconf->{$opt} = $value; # simply copy configuration
2813 die "Full clone feature is not supported for drive '$opt'\n"
2814 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2815 $fullclone->{$opt} = 1;
2817 # not full means clone instead of copy
2818 die "Linked clone feature is not supported for drive '$opt'\n"
2819 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2821 $drives->{$opt} = $drive;
2822 push @$vollist, $drive->{file
};
2825 # copy everything else
2826 $newconf->{$opt} = $value;
2830 # auto generate a new uuid
2831 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2832 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2833 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2835 # auto generate a new vmgenid if the option was set
2836 if ($newconf->{vmgenid
}) {
2837 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2840 delete $newconf->{template
};
2842 if ($param->{name
}) {
2843 $newconf->{name
} = $param->{name
};
2845 if ($oldconf->{name
}) {
2846 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2848 $newconf->{name
} = "Copy-of-VM-$vmid";
2852 if ($param->{description
}) {
2853 $newconf->{description
} = $param->{description
};
2856 # create empty/temp config - this fails if VM already exists on other node
2857 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2862 my $newvollist = [];
2869 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2871 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2873 my $bwlimit = extract_param
($param, 'bwlimit');
2875 my $total_jobs = scalar(keys %{$drives});
2878 foreach my $opt (keys %$drives) {
2879 my $drive = $drives->{$opt};
2880 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2882 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
2883 my $storage_list = [ $src_sid ];
2884 push @$storage_list, $storage if defined($storage);
2885 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
2887 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2888 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2889 $jobs, $skipcomplete, $oldconf->{agent
}, $clonelimit);
2891 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2893 PVE
::QemuConfig-
>write_config($newid, $newconf);
2897 delete $newconf->{lock};
2899 # do not write pending changes
2900 if (my @changes = keys %{$newconf->{pending
}}) {
2901 my $pending = join(',', @changes);
2902 warn "found pending changes for '$pending', discarding for clone\n";
2903 delete $newconf->{pending
};
2906 PVE
::QemuConfig-
>write_config($newid, $newconf);
2909 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2910 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2911 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2913 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2914 die "Failed to move config to node '$target' - rename failed: $!\n"
2915 if !rename($conffile, $newconffile);
2918 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2923 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2925 sleep 1; # some storage like rbd need to wait before release volume - really?
2927 foreach my $volid (@$newvollist) {
2928 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2931 die "clone failed: $err";
2937 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2939 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2942 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2943 # Aquire exclusive lock lock for $newid
2944 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2949 __PACKAGE__-
>register_method({
2950 name
=> 'move_vm_disk',
2951 path
=> '{vmid}/move_disk',
2955 description
=> "Move volume to different storage.",
2957 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2959 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2960 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2964 additionalProperties
=> 0,
2966 node
=> get_standard_option
('pve-node'),
2967 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2970 description
=> "The disk you want to move.",
2971 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2973 storage
=> get_standard_option
('pve-storage-id', {
2974 description
=> "Target storage.",
2975 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2979 description
=> "Target Format.",
2980 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2985 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2991 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2996 description
=> "Override I/O bandwidth limit (in KiB/s).",
3000 default => 'move limit from datacenter or storage config',
3006 description
=> "the task ID.",
3011 my $rpcenv = PVE
::RPCEnvironment
::get
();
3013 my $authuser = $rpcenv->get_user();
3015 my $node = extract_param
($param, 'node');
3017 my $vmid = extract_param
($param, 'vmid');
3019 my $digest = extract_param
($param, 'digest');
3021 my $disk = extract_param
($param, 'disk');
3023 my $storeid = extract_param
($param, 'storage');
3025 my $format = extract_param
($param, 'format');
3027 my $storecfg = PVE
::Storage
::config
();
3029 my $updatefn = sub {
3031 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3033 PVE
::QemuConfig-
>check_lock($conf);
3035 die "checksum missmatch (file change by other user?)\n"
3036 if $digest && $digest ne $conf->{digest
};
3038 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3040 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3042 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
3044 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3047 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3048 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3052 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
3053 (!$format || !$oldfmt || $oldfmt eq $format);
3055 # this only checks snapshots because $disk is passed!
3056 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3057 die "you can't move a disk with snapshots and delete the source\n"
3058 if $snapshotted && $param->{delete};
3060 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3062 my $running = PVE
::QemuServer
::check_running
($vmid);
3064 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3068 my $newvollist = [];
3074 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3076 warn "moving disk with snapshots, snapshots will not be moved!\n"
3079 my $bwlimit = extract_param
($param, 'bwlimit');
3080 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3082 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3083 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit);
3085 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
3087 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3089 # convert moved disk to base if part of template
3090 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3091 if PVE
::QemuConfig-
>is_template($conf);
3093 PVE
::QemuConfig-
>write_config($vmid, $conf);
3095 if ($running && PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
} && PVE
::QemuServer
::qga_check_running
($vmid)) {
3096 eval { PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-fstrim"); };
3100 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3101 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3108 foreach my $volid (@$newvollist) {
3109 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3112 die "storage migration failed: $err";
3115 if ($param->{delete}) {
3117 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3118 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3124 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3127 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3130 my $check_vm_disks_local = sub {
3131 my ($storecfg, $vmconf, $vmid) = @_;
3133 my $local_disks = {};
3135 # add some more information to the disks e.g. cdrom
3136 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3137 my ($volid, $attr) = @_;
3139 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3141 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3142 return if $scfg->{shared
};
3144 # The shared attr here is just a special case where the vdisk
3145 # is marked as shared manually
3146 return if $attr->{shared
};
3147 return if $attr->{cdrom
} and $volid eq "none";
3149 if (exists $local_disks->{$volid}) {
3150 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3152 $local_disks->{$volid} = $attr;
3153 # ensure volid is present in case it's needed
3154 $local_disks->{$volid}->{volid
} = $volid;
3158 return $local_disks;
3161 __PACKAGE__-
>register_method({
3162 name
=> 'migrate_vm_precondition',
3163 path
=> '{vmid}/migrate',
3167 description
=> "Get preconditions for migration.",
3169 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3172 additionalProperties
=> 0,
3174 node
=> get_standard_option
('pve-node'),
3175 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3176 target
=> get_standard_option
('pve-node', {
3177 description
=> "Target node.",
3178 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3186 running
=> { type
=> 'boolean' },
3190 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3192 not_allowed_nodes
=> {
3195 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3199 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3201 local_resources
=> {
3203 description
=> "List local resources e.g. pci, usb"
3210 my $rpcenv = PVE
::RPCEnvironment
::get
();
3212 my $authuser = $rpcenv->get_user();
3214 PVE
::Cluster
::check_cfs_quorum
();
3218 my $vmid = extract_param
($param, 'vmid');
3219 my $target = extract_param
($param, 'target');
3220 my $localnode = PVE
::INotify
::nodename
();
3224 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3225 my $storecfg = PVE
::Storage
::config
();
3228 # try to detect errors early
3229 PVE
::QemuConfig-
>check_lock($vmconf);
3231 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3233 # if vm is not running, return target nodes where local storage is available
3234 # for offline migration
3235 if (!$res->{running
}) {
3236 $res->{allowed_nodes
} = [];
3237 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3238 delete $checked_nodes->{$localnode};
3240 foreach my $node (keys %$checked_nodes) {
3241 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3242 push @{$res->{allowed_nodes
}}, $node;
3246 $res->{not_allowed_nodes
} = $checked_nodes;
3250 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3251 $res->{local_disks
} = [ values %$local_disks ];;
3253 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3255 $res->{local_resources
} = $local_resources;
3262 __PACKAGE__-
>register_method({
3263 name
=> 'migrate_vm',
3264 path
=> '{vmid}/migrate',
3268 description
=> "Migrate virtual machine. Creates a new migration task.",
3270 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3273 additionalProperties
=> 0,
3275 node
=> get_standard_option
('pve-node'),
3276 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3277 target
=> get_standard_option
('pve-node', {
3278 description
=> "Target node.",
3279 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3283 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3288 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3293 enum
=> ['secure', 'insecure'],
3294 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3297 migration_network
=> {
3298 type
=> 'string', format
=> 'CIDR',
3299 description
=> "CIDR of the (sub) network that is used for migration.",
3302 "with-local-disks" => {
3304 description
=> "Enable live storage migration for local disk",
3307 targetstorage
=> get_standard_option
('pve-storage-id', {
3308 description
=> "Default target storage.",
3310 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3313 description
=> "Override I/O bandwidth limit (in KiB/s).",
3317 default => 'migrate limit from datacenter or storage config',
3323 description
=> "the task ID.",
3328 my $rpcenv = PVE
::RPCEnvironment
::get
();
3329 my $authuser = $rpcenv->get_user();
3331 my $target = extract_param
($param, 'target');
3333 my $localnode = PVE
::INotify
::nodename
();
3334 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3336 PVE
::Cluster
::check_cfs_quorum
();
3338 PVE
::Cluster
::check_node_exists
($target);
3340 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3342 my $vmid = extract_param
($param, 'vmid');
3344 raise_param_exc
({ force
=> "Only root may use this option." })
3345 if $param->{force
} && $authuser ne 'root@pam';
3347 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3348 if $param->{migration_type
} && $authuser ne 'root@pam';
3350 # allow root only until better network permissions are available
3351 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3352 if $param->{migration_network
} && $authuser ne 'root@pam';
3355 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3357 # try to detect errors early
3359 PVE
::QemuConfig-
>check_lock($conf);
3361 if (PVE
::QemuServer
::check_running
($vmid)) {
3362 die "can't migrate running VM without --online\n" if !$param->{online
};
3364 warn "VM isn't running. Doing offline migration instead\n." if $param->{online
};
3365 $param->{online
} = 0;
3368 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3369 if !$param->{online
} && $param->{targetstorage
};
3371 my $storecfg = PVE
::Storage
::config
();
3373 if( $param->{targetstorage
}) {
3374 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3376 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3379 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3384 print "Requesting HA migration for VM $vmid to node $target\n";
3386 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3387 PVE
::Tools
::run_command
($cmd);
3391 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3396 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3400 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3403 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3408 __PACKAGE__-
>register_method({
3410 path
=> '{vmid}/monitor',
3414 description
=> "Execute Qemu monitor commands.",
3416 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3417 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3420 additionalProperties
=> 0,
3422 node
=> get_standard_option
('pve-node'),
3423 vmid
=> get_standard_option
('pve-vmid'),
3426 description
=> "The monitor command.",
3430 returns
=> { type
=> 'string'},
3434 my $rpcenv = PVE
::RPCEnvironment
::get
();
3435 my $authuser = $rpcenv->get_user();
3438 my $command = shift;
3439 return $command =~ m/^\s*info(\s+|$)/
3440 || $command =~ m/^\s*help\s*$/;
3443 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3444 if !&$is_ro($param->{command
});
3446 my $vmid = $param->{vmid
};
3448 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3452 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3454 $res = "ERROR: $@" if $@;
3459 __PACKAGE__-
>register_method({
3460 name
=> 'resize_vm',
3461 path
=> '{vmid}/resize',
3465 description
=> "Extend volume size.",
3467 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3470 additionalProperties
=> 0,
3472 node
=> get_standard_option
('pve-node'),
3473 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3474 skiplock
=> get_standard_option
('skiplock'),
3477 description
=> "The disk you want to resize.",
3478 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3482 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3483 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.",
3487 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3493 returns
=> { type
=> 'null'},
3497 my $rpcenv = PVE
::RPCEnvironment
::get
();
3499 my $authuser = $rpcenv->get_user();
3501 my $node = extract_param
($param, 'node');
3503 my $vmid = extract_param
($param, 'vmid');
3505 my $digest = extract_param
($param, 'digest');
3507 my $disk = extract_param
($param, 'disk');
3509 my $sizestr = extract_param
($param, 'size');
3511 my $skiplock = extract_param
($param, 'skiplock');
3512 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3513 if $skiplock && $authuser ne 'root@pam';
3515 my $storecfg = PVE
::Storage
::config
();
3517 my $updatefn = sub {
3519 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3521 die "checksum missmatch (file change by other user?)\n"
3522 if $digest && $digest ne $conf->{digest
};
3523 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3525 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3527 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3529 my (undef, undef, undef, undef, undef, undef, $format) =
3530 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3532 die "can't resize volume: $disk if snapshot exists\n"
3533 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3535 my $volid = $drive->{file
};
3537 die "disk '$disk' has no associated volume\n" if !$volid;
3539 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3541 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3543 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3545 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3546 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3548 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3550 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3551 my ($ext, $newsize, $unit) = ($1, $2, $4);
3554 $newsize = $newsize * 1024;
3555 } elsif ($unit eq 'M') {
3556 $newsize = $newsize * 1024 * 1024;
3557 } elsif ($unit eq 'G') {
3558 $newsize = $newsize * 1024 * 1024 * 1024;
3559 } elsif ($unit eq 'T') {
3560 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3563 $newsize += $size if $ext;
3564 $newsize = int($newsize);
3566 die "shrinking disks is not supported\n" if $newsize < $size;
3568 return if $size == $newsize;
3570 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3572 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3574 $drive->{size
} = $newsize;
3575 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3577 PVE
::QemuConfig-
>write_config($vmid, $conf);
3580 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3584 __PACKAGE__-
>register_method({
3585 name
=> 'snapshot_list',
3586 path
=> '{vmid}/snapshot',
3588 description
=> "List all snapshots.",
3590 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3593 protected
=> 1, # qemu pid files are only readable by root
3595 additionalProperties
=> 0,
3597 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3598 node
=> get_standard_option
('pve-node'),
3607 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3611 description
=> "Snapshot includes RAM.",
3616 description
=> "Snapshot description.",
3620 description
=> "Snapshot creation time",
3622 renderer
=> 'timestamp',
3626 description
=> "Parent snapshot identifier.",
3632 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3637 my $vmid = $param->{vmid
};
3639 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3640 my $snaphash = $conf->{snapshots
} || {};
3644 foreach my $name (keys %$snaphash) {
3645 my $d = $snaphash->{$name};
3648 snaptime
=> $d->{snaptime
} || 0,
3649 vmstate
=> $d->{vmstate
} ?
1 : 0,
3650 description
=> $d->{description
} || '',
3652 $item->{parent
} = $d->{parent
} if $d->{parent
};
3653 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3657 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3660 digest
=> $conf->{digest
},
3661 running
=> $running,
3662 description
=> "You are here!",
3664 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3666 push @$res, $current;
3671 __PACKAGE__-
>register_method({
3673 path
=> '{vmid}/snapshot',
3677 description
=> "Snapshot a VM.",
3679 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3682 additionalProperties
=> 0,
3684 node
=> get_standard_option
('pve-node'),
3685 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3686 snapname
=> get_standard_option
('pve-snapshot-name'),
3690 description
=> "Save the vmstate",
3695 description
=> "A textual description or comment.",
3701 description
=> "the task ID.",
3706 my $rpcenv = PVE
::RPCEnvironment
::get
();
3708 my $authuser = $rpcenv->get_user();
3710 my $node = extract_param
($param, 'node');
3712 my $vmid = extract_param
($param, 'vmid');
3714 my $snapname = extract_param
($param, 'snapname');
3716 die "unable to use snapshot name 'current' (reserved name)\n"
3717 if $snapname eq 'current';
3720 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3721 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3722 $param->{description
});
3725 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3728 __PACKAGE__-
>register_method({
3729 name
=> 'snapshot_cmd_idx',
3730 path
=> '{vmid}/snapshot/{snapname}',
3737 additionalProperties
=> 0,
3739 vmid
=> get_standard_option
('pve-vmid'),
3740 node
=> get_standard_option
('pve-node'),
3741 snapname
=> get_standard_option
('pve-snapshot-name'),
3750 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3757 push @$res, { cmd
=> 'rollback' };
3758 push @$res, { cmd
=> 'config' };
3763 __PACKAGE__-
>register_method({
3764 name
=> 'update_snapshot_config',
3765 path
=> '{vmid}/snapshot/{snapname}/config',
3769 description
=> "Update snapshot metadata.",
3771 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3774 additionalProperties
=> 0,
3776 node
=> get_standard_option
('pve-node'),
3777 vmid
=> get_standard_option
('pve-vmid'),
3778 snapname
=> get_standard_option
('pve-snapshot-name'),
3782 description
=> "A textual description or comment.",
3786 returns
=> { type
=> 'null' },
3790 my $rpcenv = PVE
::RPCEnvironment
::get
();
3792 my $authuser = $rpcenv->get_user();
3794 my $vmid = extract_param
($param, 'vmid');
3796 my $snapname = extract_param
($param, 'snapname');
3798 return undef if !defined($param->{description
});
3800 my $updatefn = sub {
3802 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3804 PVE
::QemuConfig-
>check_lock($conf);
3806 my $snap = $conf->{snapshots
}->{$snapname};
3808 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3810 $snap->{description
} = $param->{description
} if defined($param->{description
});
3812 PVE
::QemuConfig-
>write_config($vmid, $conf);
3815 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3820 __PACKAGE__-
>register_method({
3821 name
=> 'get_snapshot_config',
3822 path
=> '{vmid}/snapshot/{snapname}/config',
3825 description
=> "Get snapshot configuration",
3827 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3830 additionalProperties
=> 0,
3832 node
=> get_standard_option
('pve-node'),
3833 vmid
=> get_standard_option
('pve-vmid'),
3834 snapname
=> get_standard_option
('pve-snapshot-name'),
3837 returns
=> { type
=> "object" },
3841 my $rpcenv = PVE
::RPCEnvironment
::get
();
3843 my $authuser = $rpcenv->get_user();
3845 my $vmid = extract_param
($param, 'vmid');
3847 my $snapname = extract_param
($param, 'snapname');
3849 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3851 my $snap = $conf->{snapshots
}->{$snapname};
3853 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3858 __PACKAGE__-
>register_method({
3860 path
=> '{vmid}/snapshot/{snapname}/rollback',
3864 description
=> "Rollback VM state to specified snapshot.",
3866 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3869 additionalProperties
=> 0,
3871 node
=> get_standard_option
('pve-node'),
3872 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3873 snapname
=> get_standard_option
('pve-snapshot-name'),
3878 description
=> "the task ID.",
3883 my $rpcenv = PVE
::RPCEnvironment
::get
();
3885 my $authuser = $rpcenv->get_user();
3887 my $node = extract_param
($param, 'node');
3889 my $vmid = extract_param
($param, 'vmid');
3891 my $snapname = extract_param
($param, 'snapname');
3894 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3895 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3899 # hold migration lock, this makes sure that nobody create replication snapshots
3900 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3903 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3906 __PACKAGE__-
>register_method({
3907 name
=> 'delsnapshot',
3908 path
=> '{vmid}/snapshot/{snapname}',
3912 description
=> "Delete a VM snapshot.",
3914 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3917 additionalProperties
=> 0,
3919 node
=> get_standard_option
('pve-node'),
3920 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3921 snapname
=> get_standard_option
('pve-snapshot-name'),
3925 description
=> "For removal from config file, even if removing disk snapshots fails.",
3931 description
=> "the task ID.",
3936 my $rpcenv = PVE
::RPCEnvironment
::get
();
3938 my $authuser = $rpcenv->get_user();
3940 my $node = extract_param
($param, 'node');
3942 my $vmid = extract_param
($param, 'vmid');
3944 my $snapname = extract_param
($param, 'snapname');
3947 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3948 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3951 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3954 __PACKAGE__-
>register_method({
3956 path
=> '{vmid}/template',
3960 description
=> "Create a Template.",
3962 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3963 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3966 additionalProperties
=> 0,
3968 node
=> get_standard_option
('pve-node'),
3969 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3973 description
=> "If you want to convert only 1 disk to base image.",
3974 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3979 returns
=> { type
=> 'null'},
3983 my $rpcenv = PVE
::RPCEnvironment
::get
();
3985 my $authuser = $rpcenv->get_user();
3987 my $node = extract_param
($param, 'node');
3989 my $vmid = extract_param
($param, 'vmid');
3991 my $disk = extract_param
($param, 'disk');
3993 my $updatefn = sub {
3995 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3997 PVE
::QemuConfig-
>check_lock($conf);
3999 die "unable to create template, because VM contains snapshots\n"
4000 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4002 die "you can't convert a template to a template\n"
4003 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4005 die "you can't convert a VM to template if VM is running\n"
4006 if PVE
::QemuServer
::check_running
($vmid);
4009 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4012 $conf->{template
} = 1;
4013 PVE
::QemuConfig-
>write_config($vmid, $conf);
4015 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4018 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4022 __PACKAGE__-
>register_method({
4023 name
=> 'cloudinit_generated_config_dump',
4024 path
=> '{vmid}/cloudinit/dump',
4027 description
=> "Get automatically generated cloudinit config.",
4029 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4032 additionalProperties
=> 0,
4034 node
=> get_standard_option
('pve-node'),
4035 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4037 description
=> 'Config type.',
4039 enum
=> ['user', 'network', 'meta'],
4049 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4051 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});