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' || $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 ($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,
288 my $vmpoweroptions = {
295 'vmstatestorage' => 1,
298 my $cloudinitoptions = {
308 my $check_vm_modify_config_perm = sub {
309 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
311 return 1 if $authuser eq 'root@pam';
313 foreach my $opt (@$key_list) {
314 # some checks (e.g., disk, serial port, usb) need to be done somewhere
315 # else, as there the permission can be value dependend
316 next if PVE
::QemuServer
::is_valid_drivename
($opt);
317 next if $opt eq 'cdrom';
318 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
321 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
322 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
323 } elsif ($memoryoptions->{$opt}) {
324 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
325 } elsif ($hwtypeoptions->{$opt}) {
326 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
327 } elsif ($generaloptions->{$opt}) {
328 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
329 # special case for startup since it changes host behaviour
330 if ($opt eq 'startup') {
331 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
333 } elsif ($vmpoweroptions->{$opt}) {
334 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
335 } elsif ($diskoptions->{$opt}) {
336 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
337 } elsif ($cloudinitoptions->{$opt} || ($opt =~ m/^(?:net|ipconfig)\d+$/)) {
338 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
340 # catches hostpci\d+, args, lock, etc.
341 # new options will be checked here
342 die "only root can set '$opt' config\n";
349 __PACKAGE__-
>register_method({
353 description
=> "Virtual machine index (per node).",
355 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
359 protected
=> 1, # qemu pid files are only readable by root
361 additionalProperties
=> 0,
363 node
=> get_standard_option
('pve-node'),
367 description
=> "Determine the full status of active VMs.",
375 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
377 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
382 my $rpcenv = PVE
::RPCEnvironment
::get
();
383 my $authuser = $rpcenv->get_user();
385 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
388 foreach my $vmid (keys %$vmstatus) {
389 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
391 my $data = $vmstatus->{$vmid};
400 __PACKAGE__-
>register_method({
404 description
=> "Create or restore a virtual machine.",
406 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
407 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
408 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
409 user
=> 'all', # check inside
414 additionalProperties
=> 0,
415 properties
=> PVE
::QemuServer
::json_config_properties
(
417 node
=> get_standard_option
('pve-node'),
418 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
420 description
=> "The backup file.",
424 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
426 storage
=> get_standard_option
('pve-storage-id', {
427 description
=> "Default storage.",
429 completion
=> \
&PVE
::QemuServer
::complete_storage
,
434 description
=> "Allow to overwrite existing VM.",
435 requires
=> 'archive',
440 description
=> "Assign a unique random ethernet address.",
441 requires
=> 'archive',
445 type
=> 'string', format
=> 'pve-poolid',
446 description
=> "Add the VM to the specified pool.",
449 description
=> "Override I/O bandwidth limit (in KiB/s).",
453 default => 'restore limit from datacenter or storage config',
459 description
=> "Start VM after it was created successfully.",
469 my $rpcenv = PVE
::RPCEnvironment
::get
();
470 my $authuser = $rpcenv->get_user();
472 my $node = extract_param
($param, 'node');
473 my $vmid = extract_param
($param, 'vmid');
475 my $archive = extract_param
($param, 'archive');
476 my $is_restore = !!$archive;
478 my $bwlimit = extract_param
($param, 'bwlimit');
479 my $force = extract_param
($param, 'force');
480 my $pool = extract_param
($param, 'pool');
481 my $start_after_create = extract_param
($param, 'start');
482 my $storage = extract_param
($param, 'storage');
483 my $unique = extract_param
($param, 'unique');
485 if (defined(my $ssh_keys = $param->{sshkeys
})) {
486 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
487 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
490 PVE
::Cluster
::check_cfs_quorum
();
492 my $filename = PVE
::QemuConfig-
>config_file($vmid);
493 my $storecfg = PVE
::Storage
::config
();
495 if (defined($pool)) {
496 $rpcenv->check_pool_exist($pool);
499 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
500 if defined($storage);
502 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
504 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
506 } elsif ($archive && $force && (-f
$filename) &&
507 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
508 # OK: user has VM.Backup permissions, and want to restore an existing VM
514 &$resolve_cdrom_alias($param);
516 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
518 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
520 foreach my $opt (keys %$param) {
521 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
522 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
523 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
525 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
526 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
530 PVE
::QemuServer
::add_random_macs
($param);
532 my $keystr = join(' ', keys %$param);
533 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
535 if ($archive eq '-') {
536 die "pipe requires cli environment\n"
537 if $rpcenv->{type
} ne 'cli';
539 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
540 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
544 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
546 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
547 die "$emsg $@" if $@;
549 my $restorefn = sub {
550 my $conf = PVE
::QemuConfig-
>load_config($vmid);
552 PVE
::QemuConfig-
>check_protection($conf, $emsg);
554 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
557 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
563 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
564 # Convert restored VM to template if backup was VM template
565 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
566 warn "Convert to template.\n";
567 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
571 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
573 if ($start_after_create) {
574 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
579 # ensure no old replication state are exists
580 PVE
::ReplicationState
::delete_guest_states
($vmid);
582 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
586 # ensure no old replication state are exists
587 PVE
::ReplicationState
::delete_guest_states
($vmid);
595 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
599 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
601 if (!$conf->{bootdisk
}) {
602 my $firstdisk = PVE
::QemuServer
::resolve_first_disk
($conf);
603 $conf->{bootdisk
} = $firstdisk if $firstdisk;
606 # auto generate uuid if user did not specify smbios1 option
607 if (!$conf->{smbios1
}) {
608 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
611 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
612 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
615 PVE
::QemuConfig-
>write_config($vmid, $conf);
621 foreach my $volid (@$vollist) {
622 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
628 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
631 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
633 if ($start_after_create) {
634 print "Execute autostart\n";
635 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
640 my ($code, $worker_name);
642 $worker_name = 'qmrestore';
644 eval { $restorefn->() };
646 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
652 $worker_name = 'qmcreate';
654 eval { $createfn->() };
657 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
658 unlink($conffile) or die "failed to remove config file: $!\n";
666 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
669 __PACKAGE__-
>register_method({
674 description
=> "Directory index",
679 additionalProperties
=> 0,
681 node
=> get_standard_option
('pve-node'),
682 vmid
=> get_standard_option
('pve-vmid'),
690 subdir
=> { type
=> 'string' },
693 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
699 { subdir
=> 'config' },
700 { subdir
=> 'pending' },
701 { subdir
=> 'status' },
702 { subdir
=> 'unlink' },
703 { subdir
=> 'vncproxy' },
704 { subdir
=> 'termproxy' },
705 { subdir
=> 'migrate' },
706 { subdir
=> 'resize' },
707 { subdir
=> 'move' },
709 { subdir
=> 'rrddata' },
710 { subdir
=> 'monitor' },
711 { subdir
=> 'agent' },
712 { subdir
=> 'snapshot' },
713 { subdir
=> 'spiceproxy' },
714 { subdir
=> 'sendkey' },
715 { subdir
=> 'firewall' },
721 __PACKAGE__-
>register_method ({
722 subclass
=> "PVE::API2::Firewall::VM",
723 path
=> '{vmid}/firewall',
726 __PACKAGE__-
>register_method ({
727 subclass
=> "PVE::API2::Qemu::Agent",
728 path
=> '{vmid}/agent',
731 __PACKAGE__-
>register_method({
733 path
=> '{vmid}/rrd',
735 protected
=> 1, # fixme: can we avoid that?
737 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
739 description
=> "Read VM RRD statistics (returns PNG)",
741 additionalProperties
=> 0,
743 node
=> get_standard_option
('pve-node'),
744 vmid
=> get_standard_option
('pve-vmid'),
746 description
=> "Specify the time frame you are interested in.",
748 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
751 description
=> "The list of datasources you want to display.",
752 type
=> 'string', format
=> 'pve-configid-list',
755 description
=> "The RRD consolidation function",
757 enum
=> [ 'AVERAGE', 'MAX' ],
765 filename
=> { type
=> 'string' },
771 return PVE
::RRD
::create_rrd_graph
(
772 "pve2-vm/$param->{vmid}", $param->{timeframe
},
773 $param->{ds
}, $param->{cf
});
777 __PACKAGE__-
>register_method({
779 path
=> '{vmid}/rrddata',
781 protected
=> 1, # fixme: can we avoid that?
783 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
785 description
=> "Read VM RRD statistics",
787 additionalProperties
=> 0,
789 node
=> get_standard_option
('pve-node'),
790 vmid
=> get_standard_option
('pve-vmid'),
792 description
=> "Specify the time frame you are interested in.",
794 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
797 description
=> "The RRD consolidation function",
799 enum
=> [ 'AVERAGE', 'MAX' ],
814 return PVE
::RRD
::create_rrd_data
(
815 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
819 __PACKAGE__-
>register_method({
821 path
=> '{vmid}/config',
824 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
826 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
829 additionalProperties
=> 0,
831 node
=> get_standard_option
('pve-node'),
832 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
834 description
=> "Get current values (instead of pending values).",
839 snapshot
=> get_standard_option
('pve-snapshot-name', {
840 description
=> "Fetch config values from given snapshot.",
843 my ($cmd, $pname, $cur, $args) = @_;
844 PVE
::QemuConfig-
>snapshot_list($args->[0]);
850 description
=> "The current VM configuration.",
852 properties
=> PVE
::QemuServer
::json_config_properties
({
855 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
862 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
863 current
=> "cannot use 'snapshot' parameter with 'current'"})
864 if ($param->{snapshot
} && $param->{current
});
867 if ($param->{snapshot
}) {
868 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
870 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
872 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
877 __PACKAGE__-
>register_method({
878 name
=> 'vm_pending',
879 path
=> '{vmid}/pending',
882 description
=> "Get virtual machine configuration, including pending changes.",
884 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
887 additionalProperties
=> 0,
889 node
=> get_standard_option
('pve-node'),
890 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
899 description
=> "Configuration option name.",
903 description
=> "Current value.",
908 description
=> "Pending value.",
913 description
=> "Indicates a pending delete request if present and not 0. " .
914 "The value 2 indicates a force-delete request.",
926 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
928 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
930 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
931 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
933 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
936 # POST/PUT {vmid}/config implementation
938 # The original API used PUT (idempotent) an we assumed that all operations
939 # are fast. But it turned out that almost any configuration change can
940 # involve hot-plug actions, or disk alloc/free. Such actions can take long
941 # time to complete and have side effects (not idempotent).
943 # The new implementation uses POST and forks a worker process. We added
944 # a new option 'background_delay'. If specified we wait up to
945 # 'background_delay' second for the worker task to complete. It returns null
946 # if the task is finished within that time, else we return the UPID.
948 my $update_vm_api = sub {
949 my ($param, $sync) = @_;
951 my $rpcenv = PVE
::RPCEnvironment
::get
();
953 my $authuser = $rpcenv->get_user();
955 my $node = extract_param
($param, 'node');
957 my $vmid = extract_param
($param, 'vmid');
959 my $digest = extract_param
($param, 'digest');
961 my $background_delay = extract_param
($param, 'background_delay');
963 if (defined(my $cipassword = $param->{cipassword
})) {
964 # Same logic as in cloud-init (but with the regex fixed...)
965 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
966 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
969 my @paramarr = (); # used for log message
970 foreach my $key (sort keys %$param) {
971 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
972 push @paramarr, "-$key", $value;
975 my $skiplock = extract_param
($param, 'skiplock');
976 raise_param_exc
({ skiplock
=> "Only root may use this option." })
977 if $skiplock && $authuser ne 'root@pam';
979 my $delete_str = extract_param
($param, 'delete');
981 my $revert_str = extract_param
($param, 'revert');
983 my $force = extract_param
($param, 'force');
985 if (defined(my $ssh_keys = $param->{sshkeys
})) {
986 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
987 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
990 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
992 my $storecfg = PVE
::Storage
::config
();
994 my $defaults = PVE
::QemuServer
::load_defaults
();
996 &$resolve_cdrom_alias($param);
998 # now try to verify all parameters
1001 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1002 if (!PVE
::QemuServer
::option_exists
($opt)) {
1003 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1006 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1007 "-revert $opt' at the same time" })
1008 if defined($param->{$opt});
1010 $revert->{$opt} = 1;
1014 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1015 $opt = 'ide2' if $opt eq 'cdrom';
1017 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1018 "-delete $opt' at the same time" })
1019 if defined($param->{$opt});
1021 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1022 "-revert $opt' at the same time" })
1025 if (!PVE
::QemuServer
::option_exists
($opt)) {
1026 raise_param_exc
({ delete => "unknown option '$opt'" });
1032 my $repl_conf = PVE
::ReplicationConfig-
>new();
1033 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1034 my $check_replication = sub {
1036 return if !$is_replicated;
1037 my $volid = $drive->{file
};
1038 return if !$volid || !($drive->{replicate
}//1);
1039 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1041 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1042 return if $volname eq 'cloudinit';
1045 if ($volid =~ $NEW_DISK_RE) {
1047 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1049 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1051 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1052 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1053 return if $scfg->{shared
};
1054 die "cannot add non-replicatable volume to a replicated VM\n";
1057 foreach my $opt (keys %$param) {
1058 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1059 # cleanup drive path
1060 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1061 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1062 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1063 $check_replication->($drive);
1064 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1065 } elsif ($opt =~ m/^net(\d+)$/) {
1067 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1068 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1069 } elsif ($opt eq 'vmgenid') {
1070 if ($param->{$opt} eq '1') {
1071 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1073 } elsif ($opt eq 'hookscript') {
1074 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1075 raise_param_exc
({ $opt => $@ }) if $@;
1079 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1081 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1083 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1085 my $updatefn = sub {
1087 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1089 die "checksum missmatch (file change by other user?)\n"
1090 if $digest && $digest ne $conf->{digest
};
1092 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1094 foreach my $opt (keys %$revert) {
1095 if (defined($conf->{$opt})) {
1096 $param->{$opt} = $conf->{$opt};
1097 } elsif (defined($conf->{pending
}->{$opt})) {
1102 if ($param->{memory
} || defined($param->{balloon
})) {
1103 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1104 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1106 die "balloon value too large (must be smaller than assigned memory)\n"
1107 if $balloon && $balloon > $maxmem;
1110 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1114 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1116 # write updates to pending section
1118 my $modified = {}; # record what $option we modify
1120 foreach my $opt (@delete) {
1121 $modified->{$opt} = 1;
1122 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1124 # value of what we want to delete, independent if pending or not
1125 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1126 if (!defined($val)) {
1127 warn "cannot delete '$opt' - not set in current configuration!\n";
1128 $modified->{$opt} = 0;
1131 my $is_pending_val = defined($conf->{pending
}->{$opt});
1132 delete $conf->{pending
}->{$opt};
1134 if ($opt =~ m/^unused/) {
1135 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1136 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1137 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1138 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1139 delete $conf->{$opt};
1140 PVE
::QemuConfig-
>write_config($vmid, $conf);
1142 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1143 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1144 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1145 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1147 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1148 PVE
::QemuConfig-
>write_config($vmid, $conf);
1149 } elsif ($opt =~ m/^serial\d+$/) {
1150 if ($val eq 'socket') {
1151 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1152 } elsif ($authuser ne 'root@pam') {
1153 die "only root can delete '$opt' config for real devices\n";
1155 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1156 PVE
::QemuConfig-
>write_config($vmid, $conf);
1157 } elsif ($opt =~ m/^usb\d+$/) {
1158 if ($val =~ m/spice/) {
1159 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1160 } elsif ($authuser ne 'root@pam') {
1161 die "only root can delete '$opt' config for real devices\n";
1163 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1164 PVE
::QemuConfig-
>write_config($vmid, $conf);
1166 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1167 PVE
::QemuConfig-
>write_config($vmid, $conf);
1171 foreach my $opt (keys %$param) { # add/change
1172 $modified->{$opt} = 1;
1173 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1174 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1176 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
1178 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1179 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1180 # FIXME: cloudinit: CDROM or Disk?
1181 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1182 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1184 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1186 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1187 if defined($conf->{pending
}->{$opt});
1189 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1190 } elsif ($opt =~ m/^serial\d+/) {
1191 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1192 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1193 } elsif ($authuser ne 'root@pam') {
1194 die "only root can modify '$opt' config for real devices\n";
1196 $conf->{pending
}->{$opt} = $param->{$opt};
1197 } elsif ($opt =~ m/^usb\d+/) {
1198 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1199 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1200 } elsif ($authuser ne 'root@pam') {
1201 die "only root can modify '$opt' config for real devices\n";
1203 $conf->{pending
}->{$opt} = $param->{$opt};
1205 $conf->{pending
}->{$opt} = $param->{$opt};
1207 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1208 PVE
::QemuConfig-
>write_config($vmid, $conf);
1211 # remove pending changes when nothing changed
1212 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1213 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1214 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1216 return if !scalar(keys %{$conf->{pending
}});
1218 my $running = PVE
::QemuServer
::check_running
($vmid);
1220 # apply pending changes
1222 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1226 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1227 raise_param_exc
($errors) if scalar(keys %$errors);
1229 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1239 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1241 if ($background_delay) {
1243 # Note: It would be better to do that in the Event based HTTPServer
1244 # to avoid blocking call to sleep.
1246 my $end_time = time() + $background_delay;
1248 my $task = PVE
::Tools
::upid_decode
($upid);
1251 while (time() < $end_time) {
1252 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1254 sleep(1); # this gets interrupted when child process ends
1258 my $status = PVE
::Tools
::upid_read_status
($upid);
1259 return undef if $status eq 'OK';
1268 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1271 my $vm_config_perm_list = [
1276 'VM.Config.Network',
1278 'VM.Config.Options',
1281 __PACKAGE__-
>register_method({
1282 name
=> 'update_vm_async',
1283 path
=> '{vmid}/config',
1287 description
=> "Set virtual machine options (asynchrounous API).",
1289 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1292 additionalProperties
=> 0,
1293 properties
=> PVE
::QemuServer
::json_config_properties
(
1295 node
=> get_standard_option
('pve-node'),
1296 vmid
=> get_standard_option
('pve-vmid'),
1297 skiplock
=> get_standard_option
('skiplock'),
1299 type
=> 'string', format
=> 'pve-configid-list',
1300 description
=> "A list of settings you want to delete.",
1304 type
=> 'string', format
=> 'pve-configid-list',
1305 description
=> "Revert a pending change.",
1310 description
=> $opt_force_description,
1312 requires
=> 'delete',
1316 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1320 background_delay
=> {
1322 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1333 code
=> $update_vm_api,
1336 __PACKAGE__-
>register_method({
1337 name
=> 'update_vm',
1338 path
=> '{vmid}/config',
1342 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1344 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1347 additionalProperties
=> 0,
1348 properties
=> PVE
::QemuServer
::json_config_properties
(
1350 node
=> get_standard_option
('pve-node'),
1351 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1352 skiplock
=> get_standard_option
('skiplock'),
1354 type
=> 'string', format
=> 'pve-configid-list',
1355 description
=> "A list of settings you want to delete.",
1359 type
=> 'string', format
=> 'pve-configid-list',
1360 description
=> "Revert a pending change.",
1365 description
=> $opt_force_description,
1367 requires
=> 'delete',
1371 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1377 returns
=> { type
=> 'null' },
1380 &$update_vm_api($param, 1);
1385 __PACKAGE__-
>register_method({
1386 name
=> 'destroy_vm',
1391 description
=> "Destroy the vm (also delete all used/owned volumes).",
1393 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1396 additionalProperties
=> 0,
1398 node
=> get_standard_option
('pve-node'),
1399 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1400 skiplock
=> get_standard_option
('skiplock'),
1403 description
=> "Remove vmid from backup cron jobs.",
1414 my $rpcenv = PVE
::RPCEnvironment
::get
();
1415 my $authuser = $rpcenv->get_user();
1416 my $vmid = $param->{vmid
};
1418 my $skiplock = $param->{skiplock
};
1419 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1420 if $skiplock && $authuser ne 'root@pam';
1423 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1424 my $storecfg = PVE
::Storage
::config
();
1425 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1426 die "unable to remove VM $vmid - used in HA resources\n"
1427 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1429 if (!$param->{purge
}) {
1430 # don't allow destroy if with replication jobs but no purge param
1431 my $repl_conf = PVE
::ReplicationConfig-
>new();
1432 $repl_conf->check_for_existing_jobs($vmid);
1435 # early tests (repeat after locking)
1436 die "VM $vmid is running - destroy failed\n"
1437 if PVE
::QemuServer
::check_running
($vmid);
1442 syslog
('info', "destroy VM $vmid: $upid\n");
1443 PVE
::QemuConfig-
>lock_config($vmid, sub {
1444 die "VM $vmid is running - destroy failed\n"
1445 if (PVE
::QemuServer
::check_running
($vmid));
1447 PVE
::QemuServer
::destroy_vm
($storecfg, $vmid, $skiplock, { lock => 'destroyed' });
1449 PVE
::AccessControl
::remove_vm_access
($vmid);
1450 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1451 if ($param->{purge
}) {
1452 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1453 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1456 # only now remove the zombie config, else we can have reuse race
1457 PVE
::QemuConfig-
>destroy_config($vmid);
1461 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1464 __PACKAGE__-
>register_method({
1466 path
=> '{vmid}/unlink',
1470 description
=> "Unlink/delete disk images.",
1472 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1475 additionalProperties
=> 0,
1477 node
=> get_standard_option
('pve-node'),
1478 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1480 type
=> 'string', format
=> 'pve-configid-list',
1481 description
=> "A list of disk IDs you want to delete.",
1485 description
=> $opt_force_description,
1490 returns
=> { type
=> 'null'},
1494 $param->{delete} = extract_param
($param, 'idlist');
1496 __PACKAGE__-
>update_vm($param);
1503 __PACKAGE__-
>register_method({
1505 path
=> '{vmid}/vncproxy',
1509 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1511 description
=> "Creates a TCP VNC proxy connections.",
1513 additionalProperties
=> 0,
1515 node
=> get_standard_option
('pve-node'),
1516 vmid
=> get_standard_option
('pve-vmid'),
1520 description
=> "starts websockify instead of vncproxy",
1525 additionalProperties
=> 0,
1527 user
=> { type
=> 'string' },
1528 ticket
=> { type
=> 'string' },
1529 cert
=> { type
=> 'string' },
1530 port
=> { type
=> 'integer' },
1531 upid
=> { type
=> 'string' },
1537 my $rpcenv = PVE
::RPCEnvironment
::get
();
1539 my $authuser = $rpcenv->get_user();
1541 my $vmid = $param->{vmid
};
1542 my $node = $param->{node
};
1543 my $websocket = $param->{websocket
};
1545 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1546 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1548 my $authpath = "/vms/$vmid";
1550 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1552 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1558 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1559 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1560 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1561 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1562 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1564 $family = PVE
::Tools
::get_host_address_family
($node);
1567 my $port = PVE
::Tools
::next_vnc_port
($family);
1574 syslog
('info', "starting vnc proxy $upid\n");
1580 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1582 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1583 '-timeout', $timeout, '-authpath', $authpath,
1584 '-perm', 'Sys.Console'];
1586 if ($param->{websocket
}) {
1587 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1588 push @$cmd, '-notls', '-listen', 'localhost';
1591 push @$cmd, '-c', @$remcmd, @$termcmd;
1593 PVE
::Tools
::run_command
($cmd);
1597 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1599 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1601 my $sock = IO
::Socket
::IP-
>new(
1606 GetAddrInfoFlags
=> 0,
1607 ) or die "failed to create socket: $!\n";
1608 # Inside the worker we shouldn't have any previous alarms
1609 # running anyway...:
1611 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1613 accept(my $cli, $sock) or die "connection failed: $!\n";
1616 if (PVE
::Tools
::run_command
($cmd,
1617 output
=> '>&'.fileno($cli),
1618 input
=> '<&'.fileno($cli),
1621 die "Failed to run vncproxy.\n";
1628 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1630 PVE
::Tools
::wait_for_vnc_port
($port);
1641 __PACKAGE__-
>register_method({
1642 name
=> 'termproxy',
1643 path
=> '{vmid}/termproxy',
1647 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1649 description
=> "Creates a TCP proxy connections.",
1651 additionalProperties
=> 0,
1653 node
=> get_standard_option
('pve-node'),
1654 vmid
=> get_standard_option
('pve-vmid'),
1658 enum
=> [qw(serial0 serial1 serial2 serial3)],
1659 description
=> "opens a serial terminal (defaults to display)",
1664 additionalProperties
=> 0,
1666 user
=> { type
=> 'string' },
1667 ticket
=> { type
=> 'string' },
1668 port
=> { type
=> 'integer' },
1669 upid
=> { type
=> 'string' },
1675 my $rpcenv = PVE
::RPCEnvironment
::get
();
1677 my $authuser = $rpcenv->get_user();
1679 my $vmid = $param->{vmid
};
1680 my $node = $param->{node
};
1681 my $serial = $param->{serial
};
1683 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1685 if (!defined($serial)) {
1686 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1687 $serial = $conf->{vga
};
1691 my $authpath = "/vms/$vmid";
1693 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1698 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1699 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1700 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1701 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1702 push @$remcmd, '--';
1704 $family = PVE
::Tools
::get_host_address_family
($node);
1707 my $port = PVE
::Tools
::next_vnc_port
($family);
1709 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1710 push @$termcmd, '-iface', $serial if $serial;
1715 syslog
('info', "starting qemu termproxy $upid\n");
1717 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1718 '--perm', 'VM.Console', '--'];
1719 push @$cmd, @$remcmd, @$termcmd;
1721 PVE
::Tools
::run_command
($cmd);
1724 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1726 PVE
::Tools
::wait_for_vnc_port
($port);
1736 __PACKAGE__-
>register_method({
1737 name
=> 'vncwebsocket',
1738 path
=> '{vmid}/vncwebsocket',
1741 description
=> "You also need to pass a valid ticket (vncticket).",
1742 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1744 description
=> "Opens a weksocket for VNC traffic.",
1746 additionalProperties
=> 0,
1748 node
=> get_standard_option
('pve-node'),
1749 vmid
=> get_standard_option
('pve-vmid'),
1751 description
=> "Ticket from previous call to vncproxy.",
1756 description
=> "Port number returned by previous vncproxy call.",
1766 port
=> { type
=> 'string' },
1772 my $rpcenv = PVE
::RPCEnvironment
::get
();
1774 my $authuser = $rpcenv->get_user();
1776 my $vmid = $param->{vmid
};
1777 my $node = $param->{node
};
1779 my $authpath = "/vms/$vmid";
1781 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1783 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1785 # Note: VNC ports are acessible from outside, so we do not gain any
1786 # security if we verify that $param->{port} belongs to VM $vmid. This
1787 # check is done by verifying the VNC ticket (inside VNC protocol).
1789 my $port = $param->{port
};
1791 return { port
=> $port };
1794 __PACKAGE__-
>register_method({
1795 name
=> 'spiceproxy',
1796 path
=> '{vmid}/spiceproxy',
1801 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1803 description
=> "Returns a SPICE configuration to connect to the VM.",
1805 additionalProperties
=> 0,
1807 node
=> get_standard_option
('pve-node'),
1808 vmid
=> get_standard_option
('pve-vmid'),
1809 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1812 returns
=> get_standard_option
('remote-viewer-config'),
1816 my $rpcenv = PVE
::RPCEnvironment
::get
();
1818 my $authuser = $rpcenv->get_user();
1820 my $vmid = $param->{vmid
};
1821 my $node = $param->{node
};
1822 my $proxy = $param->{proxy
};
1824 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1825 my $title = "VM $vmid";
1826 $title .= " - ". $conf->{name
} if $conf->{name
};
1828 my $port = PVE
::QemuServer
::spice_port
($vmid);
1830 my ($ticket, undef, $remote_viewer_config) =
1831 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1833 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1834 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1836 return $remote_viewer_config;
1839 __PACKAGE__-
>register_method({
1841 path
=> '{vmid}/status',
1844 description
=> "Directory index",
1849 additionalProperties
=> 0,
1851 node
=> get_standard_option
('pve-node'),
1852 vmid
=> get_standard_option
('pve-vmid'),
1860 subdir
=> { type
=> 'string' },
1863 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1869 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1872 { subdir
=> 'current' },
1873 { subdir
=> 'start' },
1874 { subdir
=> 'stop' },
1875 { subdir
=> 'reset' },
1876 { subdir
=> 'shutdown' },
1877 { subdir
=> 'suspend' },
1878 { subdir
=> 'reboot' },
1884 __PACKAGE__-
>register_method({
1885 name
=> 'vm_status',
1886 path
=> '{vmid}/status/current',
1889 protected
=> 1, # qemu pid files are only readable by root
1890 description
=> "Get virtual machine status.",
1892 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1895 additionalProperties
=> 0,
1897 node
=> get_standard_option
('pve-node'),
1898 vmid
=> get_standard_option
('pve-vmid'),
1904 %$PVE::QemuServer
::vmstatus_return_properties
,
1906 description
=> "HA manager service status.",
1910 description
=> "Qemu VGA configuration supports spice.",
1915 description
=> "Qemu GuestAgent enabled in config.",
1925 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1927 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1928 my $status = $vmstatus->{$param->{vmid
}};
1930 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1932 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1933 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1938 __PACKAGE__-
>register_method({
1940 path
=> '{vmid}/status/start',
1944 description
=> "Start virtual machine.",
1946 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1949 additionalProperties
=> 0,
1951 node
=> get_standard_option
('pve-node'),
1952 vmid
=> get_standard_option
('pve-vmid',
1953 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1954 skiplock
=> get_standard_option
('skiplock'),
1955 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1956 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1959 enum
=> ['secure', 'insecure'],
1960 description
=> "Migration traffic is encrypted using an SSH " .
1961 "tunnel by default. On secure, completely private networks " .
1962 "this can be disabled to increase performance.",
1965 migration_network
=> {
1966 type
=> 'string', format
=> 'CIDR',
1967 description
=> "CIDR of the (sub) network that is used for migration.",
1970 machine
=> get_standard_option
('pve-qm-machine'),
1972 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1984 my $rpcenv = PVE
::RPCEnvironment
::get
();
1985 my $authuser = $rpcenv->get_user();
1987 my $node = extract_param
($param, 'node');
1988 my $vmid = extract_param
($param, 'vmid');
1990 my $machine = extract_param
($param, 'machine');
1992 my $get_root_param = sub {
1993 my $value = extract_param
($param, $_[0]);
1994 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
1995 if $value && $authuser ne 'root@pam';
1999 my $stateuri = $get_root_param->('stateuri');
2000 my $skiplock = $get_root_param->('skiplock');
2001 my $migratedfrom = $get_root_param->('migratedfrom');
2002 my $migration_type = $get_root_param->('migration_type');
2003 my $migration_network = $get_root_param->('migration_network');
2004 my $targetstorage = $get_root_param->('targetstorage');
2006 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2007 if $targetstorage && !$migratedfrom;
2009 # read spice ticket from STDIN
2011 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2012 if (defined(my $line = <STDIN
>)) {
2014 $spice_ticket = $line;
2018 PVE
::Cluster
::check_cfs_quorum
();
2020 my $storecfg = PVE
::Storage
::config
();
2022 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2026 print "Requesting HA start for VM $vmid\n";
2028 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2029 PVE
::Tools
::run_command
($cmd);
2033 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2040 syslog
('info', "start VM $vmid: $upid\n");
2042 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2043 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
2047 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2051 __PACKAGE__-
>register_method({
2053 path
=> '{vmid}/status/stop',
2057 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2058 "is akin to pulling the power plug of a running computer and may damage the VM data",
2060 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2063 additionalProperties
=> 0,
2065 node
=> get_standard_option
('pve-node'),
2066 vmid
=> get_standard_option
('pve-vmid',
2067 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2068 skiplock
=> get_standard_option
('skiplock'),
2069 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2071 description
=> "Wait maximal timeout seconds.",
2077 description
=> "Do not deactivate storage volumes.",
2090 my $rpcenv = PVE
::RPCEnvironment
::get
();
2091 my $authuser = $rpcenv->get_user();
2093 my $node = extract_param
($param, 'node');
2094 my $vmid = extract_param
($param, 'vmid');
2096 my $skiplock = extract_param
($param, 'skiplock');
2097 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2098 if $skiplock && $authuser ne 'root@pam';
2100 my $keepActive = extract_param
($param, 'keepActive');
2101 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2102 if $keepActive && $authuser ne 'root@pam';
2104 my $migratedfrom = extract_param
($param, 'migratedfrom');
2105 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2106 if $migratedfrom && $authuser ne 'root@pam';
2109 my $storecfg = PVE
::Storage
::config
();
2111 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2116 print "Requesting HA stop for VM $vmid\n";
2118 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2119 PVE
::Tools
::run_command
($cmd);
2123 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2129 syslog
('info', "stop VM $vmid: $upid\n");
2131 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2132 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2136 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2140 __PACKAGE__-
>register_method({
2142 path
=> '{vmid}/status/reset',
2146 description
=> "Reset virtual machine.",
2148 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2151 additionalProperties
=> 0,
2153 node
=> get_standard_option
('pve-node'),
2154 vmid
=> get_standard_option
('pve-vmid',
2155 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2156 skiplock
=> get_standard_option
('skiplock'),
2165 my $rpcenv = PVE
::RPCEnvironment
::get
();
2167 my $authuser = $rpcenv->get_user();
2169 my $node = extract_param
($param, 'node');
2171 my $vmid = extract_param
($param, 'vmid');
2173 my $skiplock = extract_param
($param, 'skiplock');
2174 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2175 if $skiplock && $authuser ne 'root@pam';
2177 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2182 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2187 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2190 __PACKAGE__-
>register_method({
2191 name
=> 'vm_shutdown',
2192 path
=> '{vmid}/status/shutdown',
2196 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2197 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2199 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2202 additionalProperties
=> 0,
2204 node
=> get_standard_option
('pve-node'),
2205 vmid
=> get_standard_option
('pve-vmid',
2206 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2207 skiplock
=> get_standard_option
('skiplock'),
2209 description
=> "Wait maximal timeout seconds.",
2215 description
=> "Make sure the VM stops.",
2221 description
=> "Do not deactivate storage volumes.",
2234 my $rpcenv = PVE
::RPCEnvironment
::get
();
2235 my $authuser = $rpcenv->get_user();
2237 my $node = extract_param
($param, 'node');
2238 my $vmid = extract_param
($param, 'vmid');
2240 my $skiplock = extract_param
($param, 'skiplock');
2241 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2242 if $skiplock && $authuser ne 'root@pam';
2244 my $keepActive = extract_param
($param, 'keepActive');
2245 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2246 if $keepActive && $authuser ne 'root@pam';
2248 my $storecfg = PVE
::Storage
::config
();
2252 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2253 # otherwise, we will infer a shutdown command, but run into the timeout,
2254 # then when the vm is resumed, it will instantly shutdown
2256 # checking the qmp status here to get feedback to the gui/cli/api
2257 # and the status query should not take too long
2258 my $qmpstatus = eval {
2259 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2260 mon_cmd
($vmid, "query-status");
2264 if (!$err && $qmpstatus->{status
} eq "paused") {
2265 if ($param->{forceStop
}) {
2266 warn "VM is paused - stop instead of shutdown\n";
2269 die "VM is paused - cannot shutdown\n";
2273 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2275 my $timeout = $param->{timeout
} // 60;
2279 print "Requesting HA stop for VM $vmid\n";
2281 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2282 PVE
::Tools
::run_command
($cmd);
2286 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2293 syslog
('info', "shutdown VM $vmid: $upid\n");
2295 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2296 $shutdown, $param->{forceStop
}, $keepActive);
2300 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2304 __PACKAGE__-
>register_method({
2305 name
=> 'vm_reboot',
2306 path
=> '{vmid}/status/reboot',
2310 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2312 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2315 additionalProperties
=> 0,
2317 node
=> get_standard_option
('pve-node'),
2318 vmid
=> get_standard_option
('pve-vmid',
2319 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2321 description
=> "Wait maximal timeout seconds for the shutdown.",
2334 my $rpcenv = PVE
::RPCEnvironment
::get
();
2335 my $authuser = $rpcenv->get_user();
2337 my $node = extract_param
($param, 'node');
2338 my $vmid = extract_param
($param, 'vmid');
2340 my $qmpstatus = eval {
2341 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2342 mon_cmd
($vmid, "query-status");
2346 if (!$err && $qmpstatus->{status
} eq "paused") {
2347 die "VM is paused - cannot shutdown\n";
2350 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2355 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2356 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2360 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2363 __PACKAGE__-
>register_method({
2364 name
=> 'vm_suspend',
2365 path
=> '{vmid}/status/suspend',
2369 description
=> "Suspend virtual machine.",
2371 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2374 additionalProperties
=> 0,
2376 node
=> get_standard_option
('pve-node'),
2377 vmid
=> get_standard_option
('pve-vmid',
2378 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2379 skiplock
=> get_standard_option
('skiplock'),
2384 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2386 statestorage
=> get_standard_option
('pve-storage-id', {
2387 description
=> "The storage for the VM state",
2388 requires
=> 'todisk',
2390 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2400 my $rpcenv = PVE
::RPCEnvironment
::get
();
2401 my $authuser = $rpcenv->get_user();
2403 my $node = extract_param
($param, 'node');
2404 my $vmid = extract_param
($param, 'vmid');
2406 my $todisk = extract_param
($param, 'todisk') // 0;
2408 my $statestorage = extract_param
($param, 'statestorage');
2410 my $skiplock = extract_param
($param, 'skiplock');
2411 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2412 if $skiplock && $authuser ne 'root@pam';
2414 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2416 die "Cannot suspend HA managed VM to disk\n"
2417 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2422 syslog
('info', "suspend VM $vmid: $upid\n");
2424 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2429 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2430 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2433 __PACKAGE__-
>register_method({
2434 name
=> 'vm_resume',
2435 path
=> '{vmid}/status/resume',
2439 description
=> "Resume virtual machine.",
2441 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2444 additionalProperties
=> 0,
2446 node
=> get_standard_option
('pve-node'),
2447 vmid
=> get_standard_option
('pve-vmid',
2448 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2449 skiplock
=> get_standard_option
('skiplock'),
2450 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2460 my $rpcenv = PVE
::RPCEnvironment
::get
();
2462 my $authuser = $rpcenv->get_user();
2464 my $node = extract_param
($param, 'node');
2466 my $vmid = extract_param
($param, 'vmid');
2468 my $skiplock = extract_param
($param, 'skiplock');
2469 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2470 if $skiplock && $authuser ne 'root@pam';
2472 my $nocheck = extract_param
($param, 'nocheck');
2474 my $to_disk_suspended;
2476 PVE
::QemuConfig-
>lock_config($vmid, sub {
2477 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2478 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2482 die "VM $vmid not running\n"
2483 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2488 syslog
('info', "resume VM $vmid: $upid\n");
2490 if (!$to_disk_suspended) {
2491 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2493 my $storecfg = PVE
::Storage
::config
();
2494 PVE
::QemuServer
::vm_start
($storecfg, $vmid, undef, $skiplock);
2500 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2503 __PACKAGE__-
>register_method({
2504 name
=> 'vm_sendkey',
2505 path
=> '{vmid}/sendkey',
2509 description
=> "Send key event to virtual machine.",
2511 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2514 additionalProperties
=> 0,
2516 node
=> get_standard_option
('pve-node'),
2517 vmid
=> get_standard_option
('pve-vmid',
2518 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2519 skiplock
=> get_standard_option
('skiplock'),
2521 description
=> "The key (qemu monitor encoding).",
2526 returns
=> { type
=> 'null'},
2530 my $rpcenv = PVE
::RPCEnvironment
::get
();
2532 my $authuser = $rpcenv->get_user();
2534 my $node = extract_param
($param, 'node');
2536 my $vmid = extract_param
($param, 'vmid');
2538 my $skiplock = extract_param
($param, 'skiplock');
2539 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2540 if $skiplock && $authuser ne 'root@pam';
2542 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2547 __PACKAGE__-
>register_method({
2548 name
=> 'vm_feature',
2549 path
=> '{vmid}/feature',
2553 description
=> "Check if feature for virtual machine is available.",
2555 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2558 additionalProperties
=> 0,
2560 node
=> get_standard_option
('pve-node'),
2561 vmid
=> get_standard_option
('pve-vmid'),
2563 description
=> "Feature to check.",
2565 enum
=> [ 'snapshot', 'clone', 'copy' ],
2567 snapname
=> get_standard_option
('pve-snapshot-name', {
2575 hasFeature
=> { type
=> 'boolean' },
2578 items
=> { type
=> 'string' },
2585 my $node = extract_param
($param, 'node');
2587 my $vmid = extract_param
($param, 'vmid');
2589 my $snapname = extract_param
($param, 'snapname');
2591 my $feature = extract_param
($param, 'feature');
2593 my $running = PVE
::QemuServer
::check_running
($vmid);
2595 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2598 my $snap = $conf->{snapshots
}->{$snapname};
2599 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2602 my $storecfg = PVE
::Storage
::config
();
2604 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2605 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2608 hasFeature
=> $hasFeature,
2609 nodes
=> [ keys %$nodelist ],
2613 __PACKAGE__-
>register_method({
2615 path
=> '{vmid}/clone',
2619 description
=> "Create a copy of virtual machine/template.",
2621 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2622 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2623 "'Datastore.AllocateSpace' on any used storage.",
2626 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2628 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2629 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2634 additionalProperties
=> 0,
2636 node
=> get_standard_option
('pve-node'),
2637 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2638 newid
=> get_standard_option
('pve-vmid', {
2639 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2640 description
=> 'VMID for the clone.' }),
2643 type
=> 'string', format
=> 'dns-name',
2644 description
=> "Set a name for the new VM.",
2649 description
=> "Description for the new VM.",
2653 type
=> 'string', format
=> 'pve-poolid',
2654 description
=> "Add the new VM to the specified pool.",
2656 snapname
=> get_standard_option
('pve-snapshot-name', {
2659 storage
=> get_standard_option
('pve-storage-id', {
2660 description
=> "Target storage for full clone.",
2664 description
=> "Target format for file storage. Only valid for full clone.",
2667 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2672 description
=> "Create a full copy of all disks. This is always done when " .
2673 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2675 target
=> get_standard_option
('pve-node', {
2676 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2680 description
=> "Override I/O bandwidth limit (in KiB/s).",
2684 default => 'clone limit from datacenter or storage config',
2694 my $rpcenv = PVE
::RPCEnvironment
::get
();
2696 my $authuser = $rpcenv->get_user();
2698 my $node = extract_param
($param, 'node');
2700 my $vmid = extract_param
($param, 'vmid');
2702 my $newid = extract_param
($param, 'newid');
2704 my $pool = extract_param
($param, 'pool');
2706 if (defined($pool)) {
2707 $rpcenv->check_pool_exist($pool);
2710 my $snapname = extract_param
($param, 'snapname');
2712 my $storage = extract_param
($param, 'storage');
2714 my $format = extract_param
($param, 'format');
2716 my $target = extract_param
($param, 'target');
2718 my $localnode = PVE
::INotify
::nodename
();
2720 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2722 PVE
::Cluster
::check_node_exists
($target) if $target;
2724 my $storecfg = PVE
::Storage
::config
();
2727 # check if storage is enabled on local node
2728 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2730 # check if storage is available on target node
2731 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2732 # clone only works if target storage is shared
2733 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2734 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2738 PVE
::Cluster
::check_cfs_quorum
();
2740 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2742 # exclusive lock if VM is running - else shared lock is enough;
2743 my $shared_lock = $running ?
0 : 1;
2747 # do all tests after lock
2748 # we also try to do all tests before we fork the worker
2750 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2752 PVE
::QemuConfig-
>check_lock($conf);
2754 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2756 die "unexpected state change\n" if $verify_running != $running;
2758 die "snapshot '$snapname' does not exist\n"
2759 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2761 my $full = extract_param
($param, 'full');
2762 if (!defined($full)) {
2763 $full = !PVE
::QemuConfig-
>is_template($conf);
2766 die "parameter 'storage' not allowed for linked clones\n"
2767 if defined($storage) && !$full;
2769 die "parameter 'format' not allowed for linked clones\n"
2770 if defined($format) && !$full;
2772 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2774 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2776 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2778 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2780 die "unable to create VM $newid: config file already exists\n"
2783 my $newconf = { lock => 'clone' };
2788 foreach my $opt (keys %$oldconf) {
2789 my $value = $oldconf->{$opt};
2791 # do not copy snapshot related info
2792 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2793 $opt eq 'vmstate' || $opt eq 'snapstate';
2795 # no need to copy unused images, because VMID(owner) changes anyways
2796 next if $opt =~ m/^unused\d+$/;
2798 # always change MAC! address
2799 if ($opt =~ m/^net(\d+)$/) {
2800 my $net = PVE
::QemuServer
::parse_net
($value);
2801 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2802 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2803 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2804 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2805 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2806 die "unable to parse drive options for '$opt'\n" if !$drive;
2807 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2808 $newconf->{$opt} = $value; # simply copy configuration
2810 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2811 die "Full clone feature is not supported for drive '$opt'\n"
2812 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2813 $fullclone->{$opt} = 1;
2815 # not full means clone instead of copy
2816 die "Linked clone feature is not supported for drive '$opt'\n"
2817 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2819 $drives->{$opt} = $drive;
2820 push @$vollist, $drive->{file
};
2823 # copy everything else
2824 $newconf->{$opt} = $value;
2828 # auto generate a new uuid
2829 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2830 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2831 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2833 # auto generate a new vmgenid if the option was set
2834 if ($newconf->{vmgenid
}) {
2835 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2838 delete $newconf->{template
};
2840 if ($param->{name
}) {
2841 $newconf->{name
} = $param->{name
};
2843 if ($oldconf->{name
}) {
2844 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2846 $newconf->{name
} = "Copy-of-VM-$vmid";
2850 if ($param->{description
}) {
2851 $newconf->{description
} = $param->{description
};
2854 # create empty/temp config - this fails if VM already exists on other node
2855 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2860 my $newvollist = [];
2867 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2869 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2871 my $bwlimit = extract_param
($param, 'bwlimit');
2873 my $total_jobs = scalar(keys %{$drives});
2876 foreach my $opt (keys %$drives) {
2877 my $drive = $drives->{$opt};
2878 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2880 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
2881 my $storage_list = [ $src_sid ];
2882 push @$storage_list, $storage if defined($storage);
2883 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
2885 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2886 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2887 $jobs, $skipcomplete, $oldconf->{agent
}, $clonelimit);
2889 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2891 PVE
::QemuConfig-
>write_config($newid, $newconf);
2895 delete $newconf->{lock};
2897 # do not write pending changes
2898 if (my @changes = keys %{$newconf->{pending
}}) {
2899 my $pending = join(',', @changes);
2900 warn "found pending changes for '$pending', discarding for clone\n";
2901 delete $newconf->{pending
};
2904 PVE
::QemuConfig-
>write_config($newid, $newconf);
2907 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2908 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2909 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2911 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2912 die "Failed to move config to node '$target' - rename failed: $!\n"
2913 if !rename($conffile, $newconffile);
2916 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2921 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2923 sleep 1; # some storage like rbd need to wait before release volume - really?
2925 foreach my $volid (@$newvollist) {
2926 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2929 die "clone failed: $err";
2935 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2937 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2940 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2941 # Aquire exclusive lock lock for $newid
2942 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2947 __PACKAGE__-
>register_method({
2948 name
=> 'move_vm_disk',
2949 path
=> '{vmid}/move_disk',
2953 description
=> "Move volume to different storage.",
2955 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2957 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2958 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2962 additionalProperties
=> 0,
2964 node
=> get_standard_option
('pve-node'),
2965 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2968 description
=> "The disk you want to move.",
2969 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2971 storage
=> get_standard_option
('pve-storage-id', {
2972 description
=> "Target storage.",
2973 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2977 description
=> "Target Format.",
2978 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2983 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2989 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2994 description
=> "Override I/O bandwidth limit (in KiB/s).",
2998 default => 'move limit from datacenter or storage config',
3004 description
=> "the task ID.",
3009 my $rpcenv = PVE
::RPCEnvironment
::get
();
3011 my $authuser = $rpcenv->get_user();
3013 my $node = extract_param
($param, 'node');
3015 my $vmid = extract_param
($param, 'vmid');
3017 my $digest = extract_param
($param, 'digest');
3019 my $disk = extract_param
($param, 'disk');
3021 my $storeid = extract_param
($param, 'storage');
3023 my $format = extract_param
($param, 'format');
3025 my $storecfg = PVE
::Storage
::config
();
3027 my $updatefn = sub {
3029 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3031 PVE
::QemuConfig-
>check_lock($conf);
3033 die "checksum missmatch (file change by other user?)\n"
3034 if $digest && $digest ne $conf->{digest
};
3036 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3038 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3040 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
3042 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3045 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3046 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3050 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
3051 (!$format || !$oldfmt || $oldfmt eq $format);
3053 # this only checks snapshots because $disk is passed!
3054 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3055 die "you can't move a disk with snapshots and delete the source\n"
3056 if $snapshotted && $param->{delete};
3058 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3060 my $running = PVE
::QemuServer
::check_running
($vmid);
3062 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3066 my $newvollist = [];
3072 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3074 warn "moving disk with snapshots, snapshots will not be moved!\n"
3077 my $bwlimit = extract_param
($param, 'bwlimit');
3078 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3080 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3081 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit);
3083 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
3085 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3087 # convert moved disk to base if part of template
3088 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3089 if PVE
::QemuConfig-
>is_template($conf);
3091 PVE
::QemuConfig-
>write_config($vmid, $conf);
3093 if ($running && PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
} && PVE
::QemuServer
::qga_check_running
($vmid)) {
3094 eval { mon_cmd
($vmid, "guest-fstrim"); };
3098 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3099 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3106 foreach my $volid (@$newvollist) {
3107 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3110 die "storage migration failed: $err";
3113 if ($param->{delete}) {
3115 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3116 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3122 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3125 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3128 my $check_vm_disks_local = sub {
3129 my ($storecfg, $vmconf, $vmid) = @_;
3131 my $local_disks = {};
3133 # add some more information to the disks e.g. cdrom
3134 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3135 my ($volid, $attr) = @_;
3137 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3139 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3140 return if $scfg->{shared
};
3142 # The shared attr here is just a special case where the vdisk
3143 # is marked as shared manually
3144 return if $attr->{shared
};
3145 return if $attr->{cdrom
} and $volid eq "none";
3147 if (exists $local_disks->{$volid}) {
3148 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3150 $local_disks->{$volid} = $attr;
3151 # ensure volid is present in case it's needed
3152 $local_disks->{$volid}->{volid
} = $volid;
3156 return $local_disks;
3159 __PACKAGE__-
>register_method({
3160 name
=> 'migrate_vm_precondition',
3161 path
=> '{vmid}/migrate',
3165 description
=> "Get preconditions for migration.",
3167 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3170 additionalProperties
=> 0,
3172 node
=> get_standard_option
('pve-node'),
3173 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3174 target
=> get_standard_option
('pve-node', {
3175 description
=> "Target node.",
3176 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3184 running
=> { type
=> 'boolean' },
3188 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3190 not_allowed_nodes
=> {
3193 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3197 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3199 local_resources
=> {
3201 description
=> "List local resources e.g. pci, usb"
3208 my $rpcenv = PVE
::RPCEnvironment
::get
();
3210 my $authuser = $rpcenv->get_user();
3212 PVE
::Cluster
::check_cfs_quorum
();
3216 my $vmid = extract_param
($param, 'vmid');
3217 my $target = extract_param
($param, 'target');
3218 my $localnode = PVE
::INotify
::nodename
();
3222 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3223 my $storecfg = PVE
::Storage
::config
();
3226 # try to detect errors early
3227 PVE
::QemuConfig-
>check_lock($vmconf);
3229 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3231 # if vm is not running, return target nodes where local storage is available
3232 # for offline migration
3233 if (!$res->{running
}) {
3234 $res->{allowed_nodes
} = [];
3235 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3236 delete $checked_nodes->{$localnode};
3238 foreach my $node (keys %$checked_nodes) {
3239 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3240 push @{$res->{allowed_nodes
}}, $node;
3244 $res->{not_allowed_nodes
} = $checked_nodes;
3248 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3249 $res->{local_disks
} = [ values %$local_disks ];;
3251 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3253 $res->{local_resources
} = $local_resources;
3260 __PACKAGE__-
>register_method({
3261 name
=> 'migrate_vm',
3262 path
=> '{vmid}/migrate',
3266 description
=> "Migrate virtual machine. Creates a new migration task.",
3268 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3271 additionalProperties
=> 0,
3273 node
=> get_standard_option
('pve-node'),
3274 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3275 target
=> get_standard_option
('pve-node', {
3276 description
=> "Target node.",
3277 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3281 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3286 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3291 enum
=> ['secure', 'insecure'],
3292 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3295 migration_network
=> {
3296 type
=> 'string', format
=> 'CIDR',
3297 description
=> "CIDR of the (sub) network that is used for migration.",
3300 "with-local-disks" => {
3302 description
=> "Enable live storage migration for local disk",
3305 targetstorage
=> get_standard_option
('pve-storage-id', {
3306 description
=> "Default target storage.",
3308 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3311 description
=> "Override I/O bandwidth limit (in KiB/s).",
3315 default => 'migrate limit from datacenter or storage config',
3321 description
=> "the task ID.",
3326 my $rpcenv = PVE
::RPCEnvironment
::get
();
3327 my $authuser = $rpcenv->get_user();
3329 my $target = extract_param
($param, 'target');
3331 my $localnode = PVE
::INotify
::nodename
();
3332 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3334 PVE
::Cluster
::check_cfs_quorum
();
3336 PVE
::Cluster
::check_node_exists
($target);
3338 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3340 my $vmid = extract_param
($param, 'vmid');
3342 raise_param_exc
({ force
=> "Only root may use this option." })
3343 if $param->{force
} && $authuser ne 'root@pam';
3345 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3346 if $param->{migration_type
} && $authuser ne 'root@pam';
3348 # allow root only until better network permissions are available
3349 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3350 if $param->{migration_network
} && $authuser ne 'root@pam';
3353 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3355 # try to detect errors early
3357 PVE
::QemuConfig-
>check_lock($conf);
3359 if (PVE
::QemuServer
::check_running
($vmid)) {
3360 die "can't migrate running VM without --online\n" if !$param->{online
};
3362 warn "VM isn't running. Doing offline migration instead\n." if $param->{online
};
3363 $param->{online
} = 0;
3366 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3367 if !$param->{online
} && $param->{targetstorage
};
3369 my $storecfg = PVE
::Storage
::config
();
3371 if( $param->{targetstorage
}) {
3372 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3374 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3377 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3382 print "Requesting HA migration for VM $vmid to node $target\n";
3384 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3385 PVE
::Tools
::run_command
($cmd);
3389 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3394 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3398 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3401 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3406 __PACKAGE__-
>register_method({
3408 path
=> '{vmid}/monitor',
3412 description
=> "Execute Qemu monitor commands.",
3414 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3415 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3418 additionalProperties
=> 0,
3420 node
=> get_standard_option
('pve-node'),
3421 vmid
=> get_standard_option
('pve-vmid'),
3424 description
=> "The monitor command.",
3428 returns
=> { type
=> 'string'},
3432 my $rpcenv = PVE
::RPCEnvironment
::get
();
3433 my $authuser = $rpcenv->get_user();
3436 my $command = shift;
3437 return $command =~ m/^\s*info(\s+|$)/
3438 || $command =~ m/^\s*help\s*$/;
3441 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3442 if !&$is_ro($param->{command
});
3444 my $vmid = $param->{vmid
};
3446 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3450 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3452 $res = "ERROR: $@" if $@;
3457 __PACKAGE__-
>register_method({
3458 name
=> 'resize_vm',
3459 path
=> '{vmid}/resize',
3463 description
=> "Extend volume size.",
3465 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3468 additionalProperties
=> 0,
3470 node
=> get_standard_option
('pve-node'),
3471 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3472 skiplock
=> get_standard_option
('skiplock'),
3475 description
=> "The disk you want to resize.",
3476 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3480 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3481 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.",
3485 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3491 returns
=> { type
=> 'null'},
3495 my $rpcenv = PVE
::RPCEnvironment
::get
();
3497 my $authuser = $rpcenv->get_user();
3499 my $node = extract_param
($param, 'node');
3501 my $vmid = extract_param
($param, 'vmid');
3503 my $digest = extract_param
($param, 'digest');
3505 my $disk = extract_param
($param, 'disk');
3507 my $sizestr = extract_param
($param, 'size');
3509 my $skiplock = extract_param
($param, 'skiplock');
3510 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3511 if $skiplock && $authuser ne 'root@pam';
3513 my $storecfg = PVE
::Storage
::config
();
3515 my $updatefn = sub {
3517 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3519 die "checksum missmatch (file change by other user?)\n"
3520 if $digest && $digest ne $conf->{digest
};
3521 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3523 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3525 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3527 my (undef, undef, undef, undef, undef, undef, $format) =
3528 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3530 die "can't resize volume: $disk if snapshot exists\n"
3531 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3533 my $volid = $drive->{file
};
3535 die "disk '$disk' has no associated volume\n" if !$volid;
3537 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3539 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3541 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3543 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3544 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3546 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3548 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3549 my ($ext, $newsize, $unit) = ($1, $2, $4);
3552 $newsize = $newsize * 1024;
3553 } elsif ($unit eq 'M') {
3554 $newsize = $newsize * 1024 * 1024;
3555 } elsif ($unit eq 'G') {
3556 $newsize = $newsize * 1024 * 1024 * 1024;
3557 } elsif ($unit eq 'T') {
3558 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3561 $newsize += $size if $ext;
3562 $newsize = int($newsize);
3564 die "shrinking disks is not supported\n" if $newsize < $size;
3566 return if $size == $newsize;
3568 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3570 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3572 $drive->{size
} = $newsize;
3573 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3575 PVE
::QemuConfig-
>write_config($vmid, $conf);
3578 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3582 __PACKAGE__-
>register_method({
3583 name
=> 'snapshot_list',
3584 path
=> '{vmid}/snapshot',
3586 description
=> "List all snapshots.",
3588 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3591 protected
=> 1, # qemu pid files are only readable by root
3593 additionalProperties
=> 0,
3595 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3596 node
=> get_standard_option
('pve-node'),
3605 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3609 description
=> "Snapshot includes RAM.",
3614 description
=> "Snapshot description.",
3618 description
=> "Snapshot creation time",
3620 renderer
=> 'timestamp',
3624 description
=> "Parent snapshot identifier.",
3630 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3635 my $vmid = $param->{vmid
};
3637 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3638 my $snaphash = $conf->{snapshots
} || {};
3642 foreach my $name (keys %$snaphash) {
3643 my $d = $snaphash->{$name};
3646 snaptime
=> $d->{snaptime
} || 0,
3647 vmstate
=> $d->{vmstate
} ?
1 : 0,
3648 description
=> $d->{description
} || '',
3650 $item->{parent
} = $d->{parent
} if $d->{parent
};
3651 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3655 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3658 digest
=> $conf->{digest
},
3659 running
=> $running,
3660 description
=> "You are here!",
3662 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3664 push @$res, $current;
3669 __PACKAGE__-
>register_method({
3671 path
=> '{vmid}/snapshot',
3675 description
=> "Snapshot a VM.",
3677 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3680 additionalProperties
=> 0,
3682 node
=> get_standard_option
('pve-node'),
3683 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3684 snapname
=> get_standard_option
('pve-snapshot-name'),
3688 description
=> "Save the vmstate",
3693 description
=> "A textual description or comment.",
3699 description
=> "the task ID.",
3704 my $rpcenv = PVE
::RPCEnvironment
::get
();
3706 my $authuser = $rpcenv->get_user();
3708 my $node = extract_param
($param, 'node');
3710 my $vmid = extract_param
($param, 'vmid');
3712 my $snapname = extract_param
($param, 'snapname');
3714 die "unable to use snapshot name 'current' (reserved name)\n"
3715 if $snapname eq 'current';
3718 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3719 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3720 $param->{description
});
3723 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3726 __PACKAGE__-
>register_method({
3727 name
=> 'snapshot_cmd_idx',
3728 path
=> '{vmid}/snapshot/{snapname}',
3735 additionalProperties
=> 0,
3737 vmid
=> get_standard_option
('pve-vmid'),
3738 node
=> get_standard_option
('pve-node'),
3739 snapname
=> get_standard_option
('pve-snapshot-name'),
3748 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3755 push @$res, { cmd
=> 'rollback' };
3756 push @$res, { cmd
=> 'config' };
3761 __PACKAGE__-
>register_method({
3762 name
=> 'update_snapshot_config',
3763 path
=> '{vmid}/snapshot/{snapname}/config',
3767 description
=> "Update snapshot metadata.",
3769 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3772 additionalProperties
=> 0,
3774 node
=> get_standard_option
('pve-node'),
3775 vmid
=> get_standard_option
('pve-vmid'),
3776 snapname
=> get_standard_option
('pve-snapshot-name'),
3780 description
=> "A textual description or comment.",
3784 returns
=> { type
=> 'null' },
3788 my $rpcenv = PVE
::RPCEnvironment
::get
();
3790 my $authuser = $rpcenv->get_user();
3792 my $vmid = extract_param
($param, 'vmid');
3794 my $snapname = extract_param
($param, 'snapname');
3796 return undef if !defined($param->{description
});
3798 my $updatefn = sub {
3800 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3802 PVE
::QemuConfig-
>check_lock($conf);
3804 my $snap = $conf->{snapshots
}->{$snapname};
3806 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3808 $snap->{description
} = $param->{description
} if defined($param->{description
});
3810 PVE
::QemuConfig-
>write_config($vmid, $conf);
3813 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3818 __PACKAGE__-
>register_method({
3819 name
=> 'get_snapshot_config',
3820 path
=> '{vmid}/snapshot/{snapname}/config',
3823 description
=> "Get snapshot configuration",
3825 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3828 additionalProperties
=> 0,
3830 node
=> get_standard_option
('pve-node'),
3831 vmid
=> get_standard_option
('pve-vmid'),
3832 snapname
=> get_standard_option
('pve-snapshot-name'),
3835 returns
=> { type
=> "object" },
3839 my $rpcenv = PVE
::RPCEnvironment
::get
();
3841 my $authuser = $rpcenv->get_user();
3843 my $vmid = extract_param
($param, 'vmid');
3845 my $snapname = extract_param
($param, 'snapname');
3847 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3849 my $snap = $conf->{snapshots
}->{$snapname};
3851 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3856 __PACKAGE__-
>register_method({
3858 path
=> '{vmid}/snapshot/{snapname}/rollback',
3862 description
=> "Rollback VM state to specified snapshot.",
3864 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3867 additionalProperties
=> 0,
3869 node
=> get_standard_option
('pve-node'),
3870 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3871 snapname
=> get_standard_option
('pve-snapshot-name'),
3876 description
=> "the task ID.",
3881 my $rpcenv = PVE
::RPCEnvironment
::get
();
3883 my $authuser = $rpcenv->get_user();
3885 my $node = extract_param
($param, 'node');
3887 my $vmid = extract_param
($param, 'vmid');
3889 my $snapname = extract_param
($param, 'snapname');
3892 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3893 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3897 # hold migration lock, this makes sure that nobody create replication snapshots
3898 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3901 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3904 __PACKAGE__-
>register_method({
3905 name
=> 'delsnapshot',
3906 path
=> '{vmid}/snapshot/{snapname}',
3910 description
=> "Delete a VM snapshot.",
3912 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3915 additionalProperties
=> 0,
3917 node
=> get_standard_option
('pve-node'),
3918 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3919 snapname
=> get_standard_option
('pve-snapshot-name'),
3923 description
=> "For removal from config file, even if removing disk snapshots fails.",
3929 description
=> "the task ID.",
3934 my $rpcenv = PVE
::RPCEnvironment
::get
();
3936 my $authuser = $rpcenv->get_user();
3938 my $node = extract_param
($param, 'node');
3940 my $vmid = extract_param
($param, 'vmid');
3942 my $snapname = extract_param
($param, 'snapname');
3945 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3946 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3949 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3952 __PACKAGE__-
>register_method({
3954 path
=> '{vmid}/template',
3958 description
=> "Create a Template.",
3960 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3961 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3964 additionalProperties
=> 0,
3966 node
=> get_standard_option
('pve-node'),
3967 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3971 description
=> "If you want to convert only 1 disk to base image.",
3972 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3977 returns
=> { type
=> 'null'},
3981 my $rpcenv = PVE
::RPCEnvironment
::get
();
3983 my $authuser = $rpcenv->get_user();
3985 my $node = extract_param
($param, 'node');
3987 my $vmid = extract_param
($param, 'vmid');
3989 my $disk = extract_param
($param, 'disk');
3991 my $updatefn = sub {
3993 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3995 PVE
::QemuConfig-
>check_lock($conf);
3997 die "unable to create template, because VM contains snapshots\n"
3998 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4000 die "you can't convert a template to a template\n"
4001 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4003 die "you can't convert a VM to template if VM is running\n"
4004 if PVE
::QemuServer
::check_running
($vmid);
4007 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4010 $conf->{template
} = 1;
4011 PVE
::QemuConfig-
>write_config($vmid, $conf);
4013 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4016 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4020 __PACKAGE__-
>register_method({
4021 name
=> 'cloudinit_generated_config_dump',
4022 path
=> '{vmid}/cloudinit/dump',
4025 description
=> "Get automatically generated cloudinit config.",
4027 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4030 additionalProperties
=> 0,
4032 node
=> get_standard_option
('pve-node'),
4033 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4035 description
=> 'Config type.',
4037 enum
=> ['user', 'network', 'meta'],
4047 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4049 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});