1 package PVE
::API2
::Qemu
;
11 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
;
23 use PVE
::QemuServer
::Monitor
qw(mon_cmd);
25 use PVE
::RPCEnvironment
;
26 use PVE
::AccessControl
;
30 use PVE
::API2
::Firewall
::VM
;
31 use PVE
::API2
::Qemu
::Agent
;
32 use PVE
::VZDump
::Plugin
;
33 use PVE
::DataCenterConfig
;
37 if (!$ENV{PVE_GENERATING_DOCS
}) {
38 require PVE
::HA
::Env
::PVE2
;
39 import PVE
::HA
::Env
::PVE2
;
40 require PVE
::HA
::Config
;
41 import PVE
::HA
::Config
;
45 use Data
::Dumper
; # fixme: remove
47 use base
qw(PVE::RESTHandler);
49 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.";
51 my $resolve_cdrom_alias = sub {
54 if (my $value = $param->{cdrom
}) {
55 $value .= ",media=cdrom" if $value !~ m/media=/;
56 $param->{ide2
} = $value;
57 delete $param->{cdrom
};
61 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
62 my $check_storage_access = sub {
63 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
65 PVE
::QemuServer
::foreach_drive
($settings, sub {
66 my ($ds, $drive) = @_;
68 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
70 my $volid = $drive->{file
};
71 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
73 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit' || (defined($volname) && $volname eq 'cloudinit'))) {
75 } elsif ($isCDROM && ($volid eq 'cdrom')) {
76 $rpcenv->check($authuser, "/", ['Sys.Console']);
77 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
78 my ($storeid, $size) = ($2 || $default_storage, $3);
79 die "no storage ID specified (and no default storage)\n" if !$storeid;
80 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
81 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
82 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
83 if !$scfg->{content
}->{images
};
85 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
89 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
90 if defined($settings->{vmstatestorage
});
93 my $check_storage_access_clone = sub {
94 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
98 PVE
::QemuServer
::foreach_drive
($conf, sub {
99 my ($ds, $drive) = @_;
101 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
103 my $volid = $drive->{file
};
105 return if !$volid || $volid eq 'none';
108 if ($volid eq 'cdrom') {
109 $rpcenv->check($authuser, "/", ['Sys.Console']);
111 # we simply allow access
112 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
113 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
114 $sharedvm = 0 if !$scfg->{shared
};
118 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
119 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
120 $sharedvm = 0 if !$scfg->{shared
};
122 $sid = $storage if $storage;
123 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
127 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
128 if defined($conf->{vmstatestorage
});
133 # Note: $pool is only needed when creating a VM, because pool permissions
134 # are automatically inherited if VM already exists inside a pool.
135 my $create_disks = sub {
136 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
143 my ($ds, $disk) = @_;
145 my $volid = $disk->{file
};
146 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
148 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
149 delete $disk->{size
};
150 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
151 } elsif (defined($volname) && $volname eq 'cloudinit') {
152 $storeid = $storeid // $default_storage;
153 die "no storage ID specified (and no default storage)\n" if !$storeid;
154 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
155 my $name = "vm-$vmid-cloudinit";
159 $fmt = $disk->{format
} // "qcow2";
162 $fmt = $disk->{format
} // "raw";
165 # Initial disk created with 4 MB and aligned to 4MB on regeneration
166 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
167 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
168 $disk->{file
} = $volid;
169 $disk->{media
} = 'cdrom';
170 push @$vollist, $volid;
171 delete $disk->{format
}; # no longer needed
172 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
173 } elsif ($volid =~ $NEW_DISK_RE) {
174 my ($storeid, $size) = ($2 || $default_storage, $3);
175 die "no storage ID specified (and no default storage)\n" if !$storeid;
176 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
177 my $fmt = $disk->{format
} || $defformat;
179 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
182 if ($ds eq 'efidisk0') {
183 ($volid, $size) = PVE
::QemuServer
::create_efidisk
($storecfg, $storeid, $vmid, $fmt, $arch);
185 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
187 push @$vollist, $volid;
188 $disk->{file
} = $volid;
189 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
190 delete $disk->{format
}; # no longer needed
191 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
194 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
196 my $volid_is_new = 1;
199 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
200 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
205 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
207 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
209 die "volume $volid does not exists\n" if !$size;
211 $disk->{size
} = $size;
214 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
218 eval { PVE
::QemuServer
::foreach_drive
($settings, $code); };
220 # free allocated images on error
222 syslog
('err', "VM $vmid creating disks failed");
223 foreach my $volid (@$vollist) {
224 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
230 # modify vm config if everything went well
231 foreach my $ds (keys %$res) {
232 $conf->{$ds} = $res->{$ds};
249 my $memoryoptions = {
255 my $hwtypeoptions = {
268 my $generaloptions = {
275 'migrate_downtime' => 1,
276 'migrate_speed' => 1,
289 my $vmpoweroptions = {
296 'vmstatestorage' => 1,
299 my $cloudinitoptions = {
309 my $check_vm_modify_config_perm = sub {
310 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
312 return 1 if $authuser eq 'root@pam';
314 foreach my $opt (@$key_list) {
315 # some checks (e.g., disk, serial port, usb) need to be done somewhere
316 # else, as there the permission can be value dependend
317 next if PVE
::QemuServer
::is_valid_drivename
($opt);
318 next if $opt eq 'vmstate';
319 next if $opt eq 'cdrom';
320 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
323 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
324 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
325 } elsif ($memoryoptions->{$opt}) {
326 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
327 } elsif ($hwtypeoptions->{$opt}) {
328 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
329 } elsif ($generaloptions->{$opt}) {
330 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
331 # special case for startup since it changes host behaviour
332 if ($opt eq 'startup') {
333 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
335 } elsif ($vmpoweroptions->{$opt}) {
336 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
337 } elsif ($diskoptions->{$opt}) {
338 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
339 } elsif ($cloudinitoptions->{$opt} || ($opt =~ m/^(?:net|ipconfig)\d+$/)) {
340 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
342 # catches hostpci\d+, args, lock, etc.
343 # new options will be checked here
344 die "only root can set '$opt' config\n";
351 __PACKAGE__-
>register_method({
355 description
=> "Virtual machine index (per node).",
357 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
361 protected
=> 1, # qemu pid files are only readable by root
363 additionalProperties
=> 0,
365 node
=> get_standard_option
('pve-node'),
369 description
=> "Determine the full status of active VMs.",
377 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
379 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
384 my $rpcenv = PVE
::RPCEnvironment
::get
();
385 my $authuser = $rpcenv->get_user();
387 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
390 foreach my $vmid (keys %$vmstatus) {
391 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
393 my $data = $vmstatus->{$vmid};
402 __PACKAGE__-
>register_method({
406 description
=> "Create or restore a virtual machine.",
408 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
409 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
410 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
411 user
=> 'all', # check inside
416 additionalProperties
=> 0,
417 properties
=> PVE
::QemuServer
::json_config_properties
(
419 node
=> get_standard_option
('pve-node'),
420 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
422 description
=> "The backup file.",
426 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
428 storage
=> get_standard_option
('pve-storage-id', {
429 description
=> "Default storage.",
431 completion
=> \
&PVE
::QemuServer
::complete_storage
,
436 description
=> "Allow to overwrite existing VM.",
437 requires
=> 'archive',
442 description
=> "Assign a unique random ethernet address.",
443 requires
=> 'archive',
447 type
=> 'string', format
=> 'pve-poolid',
448 description
=> "Add the VM to the specified pool.",
451 description
=> "Override I/O bandwidth limit (in KiB/s).",
455 default => 'restore limit from datacenter or storage config',
461 description
=> "Start VM after it was created successfully.",
471 my $rpcenv = PVE
::RPCEnvironment
::get
();
472 my $authuser = $rpcenv->get_user();
474 my $node = extract_param
($param, 'node');
475 my $vmid = extract_param
($param, 'vmid');
477 my $archive = extract_param
($param, 'archive');
478 my $is_restore = !!$archive;
480 my $bwlimit = extract_param
($param, 'bwlimit');
481 my $force = extract_param
($param, 'force');
482 my $pool = extract_param
($param, 'pool');
483 my $start_after_create = extract_param
($param, 'start');
484 my $storage = extract_param
($param, 'storage');
485 my $unique = extract_param
($param, 'unique');
487 if (defined(my $ssh_keys = $param->{sshkeys
})) {
488 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
489 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
492 PVE
::Cluster
::check_cfs_quorum
();
494 my $filename = PVE
::QemuConfig-
>config_file($vmid);
495 my $storecfg = PVE
::Storage
::config
();
497 if (defined($pool)) {
498 $rpcenv->check_pool_exist($pool);
501 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
502 if defined($storage);
504 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
506 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
508 } elsif ($archive && $force && (-f
$filename) &&
509 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
510 # OK: user has VM.Backup permissions, and want to restore an existing VM
516 &$resolve_cdrom_alias($param);
518 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
520 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
522 foreach my $opt (keys %$param) {
523 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
524 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
525 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
527 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
528 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
532 PVE
::QemuServer
::add_random_macs
($param);
534 my $keystr = join(' ', keys %$param);
535 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
537 if ($archive eq '-') {
538 die "pipe requires cli environment\n"
539 if $rpcenv->{type
} ne 'cli';
541 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
542 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
546 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
548 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
549 die "$emsg $@" if $@;
551 my $restorefn = sub {
552 my $conf = PVE
::QemuConfig-
>load_config($vmid);
554 PVE
::QemuConfig-
>check_protection($conf, $emsg);
556 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
559 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
565 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
566 # Convert restored VM to template if backup was VM template
567 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
568 warn "Convert to template.\n";
569 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
573 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
575 if ($start_after_create) {
576 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
581 # ensure no old replication state are exists
582 PVE
::ReplicationState
::delete_guest_states
($vmid);
584 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
588 # ensure no old replication state are exists
589 PVE
::ReplicationState
::delete_guest_states
($vmid);
597 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
601 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
603 if (!$conf->{bootdisk
}) {
604 my $firstdisk = PVE
::QemuServer
::resolve_first_disk
($conf);
605 $conf->{bootdisk
} = $firstdisk if $firstdisk;
608 # auto generate uuid if user did not specify smbios1 option
609 if (!$conf->{smbios1
}) {
610 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
613 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
614 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
617 PVE
::QemuConfig-
>write_config($vmid, $conf);
623 foreach my $volid (@$vollist) {
624 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
630 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
633 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
635 if ($start_after_create) {
636 print "Execute autostart\n";
637 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
642 my ($code, $worker_name);
644 $worker_name = 'qmrestore';
646 eval { $restorefn->() };
648 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
654 $worker_name = 'qmcreate';
656 eval { $createfn->() };
659 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
660 unlink($conffile) or die "failed to remove config file: $!\n";
668 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
671 __PACKAGE__-
>register_method({
676 description
=> "Directory index",
681 additionalProperties
=> 0,
683 node
=> get_standard_option
('pve-node'),
684 vmid
=> get_standard_option
('pve-vmid'),
692 subdir
=> { type
=> 'string' },
695 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
701 { subdir
=> 'config' },
702 { subdir
=> 'pending' },
703 { subdir
=> 'status' },
704 { subdir
=> 'unlink' },
705 { subdir
=> 'vncproxy' },
706 { subdir
=> 'termproxy' },
707 { subdir
=> 'migrate' },
708 { subdir
=> 'resize' },
709 { subdir
=> 'move' },
711 { subdir
=> 'rrddata' },
712 { subdir
=> 'monitor' },
713 { subdir
=> 'agent' },
714 { subdir
=> 'snapshot' },
715 { subdir
=> 'spiceproxy' },
716 { subdir
=> 'sendkey' },
717 { subdir
=> 'firewall' },
723 __PACKAGE__-
>register_method ({
724 subclass
=> "PVE::API2::Firewall::VM",
725 path
=> '{vmid}/firewall',
728 __PACKAGE__-
>register_method ({
729 subclass
=> "PVE::API2::Qemu::Agent",
730 path
=> '{vmid}/agent',
733 __PACKAGE__-
>register_method({
735 path
=> '{vmid}/rrd',
737 protected
=> 1, # fixme: can we avoid that?
739 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
741 description
=> "Read VM RRD statistics (returns PNG)",
743 additionalProperties
=> 0,
745 node
=> get_standard_option
('pve-node'),
746 vmid
=> get_standard_option
('pve-vmid'),
748 description
=> "Specify the time frame you are interested in.",
750 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
753 description
=> "The list of datasources you want to display.",
754 type
=> 'string', format
=> 'pve-configid-list',
757 description
=> "The RRD consolidation function",
759 enum
=> [ 'AVERAGE', 'MAX' ],
767 filename
=> { type
=> 'string' },
773 return PVE
::RRD
::create_rrd_graph
(
774 "pve2-vm/$param->{vmid}", $param->{timeframe
},
775 $param->{ds
}, $param->{cf
});
779 __PACKAGE__-
>register_method({
781 path
=> '{vmid}/rrddata',
783 protected
=> 1, # fixme: can we avoid that?
785 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
787 description
=> "Read VM RRD statistics",
789 additionalProperties
=> 0,
791 node
=> get_standard_option
('pve-node'),
792 vmid
=> get_standard_option
('pve-vmid'),
794 description
=> "Specify the time frame you are interested in.",
796 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
799 description
=> "The RRD consolidation function",
801 enum
=> [ 'AVERAGE', 'MAX' ],
816 return PVE
::RRD
::create_rrd_data
(
817 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
821 __PACKAGE__-
>register_method({
823 path
=> '{vmid}/config',
826 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
828 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
831 additionalProperties
=> 0,
833 node
=> get_standard_option
('pve-node'),
834 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
836 description
=> "Get current values (instead of pending values).",
841 snapshot
=> get_standard_option
('pve-snapshot-name', {
842 description
=> "Fetch config values from given snapshot.",
845 my ($cmd, $pname, $cur, $args) = @_;
846 PVE
::QemuConfig-
>snapshot_list($args->[0]);
852 description
=> "The current VM configuration.",
854 properties
=> PVE
::QemuServer
::json_config_properties
({
857 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
864 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
865 current
=> "cannot use 'snapshot' parameter with 'current'"})
866 if ($param->{snapshot
} && $param->{current
});
869 if ($param->{snapshot
}) {
870 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
872 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
874 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
879 __PACKAGE__-
>register_method({
880 name
=> 'vm_pending',
881 path
=> '{vmid}/pending',
884 description
=> "Get virtual machine configuration, including pending changes.",
886 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
889 additionalProperties
=> 0,
891 node
=> get_standard_option
('pve-node'),
892 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
901 description
=> "Configuration option name.",
905 description
=> "Current value.",
910 description
=> "Pending value.",
915 description
=> "Indicates a pending delete request if present and not 0. " .
916 "The value 2 indicates a force-delete request.",
928 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
930 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
932 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
933 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
935 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
938 # POST/PUT {vmid}/config implementation
940 # The original API used PUT (idempotent) an we assumed that all operations
941 # are fast. But it turned out that almost any configuration change can
942 # involve hot-plug actions, or disk alloc/free. Such actions can take long
943 # time to complete and have side effects (not idempotent).
945 # The new implementation uses POST and forks a worker process. We added
946 # a new option 'background_delay'. If specified we wait up to
947 # 'background_delay' second for the worker task to complete. It returns null
948 # if the task is finished within that time, else we return the UPID.
950 my $update_vm_api = sub {
951 my ($param, $sync) = @_;
953 my $rpcenv = PVE
::RPCEnvironment
::get
();
955 my $authuser = $rpcenv->get_user();
957 my $node = extract_param
($param, 'node');
959 my $vmid = extract_param
($param, 'vmid');
961 my $digest = extract_param
($param, 'digest');
963 my $background_delay = extract_param
($param, 'background_delay');
965 if (defined(my $cipassword = $param->{cipassword
})) {
966 # Same logic as in cloud-init (but with the regex fixed...)
967 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
968 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
971 my @paramarr = (); # used for log message
972 foreach my $key (sort keys %$param) {
973 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
974 push @paramarr, "-$key", $value;
977 my $skiplock = extract_param
($param, 'skiplock');
978 raise_param_exc
({ skiplock
=> "Only root may use this option." })
979 if $skiplock && $authuser ne 'root@pam';
981 my $delete_str = extract_param
($param, 'delete');
983 my $revert_str = extract_param
($param, 'revert');
985 my $force = extract_param
($param, 'force');
987 if (defined(my $ssh_keys = $param->{sshkeys
})) {
988 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
989 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
992 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
994 my $storecfg = PVE
::Storage
::config
();
996 my $defaults = PVE
::QemuServer
::load_defaults
();
998 &$resolve_cdrom_alias($param);
1000 # now try to verify all parameters
1003 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1004 if (!PVE
::QemuServer
::option_exists
($opt)) {
1005 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1008 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1009 "-revert $opt' at the same time" })
1010 if defined($param->{$opt});
1012 $revert->{$opt} = 1;
1016 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1017 $opt = 'ide2' if $opt eq 'cdrom';
1019 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1020 "-delete $opt' at the same time" })
1021 if defined($param->{$opt});
1023 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1024 "-revert $opt' at the same time" })
1027 if (!PVE
::QemuServer
::option_exists
($opt)) {
1028 raise_param_exc
({ delete => "unknown option '$opt'" });
1034 my $repl_conf = PVE
::ReplicationConfig-
>new();
1035 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1036 my $check_replication = sub {
1038 return if !$is_replicated;
1039 my $volid = $drive->{file
};
1040 return if !$volid || !($drive->{replicate
}//1);
1041 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1043 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1044 return if $volname eq 'cloudinit';
1047 if ($volid =~ $NEW_DISK_RE) {
1049 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1051 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1053 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1054 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1055 return if $scfg->{shared
};
1056 die "cannot add non-replicatable volume to a replicated VM\n";
1059 foreach my $opt (keys %$param) {
1060 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1061 # cleanup drive path
1062 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1063 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1064 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1065 $check_replication->($drive);
1066 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
1067 } elsif ($opt =~ m/^net(\d+)$/) {
1069 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1070 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1071 } elsif ($opt eq 'vmgenid') {
1072 if ($param->{$opt} eq '1') {
1073 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1075 } elsif ($opt eq 'hookscript') {
1076 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1077 raise_param_exc
({ $opt => $@ }) if $@;
1081 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1083 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1085 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1087 my $updatefn = sub {
1089 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1091 die "checksum missmatch (file change by other user?)\n"
1092 if $digest && $digest ne $conf->{digest
};
1094 # FIXME: 'suspended' lock should probabyl be a state or "weak" lock?!
1095 if (scalar(@delete) && grep { $_ eq 'vmstate'} @delete) {
1096 if (defined($conf->{lock}) && $conf->{lock} eq 'suspended') {
1097 delete $conf->{lock}; # for check lock check, not written out
1098 push @delete, 'lock'; # this is the real deal to write it out
1100 push @delete, 'runningmachine' if $conf->{runningmachine
};
1103 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1105 foreach my $opt (keys %$revert) {
1106 if (defined($conf->{$opt})) {
1107 $param->{$opt} = $conf->{$opt};
1108 } elsif (defined($conf->{pending
}->{$opt})) {
1113 if ($param->{memory
} || defined($param->{balloon
})) {
1114 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1115 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1117 die "balloon value too large (must be smaller than assigned memory)\n"
1118 if $balloon && $balloon > $maxmem;
1121 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1125 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1127 # write updates to pending section
1129 my $modified = {}; # record what $option we modify
1131 foreach my $opt (@delete) {
1132 $modified->{$opt} = 1;
1133 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1135 # value of what we want to delete, independent if pending or not
1136 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1137 if (!defined($val)) {
1138 warn "cannot delete '$opt' - not set in current configuration!\n";
1139 $modified->{$opt} = 0;
1142 my $is_pending_val = defined($conf->{pending
}->{$opt});
1143 delete $conf->{pending
}->{$opt};
1145 if ($opt =~ m/^unused/) {
1146 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1147 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1148 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1149 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1150 delete $conf->{$opt};
1151 PVE
::QemuConfig-
>write_config($vmid, $conf);
1153 } elsif ($opt eq 'vmstate') {
1154 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1155 # the user needs Disk and PowerMgmt privileges to remove the vmstate
1156 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk', 'VM.PowerMgmt' ]);
1157 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1158 delete $conf->{$opt};
1159 PVE
::QemuConfig-
>write_config($vmid, $conf);
1161 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1162 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1163 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1164 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1166 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1167 PVE
::QemuConfig-
>write_config($vmid, $conf);
1168 } elsif ($opt =~ m/^serial\d+$/) {
1169 if ($val eq 'socket') {
1170 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1171 } elsif ($authuser ne 'root@pam') {
1172 die "only root can delete '$opt' config for real devices\n";
1174 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1175 PVE
::QemuConfig-
>write_config($vmid, $conf);
1176 } elsif ($opt =~ m/^usb\d+$/) {
1177 if ($val =~ m/spice/) {
1178 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1179 } elsif ($authuser ne 'root@pam') {
1180 die "only root can delete '$opt' config for real devices\n";
1182 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1183 PVE
::QemuConfig-
>write_config($vmid, $conf);
1185 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1186 PVE
::QemuConfig-
>write_config($vmid, $conf);
1190 foreach my $opt (keys %$param) { # add/change
1191 $modified->{$opt} = 1;
1192 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1193 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1195 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1197 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1198 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1199 # FIXME: cloudinit: CDROM or Disk?
1200 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1201 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1203 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1205 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1206 if defined($conf->{pending
}->{$opt});
1208 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1209 } elsif ($opt =~ m/^serial\d+/) {
1210 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1211 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1212 } elsif ($authuser ne 'root@pam') {
1213 die "only root can modify '$opt' config for real devices\n";
1215 $conf->{pending
}->{$opt} = $param->{$opt};
1216 } elsif ($opt =~ m/^usb\d+/) {
1217 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1218 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1219 } elsif ($authuser ne 'root@pam') {
1220 die "only root can modify '$opt' config for real devices\n";
1222 $conf->{pending
}->{$opt} = $param->{$opt};
1224 $conf->{pending
}->{$opt} = $param->{$opt};
1226 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1227 PVE
::QemuConfig-
>write_config($vmid, $conf);
1230 # remove pending changes when nothing changed
1231 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1232 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1233 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1235 return if !scalar(keys %{$conf->{pending
}});
1237 my $running = PVE
::QemuServer
::check_running
($vmid);
1239 # apply pending changes
1241 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1245 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1246 raise_param_exc
($errors) if scalar(keys %$errors);
1248 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1258 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1260 if ($background_delay) {
1262 # Note: It would be better to do that in the Event based HTTPServer
1263 # to avoid blocking call to sleep.
1265 my $end_time = time() + $background_delay;
1267 my $task = PVE
::Tools
::upid_decode
($upid);
1270 while (time() < $end_time) {
1271 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1273 sleep(1); # this gets interrupted when child process ends
1277 my $status = PVE
::Tools
::upid_read_status
($upid);
1278 return undef if $status eq 'OK';
1287 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1290 my $vm_config_perm_list = [
1295 'VM.Config.Network',
1297 'VM.Config.Options',
1300 __PACKAGE__-
>register_method({
1301 name
=> 'update_vm_async',
1302 path
=> '{vmid}/config',
1306 description
=> "Set virtual machine options (asynchrounous API).",
1308 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1311 additionalProperties
=> 0,
1312 properties
=> PVE
::QemuServer
::json_config_properties
(
1314 node
=> get_standard_option
('pve-node'),
1315 vmid
=> get_standard_option
('pve-vmid'),
1316 skiplock
=> get_standard_option
('skiplock'),
1318 type
=> 'string', format
=> 'pve-configid-list',
1319 description
=> "A list of settings you want to delete.",
1323 type
=> 'string', format
=> 'pve-configid-list',
1324 description
=> "Revert a pending change.",
1329 description
=> $opt_force_description,
1331 requires
=> 'delete',
1335 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1339 background_delay
=> {
1341 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1352 code
=> $update_vm_api,
1355 __PACKAGE__-
>register_method({
1356 name
=> 'update_vm',
1357 path
=> '{vmid}/config',
1361 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1363 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1366 additionalProperties
=> 0,
1367 properties
=> PVE
::QemuServer
::json_config_properties
(
1369 node
=> get_standard_option
('pve-node'),
1370 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1371 skiplock
=> get_standard_option
('skiplock'),
1373 type
=> 'string', format
=> 'pve-configid-list',
1374 description
=> "A list of settings you want to delete.",
1378 type
=> 'string', format
=> 'pve-configid-list',
1379 description
=> "Revert a pending change.",
1384 description
=> $opt_force_description,
1386 requires
=> 'delete',
1390 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1396 returns
=> { type
=> 'null' },
1399 &$update_vm_api($param, 1);
1404 __PACKAGE__-
>register_method({
1405 name
=> 'destroy_vm',
1410 description
=> "Destroy the vm (also delete all used/owned volumes).",
1412 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1415 additionalProperties
=> 0,
1417 node
=> get_standard_option
('pve-node'),
1418 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1419 skiplock
=> get_standard_option
('skiplock'),
1422 description
=> "Remove vmid from backup cron jobs.",
1433 my $rpcenv = PVE
::RPCEnvironment
::get
();
1434 my $authuser = $rpcenv->get_user();
1435 my $vmid = $param->{vmid
};
1437 my $skiplock = $param->{skiplock
};
1438 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1439 if $skiplock && $authuser ne 'root@pam';
1442 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1443 my $storecfg = PVE
::Storage
::config
();
1444 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1445 die "unable to remove VM $vmid - used in HA resources\n"
1446 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1448 if (!$param->{purge
}) {
1449 # don't allow destroy if with replication jobs but no purge param
1450 my $repl_conf = PVE
::ReplicationConfig-
>new();
1451 $repl_conf->check_for_existing_jobs($vmid);
1454 # early tests (repeat after locking)
1455 die "VM $vmid is running - destroy failed\n"
1456 if PVE
::QemuServer
::check_running
($vmid);
1461 syslog
('info', "destroy VM $vmid: $upid\n");
1462 PVE
::QemuConfig-
>lock_config($vmid, sub {
1463 die "VM $vmid is running - destroy failed\n"
1464 if (PVE
::QemuServer
::check_running
($vmid));
1466 PVE
::QemuServer
::destroy_vm
($storecfg, $vmid, $skiplock, { lock => 'destroyed' });
1468 PVE
::AccessControl
::remove_vm_access
($vmid);
1469 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1470 if ($param->{purge
}) {
1471 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1472 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1475 # only now remove the zombie config, else we can have reuse race
1476 PVE
::QemuConfig-
>destroy_config($vmid);
1480 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1483 __PACKAGE__-
>register_method({
1485 path
=> '{vmid}/unlink',
1489 description
=> "Unlink/delete disk images.",
1491 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1494 additionalProperties
=> 0,
1496 node
=> get_standard_option
('pve-node'),
1497 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1499 type
=> 'string', format
=> 'pve-configid-list',
1500 description
=> "A list of disk IDs you want to delete.",
1504 description
=> $opt_force_description,
1509 returns
=> { type
=> 'null'},
1513 $param->{delete} = extract_param
($param, 'idlist');
1515 __PACKAGE__-
>update_vm($param);
1522 __PACKAGE__-
>register_method({
1524 path
=> '{vmid}/vncproxy',
1528 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1530 description
=> "Creates a TCP VNC proxy connections.",
1532 additionalProperties
=> 0,
1534 node
=> get_standard_option
('pve-node'),
1535 vmid
=> get_standard_option
('pve-vmid'),
1539 description
=> "starts websockify instead of vncproxy",
1544 additionalProperties
=> 0,
1546 user
=> { type
=> 'string' },
1547 ticket
=> { type
=> 'string' },
1548 cert
=> { type
=> 'string' },
1549 port
=> { type
=> 'integer' },
1550 upid
=> { type
=> 'string' },
1556 my $rpcenv = PVE
::RPCEnvironment
::get
();
1558 my $authuser = $rpcenv->get_user();
1560 my $vmid = $param->{vmid
};
1561 my $node = $param->{node
};
1562 my $websocket = $param->{websocket
};
1564 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1565 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1567 my $authpath = "/vms/$vmid";
1569 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1571 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1577 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1578 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1579 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1580 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1581 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1583 $family = PVE
::Tools
::get_host_address_family
($node);
1586 my $port = PVE
::Tools
::next_vnc_port
($family);
1593 syslog
('info', "starting vnc proxy $upid\n");
1599 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1601 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1602 '-timeout', $timeout, '-authpath', $authpath,
1603 '-perm', 'Sys.Console'];
1605 if ($param->{websocket
}) {
1606 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1607 push @$cmd, '-notls', '-listen', 'localhost';
1610 push @$cmd, '-c', @$remcmd, @$termcmd;
1612 PVE
::Tools
::run_command
($cmd);
1616 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1618 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1620 my $sock = IO
::Socket
::IP-
>new(
1625 GetAddrInfoFlags
=> 0,
1626 ) or die "failed to create socket: $!\n";
1627 # Inside the worker we shouldn't have any previous alarms
1628 # running anyway...:
1630 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1632 accept(my $cli, $sock) or die "connection failed: $!\n";
1635 if (PVE
::Tools
::run_command
($cmd,
1636 output
=> '>&'.fileno($cli),
1637 input
=> '<&'.fileno($cli),
1640 die "Failed to run vncproxy.\n";
1647 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1649 PVE
::Tools
::wait_for_vnc_port
($port);
1660 __PACKAGE__-
>register_method({
1661 name
=> 'termproxy',
1662 path
=> '{vmid}/termproxy',
1666 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1668 description
=> "Creates a TCP proxy connections.",
1670 additionalProperties
=> 0,
1672 node
=> get_standard_option
('pve-node'),
1673 vmid
=> get_standard_option
('pve-vmid'),
1677 enum
=> [qw(serial0 serial1 serial2 serial3)],
1678 description
=> "opens a serial terminal (defaults to display)",
1683 additionalProperties
=> 0,
1685 user
=> { type
=> 'string' },
1686 ticket
=> { type
=> 'string' },
1687 port
=> { type
=> 'integer' },
1688 upid
=> { type
=> 'string' },
1694 my $rpcenv = PVE
::RPCEnvironment
::get
();
1696 my $authuser = $rpcenv->get_user();
1698 my $vmid = $param->{vmid
};
1699 my $node = $param->{node
};
1700 my $serial = $param->{serial
};
1702 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1704 if (!defined($serial)) {
1705 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1706 $serial = $conf->{vga
};
1710 my $authpath = "/vms/$vmid";
1712 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1717 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1718 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1719 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1720 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1721 push @$remcmd, '--';
1723 $family = PVE
::Tools
::get_host_address_family
($node);
1726 my $port = PVE
::Tools
::next_vnc_port
($family);
1728 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1729 push @$termcmd, '-iface', $serial if $serial;
1734 syslog
('info', "starting qemu termproxy $upid\n");
1736 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1737 '--perm', 'VM.Console', '--'];
1738 push @$cmd, @$remcmd, @$termcmd;
1740 PVE
::Tools
::run_command
($cmd);
1743 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1745 PVE
::Tools
::wait_for_vnc_port
($port);
1755 __PACKAGE__-
>register_method({
1756 name
=> 'vncwebsocket',
1757 path
=> '{vmid}/vncwebsocket',
1760 description
=> "You also need to pass a valid ticket (vncticket).",
1761 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1763 description
=> "Opens a weksocket for VNC traffic.",
1765 additionalProperties
=> 0,
1767 node
=> get_standard_option
('pve-node'),
1768 vmid
=> get_standard_option
('pve-vmid'),
1770 description
=> "Ticket from previous call to vncproxy.",
1775 description
=> "Port number returned by previous vncproxy call.",
1785 port
=> { type
=> 'string' },
1791 my $rpcenv = PVE
::RPCEnvironment
::get
();
1793 my $authuser = $rpcenv->get_user();
1795 my $vmid = $param->{vmid
};
1796 my $node = $param->{node
};
1798 my $authpath = "/vms/$vmid";
1800 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1802 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1804 # Note: VNC ports are acessible from outside, so we do not gain any
1805 # security if we verify that $param->{port} belongs to VM $vmid. This
1806 # check is done by verifying the VNC ticket (inside VNC protocol).
1808 my $port = $param->{port
};
1810 return { port
=> $port };
1813 __PACKAGE__-
>register_method({
1814 name
=> 'spiceproxy',
1815 path
=> '{vmid}/spiceproxy',
1820 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1822 description
=> "Returns a SPICE configuration to connect to the VM.",
1824 additionalProperties
=> 0,
1826 node
=> get_standard_option
('pve-node'),
1827 vmid
=> get_standard_option
('pve-vmid'),
1828 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1831 returns
=> get_standard_option
('remote-viewer-config'),
1835 my $rpcenv = PVE
::RPCEnvironment
::get
();
1837 my $authuser = $rpcenv->get_user();
1839 my $vmid = $param->{vmid
};
1840 my $node = $param->{node
};
1841 my $proxy = $param->{proxy
};
1843 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1844 my $title = "VM $vmid";
1845 $title .= " - ". $conf->{name
} if $conf->{name
};
1847 my $port = PVE
::QemuServer
::spice_port
($vmid);
1849 my ($ticket, undef, $remote_viewer_config) =
1850 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1852 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1853 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1855 return $remote_viewer_config;
1858 __PACKAGE__-
>register_method({
1860 path
=> '{vmid}/status',
1863 description
=> "Directory index",
1868 additionalProperties
=> 0,
1870 node
=> get_standard_option
('pve-node'),
1871 vmid
=> get_standard_option
('pve-vmid'),
1879 subdir
=> { type
=> 'string' },
1882 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1888 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1891 { subdir
=> 'current' },
1892 { subdir
=> 'start' },
1893 { subdir
=> 'stop' },
1894 { subdir
=> 'reset' },
1895 { subdir
=> 'shutdown' },
1896 { subdir
=> 'suspend' },
1897 { subdir
=> 'reboot' },
1903 __PACKAGE__-
>register_method({
1904 name
=> 'vm_status',
1905 path
=> '{vmid}/status/current',
1908 protected
=> 1, # qemu pid files are only readable by root
1909 description
=> "Get virtual machine status.",
1911 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1914 additionalProperties
=> 0,
1916 node
=> get_standard_option
('pve-node'),
1917 vmid
=> get_standard_option
('pve-vmid'),
1923 %$PVE::QemuServer
::vmstatus_return_properties
,
1925 description
=> "HA manager service status.",
1929 description
=> "Qemu VGA configuration supports spice.",
1934 description
=> "Qemu GuestAgent enabled in config.",
1944 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1946 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1947 my $status = $vmstatus->{$param->{vmid
}};
1949 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1951 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1952 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1957 __PACKAGE__-
>register_method({
1959 path
=> '{vmid}/status/start',
1963 description
=> "Start virtual machine.",
1965 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1968 additionalProperties
=> 0,
1970 node
=> get_standard_option
('pve-node'),
1971 vmid
=> get_standard_option
('pve-vmid',
1972 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1973 skiplock
=> get_standard_option
('skiplock'),
1974 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1975 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1978 enum
=> ['secure', 'insecure'],
1979 description
=> "Migration traffic is encrypted using an SSH " .
1980 "tunnel by default. On secure, completely private networks " .
1981 "this can be disabled to increase performance.",
1984 migration_network
=> {
1985 type
=> 'string', format
=> 'CIDR',
1986 description
=> "CIDR of the (sub) network that is used for migration.",
1989 machine
=> get_standard_option
('pve-qemu-machine'),
1991 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
2003 my $rpcenv = PVE
::RPCEnvironment
::get
();
2004 my $authuser = $rpcenv->get_user();
2006 my $node = extract_param
($param, 'node');
2007 my $vmid = extract_param
($param, 'vmid');
2009 my $machine = extract_param
($param, 'machine');
2011 my $get_root_param = sub {
2012 my $value = extract_param
($param, $_[0]);
2013 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2014 if $value && $authuser ne 'root@pam';
2018 my $stateuri = $get_root_param->('stateuri');
2019 my $skiplock = $get_root_param->('skiplock');
2020 my $migratedfrom = $get_root_param->('migratedfrom');
2021 my $migration_type = $get_root_param->('migration_type');
2022 my $migration_network = $get_root_param->('migration_network');
2023 my $targetstorage = $get_root_param->('targetstorage');
2025 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2026 if $targetstorage && !$migratedfrom;
2028 # read spice ticket from STDIN
2030 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2031 if (defined(my $line = <STDIN
>)) {
2033 $spice_ticket = $line;
2037 PVE
::Cluster
::check_cfs_quorum
();
2039 my $storecfg = PVE
::Storage
::config
();
2041 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2045 print "Requesting HA start for VM $vmid\n";
2047 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2048 PVE
::Tools
::run_command
($cmd);
2052 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2059 syslog
('info', "start VM $vmid: $upid\n");
2061 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2062 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
2066 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2070 __PACKAGE__-
>register_method({
2072 path
=> '{vmid}/status/stop',
2076 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2077 "is akin to pulling the power plug of a running computer and may damage the VM data",
2079 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2082 additionalProperties
=> 0,
2084 node
=> get_standard_option
('pve-node'),
2085 vmid
=> get_standard_option
('pve-vmid',
2086 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2087 skiplock
=> get_standard_option
('skiplock'),
2088 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2090 description
=> "Wait maximal timeout seconds.",
2096 description
=> "Do not deactivate storage volumes.",
2109 my $rpcenv = PVE
::RPCEnvironment
::get
();
2110 my $authuser = $rpcenv->get_user();
2112 my $node = extract_param
($param, 'node');
2113 my $vmid = extract_param
($param, 'vmid');
2115 my $skiplock = extract_param
($param, 'skiplock');
2116 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2117 if $skiplock && $authuser ne 'root@pam';
2119 my $keepActive = extract_param
($param, 'keepActive');
2120 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2121 if $keepActive && $authuser ne 'root@pam';
2123 my $migratedfrom = extract_param
($param, 'migratedfrom');
2124 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2125 if $migratedfrom && $authuser ne 'root@pam';
2128 my $storecfg = PVE
::Storage
::config
();
2130 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2135 print "Requesting HA stop for VM $vmid\n";
2137 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2138 PVE
::Tools
::run_command
($cmd);
2142 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2148 syslog
('info', "stop VM $vmid: $upid\n");
2150 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2151 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2155 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2159 __PACKAGE__-
>register_method({
2161 path
=> '{vmid}/status/reset',
2165 description
=> "Reset virtual machine.",
2167 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2170 additionalProperties
=> 0,
2172 node
=> get_standard_option
('pve-node'),
2173 vmid
=> get_standard_option
('pve-vmid',
2174 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2175 skiplock
=> get_standard_option
('skiplock'),
2184 my $rpcenv = PVE
::RPCEnvironment
::get
();
2186 my $authuser = $rpcenv->get_user();
2188 my $node = extract_param
($param, 'node');
2190 my $vmid = extract_param
($param, 'vmid');
2192 my $skiplock = extract_param
($param, 'skiplock');
2193 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2194 if $skiplock && $authuser ne 'root@pam';
2196 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2201 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2206 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2209 __PACKAGE__-
>register_method({
2210 name
=> 'vm_shutdown',
2211 path
=> '{vmid}/status/shutdown',
2215 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2216 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2218 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2221 additionalProperties
=> 0,
2223 node
=> get_standard_option
('pve-node'),
2224 vmid
=> get_standard_option
('pve-vmid',
2225 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2226 skiplock
=> get_standard_option
('skiplock'),
2228 description
=> "Wait maximal timeout seconds.",
2234 description
=> "Make sure the VM stops.",
2240 description
=> "Do not deactivate storage volumes.",
2253 my $rpcenv = PVE
::RPCEnvironment
::get
();
2254 my $authuser = $rpcenv->get_user();
2256 my $node = extract_param
($param, 'node');
2257 my $vmid = extract_param
($param, 'vmid');
2259 my $skiplock = extract_param
($param, 'skiplock');
2260 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2261 if $skiplock && $authuser ne 'root@pam';
2263 my $keepActive = extract_param
($param, 'keepActive');
2264 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2265 if $keepActive && $authuser ne 'root@pam';
2267 my $storecfg = PVE
::Storage
::config
();
2271 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2272 # otherwise, we will infer a shutdown command, but run into the timeout,
2273 # then when the vm is resumed, it will instantly shutdown
2275 # checking the qmp status here to get feedback to the gui/cli/api
2276 # and the status query should not take too long
2277 my $qmpstatus = eval {
2278 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2279 mon_cmd
($vmid, "query-status");
2283 if (!$err && $qmpstatus->{status
} eq "paused") {
2284 if ($param->{forceStop
}) {
2285 warn "VM is paused - stop instead of shutdown\n";
2288 die "VM is paused - cannot shutdown\n";
2292 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2294 my $timeout = $param->{timeout
} // 60;
2298 print "Requesting HA stop for VM $vmid\n";
2300 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2301 PVE
::Tools
::run_command
($cmd);
2305 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2312 syslog
('info', "shutdown VM $vmid: $upid\n");
2314 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2315 $shutdown, $param->{forceStop
}, $keepActive);
2319 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2323 __PACKAGE__-
>register_method({
2324 name
=> 'vm_reboot',
2325 path
=> '{vmid}/status/reboot',
2329 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2331 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2334 additionalProperties
=> 0,
2336 node
=> get_standard_option
('pve-node'),
2337 vmid
=> get_standard_option
('pve-vmid',
2338 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2340 description
=> "Wait maximal timeout seconds for the shutdown.",
2353 my $rpcenv = PVE
::RPCEnvironment
::get
();
2354 my $authuser = $rpcenv->get_user();
2356 my $node = extract_param
($param, 'node');
2357 my $vmid = extract_param
($param, 'vmid');
2359 my $qmpstatus = eval {
2360 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2361 mon_cmd
($vmid, "query-status");
2365 if (!$err && $qmpstatus->{status
} eq "paused") {
2366 die "VM is paused - cannot shutdown\n";
2369 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2374 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2375 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2379 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2382 __PACKAGE__-
>register_method({
2383 name
=> 'vm_suspend',
2384 path
=> '{vmid}/status/suspend',
2388 description
=> "Suspend virtual machine.",
2390 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2393 additionalProperties
=> 0,
2395 node
=> get_standard_option
('pve-node'),
2396 vmid
=> get_standard_option
('pve-vmid',
2397 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2398 skiplock
=> get_standard_option
('skiplock'),
2403 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2405 statestorage
=> get_standard_option
('pve-storage-id', {
2406 description
=> "The storage for the VM state",
2407 requires
=> 'todisk',
2409 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2419 my $rpcenv = PVE
::RPCEnvironment
::get
();
2420 my $authuser = $rpcenv->get_user();
2422 my $node = extract_param
($param, 'node');
2423 my $vmid = extract_param
($param, 'vmid');
2425 my $todisk = extract_param
($param, 'todisk') // 0;
2427 my $statestorage = extract_param
($param, 'statestorage');
2429 my $skiplock = extract_param
($param, 'skiplock');
2430 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2431 if $skiplock && $authuser ne 'root@pam';
2433 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2435 die "Cannot suspend HA managed VM to disk\n"
2436 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2441 syslog
('info', "suspend VM $vmid: $upid\n");
2443 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2448 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2449 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2452 __PACKAGE__-
>register_method({
2453 name
=> 'vm_resume',
2454 path
=> '{vmid}/status/resume',
2458 description
=> "Resume virtual machine.",
2460 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2463 additionalProperties
=> 0,
2465 node
=> get_standard_option
('pve-node'),
2466 vmid
=> get_standard_option
('pve-vmid',
2467 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2468 skiplock
=> get_standard_option
('skiplock'),
2469 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2479 my $rpcenv = PVE
::RPCEnvironment
::get
();
2481 my $authuser = $rpcenv->get_user();
2483 my $node = extract_param
($param, 'node');
2485 my $vmid = extract_param
($param, 'vmid');
2487 my $skiplock = extract_param
($param, 'skiplock');
2488 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2489 if $skiplock && $authuser ne 'root@pam';
2491 my $nocheck = extract_param
($param, 'nocheck');
2493 my $to_disk_suspended;
2495 PVE
::QemuConfig-
>lock_config($vmid, sub {
2496 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2497 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2501 die "VM $vmid not running\n"
2502 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2507 syslog
('info', "resume VM $vmid: $upid\n");
2509 if (!$to_disk_suspended) {
2510 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2512 my $storecfg = PVE
::Storage
::config
();
2513 PVE
::QemuServer
::vm_start
($storecfg, $vmid, undef, $skiplock);
2519 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2522 __PACKAGE__-
>register_method({
2523 name
=> 'vm_sendkey',
2524 path
=> '{vmid}/sendkey',
2528 description
=> "Send key event to virtual machine.",
2530 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2533 additionalProperties
=> 0,
2535 node
=> get_standard_option
('pve-node'),
2536 vmid
=> get_standard_option
('pve-vmid',
2537 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2538 skiplock
=> get_standard_option
('skiplock'),
2540 description
=> "The key (qemu monitor encoding).",
2545 returns
=> { type
=> 'null'},
2549 my $rpcenv = PVE
::RPCEnvironment
::get
();
2551 my $authuser = $rpcenv->get_user();
2553 my $node = extract_param
($param, 'node');
2555 my $vmid = extract_param
($param, 'vmid');
2557 my $skiplock = extract_param
($param, 'skiplock');
2558 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2559 if $skiplock && $authuser ne 'root@pam';
2561 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2566 __PACKAGE__-
>register_method({
2567 name
=> 'vm_feature',
2568 path
=> '{vmid}/feature',
2572 description
=> "Check if feature for virtual machine is available.",
2574 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2577 additionalProperties
=> 0,
2579 node
=> get_standard_option
('pve-node'),
2580 vmid
=> get_standard_option
('pve-vmid'),
2582 description
=> "Feature to check.",
2584 enum
=> [ 'snapshot', 'clone', 'copy' ],
2586 snapname
=> get_standard_option
('pve-snapshot-name', {
2594 hasFeature
=> { type
=> 'boolean' },
2597 items
=> { type
=> 'string' },
2604 my $node = extract_param
($param, 'node');
2606 my $vmid = extract_param
($param, 'vmid');
2608 my $snapname = extract_param
($param, 'snapname');
2610 my $feature = extract_param
($param, 'feature');
2612 my $running = PVE
::QemuServer
::check_running
($vmid);
2614 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2617 my $snap = $conf->{snapshots
}->{$snapname};
2618 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2621 my $storecfg = PVE
::Storage
::config
();
2623 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2624 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2627 hasFeature
=> $hasFeature,
2628 nodes
=> [ keys %$nodelist ],
2632 __PACKAGE__-
>register_method({
2634 path
=> '{vmid}/clone',
2638 description
=> "Create a copy of virtual machine/template.",
2640 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2641 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2642 "'Datastore.AllocateSpace' on any used storage.",
2645 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2647 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2648 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2653 additionalProperties
=> 0,
2655 node
=> get_standard_option
('pve-node'),
2656 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2657 newid
=> get_standard_option
('pve-vmid', {
2658 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2659 description
=> 'VMID for the clone.' }),
2662 type
=> 'string', format
=> 'dns-name',
2663 description
=> "Set a name for the new VM.",
2668 description
=> "Description for the new VM.",
2672 type
=> 'string', format
=> 'pve-poolid',
2673 description
=> "Add the new VM to the specified pool.",
2675 snapname
=> get_standard_option
('pve-snapshot-name', {
2678 storage
=> get_standard_option
('pve-storage-id', {
2679 description
=> "Target storage for full clone.",
2683 description
=> "Target format for file storage. Only valid for full clone.",
2686 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2691 description
=> "Create a full copy of all disks. This is always done when " .
2692 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2694 target
=> get_standard_option
('pve-node', {
2695 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2699 description
=> "Override I/O bandwidth limit (in KiB/s).",
2703 default => 'clone limit from datacenter or storage config',
2713 my $rpcenv = PVE
::RPCEnvironment
::get
();
2715 my $authuser = $rpcenv->get_user();
2717 my $node = extract_param
($param, 'node');
2719 my $vmid = extract_param
($param, 'vmid');
2721 my $newid = extract_param
($param, 'newid');
2723 my $pool = extract_param
($param, 'pool');
2725 if (defined($pool)) {
2726 $rpcenv->check_pool_exist($pool);
2729 my $snapname = extract_param
($param, 'snapname');
2731 my $storage = extract_param
($param, 'storage');
2733 my $format = extract_param
($param, 'format');
2735 my $target = extract_param
($param, 'target');
2737 my $localnode = PVE
::INotify
::nodename
();
2739 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2741 PVE
::Cluster
::check_node_exists
($target) if $target;
2743 my $storecfg = PVE
::Storage
::config
();
2746 # check if storage is enabled on local node
2747 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2749 # check if storage is available on target node
2750 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2751 # clone only works if target storage is shared
2752 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2753 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2757 PVE
::Cluster
::check_cfs_quorum
();
2759 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2761 # exclusive lock if VM is running - else shared lock is enough;
2762 my $shared_lock = $running ?
0 : 1;
2766 # do all tests after lock
2767 # we also try to do all tests before we fork the worker
2769 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2771 PVE
::QemuConfig-
>check_lock($conf);
2773 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2775 die "unexpected state change\n" if $verify_running != $running;
2777 die "snapshot '$snapname' does not exist\n"
2778 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2780 my $full = extract_param
($param, 'full');
2781 if (!defined($full)) {
2782 $full = !PVE
::QemuConfig-
>is_template($conf);
2785 die "parameter 'storage' not allowed for linked clones\n"
2786 if defined($storage) && !$full;
2788 die "parameter 'format' not allowed for linked clones\n"
2789 if defined($format) && !$full;
2791 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2793 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2795 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2797 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2799 die "unable to create VM $newid: config file already exists\n"
2802 my $newconf = { lock => 'clone' };
2807 foreach my $opt (keys %$oldconf) {
2808 my $value = $oldconf->{$opt};
2810 # do not copy snapshot related info
2811 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2812 $opt eq 'vmstate' || $opt eq 'snapstate';
2814 # no need to copy unused images, because VMID(owner) changes anyways
2815 next if $opt =~ m/^unused\d+$/;
2817 # always change MAC! address
2818 if ($opt =~ m/^net(\d+)$/) {
2819 my $net = PVE
::QemuServer
::parse_net
($value);
2820 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2821 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2822 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2823 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2824 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2825 die "unable to parse drive options for '$opt'\n" if !$drive;
2826 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2827 $newconf->{$opt} = $value; # simply copy configuration
2829 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2830 die "Full clone feature is not supported for drive '$opt'\n"
2831 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2832 $fullclone->{$opt} = 1;
2834 # not full means clone instead of copy
2835 die "Linked clone feature is not supported for drive '$opt'\n"
2836 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2838 $drives->{$opt} = $drive;
2839 push @$vollist, $drive->{file
};
2842 # copy everything else
2843 $newconf->{$opt} = $value;
2847 # auto generate a new uuid
2848 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2849 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2850 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2852 # auto generate a new vmgenid if the option was set
2853 if ($newconf->{vmgenid
}) {
2854 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2857 delete $newconf->{template
};
2859 if ($param->{name
}) {
2860 $newconf->{name
} = $param->{name
};
2862 if ($oldconf->{name
}) {
2863 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2865 $newconf->{name
} = "Copy-of-VM-$vmid";
2869 if ($param->{description
}) {
2870 $newconf->{description
} = $param->{description
};
2873 # create empty/temp config - this fails if VM already exists on other node
2874 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2879 my $newvollist = [];
2886 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2888 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2890 my $bwlimit = extract_param
($param, 'bwlimit');
2892 my $total_jobs = scalar(keys %{$drives});
2895 foreach my $opt (keys %$drives) {
2896 my $drive = $drives->{$opt};
2897 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2899 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
2900 my $storage_list = [ $src_sid ];
2901 push @$storage_list, $storage if defined($storage);
2902 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
2904 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2905 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2906 $jobs, $skipcomplete, $oldconf->{agent
}, $clonelimit);
2908 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
2910 PVE
::QemuConfig-
>write_config($newid, $newconf);
2914 delete $newconf->{lock};
2916 # do not write pending changes
2917 if (my @changes = keys %{$newconf->{pending
}}) {
2918 my $pending = join(',', @changes);
2919 warn "found pending changes for '$pending', discarding for clone\n";
2920 delete $newconf->{pending
};
2923 PVE
::QemuConfig-
>write_config($newid, $newconf);
2926 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2927 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2928 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2930 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2931 die "Failed to move config to node '$target' - rename failed: $!\n"
2932 if !rename($conffile, $newconffile);
2935 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2940 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2942 sleep 1; # some storage like rbd need to wait before release volume - really?
2944 foreach my $volid (@$newvollist) {
2945 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2948 die "clone failed: $err";
2954 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2956 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2959 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2960 # Aquire exclusive lock lock for $newid
2961 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2966 __PACKAGE__-
>register_method({
2967 name
=> 'move_vm_disk',
2968 path
=> '{vmid}/move_disk',
2972 description
=> "Move volume to different storage.",
2974 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2976 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2977 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2981 additionalProperties
=> 0,
2983 node
=> get_standard_option
('pve-node'),
2984 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2987 description
=> "The disk you want to move.",
2988 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2990 storage
=> get_standard_option
('pve-storage-id', {
2991 description
=> "Target storage.",
2992 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2996 description
=> "Target Format.",
2997 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3002 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3008 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3013 description
=> "Override I/O bandwidth limit (in KiB/s).",
3017 default => 'move limit from datacenter or storage config',
3023 description
=> "the task ID.",
3028 my $rpcenv = PVE
::RPCEnvironment
::get
();
3030 my $authuser = $rpcenv->get_user();
3032 my $node = extract_param
($param, 'node');
3034 my $vmid = extract_param
($param, 'vmid');
3036 my $digest = extract_param
($param, 'digest');
3038 my $disk = extract_param
($param, 'disk');
3040 my $storeid = extract_param
($param, 'storage');
3042 my $format = extract_param
($param, 'format');
3044 my $storecfg = PVE
::Storage
::config
();
3046 my $updatefn = sub {
3048 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3050 PVE
::QemuConfig-
>check_lock($conf);
3052 die "checksum missmatch (file change by other user?)\n"
3053 if $digest && $digest ne $conf->{digest
};
3055 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3057 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3059 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
3061 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3064 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3065 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3069 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
3070 (!$format || !$oldfmt || $oldfmt eq $format);
3072 # this only checks snapshots because $disk is passed!
3073 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3074 die "you can't move a disk with snapshots and delete the source\n"
3075 if $snapshotted && $param->{delete};
3077 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3079 my $running = PVE
::QemuServer
::check_running
($vmid);
3081 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3085 my $newvollist = [];
3091 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3093 warn "moving disk with snapshots, snapshots will not be moved!\n"
3096 my $bwlimit = extract_param
($param, 'bwlimit');
3097 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3099 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3100 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit);
3102 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3104 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3106 # convert moved disk to base if part of template
3107 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3108 if PVE
::QemuConfig-
>is_template($conf);
3110 PVE
::QemuConfig-
>write_config($vmid, $conf);
3112 if ($running && PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
} && PVE
::QemuServer
::qga_check_running
($vmid)) {
3113 eval { mon_cmd
($vmid, "guest-fstrim"); };
3117 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3118 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3125 foreach my $volid (@$newvollist) {
3126 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3129 die "storage migration failed: $err";
3132 if ($param->{delete}) {
3134 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3135 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3141 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3144 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3147 my $check_vm_disks_local = sub {
3148 my ($storecfg, $vmconf, $vmid) = @_;
3150 my $local_disks = {};
3152 # add some more information to the disks e.g. cdrom
3153 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3154 my ($volid, $attr) = @_;
3156 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3158 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3159 return if $scfg->{shared
};
3161 # The shared attr here is just a special case where the vdisk
3162 # is marked as shared manually
3163 return if $attr->{shared
};
3164 return if $attr->{cdrom
} and $volid eq "none";
3166 if (exists $local_disks->{$volid}) {
3167 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3169 $local_disks->{$volid} = $attr;
3170 # ensure volid is present in case it's needed
3171 $local_disks->{$volid}->{volid
} = $volid;
3175 return $local_disks;
3178 __PACKAGE__-
>register_method({
3179 name
=> 'migrate_vm_precondition',
3180 path
=> '{vmid}/migrate',
3184 description
=> "Get preconditions for migration.",
3186 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3189 additionalProperties
=> 0,
3191 node
=> get_standard_option
('pve-node'),
3192 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3193 target
=> get_standard_option
('pve-node', {
3194 description
=> "Target node.",
3195 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3203 running
=> { type
=> 'boolean' },
3207 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3209 not_allowed_nodes
=> {
3212 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3216 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3218 local_resources
=> {
3220 description
=> "List local resources e.g. pci, usb"
3227 my $rpcenv = PVE
::RPCEnvironment
::get
();
3229 my $authuser = $rpcenv->get_user();
3231 PVE
::Cluster
::check_cfs_quorum
();
3235 my $vmid = extract_param
($param, 'vmid');
3236 my $target = extract_param
($param, 'target');
3237 my $localnode = PVE
::INotify
::nodename
();
3241 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3242 my $storecfg = PVE
::Storage
::config
();
3245 # try to detect errors early
3246 PVE
::QemuConfig-
>check_lock($vmconf);
3248 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3250 # if vm is not running, return target nodes where local storage is available
3251 # for offline migration
3252 if (!$res->{running
}) {
3253 $res->{allowed_nodes
} = [];
3254 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3255 delete $checked_nodes->{$localnode};
3257 foreach my $node (keys %$checked_nodes) {
3258 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3259 push @{$res->{allowed_nodes
}}, $node;
3263 $res->{not_allowed_nodes
} = $checked_nodes;
3267 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3268 $res->{local_disks
} = [ values %$local_disks ];;
3270 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3272 $res->{local_resources
} = $local_resources;
3279 __PACKAGE__-
>register_method({
3280 name
=> 'migrate_vm',
3281 path
=> '{vmid}/migrate',
3285 description
=> "Migrate virtual machine. Creates a new migration task.",
3287 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3290 additionalProperties
=> 0,
3292 node
=> get_standard_option
('pve-node'),
3293 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3294 target
=> get_standard_option
('pve-node', {
3295 description
=> "Target node.",
3296 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3300 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3305 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3310 enum
=> ['secure', 'insecure'],
3311 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3314 migration_network
=> {
3315 type
=> 'string', format
=> 'CIDR',
3316 description
=> "CIDR of the (sub) network that is used for migration.",
3319 "with-local-disks" => {
3321 description
=> "Enable live storage migration for local disk",
3324 targetstorage
=> get_standard_option
('pve-storage-id', {
3325 description
=> "Default target storage.",
3327 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3330 description
=> "Override I/O bandwidth limit (in KiB/s).",
3334 default => 'migrate limit from datacenter or storage config',
3340 description
=> "the task ID.",
3345 my $rpcenv = PVE
::RPCEnvironment
::get
();
3346 my $authuser = $rpcenv->get_user();
3348 my $target = extract_param
($param, 'target');
3350 my $localnode = PVE
::INotify
::nodename
();
3351 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3353 PVE
::Cluster
::check_cfs_quorum
();
3355 PVE
::Cluster
::check_node_exists
($target);
3357 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3359 my $vmid = extract_param
($param, 'vmid');
3361 raise_param_exc
({ force
=> "Only root may use this option." })
3362 if $param->{force
} && $authuser ne 'root@pam';
3364 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3365 if $param->{migration_type
} && $authuser ne 'root@pam';
3367 # allow root only until better network permissions are available
3368 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3369 if $param->{migration_network
} && $authuser ne 'root@pam';
3372 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3374 # try to detect errors early
3376 PVE
::QemuConfig-
>check_lock($conf);
3378 if (PVE
::QemuServer
::check_running
($vmid)) {
3379 die "can't migrate running VM without --online\n" if !$param->{online
};
3381 warn "VM isn't running. Doing offline migration instead\n." if $param->{online
};
3382 $param->{online
} = 0;
3385 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3386 if !$param->{online
} && $param->{targetstorage
};
3388 my $storecfg = PVE
::Storage
::config
();
3390 if( $param->{targetstorage
}) {
3391 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3393 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3396 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3401 print "Requesting HA migration for VM $vmid to node $target\n";
3403 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3404 PVE
::Tools
::run_command
($cmd);
3408 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3413 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3417 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3420 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3425 __PACKAGE__-
>register_method({
3427 path
=> '{vmid}/monitor',
3431 description
=> "Execute Qemu monitor commands.",
3433 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3434 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3437 additionalProperties
=> 0,
3439 node
=> get_standard_option
('pve-node'),
3440 vmid
=> get_standard_option
('pve-vmid'),
3443 description
=> "The monitor command.",
3447 returns
=> { type
=> 'string'},
3451 my $rpcenv = PVE
::RPCEnvironment
::get
();
3452 my $authuser = $rpcenv->get_user();
3455 my $command = shift;
3456 return $command =~ m/^\s*info(\s+|$)/
3457 || $command =~ m/^\s*help\s*$/;
3460 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3461 if !&$is_ro($param->{command
});
3463 my $vmid = $param->{vmid
};
3465 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3469 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3471 $res = "ERROR: $@" if $@;
3476 __PACKAGE__-
>register_method({
3477 name
=> 'resize_vm',
3478 path
=> '{vmid}/resize',
3482 description
=> "Extend volume size.",
3484 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3487 additionalProperties
=> 0,
3489 node
=> get_standard_option
('pve-node'),
3490 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3491 skiplock
=> get_standard_option
('skiplock'),
3494 description
=> "The disk you want to resize.",
3495 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3499 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3500 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.",
3504 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3510 returns
=> { type
=> 'null'},
3514 my $rpcenv = PVE
::RPCEnvironment
::get
();
3516 my $authuser = $rpcenv->get_user();
3518 my $node = extract_param
($param, 'node');
3520 my $vmid = extract_param
($param, 'vmid');
3522 my $digest = extract_param
($param, 'digest');
3524 my $disk = extract_param
($param, 'disk');
3526 my $sizestr = extract_param
($param, 'size');
3528 my $skiplock = extract_param
($param, 'skiplock');
3529 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3530 if $skiplock && $authuser ne 'root@pam';
3532 my $storecfg = PVE
::Storage
::config
();
3534 my $updatefn = sub {
3536 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3538 die "checksum missmatch (file change by other user?)\n"
3539 if $digest && $digest ne $conf->{digest
};
3540 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3542 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3544 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3546 my (undef, undef, undef, undef, undef, undef, $format) =
3547 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3549 die "can't resize volume: $disk if snapshot exists\n"
3550 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3552 my $volid = $drive->{file
};
3554 die "disk '$disk' has no associated volume\n" if !$volid;
3556 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3558 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3560 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3562 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3563 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3565 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3567 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3568 my ($ext, $newsize, $unit) = ($1, $2, $4);
3571 $newsize = $newsize * 1024;
3572 } elsif ($unit eq 'M') {
3573 $newsize = $newsize * 1024 * 1024;
3574 } elsif ($unit eq 'G') {
3575 $newsize = $newsize * 1024 * 1024 * 1024;
3576 } elsif ($unit eq 'T') {
3577 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3580 $newsize += $size if $ext;
3581 $newsize = int($newsize);
3583 die "shrinking disks is not supported\n" if $newsize < $size;
3585 return if $size == $newsize;
3587 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3589 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3591 $drive->{size
} = $newsize;
3592 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
3594 PVE
::QemuConfig-
>write_config($vmid, $conf);
3597 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3601 __PACKAGE__-
>register_method({
3602 name
=> 'snapshot_list',
3603 path
=> '{vmid}/snapshot',
3605 description
=> "List all snapshots.",
3607 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3610 protected
=> 1, # qemu pid files are only readable by root
3612 additionalProperties
=> 0,
3614 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3615 node
=> get_standard_option
('pve-node'),
3624 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3628 description
=> "Snapshot includes RAM.",
3633 description
=> "Snapshot description.",
3637 description
=> "Snapshot creation time",
3639 renderer
=> 'timestamp',
3643 description
=> "Parent snapshot identifier.",
3649 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3654 my $vmid = $param->{vmid
};
3656 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3657 my $snaphash = $conf->{snapshots
} || {};
3661 foreach my $name (keys %$snaphash) {
3662 my $d = $snaphash->{$name};
3665 snaptime
=> $d->{snaptime
} || 0,
3666 vmstate
=> $d->{vmstate
} ?
1 : 0,
3667 description
=> $d->{description
} || '',
3669 $item->{parent
} = $d->{parent
} if $d->{parent
};
3670 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3674 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3677 digest
=> $conf->{digest
},
3678 running
=> $running,
3679 description
=> "You are here!",
3681 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3683 push @$res, $current;
3688 __PACKAGE__-
>register_method({
3690 path
=> '{vmid}/snapshot',
3694 description
=> "Snapshot a VM.",
3696 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3699 additionalProperties
=> 0,
3701 node
=> get_standard_option
('pve-node'),
3702 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3703 snapname
=> get_standard_option
('pve-snapshot-name'),
3707 description
=> "Save the vmstate",
3712 description
=> "A textual description or comment.",
3718 description
=> "the task ID.",
3723 my $rpcenv = PVE
::RPCEnvironment
::get
();
3725 my $authuser = $rpcenv->get_user();
3727 my $node = extract_param
($param, 'node');
3729 my $vmid = extract_param
($param, 'vmid');
3731 my $snapname = extract_param
($param, 'snapname');
3733 die "unable to use snapshot name 'current' (reserved name)\n"
3734 if $snapname eq 'current';
3736 die "unable to use snapshot name 'pending' (reserved name)\n"
3737 if lc($snapname) eq 'pending';
3740 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3741 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3742 $param->{description
});
3745 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3748 __PACKAGE__-
>register_method({
3749 name
=> 'snapshot_cmd_idx',
3750 path
=> '{vmid}/snapshot/{snapname}',
3757 additionalProperties
=> 0,
3759 vmid
=> get_standard_option
('pve-vmid'),
3760 node
=> get_standard_option
('pve-node'),
3761 snapname
=> get_standard_option
('pve-snapshot-name'),
3770 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3777 push @$res, { cmd
=> 'rollback' };
3778 push @$res, { cmd
=> 'config' };
3783 __PACKAGE__-
>register_method({
3784 name
=> 'update_snapshot_config',
3785 path
=> '{vmid}/snapshot/{snapname}/config',
3789 description
=> "Update snapshot metadata.",
3791 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3794 additionalProperties
=> 0,
3796 node
=> get_standard_option
('pve-node'),
3797 vmid
=> get_standard_option
('pve-vmid'),
3798 snapname
=> get_standard_option
('pve-snapshot-name'),
3802 description
=> "A textual description or comment.",
3806 returns
=> { type
=> 'null' },
3810 my $rpcenv = PVE
::RPCEnvironment
::get
();
3812 my $authuser = $rpcenv->get_user();
3814 my $vmid = extract_param
($param, 'vmid');
3816 my $snapname = extract_param
($param, 'snapname');
3818 return undef if !defined($param->{description
});
3820 my $updatefn = sub {
3822 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3824 PVE
::QemuConfig-
>check_lock($conf);
3826 my $snap = $conf->{snapshots
}->{$snapname};
3828 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3830 $snap->{description
} = $param->{description
} if defined($param->{description
});
3832 PVE
::QemuConfig-
>write_config($vmid, $conf);
3835 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3840 __PACKAGE__-
>register_method({
3841 name
=> 'get_snapshot_config',
3842 path
=> '{vmid}/snapshot/{snapname}/config',
3845 description
=> "Get snapshot configuration",
3847 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3850 additionalProperties
=> 0,
3852 node
=> get_standard_option
('pve-node'),
3853 vmid
=> get_standard_option
('pve-vmid'),
3854 snapname
=> get_standard_option
('pve-snapshot-name'),
3857 returns
=> { type
=> "object" },
3861 my $rpcenv = PVE
::RPCEnvironment
::get
();
3863 my $authuser = $rpcenv->get_user();
3865 my $vmid = extract_param
($param, 'vmid');
3867 my $snapname = extract_param
($param, 'snapname');
3869 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3871 my $snap = $conf->{snapshots
}->{$snapname};
3873 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3878 __PACKAGE__-
>register_method({
3880 path
=> '{vmid}/snapshot/{snapname}/rollback',
3884 description
=> "Rollback VM state to specified snapshot.",
3886 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3889 additionalProperties
=> 0,
3891 node
=> get_standard_option
('pve-node'),
3892 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3893 snapname
=> get_standard_option
('pve-snapshot-name'),
3898 description
=> "the task ID.",
3903 my $rpcenv = PVE
::RPCEnvironment
::get
();
3905 my $authuser = $rpcenv->get_user();
3907 my $node = extract_param
($param, 'node');
3909 my $vmid = extract_param
($param, 'vmid');
3911 my $snapname = extract_param
($param, 'snapname');
3914 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3915 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3919 # hold migration lock, this makes sure that nobody create replication snapshots
3920 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3923 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3926 __PACKAGE__-
>register_method({
3927 name
=> 'delsnapshot',
3928 path
=> '{vmid}/snapshot/{snapname}',
3932 description
=> "Delete a VM snapshot.",
3934 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3937 additionalProperties
=> 0,
3939 node
=> get_standard_option
('pve-node'),
3940 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3941 snapname
=> get_standard_option
('pve-snapshot-name'),
3945 description
=> "For removal from config file, even if removing disk snapshots fails.",
3951 description
=> "the task ID.",
3956 my $rpcenv = PVE
::RPCEnvironment
::get
();
3958 my $authuser = $rpcenv->get_user();
3960 my $node = extract_param
($param, 'node');
3962 my $vmid = extract_param
($param, 'vmid');
3964 my $snapname = extract_param
($param, 'snapname');
3967 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3968 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3971 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3974 __PACKAGE__-
>register_method({
3976 path
=> '{vmid}/template',
3980 description
=> "Create a Template.",
3982 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3983 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3986 additionalProperties
=> 0,
3988 node
=> get_standard_option
('pve-node'),
3989 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3993 description
=> "If you want to convert only 1 disk to base image.",
3994 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3999 returns
=> { type
=> 'null'},
4003 my $rpcenv = PVE
::RPCEnvironment
::get
();
4005 my $authuser = $rpcenv->get_user();
4007 my $node = extract_param
($param, 'node');
4009 my $vmid = extract_param
($param, 'vmid');
4011 my $disk = extract_param
($param, 'disk');
4013 my $updatefn = sub {
4015 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4017 PVE
::QemuConfig-
>check_lock($conf);
4019 die "unable to create template, because VM contains snapshots\n"
4020 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4022 die "you can't convert a template to a template\n"
4023 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4025 die "you can't convert a VM to template if VM is running\n"
4026 if PVE
::QemuServer
::check_running
($vmid);
4029 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4032 $conf->{template
} = 1;
4033 PVE
::QemuConfig-
>write_config($vmid, $conf);
4035 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4038 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4042 __PACKAGE__-
>register_method({
4043 name
=> 'cloudinit_generated_config_dump',
4044 path
=> '{vmid}/cloudinit/dump',
4047 description
=> "Get automatically generated cloudinit config.",
4049 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4052 additionalProperties
=> 0,
4054 node
=> get_standard_option
('pve-node'),
4055 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4057 description
=> 'Config type.',
4059 enum
=> ['user', 'network', 'meta'],
4069 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4071 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});