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 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2391 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2392 " on the storage for the vmstate.",
2393 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2396 additionalProperties
=> 0,
2398 node
=> get_standard_option
('pve-node'),
2399 vmid
=> get_standard_option
('pve-vmid',
2400 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2401 skiplock
=> get_standard_option
('skiplock'),
2406 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2408 statestorage
=> get_standard_option
('pve-storage-id', {
2409 description
=> "The storage for the VM state",
2410 requires
=> 'todisk',
2412 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2422 my $rpcenv = PVE
::RPCEnvironment
::get
();
2423 my $authuser = $rpcenv->get_user();
2425 my $node = extract_param
($param, 'node');
2426 my $vmid = extract_param
($param, 'vmid');
2428 my $todisk = extract_param
($param, 'todisk') // 0;
2430 my $statestorage = extract_param
($param, 'statestorage');
2432 my $skiplock = extract_param
($param, 'skiplock');
2433 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2434 if $skiplock && $authuser ne 'root@pam';
2436 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2438 die "Cannot suspend HA managed VM to disk\n"
2439 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2441 # early check for storage permission, for better user feedback
2443 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2445 if (!$statestorage) {
2446 # get statestorage from config if none is given
2447 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2448 my $storecfg = PVE
::Storage
::config
();
2449 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2452 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2458 syslog
('info', "suspend VM $vmid: $upid\n");
2460 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2465 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2466 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2469 __PACKAGE__-
>register_method({
2470 name
=> 'vm_resume',
2471 path
=> '{vmid}/status/resume',
2475 description
=> "Resume virtual machine.",
2477 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2480 additionalProperties
=> 0,
2482 node
=> get_standard_option
('pve-node'),
2483 vmid
=> get_standard_option
('pve-vmid',
2484 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2485 skiplock
=> get_standard_option
('skiplock'),
2486 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2496 my $rpcenv = PVE
::RPCEnvironment
::get
();
2498 my $authuser = $rpcenv->get_user();
2500 my $node = extract_param
($param, 'node');
2502 my $vmid = extract_param
($param, 'vmid');
2504 my $skiplock = extract_param
($param, 'skiplock');
2505 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2506 if $skiplock && $authuser ne 'root@pam';
2508 my $nocheck = extract_param
($param, 'nocheck');
2510 my $to_disk_suspended;
2512 PVE
::QemuConfig-
>lock_config($vmid, sub {
2513 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2514 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2518 die "VM $vmid not running\n"
2519 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2524 syslog
('info', "resume VM $vmid: $upid\n");
2526 if (!$to_disk_suspended) {
2527 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2529 my $storecfg = PVE
::Storage
::config
();
2530 PVE
::QemuServer
::vm_start
($storecfg, $vmid, undef, $skiplock);
2536 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2539 __PACKAGE__-
>register_method({
2540 name
=> 'vm_sendkey',
2541 path
=> '{vmid}/sendkey',
2545 description
=> "Send key event to virtual machine.",
2547 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2550 additionalProperties
=> 0,
2552 node
=> get_standard_option
('pve-node'),
2553 vmid
=> get_standard_option
('pve-vmid',
2554 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2555 skiplock
=> get_standard_option
('skiplock'),
2557 description
=> "The key (qemu monitor encoding).",
2562 returns
=> { type
=> 'null'},
2566 my $rpcenv = PVE
::RPCEnvironment
::get
();
2568 my $authuser = $rpcenv->get_user();
2570 my $node = extract_param
($param, 'node');
2572 my $vmid = extract_param
($param, 'vmid');
2574 my $skiplock = extract_param
($param, 'skiplock');
2575 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2576 if $skiplock && $authuser ne 'root@pam';
2578 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2583 __PACKAGE__-
>register_method({
2584 name
=> 'vm_feature',
2585 path
=> '{vmid}/feature',
2589 description
=> "Check if feature for virtual machine is available.",
2591 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2594 additionalProperties
=> 0,
2596 node
=> get_standard_option
('pve-node'),
2597 vmid
=> get_standard_option
('pve-vmid'),
2599 description
=> "Feature to check.",
2601 enum
=> [ 'snapshot', 'clone', 'copy' ],
2603 snapname
=> get_standard_option
('pve-snapshot-name', {
2611 hasFeature
=> { type
=> 'boolean' },
2614 items
=> { type
=> 'string' },
2621 my $node = extract_param
($param, 'node');
2623 my $vmid = extract_param
($param, 'vmid');
2625 my $snapname = extract_param
($param, 'snapname');
2627 my $feature = extract_param
($param, 'feature');
2629 my $running = PVE
::QemuServer
::check_running
($vmid);
2631 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2634 my $snap = $conf->{snapshots
}->{$snapname};
2635 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2638 my $storecfg = PVE
::Storage
::config
();
2640 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2641 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2644 hasFeature
=> $hasFeature,
2645 nodes
=> [ keys %$nodelist ],
2649 __PACKAGE__-
>register_method({
2651 path
=> '{vmid}/clone',
2655 description
=> "Create a copy of virtual machine/template.",
2657 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2658 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2659 "'Datastore.AllocateSpace' on any used storage.",
2662 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2664 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2665 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2670 additionalProperties
=> 0,
2672 node
=> get_standard_option
('pve-node'),
2673 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2674 newid
=> get_standard_option
('pve-vmid', {
2675 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2676 description
=> 'VMID for the clone.' }),
2679 type
=> 'string', format
=> 'dns-name',
2680 description
=> "Set a name for the new VM.",
2685 description
=> "Description for the new VM.",
2689 type
=> 'string', format
=> 'pve-poolid',
2690 description
=> "Add the new VM to the specified pool.",
2692 snapname
=> get_standard_option
('pve-snapshot-name', {
2695 storage
=> get_standard_option
('pve-storage-id', {
2696 description
=> "Target storage for full clone.",
2700 description
=> "Target format for file storage. Only valid for full clone.",
2703 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2708 description
=> "Create a full copy of all disks. This is always done when " .
2709 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2711 target
=> get_standard_option
('pve-node', {
2712 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2716 description
=> "Override I/O bandwidth limit (in KiB/s).",
2720 default => 'clone limit from datacenter or storage config',
2730 my $rpcenv = PVE
::RPCEnvironment
::get
();
2732 my $authuser = $rpcenv->get_user();
2734 my $node = extract_param
($param, 'node');
2736 my $vmid = extract_param
($param, 'vmid');
2738 my $newid = extract_param
($param, 'newid');
2740 my $pool = extract_param
($param, 'pool');
2742 if (defined($pool)) {
2743 $rpcenv->check_pool_exist($pool);
2746 my $snapname = extract_param
($param, 'snapname');
2748 my $storage = extract_param
($param, 'storage');
2750 my $format = extract_param
($param, 'format');
2752 my $target = extract_param
($param, 'target');
2754 my $localnode = PVE
::INotify
::nodename
();
2756 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2758 PVE
::Cluster
::check_node_exists
($target) if $target;
2760 my $storecfg = PVE
::Storage
::config
();
2763 # check if storage is enabled on local node
2764 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2766 # check if storage is available on target node
2767 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2768 # clone only works if target storage is shared
2769 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2770 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2774 PVE
::Cluster
::check_cfs_quorum
();
2776 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2778 # exclusive lock if VM is running - else shared lock is enough;
2779 my $shared_lock = $running ?
0 : 1;
2783 # do all tests after lock
2784 # we also try to do all tests before we fork the worker
2786 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2788 PVE
::QemuConfig-
>check_lock($conf);
2790 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2792 die "unexpected state change\n" if $verify_running != $running;
2794 die "snapshot '$snapname' does not exist\n"
2795 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2797 my $full = extract_param
($param, 'full');
2798 if (!defined($full)) {
2799 $full = !PVE
::QemuConfig-
>is_template($conf);
2802 die "parameter 'storage' not allowed for linked clones\n"
2803 if defined($storage) && !$full;
2805 die "parameter 'format' not allowed for linked clones\n"
2806 if defined($format) && !$full;
2808 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2810 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2812 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2814 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2816 die "unable to create VM $newid: config file already exists\n"
2819 my $newconf = { lock => 'clone' };
2824 foreach my $opt (keys %$oldconf) {
2825 my $value = $oldconf->{$opt};
2827 # do not copy snapshot related info
2828 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2829 $opt eq 'vmstate' || $opt eq 'snapstate';
2831 # no need to copy unused images, because VMID(owner) changes anyways
2832 next if $opt =~ m/^unused\d+$/;
2834 # always change MAC! address
2835 if ($opt =~ m/^net(\d+)$/) {
2836 my $net = PVE
::QemuServer
::parse_net
($value);
2837 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2838 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2839 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2840 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2841 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2842 die "unable to parse drive options for '$opt'\n" if !$drive;
2843 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2844 $newconf->{$opt} = $value; # simply copy configuration
2846 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2847 die "Full clone feature is not supported for drive '$opt'\n"
2848 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2849 $fullclone->{$opt} = 1;
2851 # not full means clone instead of copy
2852 die "Linked clone feature is not supported for drive '$opt'\n"
2853 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2855 $drives->{$opt} = $drive;
2856 push @$vollist, $drive->{file
};
2859 # copy everything else
2860 $newconf->{$opt} = $value;
2864 # auto generate a new uuid
2865 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2866 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2867 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2869 # auto generate a new vmgenid if the option was set
2870 if ($newconf->{vmgenid
}) {
2871 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2874 delete $newconf->{template
};
2876 if ($param->{name
}) {
2877 $newconf->{name
} = $param->{name
};
2879 if ($oldconf->{name
}) {
2880 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2882 $newconf->{name
} = "Copy-of-VM-$vmid";
2886 if ($param->{description
}) {
2887 $newconf->{description
} = $param->{description
};
2890 # create empty/temp config - this fails if VM already exists on other node
2891 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2896 my $newvollist = [];
2903 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2905 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2907 my $bwlimit = extract_param
($param, 'bwlimit');
2909 my $total_jobs = scalar(keys %{$drives});
2912 foreach my $opt (keys %$drives) {
2913 my $drive = $drives->{$opt};
2914 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2916 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
2917 my $storage_list = [ $src_sid ];
2918 push @$storage_list, $storage if defined($storage);
2919 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
2921 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2922 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2923 $jobs, $skipcomplete, $oldconf->{agent
}, $clonelimit);
2925 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
2927 PVE
::QemuConfig-
>write_config($newid, $newconf);
2931 delete $newconf->{lock};
2933 # do not write pending changes
2934 if (my @changes = keys %{$newconf->{pending
}}) {
2935 my $pending = join(',', @changes);
2936 warn "found pending changes for '$pending', discarding for clone\n";
2937 delete $newconf->{pending
};
2940 PVE
::QemuConfig-
>write_config($newid, $newconf);
2943 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2944 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2945 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2947 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2948 die "Failed to move config to node '$target' - rename failed: $!\n"
2949 if !rename($conffile, $newconffile);
2952 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2957 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2959 sleep 1; # some storage like rbd need to wait before release volume - really?
2961 foreach my $volid (@$newvollist) {
2962 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2965 die "clone failed: $err";
2971 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2973 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2976 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2977 # Aquire exclusive lock lock for $newid
2978 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2983 __PACKAGE__-
>register_method({
2984 name
=> 'move_vm_disk',
2985 path
=> '{vmid}/move_disk',
2989 description
=> "Move volume to different storage.",
2991 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2993 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2994 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2998 additionalProperties
=> 0,
3000 node
=> get_standard_option
('pve-node'),
3001 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3004 description
=> "The disk you want to move.",
3005 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
3007 storage
=> get_standard_option
('pve-storage-id', {
3008 description
=> "Target storage.",
3009 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3013 description
=> "Target Format.",
3014 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3019 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3025 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3030 description
=> "Override I/O bandwidth limit (in KiB/s).",
3034 default => 'move limit from datacenter or storage config',
3040 description
=> "the task ID.",
3045 my $rpcenv = PVE
::RPCEnvironment
::get
();
3047 my $authuser = $rpcenv->get_user();
3049 my $node = extract_param
($param, 'node');
3051 my $vmid = extract_param
($param, 'vmid');
3053 my $digest = extract_param
($param, 'digest');
3055 my $disk = extract_param
($param, 'disk');
3057 my $storeid = extract_param
($param, 'storage');
3059 my $format = extract_param
($param, 'format');
3061 my $storecfg = PVE
::Storage
::config
();
3063 my $updatefn = sub {
3065 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3067 PVE
::QemuConfig-
>check_lock($conf);
3069 die "checksum missmatch (file change by other user?)\n"
3070 if $digest && $digest ne $conf->{digest
};
3072 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3074 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3076 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
3078 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3081 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3082 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3086 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
3087 (!$format || !$oldfmt || $oldfmt eq $format);
3089 # this only checks snapshots because $disk is passed!
3090 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3091 die "you can't move a disk with snapshots and delete the source\n"
3092 if $snapshotted && $param->{delete};
3094 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3096 my $running = PVE
::QemuServer
::check_running
($vmid);
3098 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3102 my $newvollist = [];
3108 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3110 warn "moving disk with snapshots, snapshots will not be moved!\n"
3113 my $bwlimit = extract_param
($param, 'bwlimit');
3114 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3116 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3117 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit);
3119 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3121 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3123 # convert moved disk to base if part of template
3124 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3125 if PVE
::QemuConfig-
>is_template($conf);
3127 PVE
::QemuConfig-
>write_config($vmid, $conf);
3129 if ($running && PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
} && PVE
::QemuServer
::qga_check_running
($vmid)) {
3130 eval { mon_cmd
($vmid, "guest-fstrim"); };
3134 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3135 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3142 foreach my $volid (@$newvollist) {
3143 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3146 die "storage migration failed: $err";
3149 if ($param->{delete}) {
3151 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3152 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3158 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3161 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3164 my $check_vm_disks_local = sub {
3165 my ($storecfg, $vmconf, $vmid) = @_;
3167 my $local_disks = {};
3169 # add some more information to the disks e.g. cdrom
3170 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3171 my ($volid, $attr) = @_;
3173 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3175 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3176 return if $scfg->{shared
};
3178 # The shared attr here is just a special case where the vdisk
3179 # is marked as shared manually
3180 return if $attr->{shared
};
3181 return if $attr->{cdrom
} and $volid eq "none";
3183 if (exists $local_disks->{$volid}) {
3184 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3186 $local_disks->{$volid} = $attr;
3187 # ensure volid is present in case it's needed
3188 $local_disks->{$volid}->{volid
} = $volid;
3192 return $local_disks;
3195 __PACKAGE__-
>register_method({
3196 name
=> 'migrate_vm_precondition',
3197 path
=> '{vmid}/migrate',
3201 description
=> "Get preconditions for migration.",
3203 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3206 additionalProperties
=> 0,
3208 node
=> get_standard_option
('pve-node'),
3209 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3210 target
=> get_standard_option
('pve-node', {
3211 description
=> "Target node.",
3212 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3220 running
=> { type
=> 'boolean' },
3224 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3226 not_allowed_nodes
=> {
3229 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3233 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3235 local_resources
=> {
3237 description
=> "List local resources e.g. pci, usb"
3244 my $rpcenv = PVE
::RPCEnvironment
::get
();
3246 my $authuser = $rpcenv->get_user();
3248 PVE
::Cluster
::check_cfs_quorum
();
3252 my $vmid = extract_param
($param, 'vmid');
3253 my $target = extract_param
($param, 'target');
3254 my $localnode = PVE
::INotify
::nodename
();
3258 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3259 my $storecfg = PVE
::Storage
::config
();
3262 # try to detect errors early
3263 PVE
::QemuConfig-
>check_lock($vmconf);
3265 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3267 # if vm is not running, return target nodes where local storage is available
3268 # for offline migration
3269 if (!$res->{running
}) {
3270 $res->{allowed_nodes
} = [];
3271 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3272 delete $checked_nodes->{$localnode};
3274 foreach my $node (keys %$checked_nodes) {
3275 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3276 push @{$res->{allowed_nodes
}}, $node;
3280 $res->{not_allowed_nodes
} = $checked_nodes;
3284 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3285 $res->{local_disks
} = [ values %$local_disks ];;
3287 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3289 $res->{local_resources
} = $local_resources;
3296 __PACKAGE__-
>register_method({
3297 name
=> 'migrate_vm',
3298 path
=> '{vmid}/migrate',
3302 description
=> "Migrate virtual machine. Creates a new migration task.",
3304 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3307 additionalProperties
=> 0,
3309 node
=> get_standard_option
('pve-node'),
3310 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3311 target
=> get_standard_option
('pve-node', {
3312 description
=> "Target node.",
3313 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3317 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3322 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3327 enum
=> ['secure', 'insecure'],
3328 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3331 migration_network
=> {
3332 type
=> 'string', format
=> 'CIDR',
3333 description
=> "CIDR of the (sub) network that is used for migration.",
3336 "with-local-disks" => {
3338 description
=> "Enable live storage migration for local disk",
3341 targetstorage
=> get_standard_option
('pve-storage-id', {
3342 description
=> "Default target storage.",
3344 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3347 description
=> "Override I/O bandwidth limit (in KiB/s).",
3351 default => 'migrate limit from datacenter or storage config',
3357 description
=> "the task ID.",
3362 my $rpcenv = PVE
::RPCEnvironment
::get
();
3363 my $authuser = $rpcenv->get_user();
3365 my $target = extract_param
($param, 'target');
3367 my $localnode = PVE
::INotify
::nodename
();
3368 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3370 PVE
::Cluster
::check_cfs_quorum
();
3372 PVE
::Cluster
::check_node_exists
($target);
3374 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3376 my $vmid = extract_param
($param, 'vmid');
3378 raise_param_exc
({ force
=> "Only root may use this option." })
3379 if $param->{force
} && $authuser ne 'root@pam';
3381 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3382 if $param->{migration_type
} && $authuser ne 'root@pam';
3384 # allow root only until better network permissions are available
3385 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3386 if $param->{migration_network
} && $authuser ne 'root@pam';
3389 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3391 # try to detect errors early
3393 PVE
::QemuConfig-
>check_lock($conf);
3395 if (PVE
::QemuServer
::check_running
($vmid)) {
3396 die "can't migrate running VM without --online\n" if !$param->{online
};
3398 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
3399 $param->{online
} = 0;
3402 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3403 if !$param->{online
} && $param->{targetstorage
};
3405 my $storecfg = PVE
::Storage
::config
();
3407 if( $param->{targetstorage
}) {
3408 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3410 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3413 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3418 print "Requesting HA migration for VM $vmid to node $target\n";
3420 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3421 PVE
::Tools
::run_command
($cmd);
3425 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3430 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3434 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3437 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3442 __PACKAGE__-
>register_method({
3444 path
=> '{vmid}/monitor',
3448 description
=> "Execute Qemu monitor commands.",
3450 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3451 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3454 additionalProperties
=> 0,
3456 node
=> get_standard_option
('pve-node'),
3457 vmid
=> get_standard_option
('pve-vmid'),
3460 description
=> "The monitor command.",
3464 returns
=> { type
=> 'string'},
3468 my $rpcenv = PVE
::RPCEnvironment
::get
();
3469 my $authuser = $rpcenv->get_user();
3472 my $command = shift;
3473 return $command =~ m/^\s*info(\s+|$)/
3474 || $command =~ m/^\s*help\s*$/;
3477 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3478 if !&$is_ro($param->{command
});
3480 my $vmid = $param->{vmid
};
3482 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3486 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3488 $res = "ERROR: $@" if $@;
3493 __PACKAGE__-
>register_method({
3494 name
=> 'resize_vm',
3495 path
=> '{vmid}/resize',
3499 description
=> "Extend volume size.",
3501 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3504 additionalProperties
=> 0,
3506 node
=> get_standard_option
('pve-node'),
3507 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3508 skiplock
=> get_standard_option
('skiplock'),
3511 description
=> "The disk you want to resize.",
3512 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3516 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3517 description
=> "The new size. With the `+` sign the value is added to the actual size of the volume and without it, the value is taken as an absolute one. Shrinking disk size is not supported.",
3521 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3527 returns
=> { type
=> 'null'},
3531 my $rpcenv = PVE
::RPCEnvironment
::get
();
3533 my $authuser = $rpcenv->get_user();
3535 my $node = extract_param
($param, 'node');
3537 my $vmid = extract_param
($param, 'vmid');
3539 my $digest = extract_param
($param, 'digest');
3541 my $disk = extract_param
($param, 'disk');
3543 my $sizestr = extract_param
($param, 'size');
3545 my $skiplock = extract_param
($param, 'skiplock');
3546 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3547 if $skiplock && $authuser ne 'root@pam';
3549 my $storecfg = PVE
::Storage
::config
();
3551 my $updatefn = sub {
3553 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3555 die "checksum missmatch (file change by other user?)\n"
3556 if $digest && $digest ne $conf->{digest
};
3557 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3559 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3561 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3563 my (undef, undef, undef, undef, undef, undef, $format) =
3564 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3566 die "can't resize volume: $disk if snapshot exists\n"
3567 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3569 my $volid = $drive->{file
};
3571 die "disk '$disk' has no associated volume\n" if !$volid;
3573 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3575 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3577 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3579 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3580 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3582 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3584 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3585 my ($ext, $newsize, $unit) = ($1, $2, $4);
3588 $newsize = $newsize * 1024;
3589 } elsif ($unit eq 'M') {
3590 $newsize = $newsize * 1024 * 1024;
3591 } elsif ($unit eq 'G') {
3592 $newsize = $newsize * 1024 * 1024 * 1024;
3593 } elsif ($unit eq 'T') {
3594 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3597 $newsize += $size if $ext;
3598 $newsize = int($newsize);
3600 die "shrinking disks is not supported\n" if $newsize < $size;
3602 return if $size == $newsize;
3604 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3606 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3608 $drive->{size
} = $newsize;
3609 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
3611 PVE
::QemuConfig-
>write_config($vmid, $conf);
3614 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3618 __PACKAGE__-
>register_method({
3619 name
=> 'snapshot_list',
3620 path
=> '{vmid}/snapshot',
3622 description
=> "List all snapshots.",
3624 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3627 protected
=> 1, # qemu pid files are only readable by root
3629 additionalProperties
=> 0,
3631 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3632 node
=> get_standard_option
('pve-node'),
3641 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3645 description
=> "Snapshot includes RAM.",
3650 description
=> "Snapshot description.",
3654 description
=> "Snapshot creation time",
3656 renderer
=> 'timestamp',
3660 description
=> "Parent snapshot identifier.",
3666 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3671 my $vmid = $param->{vmid
};
3673 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3674 my $snaphash = $conf->{snapshots
} || {};
3678 foreach my $name (keys %$snaphash) {
3679 my $d = $snaphash->{$name};
3682 snaptime
=> $d->{snaptime
} || 0,
3683 vmstate
=> $d->{vmstate
} ?
1 : 0,
3684 description
=> $d->{description
} || '',
3686 $item->{parent
} = $d->{parent
} if $d->{parent
};
3687 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3691 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3694 digest
=> $conf->{digest
},
3695 running
=> $running,
3696 description
=> "You are here!",
3698 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3700 push @$res, $current;
3705 __PACKAGE__-
>register_method({
3707 path
=> '{vmid}/snapshot',
3711 description
=> "Snapshot a VM.",
3713 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3716 additionalProperties
=> 0,
3718 node
=> get_standard_option
('pve-node'),
3719 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3720 snapname
=> get_standard_option
('pve-snapshot-name'),
3724 description
=> "Save the vmstate",
3729 description
=> "A textual description or comment.",
3735 description
=> "the task ID.",
3740 my $rpcenv = PVE
::RPCEnvironment
::get
();
3742 my $authuser = $rpcenv->get_user();
3744 my $node = extract_param
($param, 'node');
3746 my $vmid = extract_param
($param, 'vmid');
3748 my $snapname = extract_param
($param, 'snapname');
3750 die "unable to use snapshot name 'current' (reserved name)\n"
3751 if $snapname eq 'current';
3753 die "unable to use snapshot name 'pending' (reserved name)\n"
3754 if lc($snapname) eq 'pending';
3757 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3758 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3759 $param->{description
});
3762 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3765 __PACKAGE__-
>register_method({
3766 name
=> 'snapshot_cmd_idx',
3767 path
=> '{vmid}/snapshot/{snapname}',
3774 additionalProperties
=> 0,
3776 vmid
=> get_standard_option
('pve-vmid'),
3777 node
=> get_standard_option
('pve-node'),
3778 snapname
=> get_standard_option
('pve-snapshot-name'),
3787 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3794 push @$res, { cmd
=> 'rollback' };
3795 push @$res, { cmd
=> 'config' };
3800 __PACKAGE__-
>register_method({
3801 name
=> 'update_snapshot_config',
3802 path
=> '{vmid}/snapshot/{snapname}/config',
3806 description
=> "Update snapshot metadata.",
3808 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3811 additionalProperties
=> 0,
3813 node
=> get_standard_option
('pve-node'),
3814 vmid
=> get_standard_option
('pve-vmid'),
3815 snapname
=> get_standard_option
('pve-snapshot-name'),
3819 description
=> "A textual description or comment.",
3823 returns
=> { type
=> 'null' },
3827 my $rpcenv = PVE
::RPCEnvironment
::get
();
3829 my $authuser = $rpcenv->get_user();
3831 my $vmid = extract_param
($param, 'vmid');
3833 my $snapname = extract_param
($param, 'snapname');
3835 return undef if !defined($param->{description
});
3837 my $updatefn = sub {
3839 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3841 PVE
::QemuConfig-
>check_lock($conf);
3843 my $snap = $conf->{snapshots
}->{$snapname};
3845 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3847 $snap->{description
} = $param->{description
} if defined($param->{description
});
3849 PVE
::QemuConfig-
>write_config($vmid, $conf);
3852 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3857 __PACKAGE__-
>register_method({
3858 name
=> 'get_snapshot_config',
3859 path
=> '{vmid}/snapshot/{snapname}/config',
3862 description
=> "Get snapshot configuration",
3864 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3867 additionalProperties
=> 0,
3869 node
=> get_standard_option
('pve-node'),
3870 vmid
=> get_standard_option
('pve-vmid'),
3871 snapname
=> get_standard_option
('pve-snapshot-name'),
3874 returns
=> { type
=> "object" },
3878 my $rpcenv = PVE
::RPCEnvironment
::get
();
3880 my $authuser = $rpcenv->get_user();
3882 my $vmid = extract_param
($param, 'vmid');
3884 my $snapname = extract_param
($param, 'snapname');
3886 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3888 my $snap = $conf->{snapshots
}->{$snapname};
3890 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3895 __PACKAGE__-
>register_method({
3897 path
=> '{vmid}/snapshot/{snapname}/rollback',
3901 description
=> "Rollback VM state to specified snapshot.",
3903 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3906 additionalProperties
=> 0,
3908 node
=> get_standard_option
('pve-node'),
3909 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3910 snapname
=> get_standard_option
('pve-snapshot-name'),
3915 description
=> "the task ID.",
3920 my $rpcenv = PVE
::RPCEnvironment
::get
();
3922 my $authuser = $rpcenv->get_user();
3924 my $node = extract_param
($param, 'node');
3926 my $vmid = extract_param
($param, 'vmid');
3928 my $snapname = extract_param
($param, 'snapname');
3931 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3932 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3936 # hold migration lock, this makes sure that nobody create replication snapshots
3937 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3940 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3943 __PACKAGE__-
>register_method({
3944 name
=> 'delsnapshot',
3945 path
=> '{vmid}/snapshot/{snapname}',
3949 description
=> "Delete a VM snapshot.",
3951 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3954 additionalProperties
=> 0,
3956 node
=> get_standard_option
('pve-node'),
3957 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3958 snapname
=> get_standard_option
('pve-snapshot-name'),
3962 description
=> "For removal from config file, even if removing disk snapshots fails.",
3968 description
=> "the task ID.",
3973 my $rpcenv = PVE
::RPCEnvironment
::get
();
3975 my $authuser = $rpcenv->get_user();
3977 my $node = extract_param
($param, 'node');
3979 my $vmid = extract_param
($param, 'vmid');
3981 my $snapname = extract_param
($param, 'snapname');
3984 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3985 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3988 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3991 __PACKAGE__-
>register_method({
3993 path
=> '{vmid}/template',
3997 description
=> "Create a Template.",
3999 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4000 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4003 additionalProperties
=> 0,
4005 node
=> get_standard_option
('pve-node'),
4006 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4010 description
=> "If you want to convert only 1 disk to base image.",
4011 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
4016 returns
=> { type
=> 'null'},
4020 my $rpcenv = PVE
::RPCEnvironment
::get
();
4022 my $authuser = $rpcenv->get_user();
4024 my $node = extract_param
($param, 'node');
4026 my $vmid = extract_param
($param, 'vmid');
4028 my $disk = extract_param
($param, 'disk');
4030 my $updatefn = sub {
4032 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4034 PVE
::QemuConfig-
>check_lock($conf);
4036 die "unable to create template, because VM contains snapshots\n"
4037 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4039 die "you can't convert a template to a template\n"
4040 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4042 die "you can't convert a VM to template if VM is running\n"
4043 if PVE
::QemuServer
::check_running
($vmid);
4046 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4049 $conf->{template
} = 1;
4050 PVE
::QemuConfig-
>write_config($vmid, $conf);
4052 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4055 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4059 __PACKAGE__-
>register_method({
4060 name
=> 'cloudinit_generated_config_dump',
4061 path
=> '{vmid}/cloudinit/dump',
4064 description
=> "Get automatically generated cloudinit config.",
4066 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4069 additionalProperties
=> 0,
4071 node
=> get_standard_option
('pve-node'),
4072 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4074 description
=> 'Config type.',
4076 enum
=> ['user', 'network', 'meta'],
4086 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4088 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});