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
($vmid, $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
($vmid, $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
($vmid, $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
($vmid, $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
($vmid, $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
($vmid, $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 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1096 foreach my $opt (keys %$revert) {
1097 if (defined($conf->{$opt})) {
1098 $param->{$opt} = $conf->{$opt};
1099 } elsif (defined($conf->{pending
}->{$opt})) {
1104 if ($param->{memory
} || defined($param->{balloon
})) {
1105 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1106 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1108 die "balloon value too large (must be smaller than assigned memory)\n"
1109 if $balloon && $balloon > $maxmem;
1112 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1116 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1118 # write updates to pending section
1120 my $modified = {}; # record what $option we modify
1122 foreach my $opt (@delete) {
1123 $modified->{$opt} = 1;
1124 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1126 # value of what we want to delete, independent if pending or not
1127 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1128 if (!defined($val)) {
1129 warn "cannot delete '$opt' - not set in current configuration!\n";
1130 $modified->{$opt} = 0;
1133 my $is_pending_val = defined($conf->{pending
}->{$opt});
1134 delete $conf->{pending
}->{$opt};
1136 if ($opt =~ m/^unused/) {
1137 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1138 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1139 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1140 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1141 delete $conf->{$opt};
1142 PVE
::QemuConfig-
>write_config($vmid, $conf);
1144 } elsif ($opt eq 'vmstate') {
1145 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1146 # the user needs Disk and PowerMgmt privileges to remove the vmstate
1147 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk', 'VM.PowerMgmt' ]);
1148 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1149 delete $conf->{$opt};
1150 PVE
::QemuConfig-
>write_config($vmid, $conf);
1152 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1153 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1154 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1155 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1157 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1158 PVE
::QemuConfig-
>write_config($vmid, $conf);
1159 } elsif ($opt =~ m/^serial\d+$/) {
1160 if ($val eq 'socket') {
1161 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1162 } elsif ($authuser ne 'root@pam') {
1163 die "only root can delete '$opt' config for real devices\n";
1165 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1166 PVE
::QemuConfig-
>write_config($vmid, $conf);
1167 } elsif ($opt =~ m/^usb\d+$/) {
1168 if ($val =~ m/spice/) {
1169 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1170 } elsif ($authuser ne 'root@pam') {
1171 die "only root can delete '$opt' config for real devices\n";
1173 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1174 PVE
::QemuConfig-
>write_config($vmid, $conf);
1176 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1177 PVE
::QemuConfig-
>write_config($vmid, $conf);
1181 foreach my $opt (keys %$param) { # add/change
1182 $modified->{$opt} = 1;
1183 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1184 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1186 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1188 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1189 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1190 # FIXME: cloudinit: CDROM or Disk?
1191 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1192 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1194 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1196 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1197 if defined($conf->{pending
}->{$opt});
1199 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1200 } elsif ($opt =~ m/^serial\d+/) {
1201 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1202 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1203 } elsif ($authuser ne 'root@pam') {
1204 die "only root can modify '$opt' config for real devices\n";
1206 $conf->{pending
}->{$opt} = $param->{$opt};
1207 } elsif ($opt =~ m/^usb\d+/) {
1208 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1209 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1210 } elsif ($authuser ne 'root@pam') {
1211 die "only root can modify '$opt' config for real devices\n";
1213 $conf->{pending
}->{$opt} = $param->{$opt};
1215 $conf->{pending
}->{$opt} = $param->{$opt};
1217 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1218 PVE
::QemuConfig-
>write_config($vmid, $conf);
1221 # remove pending changes when nothing changed
1222 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1223 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1224 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1226 return if !scalar(keys %{$conf->{pending
}});
1228 my $running = PVE
::QemuServer
::check_running
($vmid);
1230 # apply pending changes
1232 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1236 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1237 raise_param_exc
($errors) if scalar(keys %$errors);
1239 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1249 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1251 if ($background_delay) {
1253 # Note: It would be better to do that in the Event based HTTPServer
1254 # to avoid blocking call to sleep.
1256 my $end_time = time() + $background_delay;
1258 my $task = PVE
::Tools
::upid_decode
($upid);
1261 while (time() < $end_time) {
1262 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1264 sleep(1); # this gets interrupted when child process ends
1268 my $status = PVE
::Tools
::upid_read_status
($upid);
1269 return undef if $status eq 'OK';
1278 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1281 my $vm_config_perm_list = [
1286 'VM.Config.Network',
1288 'VM.Config.Options',
1291 __PACKAGE__-
>register_method({
1292 name
=> 'update_vm_async',
1293 path
=> '{vmid}/config',
1297 description
=> "Set virtual machine options (asynchrounous API).",
1299 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1302 additionalProperties
=> 0,
1303 properties
=> PVE
::QemuServer
::json_config_properties
(
1305 node
=> get_standard_option
('pve-node'),
1306 vmid
=> get_standard_option
('pve-vmid'),
1307 skiplock
=> get_standard_option
('skiplock'),
1309 type
=> 'string', format
=> 'pve-configid-list',
1310 description
=> "A list of settings you want to delete.",
1314 type
=> 'string', format
=> 'pve-configid-list',
1315 description
=> "Revert a pending change.",
1320 description
=> $opt_force_description,
1322 requires
=> 'delete',
1326 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1330 background_delay
=> {
1332 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1343 code
=> $update_vm_api,
1346 __PACKAGE__-
>register_method({
1347 name
=> 'update_vm',
1348 path
=> '{vmid}/config',
1352 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1354 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1357 additionalProperties
=> 0,
1358 properties
=> PVE
::QemuServer
::json_config_properties
(
1360 node
=> get_standard_option
('pve-node'),
1361 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1362 skiplock
=> get_standard_option
('skiplock'),
1364 type
=> 'string', format
=> 'pve-configid-list',
1365 description
=> "A list of settings you want to delete.",
1369 type
=> 'string', format
=> 'pve-configid-list',
1370 description
=> "Revert a pending change.",
1375 description
=> $opt_force_description,
1377 requires
=> 'delete',
1381 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1387 returns
=> { type
=> 'null' },
1390 &$update_vm_api($param, 1);
1395 __PACKAGE__-
>register_method({
1396 name
=> 'destroy_vm',
1401 description
=> "Destroy the vm (also delete all used/owned volumes).",
1403 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1406 additionalProperties
=> 0,
1408 node
=> get_standard_option
('pve-node'),
1409 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1410 skiplock
=> get_standard_option
('skiplock'),
1413 description
=> "Remove vmid from backup cron jobs.",
1424 my $rpcenv = PVE
::RPCEnvironment
::get
();
1425 my $authuser = $rpcenv->get_user();
1426 my $vmid = $param->{vmid
};
1428 my $skiplock = $param->{skiplock
};
1429 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1430 if $skiplock && $authuser ne 'root@pam';
1433 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1434 my $storecfg = PVE
::Storage
::config
();
1435 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1436 die "unable to remove VM $vmid - used in HA resources\n"
1437 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1439 if (!$param->{purge
}) {
1440 # don't allow destroy if with replication jobs but no purge param
1441 my $repl_conf = PVE
::ReplicationConfig-
>new();
1442 $repl_conf->check_for_existing_jobs($vmid);
1445 # early tests (repeat after locking)
1446 die "VM $vmid is running - destroy failed\n"
1447 if PVE
::QemuServer
::check_running
($vmid);
1452 syslog
('info', "destroy VM $vmid: $upid\n");
1453 PVE
::QemuConfig-
>lock_config($vmid, sub {
1454 die "VM $vmid is running - destroy failed\n"
1455 if (PVE
::QemuServer
::check_running
($vmid));
1457 PVE
::QemuServer
::destroy_vm
($storecfg, $vmid, $skiplock, { lock => 'destroyed' });
1459 PVE
::AccessControl
::remove_vm_access
($vmid);
1460 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1461 if ($param->{purge
}) {
1462 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1463 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1466 # only now remove the zombie config, else we can have reuse race
1467 PVE
::QemuConfig-
>destroy_config($vmid);
1471 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1474 __PACKAGE__-
>register_method({
1476 path
=> '{vmid}/unlink',
1480 description
=> "Unlink/delete disk images.",
1482 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1485 additionalProperties
=> 0,
1487 node
=> get_standard_option
('pve-node'),
1488 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1490 type
=> 'string', format
=> 'pve-configid-list',
1491 description
=> "A list of disk IDs you want to delete.",
1495 description
=> $opt_force_description,
1500 returns
=> { type
=> 'null'},
1504 $param->{delete} = extract_param
($param, 'idlist');
1506 __PACKAGE__-
>update_vm($param);
1513 __PACKAGE__-
>register_method({
1515 path
=> '{vmid}/vncproxy',
1519 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1521 description
=> "Creates a TCP VNC proxy connections.",
1523 additionalProperties
=> 0,
1525 node
=> get_standard_option
('pve-node'),
1526 vmid
=> get_standard_option
('pve-vmid'),
1530 description
=> "starts websockify instead of vncproxy",
1535 additionalProperties
=> 0,
1537 user
=> { type
=> 'string' },
1538 ticket
=> { type
=> 'string' },
1539 cert
=> { type
=> 'string' },
1540 port
=> { type
=> 'integer' },
1541 upid
=> { type
=> 'string' },
1547 my $rpcenv = PVE
::RPCEnvironment
::get
();
1549 my $authuser = $rpcenv->get_user();
1551 my $vmid = $param->{vmid
};
1552 my $node = $param->{node
};
1553 my $websocket = $param->{websocket
};
1555 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1556 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1558 my $authpath = "/vms/$vmid";
1560 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1562 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1568 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1569 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1570 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1571 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1572 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1574 $family = PVE
::Tools
::get_host_address_family
($node);
1577 my $port = PVE
::Tools
::next_vnc_port
($family);
1584 syslog
('info', "starting vnc proxy $upid\n");
1590 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1592 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1593 '-timeout', $timeout, '-authpath', $authpath,
1594 '-perm', 'Sys.Console'];
1596 if ($param->{websocket
}) {
1597 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1598 push @$cmd, '-notls', '-listen', 'localhost';
1601 push @$cmd, '-c', @$remcmd, @$termcmd;
1603 PVE
::Tools
::run_command
($cmd);
1607 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1609 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1611 my $sock = IO
::Socket
::IP-
>new(
1616 GetAddrInfoFlags
=> 0,
1617 ) or die "failed to create socket: $!\n";
1618 # Inside the worker we shouldn't have any previous alarms
1619 # running anyway...:
1621 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1623 accept(my $cli, $sock) or die "connection failed: $!\n";
1626 if (PVE
::Tools
::run_command
($cmd,
1627 output
=> '>&'.fileno($cli),
1628 input
=> '<&'.fileno($cli),
1631 die "Failed to run vncproxy.\n";
1638 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1640 PVE
::Tools
::wait_for_vnc_port
($port);
1651 __PACKAGE__-
>register_method({
1652 name
=> 'termproxy',
1653 path
=> '{vmid}/termproxy',
1657 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1659 description
=> "Creates a TCP proxy connections.",
1661 additionalProperties
=> 0,
1663 node
=> get_standard_option
('pve-node'),
1664 vmid
=> get_standard_option
('pve-vmid'),
1668 enum
=> [qw(serial0 serial1 serial2 serial3)],
1669 description
=> "opens a serial terminal (defaults to display)",
1674 additionalProperties
=> 0,
1676 user
=> { type
=> 'string' },
1677 ticket
=> { type
=> 'string' },
1678 port
=> { type
=> 'integer' },
1679 upid
=> { type
=> 'string' },
1685 my $rpcenv = PVE
::RPCEnvironment
::get
();
1687 my $authuser = $rpcenv->get_user();
1689 my $vmid = $param->{vmid
};
1690 my $node = $param->{node
};
1691 my $serial = $param->{serial
};
1693 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1695 if (!defined($serial)) {
1696 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1697 $serial = $conf->{vga
};
1701 my $authpath = "/vms/$vmid";
1703 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1708 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1709 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1710 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1711 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1712 push @$remcmd, '--';
1714 $family = PVE
::Tools
::get_host_address_family
($node);
1717 my $port = PVE
::Tools
::next_vnc_port
($family);
1719 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1720 push @$termcmd, '-iface', $serial if $serial;
1725 syslog
('info', "starting qemu termproxy $upid\n");
1727 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1728 '--perm', 'VM.Console', '--'];
1729 push @$cmd, @$remcmd, @$termcmd;
1731 PVE
::Tools
::run_command
($cmd);
1734 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1736 PVE
::Tools
::wait_for_vnc_port
($port);
1746 __PACKAGE__-
>register_method({
1747 name
=> 'vncwebsocket',
1748 path
=> '{vmid}/vncwebsocket',
1751 description
=> "You also need to pass a valid ticket (vncticket).",
1752 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1754 description
=> "Opens a weksocket for VNC traffic.",
1756 additionalProperties
=> 0,
1758 node
=> get_standard_option
('pve-node'),
1759 vmid
=> get_standard_option
('pve-vmid'),
1761 description
=> "Ticket from previous call to vncproxy.",
1766 description
=> "Port number returned by previous vncproxy call.",
1776 port
=> { type
=> 'string' },
1782 my $rpcenv = PVE
::RPCEnvironment
::get
();
1784 my $authuser = $rpcenv->get_user();
1786 my $vmid = $param->{vmid
};
1787 my $node = $param->{node
};
1789 my $authpath = "/vms/$vmid";
1791 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1793 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1795 # Note: VNC ports are acessible from outside, so we do not gain any
1796 # security if we verify that $param->{port} belongs to VM $vmid. This
1797 # check is done by verifying the VNC ticket (inside VNC protocol).
1799 my $port = $param->{port
};
1801 return { port
=> $port };
1804 __PACKAGE__-
>register_method({
1805 name
=> 'spiceproxy',
1806 path
=> '{vmid}/spiceproxy',
1811 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1813 description
=> "Returns a SPICE configuration to connect to the VM.",
1815 additionalProperties
=> 0,
1817 node
=> get_standard_option
('pve-node'),
1818 vmid
=> get_standard_option
('pve-vmid'),
1819 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1822 returns
=> get_standard_option
('remote-viewer-config'),
1826 my $rpcenv = PVE
::RPCEnvironment
::get
();
1828 my $authuser = $rpcenv->get_user();
1830 my $vmid = $param->{vmid
};
1831 my $node = $param->{node
};
1832 my $proxy = $param->{proxy
};
1834 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1835 my $title = "VM $vmid";
1836 $title .= " - ". $conf->{name
} if $conf->{name
};
1838 my $port = PVE
::QemuServer
::spice_port
($vmid);
1840 my ($ticket, undef, $remote_viewer_config) =
1841 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1843 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1844 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1846 return $remote_viewer_config;
1849 __PACKAGE__-
>register_method({
1851 path
=> '{vmid}/status',
1854 description
=> "Directory index",
1859 additionalProperties
=> 0,
1861 node
=> get_standard_option
('pve-node'),
1862 vmid
=> get_standard_option
('pve-vmid'),
1870 subdir
=> { type
=> 'string' },
1873 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1879 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1882 { subdir
=> 'current' },
1883 { subdir
=> 'start' },
1884 { subdir
=> 'stop' },
1885 { subdir
=> 'reset' },
1886 { subdir
=> 'shutdown' },
1887 { subdir
=> 'suspend' },
1888 { subdir
=> 'reboot' },
1894 __PACKAGE__-
>register_method({
1895 name
=> 'vm_status',
1896 path
=> '{vmid}/status/current',
1899 protected
=> 1, # qemu pid files are only readable by root
1900 description
=> "Get virtual machine status.",
1902 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1905 additionalProperties
=> 0,
1907 node
=> get_standard_option
('pve-node'),
1908 vmid
=> get_standard_option
('pve-vmid'),
1914 %$PVE::QemuServer
::vmstatus_return_properties
,
1916 description
=> "HA manager service status.",
1920 description
=> "Qemu VGA configuration supports spice.",
1925 description
=> "Qemu GuestAgent enabled in config.",
1935 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1937 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1938 my $status = $vmstatus->{$param->{vmid
}};
1940 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1942 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1943 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1948 __PACKAGE__-
>register_method({
1950 path
=> '{vmid}/status/start',
1954 description
=> "Start virtual machine.",
1956 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1959 additionalProperties
=> 0,
1961 node
=> get_standard_option
('pve-node'),
1962 vmid
=> get_standard_option
('pve-vmid',
1963 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1964 skiplock
=> get_standard_option
('skiplock'),
1965 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1966 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1969 enum
=> ['secure', 'insecure'],
1970 description
=> "Migration traffic is encrypted using an SSH " .
1971 "tunnel by default. On secure, completely private networks " .
1972 "this can be disabled to increase performance.",
1975 migration_network
=> {
1976 type
=> 'string', format
=> 'CIDR',
1977 description
=> "CIDR of the (sub) network that is used for migration.",
1980 machine
=> get_standard_option
('pve-qemu-machine'),
1982 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1994 my $rpcenv = PVE
::RPCEnvironment
::get
();
1995 my $authuser = $rpcenv->get_user();
1997 my $node = extract_param
($param, 'node');
1998 my $vmid = extract_param
($param, 'vmid');
2000 my $machine = extract_param
($param, 'machine');
2002 my $get_root_param = sub {
2003 my $value = extract_param
($param, $_[0]);
2004 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2005 if $value && $authuser ne 'root@pam';
2009 my $stateuri = $get_root_param->('stateuri');
2010 my $skiplock = $get_root_param->('skiplock');
2011 my $migratedfrom = $get_root_param->('migratedfrom');
2012 my $migration_type = $get_root_param->('migration_type');
2013 my $migration_network = $get_root_param->('migration_network');
2014 my $targetstorage = $get_root_param->('targetstorage');
2016 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2017 if $targetstorage && !$migratedfrom;
2019 # read spice ticket from STDIN
2021 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2022 if (defined(my $line = <STDIN
>)) {
2024 $spice_ticket = $line;
2028 PVE
::Cluster
::check_cfs_quorum
();
2030 my $storecfg = PVE
::Storage
::config
();
2032 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2036 print "Requesting HA start for VM $vmid\n";
2038 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2039 PVE
::Tools
::run_command
($cmd);
2043 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2050 syslog
('info', "start VM $vmid: $upid\n");
2052 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2053 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
2057 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2061 __PACKAGE__-
>register_method({
2063 path
=> '{vmid}/status/stop',
2067 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2068 "is akin to pulling the power plug of a running computer and may damage the VM data",
2070 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2073 additionalProperties
=> 0,
2075 node
=> get_standard_option
('pve-node'),
2076 vmid
=> get_standard_option
('pve-vmid',
2077 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2078 skiplock
=> get_standard_option
('skiplock'),
2079 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2081 description
=> "Wait maximal timeout seconds.",
2087 description
=> "Do not deactivate storage volumes.",
2100 my $rpcenv = PVE
::RPCEnvironment
::get
();
2101 my $authuser = $rpcenv->get_user();
2103 my $node = extract_param
($param, 'node');
2104 my $vmid = extract_param
($param, 'vmid');
2106 my $skiplock = extract_param
($param, 'skiplock');
2107 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2108 if $skiplock && $authuser ne 'root@pam';
2110 my $keepActive = extract_param
($param, 'keepActive');
2111 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2112 if $keepActive && $authuser ne 'root@pam';
2114 my $migratedfrom = extract_param
($param, 'migratedfrom');
2115 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2116 if $migratedfrom && $authuser ne 'root@pam';
2119 my $storecfg = PVE
::Storage
::config
();
2121 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2126 print "Requesting HA stop for VM $vmid\n";
2128 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2129 PVE
::Tools
::run_command
($cmd);
2133 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2139 syslog
('info', "stop VM $vmid: $upid\n");
2141 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2142 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2146 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2150 __PACKAGE__-
>register_method({
2152 path
=> '{vmid}/status/reset',
2156 description
=> "Reset virtual machine.",
2158 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2161 additionalProperties
=> 0,
2163 node
=> get_standard_option
('pve-node'),
2164 vmid
=> get_standard_option
('pve-vmid',
2165 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2166 skiplock
=> get_standard_option
('skiplock'),
2175 my $rpcenv = PVE
::RPCEnvironment
::get
();
2177 my $authuser = $rpcenv->get_user();
2179 my $node = extract_param
($param, 'node');
2181 my $vmid = extract_param
($param, 'vmid');
2183 my $skiplock = extract_param
($param, 'skiplock');
2184 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2185 if $skiplock && $authuser ne 'root@pam';
2187 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2192 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2197 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2200 __PACKAGE__-
>register_method({
2201 name
=> 'vm_shutdown',
2202 path
=> '{vmid}/status/shutdown',
2206 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2207 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2209 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2212 additionalProperties
=> 0,
2214 node
=> get_standard_option
('pve-node'),
2215 vmid
=> get_standard_option
('pve-vmid',
2216 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2217 skiplock
=> get_standard_option
('skiplock'),
2219 description
=> "Wait maximal timeout seconds.",
2225 description
=> "Make sure the VM stops.",
2231 description
=> "Do not deactivate storage volumes.",
2244 my $rpcenv = PVE
::RPCEnvironment
::get
();
2245 my $authuser = $rpcenv->get_user();
2247 my $node = extract_param
($param, 'node');
2248 my $vmid = extract_param
($param, 'vmid');
2250 my $skiplock = extract_param
($param, 'skiplock');
2251 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2252 if $skiplock && $authuser ne 'root@pam';
2254 my $keepActive = extract_param
($param, 'keepActive');
2255 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2256 if $keepActive && $authuser ne 'root@pam';
2258 my $storecfg = PVE
::Storage
::config
();
2262 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2263 # otherwise, we will infer a shutdown command, but run into the timeout,
2264 # then when the vm is resumed, it will instantly shutdown
2266 # checking the qmp status here to get feedback to the gui/cli/api
2267 # and the status query should not take too long
2268 my $qmpstatus = eval {
2269 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2270 mon_cmd
($vmid, "query-status");
2274 if (!$err && $qmpstatus->{status
} eq "paused") {
2275 if ($param->{forceStop
}) {
2276 warn "VM is paused - stop instead of shutdown\n";
2279 die "VM is paused - cannot shutdown\n";
2283 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2285 my $timeout = $param->{timeout
} // 60;
2289 print "Requesting HA stop for VM $vmid\n";
2291 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2292 PVE
::Tools
::run_command
($cmd);
2296 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2303 syslog
('info', "shutdown VM $vmid: $upid\n");
2305 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2306 $shutdown, $param->{forceStop
}, $keepActive);
2310 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2314 __PACKAGE__-
>register_method({
2315 name
=> 'vm_reboot',
2316 path
=> '{vmid}/status/reboot',
2320 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2322 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2325 additionalProperties
=> 0,
2327 node
=> get_standard_option
('pve-node'),
2328 vmid
=> get_standard_option
('pve-vmid',
2329 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2331 description
=> "Wait maximal timeout seconds for the shutdown.",
2344 my $rpcenv = PVE
::RPCEnvironment
::get
();
2345 my $authuser = $rpcenv->get_user();
2347 my $node = extract_param
($param, 'node');
2348 my $vmid = extract_param
($param, 'vmid');
2350 my $qmpstatus = eval {
2351 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2352 mon_cmd
($vmid, "query-status");
2356 if (!$err && $qmpstatus->{status
} eq "paused") {
2357 die "VM is paused - cannot shutdown\n";
2360 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2365 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2366 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2370 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2373 __PACKAGE__-
>register_method({
2374 name
=> 'vm_suspend',
2375 path
=> '{vmid}/status/suspend',
2379 description
=> "Suspend virtual machine.",
2381 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2384 additionalProperties
=> 0,
2386 node
=> get_standard_option
('pve-node'),
2387 vmid
=> get_standard_option
('pve-vmid',
2388 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2389 skiplock
=> get_standard_option
('skiplock'),
2394 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2396 statestorage
=> get_standard_option
('pve-storage-id', {
2397 description
=> "The storage for the VM state",
2398 requires
=> 'todisk',
2400 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2410 my $rpcenv = PVE
::RPCEnvironment
::get
();
2411 my $authuser = $rpcenv->get_user();
2413 my $node = extract_param
($param, 'node');
2414 my $vmid = extract_param
($param, 'vmid');
2416 my $todisk = extract_param
($param, 'todisk') // 0;
2418 my $statestorage = extract_param
($param, 'statestorage');
2420 my $skiplock = extract_param
($param, 'skiplock');
2421 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2422 if $skiplock && $authuser ne 'root@pam';
2424 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2426 die "Cannot suspend HA managed VM to disk\n"
2427 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2432 syslog
('info', "suspend VM $vmid: $upid\n");
2434 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2439 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2440 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2443 __PACKAGE__-
>register_method({
2444 name
=> 'vm_resume',
2445 path
=> '{vmid}/status/resume',
2449 description
=> "Resume virtual machine.",
2451 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2454 additionalProperties
=> 0,
2456 node
=> get_standard_option
('pve-node'),
2457 vmid
=> get_standard_option
('pve-vmid',
2458 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2459 skiplock
=> get_standard_option
('skiplock'),
2460 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2470 my $rpcenv = PVE
::RPCEnvironment
::get
();
2472 my $authuser = $rpcenv->get_user();
2474 my $node = extract_param
($param, 'node');
2476 my $vmid = extract_param
($param, 'vmid');
2478 my $skiplock = extract_param
($param, 'skiplock');
2479 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2480 if $skiplock && $authuser ne 'root@pam';
2482 my $nocheck = extract_param
($param, 'nocheck');
2484 my $to_disk_suspended;
2486 PVE
::QemuConfig-
>lock_config($vmid, sub {
2487 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2488 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2492 die "VM $vmid not running\n"
2493 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2498 syslog
('info', "resume VM $vmid: $upid\n");
2500 if (!$to_disk_suspended) {
2501 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2503 my $storecfg = PVE
::Storage
::config
();
2504 PVE
::QemuServer
::vm_start
($storecfg, $vmid, undef, $skiplock);
2510 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2513 __PACKAGE__-
>register_method({
2514 name
=> 'vm_sendkey',
2515 path
=> '{vmid}/sendkey',
2519 description
=> "Send key event to virtual machine.",
2521 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2524 additionalProperties
=> 0,
2526 node
=> get_standard_option
('pve-node'),
2527 vmid
=> get_standard_option
('pve-vmid',
2528 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2529 skiplock
=> get_standard_option
('skiplock'),
2531 description
=> "The key (qemu monitor encoding).",
2536 returns
=> { type
=> 'null'},
2540 my $rpcenv = PVE
::RPCEnvironment
::get
();
2542 my $authuser = $rpcenv->get_user();
2544 my $node = extract_param
($param, 'node');
2546 my $vmid = extract_param
($param, 'vmid');
2548 my $skiplock = extract_param
($param, 'skiplock');
2549 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2550 if $skiplock && $authuser ne 'root@pam';
2552 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2557 __PACKAGE__-
>register_method({
2558 name
=> 'vm_feature',
2559 path
=> '{vmid}/feature',
2563 description
=> "Check if feature for virtual machine is available.",
2565 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2568 additionalProperties
=> 0,
2570 node
=> get_standard_option
('pve-node'),
2571 vmid
=> get_standard_option
('pve-vmid'),
2573 description
=> "Feature to check.",
2575 enum
=> [ 'snapshot', 'clone', 'copy' ],
2577 snapname
=> get_standard_option
('pve-snapshot-name', {
2585 hasFeature
=> { type
=> 'boolean' },
2588 items
=> { type
=> 'string' },
2595 my $node = extract_param
($param, 'node');
2597 my $vmid = extract_param
($param, 'vmid');
2599 my $snapname = extract_param
($param, 'snapname');
2601 my $feature = extract_param
($param, 'feature');
2603 my $running = PVE
::QemuServer
::check_running
($vmid);
2605 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2608 my $snap = $conf->{snapshots
}->{$snapname};
2609 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2612 my $storecfg = PVE
::Storage
::config
();
2614 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2615 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2618 hasFeature
=> $hasFeature,
2619 nodes
=> [ keys %$nodelist ],
2623 __PACKAGE__-
>register_method({
2625 path
=> '{vmid}/clone',
2629 description
=> "Create a copy of virtual machine/template.",
2631 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2632 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2633 "'Datastore.AllocateSpace' on any used storage.",
2636 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2638 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2639 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2644 additionalProperties
=> 0,
2646 node
=> get_standard_option
('pve-node'),
2647 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2648 newid
=> get_standard_option
('pve-vmid', {
2649 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2650 description
=> 'VMID for the clone.' }),
2653 type
=> 'string', format
=> 'dns-name',
2654 description
=> "Set a name for the new VM.",
2659 description
=> "Description for the new VM.",
2663 type
=> 'string', format
=> 'pve-poolid',
2664 description
=> "Add the new VM to the specified pool.",
2666 snapname
=> get_standard_option
('pve-snapshot-name', {
2669 storage
=> get_standard_option
('pve-storage-id', {
2670 description
=> "Target storage for full clone.",
2674 description
=> "Target format for file storage. Only valid for full clone.",
2677 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2682 description
=> "Create a full copy of all disks. This is always done when " .
2683 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2685 target
=> get_standard_option
('pve-node', {
2686 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2690 description
=> "Override I/O bandwidth limit (in KiB/s).",
2694 default => 'clone limit from datacenter or storage config',
2704 my $rpcenv = PVE
::RPCEnvironment
::get
();
2706 my $authuser = $rpcenv->get_user();
2708 my $node = extract_param
($param, 'node');
2710 my $vmid = extract_param
($param, 'vmid');
2712 my $newid = extract_param
($param, 'newid');
2714 my $pool = extract_param
($param, 'pool');
2716 if (defined($pool)) {
2717 $rpcenv->check_pool_exist($pool);
2720 my $snapname = extract_param
($param, 'snapname');
2722 my $storage = extract_param
($param, 'storage');
2724 my $format = extract_param
($param, 'format');
2726 my $target = extract_param
($param, 'target');
2728 my $localnode = PVE
::INotify
::nodename
();
2730 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2732 PVE
::Cluster
::check_node_exists
($target) if $target;
2734 my $storecfg = PVE
::Storage
::config
();
2737 # check if storage is enabled on local node
2738 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2740 # check if storage is available on target node
2741 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2742 # clone only works if target storage is shared
2743 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2744 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2748 PVE
::Cluster
::check_cfs_quorum
();
2750 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2752 # exclusive lock if VM is running - else shared lock is enough;
2753 my $shared_lock = $running ?
0 : 1;
2757 # do all tests after lock
2758 # we also try to do all tests before we fork the worker
2760 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2762 PVE
::QemuConfig-
>check_lock($conf);
2764 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2766 die "unexpected state change\n" if $verify_running != $running;
2768 die "snapshot '$snapname' does not exist\n"
2769 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2771 my $full = extract_param
($param, 'full');
2772 if (!defined($full)) {
2773 $full = !PVE
::QemuConfig-
>is_template($conf);
2776 die "parameter 'storage' not allowed for linked clones\n"
2777 if defined($storage) && !$full;
2779 die "parameter 'format' not allowed for linked clones\n"
2780 if defined($format) && !$full;
2782 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2784 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2786 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2788 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2790 die "unable to create VM $newid: config file already exists\n"
2793 my $newconf = { lock => 'clone' };
2798 foreach my $opt (keys %$oldconf) {
2799 my $value = $oldconf->{$opt};
2801 # do not copy snapshot related info
2802 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2803 $opt eq 'vmstate' || $opt eq 'snapstate';
2805 # no need to copy unused images, because VMID(owner) changes anyways
2806 next if $opt =~ m/^unused\d+$/;
2808 # always change MAC! address
2809 if ($opt =~ m/^net(\d+)$/) {
2810 my $net = PVE
::QemuServer
::parse_net
($value);
2811 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2812 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2813 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2814 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2815 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2816 die "unable to parse drive options for '$opt'\n" if !$drive;
2817 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2818 $newconf->{$opt} = $value; # simply copy configuration
2820 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2821 die "Full clone feature is not supported for drive '$opt'\n"
2822 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2823 $fullclone->{$opt} = 1;
2825 # not full means clone instead of copy
2826 die "Linked clone feature is not supported for drive '$opt'\n"
2827 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2829 $drives->{$opt} = $drive;
2830 push @$vollist, $drive->{file
};
2833 # copy everything else
2834 $newconf->{$opt} = $value;
2838 # auto generate a new uuid
2839 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2840 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2841 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2843 # auto generate a new vmgenid if the option was set
2844 if ($newconf->{vmgenid
}) {
2845 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2848 delete $newconf->{template
};
2850 if ($param->{name
}) {
2851 $newconf->{name
} = $param->{name
};
2853 if ($oldconf->{name
}) {
2854 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2856 $newconf->{name
} = "Copy-of-VM-$vmid";
2860 if ($param->{description
}) {
2861 $newconf->{description
} = $param->{description
};
2864 # create empty/temp config - this fails if VM already exists on other node
2865 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2870 my $newvollist = [];
2877 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2879 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2881 my $bwlimit = extract_param
($param, 'bwlimit');
2883 my $total_jobs = scalar(keys %{$drives});
2886 foreach my $opt (keys %$drives) {
2887 my $drive = $drives->{$opt};
2888 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2890 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
2891 my $storage_list = [ $src_sid ];
2892 push @$storage_list, $storage if defined($storage);
2893 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
2895 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2896 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2897 $jobs, $skipcomplete, $oldconf->{agent
}, $clonelimit);
2899 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2901 PVE
::QemuConfig-
>write_config($newid, $newconf);
2905 delete $newconf->{lock};
2907 # do not write pending changes
2908 if (my @changes = keys %{$newconf->{pending
}}) {
2909 my $pending = join(',', @changes);
2910 warn "found pending changes for '$pending', discarding for clone\n";
2911 delete $newconf->{pending
};
2914 PVE
::QemuConfig-
>write_config($newid, $newconf);
2917 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2918 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2919 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2921 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2922 die "Failed to move config to node '$target' - rename failed: $!\n"
2923 if !rename($conffile, $newconffile);
2926 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2931 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2933 sleep 1; # some storage like rbd need to wait before release volume - really?
2935 foreach my $volid (@$newvollist) {
2936 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2939 die "clone failed: $err";
2945 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2947 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2950 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2951 # Aquire exclusive lock lock for $newid
2952 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2957 __PACKAGE__-
>register_method({
2958 name
=> 'move_vm_disk',
2959 path
=> '{vmid}/move_disk',
2963 description
=> "Move volume to different storage.",
2965 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2967 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2968 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2972 additionalProperties
=> 0,
2974 node
=> get_standard_option
('pve-node'),
2975 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2978 description
=> "The disk you want to move.",
2979 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2981 storage
=> get_standard_option
('pve-storage-id', {
2982 description
=> "Target storage.",
2983 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2987 description
=> "Target Format.",
2988 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2993 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2999 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3004 description
=> "Override I/O bandwidth limit (in KiB/s).",
3008 default => 'move limit from datacenter or storage config',
3014 description
=> "the task ID.",
3019 my $rpcenv = PVE
::RPCEnvironment
::get
();
3021 my $authuser = $rpcenv->get_user();
3023 my $node = extract_param
($param, 'node');
3025 my $vmid = extract_param
($param, 'vmid');
3027 my $digest = extract_param
($param, 'digest');
3029 my $disk = extract_param
($param, 'disk');
3031 my $storeid = extract_param
($param, 'storage');
3033 my $format = extract_param
($param, 'format');
3035 my $storecfg = PVE
::Storage
::config
();
3037 my $updatefn = sub {
3039 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3041 PVE
::QemuConfig-
>check_lock($conf);
3043 die "checksum missmatch (file change by other user?)\n"
3044 if $digest && $digest ne $conf->{digest
};
3046 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3048 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3050 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
3052 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3055 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3056 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3060 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
3061 (!$format || !$oldfmt || $oldfmt eq $format);
3063 # this only checks snapshots because $disk is passed!
3064 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3065 die "you can't move a disk with snapshots and delete the source\n"
3066 if $snapshotted && $param->{delete};
3068 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3070 my $running = PVE
::QemuServer
::check_running
($vmid);
3072 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3076 my $newvollist = [];
3082 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3084 warn "moving disk with snapshots, snapshots will not be moved!\n"
3087 my $bwlimit = extract_param
($param, 'bwlimit');
3088 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3090 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3091 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit);
3093 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
3095 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3097 # convert moved disk to base if part of template
3098 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3099 if PVE
::QemuConfig-
>is_template($conf);
3101 PVE
::QemuConfig-
>write_config($vmid, $conf);
3103 if ($running && PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
} && PVE
::QemuServer
::qga_check_running
($vmid)) {
3104 eval { mon_cmd
($vmid, "guest-fstrim"); };
3108 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3109 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3116 foreach my $volid (@$newvollist) {
3117 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3120 die "storage migration failed: $err";
3123 if ($param->{delete}) {
3125 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3126 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3132 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3135 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3138 my $check_vm_disks_local = sub {
3139 my ($storecfg, $vmconf, $vmid) = @_;
3141 my $local_disks = {};
3143 # add some more information to the disks e.g. cdrom
3144 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3145 my ($volid, $attr) = @_;
3147 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3149 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3150 return if $scfg->{shared
};
3152 # The shared attr here is just a special case where the vdisk
3153 # is marked as shared manually
3154 return if $attr->{shared
};
3155 return if $attr->{cdrom
} and $volid eq "none";
3157 if (exists $local_disks->{$volid}) {
3158 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3160 $local_disks->{$volid} = $attr;
3161 # ensure volid is present in case it's needed
3162 $local_disks->{$volid}->{volid
} = $volid;
3166 return $local_disks;
3169 __PACKAGE__-
>register_method({
3170 name
=> 'migrate_vm_precondition',
3171 path
=> '{vmid}/migrate',
3175 description
=> "Get preconditions for migration.",
3177 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3180 additionalProperties
=> 0,
3182 node
=> get_standard_option
('pve-node'),
3183 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3184 target
=> get_standard_option
('pve-node', {
3185 description
=> "Target node.",
3186 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3194 running
=> { type
=> 'boolean' },
3198 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3200 not_allowed_nodes
=> {
3203 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3207 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3209 local_resources
=> {
3211 description
=> "List local resources e.g. pci, usb"
3218 my $rpcenv = PVE
::RPCEnvironment
::get
();
3220 my $authuser = $rpcenv->get_user();
3222 PVE
::Cluster
::check_cfs_quorum
();
3226 my $vmid = extract_param
($param, 'vmid');
3227 my $target = extract_param
($param, 'target');
3228 my $localnode = PVE
::INotify
::nodename
();
3232 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3233 my $storecfg = PVE
::Storage
::config
();
3236 # try to detect errors early
3237 PVE
::QemuConfig-
>check_lock($vmconf);
3239 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3241 # if vm is not running, return target nodes where local storage is available
3242 # for offline migration
3243 if (!$res->{running
}) {
3244 $res->{allowed_nodes
} = [];
3245 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3246 delete $checked_nodes->{$localnode};
3248 foreach my $node (keys %$checked_nodes) {
3249 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3250 push @{$res->{allowed_nodes
}}, $node;
3254 $res->{not_allowed_nodes
} = $checked_nodes;
3258 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3259 $res->{local_disks
} = [ values %$local_disks ];;
3261 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3263 $res->{local_resources
} = $local_resources;
3270 __PACKAGE__-
>register_method({
3271 name
=> 'migrate_vm',
3272 path
=> '{vmid}/migrate',
3276 description
=> "Migrate virtual machine. Creates a new migration task.",
3278 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3281 additionalProperties
=> 0,
3283 node
=> get_standard_option
('pve-node'),
3284 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3285 target
=> get_standard_option
('pve-node', {
3286 description
=> "Target node.",
3287 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3291 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3296 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3301 enum
=> ['secure', 'insecure'],
3302 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3305 migration_network
=> {
3306 type
=> 'string', format
=> 'CIDR',
3307 description
=> "CIDR of the (sub) network that is used for migration.",
3310 "with-local-disks" => {
3312 description
=> "Enable live storage migration for local disk",
3315 targetstorage
=> get_standard_option
('pve-storage-id', {
3316 description
=> "Default target storage.",
3318 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3321 description
=> "Override I/O bandwidth limit (in KiB/s).",
3325 default => 'migrate limit from datacenter or storage config',
3331 description
=> "the task ID.",
3336 my $rpcenv = PVE
::RPCEnvironment
::get
();
3337 my $authuser = $rpcenv->get_user();
3339 my $target = extract_param
($param, 'target');
3341 my $localnode = PVE
::INotify
::nodename
();
3342 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3344 PVE
::Cluster
::check_cfs_quorum
();
3346 PVE
::Cluster
::check_node_exists
($target);
3348 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3350 my $vmid = extract_param
($param, 'vmid');
3352 raise_param_exc
({ force
=> "Only root may use this option." })
3353 if $param->{force
} && $authuser ne 'root@pam';
3355 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3356 if $param->{migration_type
} && $authuser ne 'root@pam';
3358 # allow root only until better network permissions are available
3359 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3360 if $param->{migration_network
} && $authuser ne 'root@pam';
3363 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3365 # try to detect errors early
3367 PVE
::QemuConfig-
>check_lock($conf);
3369 if (PVE
::QemuServer
::check_running
($vmid)) {
3370 die "can't migrate running VM without --online\n" if !$param->{online
};
3372 warn "VM isn't running. Doing offline migration instead\n." if $param->{online
};
3373 $param->{online
} = 0;
3376 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3377 if !$param->{online
} && $param->{targetstorage
};
3379 my $storecfg = PVE
::Storage
::config
();
3381 if( $param->{targetstorage
}) {
3382 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3384 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3387 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3392 print "Requesting HA migration for VM $vmid to node $target\n";
3394 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3395 PVE
::Tools
::run_command
($cmd);
3399 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3404 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3408 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3411 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3416 __PACKAGE__-
>register_method({
3418 path
=> '{vmid}/monitor',
3422 description
=> "Execute Qemu monitor commands.",
3424 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3425 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3428 additionalProperties
=> 0,
3430 node
=> get_standard_option
('pve-node'),
3431 vmid
=> get_standard_option
('pve-vmid'),
3434 description
=> "The monitor command.",
3438 returns
=> { type
=> 'string'},
3442 my $rpcenv = PVE
::RPCEnvironment
::get
();
3443 my $authuser = $rpcenv->get_user();
3446 my $command = shift;
3447 return $command =~ m/^\s*info(\s+|$)/
3448 || $command =~ m/^\s*help\s*$/;
3451 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3452 if !&$is_ro($param->{command
});
3454 my $vmid = $param->{vmid
};
3456 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3460 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3462 $res = "ERROR: $@" if $@;
3467 __PACKAGE__-
>register_method({
3468 name
=> 'resize_vm',
3469 path
=> '{vmid}/resize',
3473 description
=> "Extend volume size.",
3475 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3478 additionalProperties
=> 0,
3480 node
=> get_standard_option
('pve-node'),
3481 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3482 skiplock
=> get_standard_option
('skiplock'),
3485 description
=> "The disk you want to resize.",
3486 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3490 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3491 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.",
3495 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3501 returns
=> { type
=> 'null'},
3505 my $rpcenv = PVE
::RPCEnvironment
::get
();
3507 my $authuser = $rpcenv->get_user();
3509 my $node = extract_param
($param, 'node');
3511 my $vmid = extract_param
($param, 'vmid');
3513 my $digest = extract_param
($param, 'digest');
3515 my $disk = extract_param
($param, 'disk');
3517 my $sizestr = extract_param
($param, 'size');
3519 my $skiplock = extract_param
($param, 'skiplock');
3520 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3521 if $skiplock && $authuser ne 'root@pam';
3523 my $storecfg = PVE
::Storage
::config
();
3525 my $updatefn = sub {
3527 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3529 die "checksum missmatch (file change by other user?)\n"
3530 if $digest && $digest ne $conf->{digest
};
3531 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3533 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3535 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3537 my (undef, undef, undef, undef, undef, undef, $format) =
3538 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3540 die "can't resize volume: $disk if snapshot exists\n"
3541 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3543 my $volid = $drive->{file
};
3545 die "disk '$disk' has no associated volume\n" if !$volid;
3547 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3549 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3551 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3553 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3554 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3556 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3558 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3559 my ($ext, $newsize, $unit) = ($1, $2, $4);
3562 $newsize = $newsize * 1024;
3563 } elsif ($unit eq 'M') {
3564 $newsize = $newsize * 1024 * 1024;
3565 } elsif ($unit eq 'G') {
3566 $newsize = $newsize * 1024 * 1024 * 1024;
3567 } elsif ($unit eq 'T') {
3568 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3571 $newsize += $size if $ext;
3572 $newsize = int($newsize);
3574 die "shrinking disks is not supported\n" if $newsize < $size;
3576 return if $size == $newsize;
3578 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3580 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3582 $drive->{size
} = $newsize;
3583 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3585 PVE
::QemuConfig-
>write_config($vmid, $conf);
3588 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3592 __PACKAGE__-
>register_method({
3593 name
=> 'snapshot_list',
3594 path
=> '{vmid}/snapshot',
3596 description
=> "List all snapshots.",
3598 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3601 protected
=> 1, # qemu pid files are only readable by root
3603 additionalProperties
=> 0,
3605 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3606 node
=> get_standard_option
('pve-node'),
3615 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3619 description
=> "Snapshot includes RAM.",
3624 description
=> "Snapshot description.",
3628 description
=> "Snapshot creation time",
3630 renderer
=> 'timestamp',
3634 description
=> "Parent snapshot identifier.",
3640 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3645 my $vmid = $param->{vmid
};
3647 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3648 my $snaphash = $conf->{snapshots
} || {};
3652 foreach my $name (keys %$snaphash) {
3653 my $d = $snaphash->{$name};
3656 snaptime
=> $d->{snaptime
} || 0,
3657 vmstate
=> $d->{vmstate
} ?
1 : 0,
3658 description
=> $d->{description
} || '',
3660 $item->{parent
} = $d->{parent
} if $d->{parent
};
3661 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3665 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3668 digest
=> $conf->{digest
},
3669 running
=> $running,
3670 description
=> "You are here!",
3672 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3674 push @$res, $current;
3679 __PACKAGE__-
>register_method({
3681 path
=> '{vmid}/snapshot',
3685 description
=> "Snapshot a VM.",
3687 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3690 additionalProperties
=> 0,
3692 node
=> get_standard_option
('pve-node'),
3693 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3694 snapname
=> get_standard_option
('pve-snapshot-name'),
3698 description
=> "Save the vmstate",
3703 description
=> "A textual description or comment.",
3709 description
=> "the task ID.",
3714 my $rpcenv = PVE
::RPCEnvironment
::get
();
3716 my $authuser = $rpcenv->get_user();
3718 my $node = extract_param
($param, 'node');
3720 my $vmid = extract_param
($param, 'vmid');
3722 my $snapname = extract_param
($param, 'snapname');
3724 die "unable to use snapshot name 'current' (reserved name)\n"
3725 if $snapname eq 'current';
3727 die "unable to use snapshot name 'pending' (reserved name)\n"
3728 if lc($snapname) eq 'pending';
3731 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3732 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3733 $param->{description
});
3736 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3739 __PACKAGE__-
>register_method({
3740 name
=> 'snapshot_cmd_idx',
3741 path
=> '{vmid}/snapshot/{snapname}',
3748 additionalProperties
=> 0,
3750 vmid
=> get_standard_option
('pve-vmid'),
3751 node
=> get_standard_option
('pve-node'),
3752 snapname
=> get_standard_option
('pve-snapshot-name'),
3761 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3768 push @$res, { cmd
=> 'rollback' };
3769 push @$res, { cmd
=> 'config' };
3774 __PACKAGE__-
>register_method({
3775 name
=> 'update_snapshot_config',
3776 path
=> '{vmid}/snapshot/{snapname}/config',
3780 description
=> "Update snapshot metadata.",
3782 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3785 additionalProperties
=> 0,
3787 node
=> get_standard_option
('pve-node'),
3788 vmid
=> get_standard_option
('pve-vmid'),
3789 snapname
=> get_standard_option
('pve-snapshot-name'),
3793 description
=> "A textual description or comment.",
3797 returns
=> { type
=> 'null' },
3801 my $rpcenv = PVE
::RPCEnvironment
::get
();
3803 my $authuser = $rpcenv->get_user();
3805 my $vmid = extract_param
($param, 'vmid');
3807 my $snapname = extract_param
($param, 'snapname');
3809 return undef if !defined($param->{description
});
3811 my $updatefn = sub {
3813 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3815 PVE
::QemuConfig-
>check_lock($conf);
3817 my $snap = $conf->{snapshots
}->{$snapname};
3819 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3821 $snap->{description
} = $param->{description
} if defined($param->{description
});
3823 PVE
::QemuConfig-
>write_config($vmid, $conf);
3826 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3831 __PACKAGE__-
>register_method({
3832 name
=> 'get_snapshot_config',
3833 path
=> '{vmid}/snapshot/{snapname}/config',
3836 description
=> "Get snapshot configuration",
3838 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3841 additionalProperties
=> 0,
3843 node
=> get_standard_option
('pve-node'),
3844 vmid
=> get_standard_option
('pve-vmid'),
3845 snapname
=> get_standard_option
('pve-snapshot-name'),
3848 returns
=> { type
=> "object" },
3852 my $rpcenv = PVE
::RPCEnvironment
::get
();
3854 my $authuser = $rpcenv->get_user();
3856 my $vmid = extract_param
($param, 'vmid');
3858 my $snapname = extract_param
($param, 'snapname');
3860 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3862 my $snap = $conf->{snapshots
}->{$snapname};
3864 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3869 __PACKAGE__-
>register_method({
3871 path
=> '{vmid}/snapshot/{snapname}/rollback',
3875 description
=> "Rollback VM state to specified snapshot.",
3877 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3880 additionalProperties
=> 0,
3882 node
=> get_standard_option
('pve-node'),
3883 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3884 snapname
=> get_standard_option
('pve-snapshot-name'),
3889 description
=> "the task ID.",
3894 my $rpcenv = PVE
::RPCEnvironment
::get
();
3896 my $authuser = $rpcenv->get_user();
3898 my $node = extract_param
($param, 'node');
3900 my $vmid = extract_param
($param, 'vmid');
3902 my $snapname = extract_param
($param, 'snapname');
3905 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3906 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3910 # hold migration lock, this makes sure that nobody create replication snapshots
3911 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3914 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3917 __PACKAGE__-
>register_method({
3918 name
=> 'delsnapshot',
3919 path
=> '{vmid}/snapshot/{snapname}',
3923 description
=> "Delete a VM snapshot.",
3925 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3928 additionalProperties
=> 0,
3930 node
=> get_standard_option
('pve-node'),
3931 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3932 snapname
=> get_standard_option
('pve-snapshot-name'),
3936 description
=> "For removal from config file, even if removing disk snapshots fails.",
3942 description
=> "the task ID.",
3947 my $rpcenv = PVE
::RPCEnvironment
::get
();
3949 my $authuser = $rpcenv->get_user();
3951 my $node = extract_param
($param, 'node');
3953 my $vmid = extract_param
($param, 'vmid');
3955 my $snapname = extract_param
($param, 'snapname');
3958 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3959 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3962 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3965 __PACKAGE__-
>register_method({
3967 path
=> '{vmid}/template',
3971 description
=> "Create a Template.",
3973 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3974 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3977 additionalProperties
=> 0,
3979 node
=> get_standard_option
('pve-node'),
3980 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3984 description
=> "If you want to convert only 1 disk to base image.",
3985 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3990 returns
=> { type
=> 'null'},
3994 my $rpcenv = PVE
::RPCEnvironment
::get
();
3996 my $authuser = $rpcenv->get_user();
3998 my $node = extract_param
($param, 'node');
4000 my $vmid = extract_param
($param, 'vmid');
4002 my $disk = extract_param
($param, 'disk');
4004 my $updatefn = sub {
4006 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4008 PVE
::QemuConfig-
>check_lock($conf);
4010 die "unable to create template, because VM contains snapshots\n"
4011 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4013 die "you can't convert a template to a template\n"
4014 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4016 die "you can't convert a VM to template if VM is running\n"
4017 if PVE
::QemuServer
::check_running
($vmid);
4020 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4023 $conf->{template
} = 1;
4024 PVE
::QemuConfig-
>write_config($vmid, $conf);
4026 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4029 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4033 __PACKAGE__-
>register_method({
4034 name
=> 'cloudinit_generated_config_dump',
4035 path
=> '{vmid}/cloudinit/dump',
4038 description
=> "Get automatically generated cloudinit config.",
4040 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4043 additionalProperties
=> 0,
4045 node
=> get_standard_option
('pve-node'),
4046 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4048 description
=> 'Config type.',
4050 enum
=> ['user', 'network', 'meta'],
4060 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4062 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});