1 package PVE
::API2
::Qemu
;
12 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
14 use PVE
::Tools
qw(extract_param);
15 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
17 use PVE
::JSONSchema
qw(get_standard_option);
19 use PVE
::ReplicationConfig
;
20 use PVE
::GuestHelpers
;
24 use PVE
::RPCEnvironment
;
25 use PVE
::AccessControl
;
29 use PVE
::API2
::Firewall
::VM
;
30 use PVE
::API2
::Qemu
::Agent
;
33 if (!$ENV{PVE_GENERATING_DOCS
}) {
34 require PVE
::HA
::Env
::PVE2
;
35 import PVE
::HA
::Env
::PVE2
;
36 require PVE
::HA
::Config
;
37 import PVE
::HA
::Config
;
41 use Data
::Dumper
; # fixme: remove
43 use base
qw(PVE::RESTHandler);
45 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.";
47 my $resolve_cdrom_alias = sub {
50 if (my $value = $param->{cdrom
}) {
51 $value .= ",media=cdrom" if $value !~ m/media=/;
52 $param->{ide2
} = $value;
53 delete $param->{cdrom
};
57 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
58 my $check_storage_access = sub {
59 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
61 PVE
::QemuServer
::foreach_drive
($settings, sub {
62 my ($ds, $drive) = @_;
64 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
66 my $volid = $drive->{file
};
68 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit')) {
70 } elsif ($volid =~ m/^(([^:\s]+):)?(cloudinit)$/) {
72 } elsif ($isCDROM && ($volid eq 'cdrom')) {
73 $rpcenv->check($authuser, "/", ['Sys.Console']);
74 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
75 my ($storeid, $size) = ($2 || $default_storage, $3);
76 die "no storage ID specified (and no default storage)\n" if !$storeid;
77 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
78 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
79 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
80 if !$scfg->{content
}->{images
};
82 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
86 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
87 if defined($settings->{vmstatestorage
});
90 my $check_storage_access_clone = sub {
91 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
95 PVE
::QemuServer
::foreach_drive
($conf, sub {
96 my ($ds, $drive) = @_;
98 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
100 my $volid = $drive->{file
};
102 return if !$volid || $volid eq 'none';
105 if ($volid eq 'cdrom') {
106 $rpcenv->check($authuser, "/", ['Sys.Console']);
108 # we simply allow access
109 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
110 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
111 $sharedvm = 0 if !$scfg->{shared
};
115 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
116 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
117 $sharedvm = 0 if !$scfg->{shared
};
119 $sid = $storage if $storage;
120 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
124 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
125 if defined($conf->{vmstatestorage
});
130 # Note: $pool is only needed when creating a VM, because pool permissions
131 # are automatically inherited if VM already exists inside a pool.
132 my $create_disks = sub {
133 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
140 my ($ds, $disk) = @_;
142 my $volid = $disk->{file
};
144 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
145 delete $disk->{size
};
146 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
147 } elsif ($volid =~ m!^(?:([^/:\s]+):)?cloudinit$!) {
148 my $storeid = $1 || $default_storage;
149 die "no storage ID specified (and no default storage)\n" if !$storeid;
150 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
151 my $name = "vm-$vmid-cloudinit";
155 $fmt = $disk->{format
} // "qcow2";
158 $fmt = $disk->{format
} // "raw";
161 # Initial disk created with 4 MB and aligned to 4MB on regeneration
162 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
163 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
164 $disk->{file
} = $volid;
165 $disk->{media
} = 'cdrom';
166 push @$vollist, $volid;
167 delete $disk->{format
}; # no longer needed
168 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
169 } elsif ($volid =~ $NEW_DISK_RE) {
170 my ($storeid, $size) = ($2 || $default_storage, $3);
171 die "no storage ID specified (and no default storage)\n" if !$storeid;
172 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
173 my $fmt = $disk->{format
} || $defformat;
175 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
178 if ($ds eq 'efidisk0') {
179 ($volid, $size) = PVE
::QemuServer
::create_efidisk
($storecfg, $storeid, $vmid, $fmt, $arch);
181 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
183 push @$vollist, $volid;
184 $disk->{file
} = $volid;
185 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
186 delete $disk->{format
}; # no longer needed
187 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
190 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
192 my $volid_is_new = 1;
195 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
196 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
201 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
203 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
205 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
207 die "volume $volid does not exists\n" if !$size;
209 $disk->{size
} = $size;
212 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
216 eval { PVE
::QemuServer
::foreach_drive
($settings, $code); };
218 # free allocated images on error
220 syslog
('err', "VM $vmid creating disks failed");
221 foreach my $volid (@$vollist) {
222 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
228 # modify vm config if everything went well
229 foreach my $ds (keys %$res) {
230 $conf->{$ds} = $res->{$ds};
247 my $memoryoptions = {
253 my $hwtypeoptions = {
266 my $generaloptions = {
273 'migrate_downtime' => 1,
274 'migrate_speed' => 1,
286 my $vmpoweroptions = {
293 'vmstatestorage' => 1,
296 my $cloudinitoptions = {
306 my $check_vm_modify_config_perm = sub {
307 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
309 return 1 if $authuser eq 'root@pam';
311 foreach my $opt (@$key_list) {
312 # some checks (e.g., disk, serial port, usb) need to be done somewhere
313 # else, as there the permission can be value dependend
314 next if PVE
::QemuServer
::is_valid_drivename
($opt);
315 next if $opt eq 'cdrom';
316 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
319 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
320 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
321 } elsif ($memoryoptions->{$opt}) {
322 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
323 } elsif ($hwtypeoptions->{$opt}) {
324 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
325 } elsif ($generaloptions->{$opt}) {
326 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
327 # special case for startup since it changes host behaviour
328 if ($opt eq 'startup') {
329 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
331 } elsif ($vmpoweroptions->{$opt}) {
332 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
333 } elsif ($diskoptions->{$opt}) {
334 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
335 } elsif ($cloudinitoptions->{$opt} || ($opt =~ m/^(?:net|ipconfig)\d+$/)) {
336 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
338 # catches hostpci\d+, args, lock, etc.
339 # new options will be checked here
340 die "only root can set '$opt' config\n";
347 __PACKAGE__-
>register_method({
351 description
=> "Virtual machine index (per node).",
353 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
357 protected
=> 1, # qemu pid files are only readable by root
359 additionalProperties
=> 0,
361 node
=> get_standard_option
('pve-node'),
365 description
=> "Determine the full status of active VMs.",
373 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
375 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
380 my $rpcenv = PVE
::RPCEnvironment
::get
();
381 my $authuser = $rpcenv->get_user();
383 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
386 foreach my $vmid (keys %$vmstatus) {
387 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
389 my $data = $vmstatus->{$vmid};
398 __PACKAGE__-
>register_method({
402 description
=> "Create or restore a virtual machine.",
404 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
405 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
406 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
407 user
=> 'all', # check inside
412 additionalProperties
=> 0,
413 properties
=> PVE
::QemuServer
::json_config_properties
(
415 node
=> get_standard_option
('pve-node'),
416 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
418 description
=> "The backup file.",
422 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
424 storage
=> get_standard_option
('pve-storage-id', {
425 description
=> "Default storage.",
427 completion
=> \
&PVE
::QemuServer
::complete_storage
,
432 description
=> "Allow to overwrite existing VM.",
433 requires
=> 'archive',
438 description
=> "Assign a unique random ethernet address.",
439 requires
=> 'archive',
443 type
=> 'string', format
=> 'pve-poolid',
444 description
=> "Add the VM to the specified pool.",
447 description
=> "Override I/O bandwidth limit (in KiB/s).",
451 default => 'restore limit from datacenter or storage config',
457 description
=> "Start VM after it was created successfully.",
467 my $rpcenv = PVE
::RPCEnvironment
::get
();
469 my $authuser = $rpcenv->get_user();
471 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 $storage = extract_param
($param, 'storage');
480 my $force = extract_param
($param, 'force');
482 my $unique = extract_param
($param, 'unique');
484 my $pool = extract_param
($param, 'pool');
486 my $bwlimit = extract_param
($param, 'bwlimit');
488 my $start_after_create = extract_param
($param, 'start');
490 my $filename = PVE
::QemuConfig-
>config_file($vmid);
492 my $storecfg = PVE
::Storage
::config
();
494 if (defined(my $ssh_keys = $param->{sshkeys
})) {
495 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
496 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
499 PVE
::Cluster
::check_cfs_quorum
();
501 if (defined($pool)) {
502 $rpcenv->check_pool_exist($pool);
505 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
506 if defined($storage);
508 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
510 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
512 } elsif ($archive && $force && (-f
$filename) &&
513 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
514 # OK: user has VM.Backup permissions, and want to restore an existing VM
520 &$resolve_cdrom_alias($param);
522 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
524 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
526 foreach my $opt (keys %$param) {
527 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
528 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
529 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
531 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
532 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
536 PVE
::QemuServer
::add_random_macs
($param);
538 my $keystr = join(' ', keys %$param);
539 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
541 if ($archive eq '-') {
542 die "pipe requires cli environment\n"
543 if $rpcenv->{type
} ne 'cli';
545 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
546 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
550 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
552 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
553 die "$emsg $@" if $@;
555 my $restorefn = sub {
556 my $conf = PVE
::QemuConfig-
>load_config($vmid);
558 PVE
::QemuConfig-
>check_protection($conf, $emsg);
560 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
563 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
569 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
570 # Convert restored VM to template if backup was VM template
571 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
572 warn "Convert to template.\n";
573 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
577 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
579 if ($start_after_create) {
580 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
585 # ensure no old replication state are exists
586 PVE
::ReplicationState
::delete_guest_states
($vmid);
588 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
592 # ensure no old replication state are exists
593 PVE
::ReplicationState
::delete_guest_states
($vmid);
601 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
605 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
607 if (!$conf->{bootdisk
}) {
608 my $firstdisk = PVE
::QemuServer
::resolve_first_disk
($conf);
609 $conf->{bootdisk
} = $firstdisk if $firstdisk;
612 # auto generate uuid if user did not specify smbios1 option
613 if (!$conf->{smbios1
}) {
614 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
617 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
618 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
621 PVE
::QemuConfig-
>write_config($vmid, $conf);
627 foreach my $volid (@$vollist) {
628 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
634 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
637 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
639 if ($start_after_create) {
640 print "Execute autostart\n";
641 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
646 my ($code, $worker_name);
648 $worker_name = 'qmrestore';
650 eval { $restorefn->() };
652 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
658 $worker_name = 'qmcreate';
660 eval { $createfn->() };
663 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
664 unlink($conffile) or die "failed to remove config file: $!\n";
672 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
675 __PACKAGE__-
>register_method({
680 description
=> "Directory index",
685 additionalProperties
=> 0,
687 node
=> get_standard_option
('pve-node'),
688 vmid
=> get_standard_option
('pve-vmid'),
696 subdir
=> { type
=> 'string' },
699 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
705 { subdir
=> 'config' },
706 { subdir
=> 'pending' },
707 { subdir
=> 'status' },
708 { subdir
=> 'unlink' },
709 { subdir
=> 'vncproxy' },
710 { subdir
=> 'termproxy' },
711 { subdir
=> 'migrate' },
712 { subdir
=> 'resize' },
713 { subdir
=> 'move' },
715 { subdir
=> 'rrddata' },
716 { subdir
=> 'monitor' },
717 { subdir
=> 'agent' },
718 { subdir
=> 'snapshot' },
719 { subdir
=> 'spiceproxy' },
720 { subdir
=> 'sendkey' },
721 { subdir
=> 'firewall' },
727 __PACKAGE__-
>register_method ({
728 subclass
=> "PVE::API2::Firewall::VM",
729 path
=> '{vmid}/firewall',
732 __PACKAGE__-
>register_method ({
733 subclass
=> "PVE::API2::Qemu::Agent",
734 path
=> '{vmid}/agent',
737 __PACKAGE__-
>register_method({
739 path
=> '{vmid}/rrd',
741 protected
=> 1, # fixme: can we avoid that?
743 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
745 description
=> "Read VM RRD statistics (returns PNG)",
747 additionalProperties
=> 0,
749 node
=> get_standard_option
('pve-node'),
750 vmid
=> get_standard_option
('pve-vmid'),
752 description
=> "Specify the time frame you are interested in.",
754 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
757 description
=> "The list of datasources you want to display.",
758 type
=> 'string', format
=> 'pve-configid-list',
761 description
=> "The RRD consolidation function",
763 enum
=> [ 'AVERAGE', 'MAX' ],
771 filename
=> { type
=> 'string' },
777 return PVE
::Cluster
::create_rrd_graph
(
778 "pve2-vm/$param->{vmid}", $param->{timeframe
},
779 $param->{ds
}, $param->{cf
});
783 __PACKAGE__-
>register_method({
785 path
=> '{vmid}/rrddata',
787 protected
=> 1, # fixme: can we avoid that?
789 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
791 description
=> "Read VM RRD statistics",
793 additionalProperties
=> 0,
795 node
=> get_standard_option
('pve-node'),
796 vmid
=> get_standard_option
('pve-vmid'),
798 description
=> "Specify the time frame you are interested in.",
800 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
803 description
=> "The RRD consolidation function",
805 enum
=> [ 'AVERAGE', 'MAX' ],
820 return PVE
::Cluster
::create_rrd_data
(
821 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
825 __PACKAGE__-
>register_method({
827 path
=> '{vmid}/config',
830 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
832 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
835 additionalProperties
=> 0,
837 node
=> get_standard_option
('pve-node'),
838 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
840 description
=> "Get current values (instead of pending values).",
845 snapshot
=> get_standard_option
('pve-snapshot-name', {
846 description
=> "Fetch config values from given snapshot.",
849 my ($cmd, $pname, $cur, $args) = @_;
850 PVE
::QemuConfig-
>snapshot_list($args->[0]);
856 description
=> "The current VM configuration.",
858 properties
=> PVE
::QemuServer
::json_config_properties
({
861 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
868 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
869 current
=> "cannot use 'snapshot' parameter with 'current'"})
870 if ($param->{snapshot
} && $param->{current
});
873 if ($param->{snapshot
}) {
874 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
876 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
878 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
883 __PACKAGE__-
>register_method({
884 name
=> 'vm_pending',
885 path
=> '{vmid}/pending',
888 description
=> "Get virtual machine configuration, including pending changes.",
890 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
893 additionalProperties
=> 0,
895 node
=> get_standard_option
('pve-node'),
896 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
905 description
=> "Configuration option name.",
909 description
=> "Current value.",
914 description
=> "Pending value.",
919 description
=> "Indicates a pending delete request if present and not 0. " .
920 "The value 2 indicates a force-delete request.",
932 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
934 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
938 foreach my $opt (keys %$conf) {
939 next if ref($conf->{$opt});
940 my $item = { key
=> $opt };
941 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
942 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
943 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
945 # hide cloudinit password
946 if ($opt eq 'cipassword') {
947 $item->{value
} = '**********' if defined($item->{value
});
948 # the trailing space so that the pending string is different
949 $item->{pending
} = '********** ' if defined($item->{pending
});
954 foreach my $opt (keys %{$conf->{pending
}}) {
955 next if $opt eq 'delete';
956 next if ref($conf->{pending
}->{$opt}); # just to be sure
957 next if defined($conf->{$opt});
958 my $item = { key
=> $opt };
959 $item->{pending
} = $conf->{pending
}->{$opt};
961 # hide cloudinit password
962 if ($opt eq 'cipassword') {
963 $item->{pending
} = '**********' if defined($item->{pending
});
968 while (my ($opt, $force) = each %$pending_delete_hash) {
969 next if $conf->{pending
}->{$opt}; # just to be sure
970 next if $conf->{$opt};
971 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
978 # POST/PUT {vmid}/config implementation
980 # The original API used PUT (idempotent) an we assumed that all operations
981 # are fast. But it turned out that almost any configuration change can
982 # involve hot-plug actions, or disk alloc/free. Such actions can take long
983 # time to complete and have side effects (not idempotent).
985 # The new implementation uses POST and forks a worker process. We added
986 # a new option 'background_delay'. If specified we wait up to
987 # 'background_delay' second for the worker task to complete. It returns null
988 # if the task is finished within that time, else we return the UPID.
990 my $update_vm_api = sub {
991 my ($param, $sync) = @_;
993 my $rpcenv = PVE
::RPCEnvironment
::get
();
995 my $authuser = $rpcenv->get_user();
997 my $node = extract_param
($param, 'node');
999 my $vmid = extract_param
($param, 'vmid');
1001 my $digest = extract_param
($param, 'digest');
1003 my $background_delay = extract_param
($param, 'background_delay');
1005 if (defined(my $cipassword = $param->{cipassword
})) {
1006 # Same logic as in cloud-init (but with the regex fixed...)
1007 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
1008 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1011 my @paramarr = (); # used for log message
1012 foreach my $key (sort keys %$param) {
1013 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1014 push @paramarr, "-$key", $value;
1017 my $skiplock = extract_param
($param, 'skiplock');
1018 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1019 if $skiplock && $authuser ne 'root@pam';
1021 my $delete_str = extract_param
($param, 'delete');
1023 my $revert_str = extract_param
($param, 'revert');
1025 my $force = extract_param
($param, 'force');
1027 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1028 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1029 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1032 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1034 my $storecfg = PVE
::Storage
::config
();
1036 my $defaults = PVE
::QemuServer
::load_defaults
();
1038 &$resolve_cdrom_alias($param);
1040 # now try to verify all parameters
1043 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1044 if (!PVE
::QemuServer
::option_exists
($opt)) {
1045 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1048 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1049 "-revert $opt' at the same time" })
1050 if defined($param->{$opt});
1052 $revert->{$opt} = 1;
1056 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1057 $opt = 'ide2' if $opt eq 'cdrom';
1059 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1060 "-delete $opt' at the same time" })
1061 if defined($param->{$opt});
1063 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1064 "-revert $opt' at the same time" })
1067 if (!PVE
::QemuServer
::option_exists
($opt)) {
1068 raise_param_exc
({ delete => "unknown option '$opt'" });
1074 my $repl_conf = PVE
::ReplicationConfig-
>new();
1075 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1076 my $check_replication = sub {
1078 return if !$is_replicated;
1079 my $volid = $drive->{file
};
1080 return if !$volid || !($drive->{replicate
}//1);
1081 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1082 my ($storeid, $format);
1083 if ($volid =~ $NEW_DISK_RE) {
1085 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1087 ($storeid, undef) = PVE
::Storage
::parse_volume_id
($volid, 1);
1088 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1090 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1091 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1092 return if $scfg->{shared
};
1093 die "cannot add non-replicatable volume to a replicated VM\n";
1096 foreach my $opt (keys %$param) {
1097 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1098 # cleanup drive path
1099 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1100 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1101 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1102 $check_replication->($drive);
1103 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1104 } elsif ($opt =~ m/^net(\d+)$/) {
1106 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1107 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1108 } elsif ($opt eq 'vmgenid') {
1109 if ($param->{$opt} eq '1') {
1110 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1112 } elsif ($opt eq 'hookscript') {
1113 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1114 raise_param_exc
({ $opt => $@ }) if $@;
1118 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1120 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1122 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1124 my $updatefn = sub {
1126 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1128 die "checksum missmatch (file change by other user?)\n"
1129 if $digest && $digest ne $conf->{digest
};
1131 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1133 foreach my $opt (keys %$revert) {
1134 if (defined($conf->{$opt})) {
1135 $param->{$opt} = $conf->{$opt};
1136 } elsif (defined($conf->{pending
}->{$opt})) {
1141 if ($param->{memory
} || defined($param->{balloon
})) {
1142 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1143 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1145 die "balloon value too large (must be smaller than assigned memory)\n"
1146 if $balloon && $balloon > $maxmem;
1149 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1153 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1155 # write updates to pending section
1157 my $modified = {}; # record what $option we modify
1159 foreach my $opt (@delete) {
1160 $modified->{$opt} = 1;
1161 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1163 # value of what we want to delete, independent if pending or not
1164 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1165 if (!defined($val)) {
1166 warn "cannot delete '$opt' - not set in current configuration!\n";
1167 $modified->{$opt} = 0;
1170 my $is_pending_val = defined($conf->{pending
}->{$opt});
1172 if ($opt =~ m/^unused/) {
1173 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1174 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1175 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1176 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1177 delete $conf->{$opt};
1178 PVE
::QemuConfig-
>write_config($vmid, $conf);
1180 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1181 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1182 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1183 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1185 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1186 PVE
::QemuConfig-
>write_config($vmid, $conf);
1187 } elsif ($opt =~ m/^serial\d+$/) {
1188 if ($val eq 'socket') {
1189 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1190 } elsif ($authuser ne 'root@pam') {
1191 die "only root can delete '$opt' config for real devices\n";
1193 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1194 PVE
::QemuConfig-
>write_config($vmid, $conf);
1195 } elsif ($opt =~ m/^usb\d+$/) {
1196 if ($val =~ m/spice/) {
1197 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1198 } elsif ($authuser ne 'root@pam') {
1199 die "only root can delete '$opt' config for real devices\n";
1201 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1202 PVE
::QemuConfig-
>write_config($vmid, $conf);
1204 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1205 PVE
::QemuConfig-
>write_config($vmid, $conf);
1209 foreach my $opt (keys %$param) { # add/change
1210 $modified->{$opt} = 1;
1211 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1212 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1214 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
1216 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1217 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1218 # FIXME: cloudinit: CDROM or Disk?
1219 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1220 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1222 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1224 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1225 if defined($conf->{pending
}->{$opt});
1227 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1228 } elsif ($opt =~ m/^serial\d+/) {
1229 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1230 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1231 } elsif ($authuser ne 'root@pam') {
1232 die "only root can modify '$opt' config for real devices\n";
1234 $conf->{pending
}->{$opt} = $param->{$opt};
1235 } elsif ($opt =~ m/^usb\d+/) {
1236 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1237 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1238 } elsif ($authuser ne 'root@pam') {
1239 die "only root can modify '$opt' config for real devices\n";
1241 $conf->{pending
}->{$opt} = $param->{$opt};
1243 $conf->{pending
}->{$opt} = $param->{$opt};
1245 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1246 PVE
::QemuConfig-
>write_config($vmid, $conf);
1249 # remove pending changes when nothing changed
1250 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1251 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1252 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1254 return if !scalar(keys %{$conf->{pending
}});
1256 my $running = PVE
::QemuServer
::check_running
($vmid);
1258 # apply pending changes
1260 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1264 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1265 raise_param_exc
($errors) if scalar(keys %$errors);
1267 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1277 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1279 if ($background_delay) {
1281 # Note: It would be better to do that in the Event based HTTPServer
1282 # to avoid blocking call to sleep.
1284 my $end_time = time() + $background_delay;
1286 my $task = PVE
::Tools
::upid_decode
($upid);
1289 while (time() < $end_time) {
1290 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1292 sleep(1); # this gets interrupted when child process ends
1296 my $status = PVE
::Tools
::upid_read_status
($upid);
1297 return undef if $status eq 'OK';
1306 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1309 my $vm_config_perm_list = [
1314 'VM.Config.Network',
1316 'VM.Config.Options',
1319 __PACKAGE__-
>register_method({
1320 name
=> 'update_vm_async',
1321 path
=> '{vmid}/config',
1325 description
=> "Set virtual machine options (asynchrounous API).",
1327 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1330 additionalProperties
=> 0,
1331 properties
=> PVE
::QemuServer
::json_config_properties
(
1333 node
=> get_standard_option
('pve-node'),
1334 vmid
=> get_standard_option
('pve-vmid'),
1335 skiplock
=> get_standard_option
('skiplock'),
1337 type
=> 'string', format
=> 'pve-configid-list',
1338 description
=> "A list of settings you want to delete.",
1342 type
=> 'string', format
=> 'pve-configid-list',
1343 description
=> "Revert a pending change.",
1348 description
=> $opt_force_description,
1350 requires
=> 'delete',
1354 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1358 background_delay
=> {
1360 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1371 code
=> $update_vm_api,
1374 __PACKAGE__-
>register_method({
1375 name
=> 'update_vm',
1376 path
=> '{vmid}/config',
1380 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1382 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1385 additionalProperties
=> 0,
1386 properties
=> PVE
::QemuServer
::json_config_properties
(
1388 node
=> get_standard_option
('pve-node'),
1389 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1390 skiplock
=> get_standard_option
('skiplock'),
1392 type
=> 'string', format
=> 'pve-configid-list',
1393 description
=> "A list of settings you want to delete.",
1397 type
=> 'string', format
=> 'pve-configid-list',
1398 description
=> "Revert a pending change.",
1403 description
=> $opt_force_description,
1405 requires
=> 'delete',
1409 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1415 returns
=> { type
=> 'null' },
1418 &$update_vm_api($param, 1);
1423 __PACKAGE__-
>register_method({
1424 name
=> 'destroy_vm',
1429 description
=> "Destroy the vm (also delete all used/owned volumes).",
1431 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1434 additionalProperties
=> 0,
1436 node
=> get_standard_option
('pve-node'),
1437 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1438 skiplock
=> get_standard_option
('skiplock'),
1447 my $rpcenv = PVE
::RPCEnvironment
::get
();
1448 my $authuser = $rpcenv->get_user();
1449 my $vmid = $param->{vmid
};
1451 my $skiplock = $param->{skiplock
};
1452 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1453 if $skiplock && $authuser ne 'root@pam';
1456 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1457 my $storecfg = PVE
::Storage
::config
();
1458 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1459 die "unable to remove VM $vmid - used in HA resources\n"
1460 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1462 # do not allow destroy if there are replication jobs
1463 my $repl_conf = PVE
::ReplicationConfig-
>new();
1464 $repl_conf->check_for_existing_jobs($vmid);
1466 # early tests (repeat after locking)
1467 die "VM $vmid is running - destroy failed\n"
1468 if PVE
::QemuServer
::check_running
($vmid);
1473 syslog
('info', "destroy VM $vmid: $upid\n");
1474 PVE
::QemuConfig-
>lock_config($vmid, sub {
1475 die "VM $vmid is running - destroy failed\n"
1476 if (PVE
::QemuServer
::check_running
($vmid));
1477 PVE
::QemuServer
::destroy_vm
($storecfg, $vmid, 1, $skiplock);
1478 PVE
::AccessControl
::remove_vm_access
($vmid);
1479 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1481 # only now remove the zombie config, else we can have reuse race
1482 PVE
::QemuConfig-
>destroy_config($vmid);
1486 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1489 __PACKAGE__-
>register_method({
1491 path
=> '{vmid}/unlink',
1495 description
=> "Unlink/delete disk images.",
1497 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1500 additionalProperties
=> 0,
1502 node
=> get_standard_option
('pve-node'),
1503 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1505 type
=> 'string', format
=> 'pve-configid-list',
1506 description
=> "A list of disk IDs you want to delete.",
1510 description
=> $opt_force_description,
1515 returns
=> { type
=> 'null'},
1519 $param->{delete} = extract_param
($param, 'idlist');
1521 __PACKAGE__-
>update_vm($param);
1528 __PACKAGE__-
>register_method({
1530 path
=> '{vmid}/vncproxy',
1534 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1536 description
=> "Creates a TCP VNC proxy connections.",
1538 additionalProperties
=> 0,
1540 node
=> get_standard_option
('pve-node'),
1541 vmid
=> get_standard_option
('pve-vmid'),
1545 description
=> "starts websockify instead of vncproxy",
1550 additionalProperties
=> 0,
1552 user
=> { type
=> 'string' },
1553 ticket
=> { type
=> 'string' },
1554 cert
=> { type
=> 'string' },
1555 port
=> { type
=> 'integer' },
1556 upid
=> { type
=> 'string' },
1562 my $rpcenv = PVE
::RPCEnvironment
::get
();
1564 my $authuser = $rpcenv->get_user();
1566 my $vmid = $param->{vmid
};
1567 my $node = $param->{node
};
1568 my $websocket = $param->{websocket
};
1570 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1571 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1573 my $authpath = "/vms/$vmid";
1575 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1577 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1583 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1584 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1585 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1586 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1587 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1589 $family = PVE
::Tools
::get_host_address_family
($node);
1592 my $port = PVE
::Tools
::next_vnc_port
($family);
1599 syslog
('info', "starting vnc proxy $upid\n");
1605 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1607 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1608 '-timeout', $timeout, '-authpath', $authpath,
1609 '-perm', 'Sys.Console'];
1611 if ($param->{websocket
}) {
1612 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1613 push @$cmd, '-notls', '-listen', 'localhost';
1616 push @$cmd, '-c', @$remcmd, @$termcmd;
1618 PVE
::Tools
::run_command
($cmd);
1622 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1624 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1626 my $sock = IO
::Socket
::IP-
>new(
1631 GetAddrInfoFlags
=> 0,
1632 ) or die "failed to create socket: $!\n";
1633 # Inside the worker we shouldn't have any previous alarms
1634 # running anyway...:
1636 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1638 accept(my $cli, $sock) or die "connection failed: $!\n";
1641 if (PVE
::Tools
::run_command
($cmd,
1642 output
=> '>&'.fileno($cli),
1643 input
=> '<&'.fileno($cli),
1646 die "Failed to run vncproxy.\n";
1653 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1655 PVE
::Tools
::wait_for_vnc_port
($port);
1666 __PACKAGE__-
>register_method({
1667 name
=> 'termproxy',
1668 path
=> '{vmid}/termproxy',
1672 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1674 description
=> "Creates a TCP proxy connections.",
1676 additionalProperties
=> 0,
1678 node
=> get_standard_option
('pve-node'),
1679 vmid
=> get_standard_option
('pve-vmid'),
1683 enum
=> [qw(serial0 serial1 serial2 serial3)],
1684 description
=> "opens a serial terminal (defaults to display)",
1689 additionalProperties
=> 0,
1691 user
=> { type
=> 'string' },
1692 ticket
=> { type
=> 'string' },
1693 port
=> { type
=> 'integer' },
1694 upid
=> { type
=> 'string' },
1700 my $rpcenv = PVE
::RPCEnvironment
::get
();
1702 my $authuser = $rpcenv->get_user();
1704 my $vmid = $param->{vmid
};
1705 my $node = $param->{node
};
1706 my $serial = $param->{serial
};
1708 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1710 if (!defined($serial)) {
1711 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1712 $serial = $conf->{vga
};
1716 my $authpath = "/vms/$vmid";
1718 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1723 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1724 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1725 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1726 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, '-t');
1727 push @$remcmd, '--';
1729 $family = PVE
::Tools
::get_host_address_family
($node);
1732 my $port = PVE
::Tools
::next_vnc_port
($family);
1734 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1735 push @$termcmd, '-iface', $serial if $serial;
1740 syslog
('info', "starting qemu termproxy $upid\n");
1742 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1743 '--perm', 'VM.Console', '--'];
1744 push @$cmd, @$remcmd, @$termcmd;
1746 PVE
::Tools
::run_command
($cmd);
1749 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1751 PVE
::Tools
::wait_for_vnc_port
($port);
1761 __PACKAGE__-
>register_method({
1762 name
=> 'vncwebsocket',
1763 path
=> '{vmid}/vncwebsocket',
1766 description
=> "You also need to pass a valid ticket (vncticket).",
1767 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1769 description
=> "Opens a weksocket for VNC traffic.",
1771 additionalProperties
=> 0,
1773 node
=> get_standard_option
('pve-node'),
1774 vmid
=> get_standard_option
('pve-vmid'),
1776 description
=> "Ticket from previous call to vncproxy.",
1781 description
=> "Port number returned by previous vncproxy call.",
1791 port
=> { type
=> 'string' },
1797 my $rpcenv = PVE
::RPCEnvironment
::get
();
1799 my $authuser = $rpcenv->get_user();
1801 my $vmid = $param->{vmid
};
1802 my $node = $param->{node
};
1804 my $authpath = "/vms/$vmid";
1806 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1808 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1810 # Note: VNC ports are acessible from outside, so we do not gain any
1811 # security if we verify that $param->{port} belongs to VM $vmid. This
1812 # check is done by verifying the VNC ticket (inside VNC protocol).
1814 my $port = $param->{port
};
1816 return { port
=> $port };
1819 __PACKAGE__-
>register_method({
1820 name
=> 'spiceproxy',
1821 path
=> '{vmid}/spiceproxy',
1826 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1828 description
=> "Returns a SPICE configuration to connect to the VM.",
1830 additionalProperties
=> 0,
1832 node
=> get_standard_option
('pve-node'),
1833 vmid
=> get_standard_option
('pve-vmid'),
1834 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1837 returns
=> get_standard_option
('remote-viewer-config'),
1841 my $rpcenv = PVE
::RPCEnvironment
::get
();
1843 my $authuser = $rpcenv->get_user();
1845 my $vmid = $param->{vmid
};
1846 my $node = $param->{node
};
1847 my $proxy = $param->{proxy
};
1849 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1850 my $title = "VM $vmid";
1851 $title .= " - ". $conf->{name
} if $conf->{name
};
1853 my $port = PVE
::QemuServer
::spice_port
($vmid);
1855 my ($ticket, undef, $remote_viewer_config) =
1856 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1858 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1859 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1861 return $remote_viewer_config;
1864 __PACKAGE__-
>register_method({
1866 path
=> '{vmid}/status',
1869 description
=> "Directory index",
1874 additionalProperties
=> 0,
1876 node
=> get_standard_option
('pve-node'),
1877 vmid
=> get_standard_option
('pve-vmid'),
1885 subdir
=> { type
=> 'string' },
1888 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1894 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1897 { subdir
=> 'current' },
1898 { subdir
=> 'start' },
1899 { subdir
=> 'stop' },
1900 { subdir
=> 'reset' },
1901 { subdir
=> 'shutdown' },
1902 { subdir
=> 'suspend' },
1903 { subdir
=> 'reboot' },
1909 __PACKAGE__-
>register_method({
1910 name
=> 'vm_status',
1911 path
=> '{vmid}/status/current',
1914 protected
=> 1, # qemu pid files are only readable by root
1915 description
=> "Get virtual machine status.",
1917 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1920 additionalProperties
=> 0,
1922 node
=> get_standard_option
('pve-node'),
1923 vmid
=> get_standard_option
('pve-vmid'),
1929 %$PVE::QemuServer
::vmstatus_return_properties
,
1931 description
=> "HA manager service status.",
1935 description
=> "Qemu VGA configuration supports spice.",
1940 description
=> "Qemu GuestAgent enabled in config.",
1950 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1952 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1953 my $status = $vmstatus->{$param->{vmid
}};
1955 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1957 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1958 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1963 __PACKAGE__-
>register_method({
1965 path
=> '{vmid}/status/start',
1969 description
=> "Start virtual machine.",
1971 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1974 additionalProperties
=> 0,
1976 node
=> get_standard_option
('pve-node'),
1977 vmid
=> get_standard_option
('pve-vmid',
1978 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1979 skiplock
=> get_standard_option
('skiplock'),
1980 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1981 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1984 enum
=> ['secure', 'insecure'],
1985 description
=> "Migration traffic is encrypted using an SSH " .
1986 "tunnel by default. On secure, completely private networks " .
1987 "this can be disabled to increase performance.",
1990 migration_network
=> {
1991 type
=> 'string', format
=> 'CIDR',
1992 description
=> "CIDR of the (sub) network that is used for migration.",
1995 machine
=> get_standard_option
('pve-qm-machine'),
1997 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
2009 my $rpcenv = PVE
::RPCEnvironment
::get
();
2010 my $authuser = $rpcenv->get_user();
2012 my $node = extract_param
($param, 'node');
2013 my $vmid = extract_param
($param, 'vmid');
2015 my $machine = extract_param
($param, 'machine');
2017 my $get_root_param = sub {
2018 my $value = extract_param
($param, $_[0]);
2019 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2020 if $value && $authuser ne 'root@pam';
2024 my $stateuri = $get_root_param->('stateuri');
2025 my $skiplock = $get_root_param->('skiplock');
2026 my $migratedfrom = $get_root_param->('migratedfrom');
2027 my $migration_type = $get_root_param->('migration_type');
2028 my $migration_network = $get_root_param->('migration_network');
2029 my $targetstorage = $get_root_param->('targetstorage');
2031 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2032 if $targetstorage && !$migratedfrom;
2034 # read spice ticket from STDIN
2036 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2037 if (defined(my $line = <STDIN
>)) {
2039 $spice_ticket = $line;
2043 PVE
::Cluster
::check_cfs_quorum
();
2045 my $storecfg = PVE
::Storage
::config
();
2047 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2051 print "Requesting HA start for VM $vmid\n";
2053 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2054 PVE
::Tools
::run_command
($cmd);
2058 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2065 syslog
('info', "start VM $vmid: $upid\n");
2067 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2068 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
2072 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2076 __PACKAGE__-
>register_method({
2078 path
=> '{vmid}/status/stop',
2082 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2083 "is akin to pulling the power plug of a running computer and may damage the VM data",
2085 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2088 additionalProperties
=> 0,
2090 node
=> get_standard_option
('pve-node'),
2091 vmid
=> get_standard_option
('pve-vmid',
2092 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2093 skiplock
=> get_standard_option
('skiplock'),
2094 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2096 description
=> "Wait maximal timeout seconds.",
2102 description
=> "Do not deactivate storage volumes.",
2115 my $rpcenv = PVE
::RPCEnvironment
::get
();
2116 my $authuser = $rpcenv->get_user();
2118 my $node = extract_param
($param, 'node');
2119 my $vmid = extract_param
($param, 'vmid');
2121 my $skiplock = extract_param
($param, 'skiplock');
2122 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2123 if $skiplock && $authuser ne 'root@pam';
2125 my $keepActive = extract_param
($param, 'keepActive');
2126 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2127 if $keepActive && $authuser ne 'root@pam';
2129 my $migratedfrom = extract_param
($param, 'migratedfrom');
2130 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2131 if $migratedfrom && $authuser ne 'root@pam';
2134 my $storecfg = PVE
::Storage
::config
();
2136 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2141 print "Requesting HA stop for VM $vmid\n";
2143 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'stopped'];
2144 PVE
::Tools
::run_command
($cmd);
2148 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2154 syslog
('info', "stop VM $vmid: $upid\n");
2156 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2157 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2161 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2165 __PACKAGE__-
>register_method({
2167 path
=> '{vmid}/status/reset',
2171 description
=> "Reset virtual machine.",
2173 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2176 additionalProperties
=> 0,
2178 node
=> get_standard_option
('pve-node'),
2179 vmid
=> get_standard_option
('pve-vmid',
2180 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2181 skiplock
=> get_standard_option
('skiplock'),
2190 my $rpcenv = PVE
::RPCEnvironment
::get
();
2192 my $authuser = $rpcenv->get_user();
2194 my $node = extract_param
($param, 'node');
2196 my $vmid = extract_param
($param, 'vmid');
2198 my $skiplock = extract_param
($param, 'skiplock');
2199 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2200 if $skiplock && $authuser ne 'root@pam';
2202 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2207 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2212 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2215 __PACKAGE__-
>register_method({
2216 name
=> 'vm_shutdown',
2217 path
=> '{vmid}/status/shutdown',
2221 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2222 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2224 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2227 additionalProperties
=> 0,
2229 node
=> get_standard_option
('pve-node'),
2230 vmid
=> get_standard_option
('pve-vmid',
2231 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2232 skiplock
=> get_standard_option
('skiplock'),
2234 description
=> "Wait maximal timeout seconds.",
2240 description
=> "Make sure the VM stops.",
2246 description
=> "Do not deactivate storage volumes.",
2259 my $rpcenv = PVE
::RPCEnvironment
::get
();
2260 my $authuser = $rpcenv->get_user();
2262 my $node = extract_param
($param, 'node');
2263 my $vmid = extract_param
($param, 'vmid');
2265 my $skiplock = extract_param
($param, 'skiplock');
2266 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2267 if $skiplock && $authuser ne 'root@pam';
2269 my $keepActive = extract_param
($param, 'keepActive');
2270 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2271 if $keepActive && $authuser ne 'root@pam';
2273 my $storecfg = PVE
::Storage
::config
();
2277 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2278 # otherwise, we will infer a shutdown command, but run into the timeout,
2279 # then when the vm is resumed, it will instantly shutdown
2281 # checking the qmp status here to get feedback to the gui/cli/api
2282 # and the status query should not take too long
2283 my $qmpstatus = eval {
2284 PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2288 if (!$err && $qmpstatus->{status
} eq "paused") {
2289 if ($param->{forceStop
}) {
2290 warn "VM is paused - stop instead of shutdown\n";
2293 die "VM is paused - cannot shutdown\n";
2297 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2302 print "Requesting HA stop for VM $vmid\n";
2304 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'stopped'];
2305 PVE
::Tools
::run_command
($cmd);
2309 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2316 syslog
('info', "shutdown VM $vmid: $upid\n");
2318 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2319 $shutdown, $param->{forceStop
}, $keepActive);
2323 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2327 __PACKAGE__-
>register_method({
2328 name
=> 'vm_reboot',
2329 path
=> '{vmid}/status/reboot',
2333 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2335 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2338 additionalProperties
=> 0,
2340 node
=> get_standard_option
('pve-node'),
2341 vmid
=> get_standard_option
('pve-vmid',
2342 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2344 description
=> "Wait maximal timeout seconds for the shutdown.",
2357 my $rpcenv = PVE
::RPCEnvironment
::get
();
2358 my $authuser = $rpcenv->get_user();
2360 my $node = extract_param
($param, 'node');
2361 my $vmid = extract_param
($param, 'vmid');
2363 my $qmpstatus = eval {
2364 PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2368 if (!$err && $qmpstatus->{status
} eq "paused") {
2369 die "VM is paused - cannot shutdown\n";
2372 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2377 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2378 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2382 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2385 __PACKAGE__-
>register_method({
2386 name
=> 'vm_suspend',
2387 path
=> '{vmid}/status/suspend',
2391 description
=> "Suspend virtual machine.",
2393 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2396 additionalProperties
=> 0,
2398 node
=> get_standard_option
('pve-node'),
2399 vmid
=> get_standard_option
('pve-vmid',
2400 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2401 skiplock
=> get_standard_option
('skiplock'),
2406 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2408 statestorage
=> get_standard_option
('pve-storage-id', {
2409 description
=> "The storage for the VM state",
2410 requires
=> 'todisk',
2412 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2422 my $rpcenv = PVE
::RPCEnvironment
::get
();
2423 my $authuser = $rpcenv->get_user();
2425 my $node = extract_param
($param, 'node');
2426 my $vmid = extract_param
($param, 'vmid');
2428 my $todisk = extract_param
($param, 'todisk') // 0;
2430 my $statestorage = extract_param
($param, 'statestorage');
2432 my $skiplock = extract_param
($param, 'skiplock');
2433 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2434 if $skiplock && $authuser ne 'root@pam';
2436 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2438 die "Cannot suspend HA managed VM to disk\n"
2439 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2444 syslog
('info', "suspend VM $vmid: $upid\n");
2446 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2451 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2452 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2455 __PACKAGE__-
>register_method({
2456 name
=> 'vm_resume',
2457 path
=> '{vmid}/status/resume',
2461 description
=> "Resume virtual machine.",
2463 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2466 additionalProperties
=> 0,
2468 node
=> get_standard_option
('pve-node'),
2469 vmid
=> get_standard_option
('pve-vmid',
2470 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2471 skiplock
=> get_standard_option
('skiplock'),
2472 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2482 my $rpcenv = PVE
::RPCEnvironment
::get
();
2484 my $authuser = $rpcenv->get_user();
2486 my $node = extract_param
($param, 'node');
2488 my $vmid = extract_param
($param, 'vmid');
2490 my $skiplock = extract_param
($param, 'skiplock');
2491 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2492 if $skiplock && $authuser ne 'root@pam';
2494 my $nocheck = extract_param
($param, 'nocheck');
2496 my $to_disk_suspended;
2498 PVE
::QemuConfig-
>lock_config($vmid, sub {
2499 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2500 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2504 die "VM $vmid not running\n"
2505 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2510 syslog
('info', "resume VM $vmid: $upid\n");
2512 if (!$to_disk_suspended) {
2513 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2515 my $storecfg = PVE
::Storage
::config
();
2516 PVE
::QemuServer
::vm_start
($storecfg, $vmid, undef, $skiplock);
2522 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2525 __PACKAGE__-
>register_method({
2526 name
=> 'vm_sendkey',
2527 path
=> '{vmid}/sendkey',
2531 description
=> "Send key event to virtual machine.",
2533 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2536 additionalProperties
=> 0,
2538 node
=> get_standard_option
('pve-node'),
2539 vmid
=> get_standard_option
('pve-vmid',
2540 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2541 skiplock
=> get_standard_option
('skiplock'),
2543 description
=> "The key (qemu monitor encoding).",
2548 returns
=> { type
=> 'null'},
2552 my $rpcenv = PVE
::RPCEnvironment
::get
();
2554 my $authuser = $rpcenv->get_user();
2556 my $node = extract_param
($param, 'node');
2558 my $vmid = extract_param
($param, 'vmid');
2560 my $skiplock = extract_param
($param, 'skiplock');
2561 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2562 if $skiplock && $authuser ne 'root@pam';
2564 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2569 __PACKAGE__-
>register_method({
2570 name
=> 'vm_feature',
2571 path
=> '{vmid}/feature',
2575 description
=> "Check if feature for virtual machine is available.",
2577 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2580 additionalProperties
=> 0,
2582 node
=> get_standard_option
('pve-node'),
2583 vmid
=> get_standard_option
('pve-vmid'),
2585 description
=> "Feature to check.",
2587 enum
=> [ 'snapshot', 'clone', 'copy' ],
2589 snapname
=> get_standard_option
('pve-snapshot-name', {
2597 hasFeature
=> { type
=> 'boolean' },
2600 items
=> { type
=> 'string' },
2607 my $node = extract_param
($param, 'node');
2609 my $vmid = extract_param
($param, 'vmid');
2611 my $snapname = extract_param
($param, 'snapname');
2613 my $feature = extract_param
($param, 'feature');
2615 my $running = PVE
::QemuServer
::check_running
($vmid);
2617 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2620 my $snap = $conf->{snapshots
}->{$snapname};
2621 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2624 my $storecfg = PVE
::Storage
::config
();
2626 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2627 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2630 hasFeature
=> $hasFeature,
2631 nodes
=> [ keys %$nodelist ],
2635 __PACKAGE__-
>register_method({
2637 path
=> '{vmid}/clone',
2641 description
=> "Create a copy of virtual machine/template.",
2643 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2644 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2645 "'Datastore.AllocateSpace' on any used storage.",
2648 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2650 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2651 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2656 additionalProperties
=> 0,
2658 node
=> get_standard_option
('pve-node'),
2659 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2660 newid
=> get_standard_option
('pve-vmid', {
2661 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2662 description
=> 'VMID for the clone.' }),
2665 type
=> 'string', format
=> 'dns-name',
2666 description
=> "Set a name for the new VM.",
2671 description
=> "Description for the new VM.",
2675 type
=> 'string', format
=> 'pve-poolid',
2676 description
=> "Add the new VM to the specified pool.",
2678 snapname
=> get_standard_option
('pve-snapshot-name', {
2681 storage
=> get_standard_option
('pve-storage-id', {
2682 description
=> "Target storage for full clone.",
2686 description
=> "Target format for file storage. Only valid for full clone.",
2689 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2694 description
=> "Create a full copy of all disks. This is always done when " .
2695 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2697 target
=> get_standard_option
('pve-node', {
2698 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2702 description
=> "Override I/O bandwidth limit (in KiB/s).",
2706 default => 'clone limit from datacenter or storage config',
2716 my $rpcenv = PVE
::RPCEnvironment
::get
();
2718 my $authuser = $rpcenv->get_user();
2720 my $node = extract_param
($param, 'node');
2722 my $vmid = extract_param
($param, 'vmid');
2724 my $newid = extract_param
($param, 'newid');
2726 my $pool = extract_param
($param, 'pool');
2728 if (defined($pool)) {
2729 $rpcenv->check_pool_exist($pool);
2732 my $snapname = extract_param
($param, 'snapname');
2734 my $storage = extract_param
($param, 'storage');
2736 my $format = extract_param
($param, 'format');
2738 my $target = extract_param
($param, 'target');
2740 my $localnode = PVE
::INotify
::nodename
();
2742 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2744 PVE
::Cluster
::check_node_exists
($target) if $target;
2746 my $storecfg = PVE
::Storage
::config
();
2749 # check if storage is enabled on local node
2750 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2752 # check if storage is available on target node
2753 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2754 # clone only works if target storage is shared
2755 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2756 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2760 PVE
::Cluster
::check_cfs_quorum
();
2762 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2764 # exclusive lock if VM is running - else shared lock is enough;
2765 my $shared_lock = $running ?
0 : 1;
2769 # do all tests after lock
2770 # we also try to do all tests before we fork the worker
2772 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2774 PVE
::QemuConfig-
>check_lock($conf);
2776 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2778 die "unexpected state change\n" if $verify_running != $running;
2780 die "snapshot '$snapname' does not exist\n"
2781 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2783 my $full = extract_param
($param, 'full');
2784 if (!defined($full)) {
2785 $full = !PVE
::QemuConfig-
>is_template($conf);
2788 die "parameter 'storage' not allowed for linked clones\n"
2789 if defined($storage) && !$full;
2791 die "parameter 'format' not allowed for linked clones\n"
2792 if defined($format) && !$full;
2794 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2796 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2798 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2800 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2802 die "unable to create VM $newid: config file already exists\n"
2805 my $newconf = { lock => 'clone' };
2810 foreach my $opt (keys %$oldconf) {
2811 my $value = $oldconf->{$opt};
2813 # do not copy snapshot related info
2814 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2815 $opt eq 'vmstate' || $opt eq 'snapstate';
2817 # no need to copy unused images, because VMID(owner) changes anyways
2818 next if $opt =~ m/^unused\d+$/;
2820 # always change MAC! address
2821 if ($opt =~ m/^net(\d+)$/) {
2822 my $net = PVE
::QemuServer
::parse_net
($value);
2823 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2824 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2825 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2826 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2827 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2828 die "unable to parse drive options for '$opt'\n" if !$drive;
2829 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2830 $newconf->{$opt} = $value; # simply copy configuration
2833 die "Full clone feature is not supported for drive '$opt'\n"
2834 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2835 $fullclone->{$opt} = 1;
2837 # not full means clone instead of copy
2838 die "Linked clone feature is not supported for drive '$opt'\n"
2839 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2841 $drives->{$opt} = $drive;
2842 push @$vollist, $drive->{file
};
2845 # copy everything else
2846 $newconf->{$opt} = $value;
2850 # auto generate a new uuid
2851 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2852 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2853 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2855 # auto generate a new vmgenid if the option was set
2856 if ($newconf->{vmgenid
}) {
2857 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2860 delete $newconf->{template
};
2862 if ($param->{name
}) {
2863 $newconf->{name
} = $param->{name
};
2865 if ($oldconf->{name
}) {
2866 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2868 $newconf->{name
} = "Copy-of-VM-$vmid";
2872 if ($param->{description
}) {
2873 $newconf->{description
} = $param->{description
};
2876 # create empty/temp config - this fails if VM already exists on other node
2877 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2882 my $newvollist = [];
2889 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2891 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2893 my $bwlimit = extract_param
($param, 'bwlimit');
2895 my $total_jobs = scalar(keys %{$drives});
2898 foreach my $opt (keys %$drives) {
2899 my $drive = $drives->{$opt};
2900 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2902 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
2903 my $storage_list = [ $src_sid ];
2904 push @$storage_list, $storage if defined($storage);
2905 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
2907 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2908 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2909 $jobs, $skipcomplete, $oldconf->{agent
}, $clonelimit);
2911 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2913 PVE
::QemuConfig-
>write_config($newid, $newconf);
2917 delete $newconf->{lock};
2919 # do not write pending changes
2920 if (my @changes = keys %{$newconf->{pending
}}) {
2921 my $pending = join(',', @changes);
2922 warn "found pending changes for '$pending', discarding for clone\n";
2923 delete $newconf->{pending
};
2926 PVE
::QemuConfig-
>write_config($newid, $newconf);
2929 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2930 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2931 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2933 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2934 die "Failed to move config to node '$target' - rename failed: $!\n"
2935 if !rename($conffile, $newconffile);
2938 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2943 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2945 sleep 1; # some storage like rbd need to wait before release volume - really?
2947 foreach my $volid (@$newvollist) {
2948 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2951 die "clone failed: $err";
2957 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2959 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2962 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2963 # Aquire exclusive lock lock for $newid
2964 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2969 __PACKAGE__-
>register_method({
2970 name
=> 'move_vm_disk',
2971 path
=> '{vmid}/move_disk',
2975 description
=> "Move volume to different storage.",
2977 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2979 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2980 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2984 additionalProperties
=> 0,
2986 node
=> get_standard_option
('pve-node'),
2987 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2990 description
=> "The disk you want to move.",
2991 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2993 storage
=> get_standard_option
('pve-storage-id', {
2994 description
=> "Target storage.",
2995 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2999 description
=> "Target Format.",
3000 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3005 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3011 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3016 description
=> "Override I/O bandwidth limit (in KiB/s).",
3020 default => 'move limit from datacenter or storage config',
3026 description
=> "the task ID.",
3031 my $rpcenv = PVE
::RPCEnvironment
::get
();
3033 my $authuser = $rpcenv->get_user();
3035 my $node = extract_param
($param, 'node');
3037 my $vmid = extract_param
($param, 'vmid');
3039 my $digest = extract_param
($param, 'digest');
3041 my $disk = extract_param
($param, 'disk');
3043 my $storeid = extract_param
($param, 'storage');
3045 my $format = extract_param
($param, 'format');
3047 my $storecfg = PVE
::Storage
::config
();
3049 my $updatefn = sub {
3051 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3053 PVE
::QemuConfig-
>check_lock($conf);
3055 die "checksum missmatch (file change by other user?)\n"
3056 if $digest && $digest ne $conf->{digest
};
3058 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3060 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3062 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
3064 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3067 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3068 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3072 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
3073 (!$format || !$oldfmt || $oldfmt eq $format);
3075 # this only checks snapshots because $disk is passed!
3076 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3077 die "you can't move a disk with snapshots and delete the source\n"
3078 if $snapshotted && $param->{delete};
3080 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3082 my $running = PVE
::QemuServer
::check_running
($vmid);
3084 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3088 my $newvollist = [];
3094 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3096 warn "moving disk with snapshots, snapshots will not be moved!\n"
3099 my $bwlimit = extract_param
($param, 'bwlimit');
3100 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3102 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3103 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit);
3105 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
3107 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3109 # convert moved disk to base if part of template
3110 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3111 if PVE
::QemuConfig-
>is_template($conf);
3113 PVE
::QemuConfig-
>write_config($vmid, $conf);
3115 if ($running && PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
} && PVE
::QemuServer
::qga_check_running
($vmid)) {
3116 eval { PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-fstrim"); };
3120 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3121 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3128 foreach my $volid (@$newvollist) {
3129 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3132 die "storage migration failed: $err";
3135 if ($param->{delete}) {
3137 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3138 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3144 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3147 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3150 my $check_vm_disks_local = sub {
3151 my ($storecfg, $vmconf, $vmid) = @_;
3153 my $local_disks = {};
3155 # add some more information to the disks e.g. cdrom
3156 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3157 my ($volid, $attr) = @_;
3159 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3161 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3162 return if $scfg->{shared
};
3164 # The shared attr here is just a special case where the vdisk
3165 # is marked as shared manually
3166 return if $attr->{shared
};
3167 return if $attr->{cdrom
} and $volid eq "none";
3169 if (exists $local_disks->{$volid}) {
3170 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3172 $local_disks->{$volid} = $attr;
3173 # ensure volid is present in case it's needed
3174 $local_disks->{$volid}->{volid
} = $volid;
3178 return $local_disks;
3181 __PACKAGE__-
>register_method({
3182 name
=> 'migrate_vm_precondition',
3183 path
=> '{vmid}/migrate',
3187 description
=> "Get preconditions for migration.",
3189 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3192 additionalProperties
=> 0,
3194 node
=> get_standard_option
('pve-node'),
3195 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3196 target
=> get_standard_option
('pve-node', {
3197 description
=> "Target node.",
3198 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3206 running
=> { type
=> 'boolean' },
3210 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3212 not_allowed_nodes
=> {
3215 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3219 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3221 local_resources
=> {
3223 description
=> "List local resources e.g. pci, usb"
3230 my $rpcenv = PVE
::RPCEnvironment
::get
();
3232 my $authuser = $rpcenv->get_user();
3234 PVE
::Cluster
::check_cfs_quorum
();
3238 my $vmid = extract_param
($param, 'vmid');
3239 my $target = extract_param
($param, 'target');
3240 my $localnode = PVE
::INotify
::nodename
();
3244 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3245 my $storecfg = PVE
::Storage
::config
();
3248 # try to detect errors early
3249 PVE
::QemuConfig-
>check_lock($vmconf);
3251 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3253 # if vm is not running, return target nodes where local storage is available
3254 # for offline migration
3255 if (!$res->{running
}) {
3256 $res->{allowed_nodes
} = [];
3257 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3258 delete $checked_nodes->{$localnode};
3260 foreach my $node (keys %$checked_nodes) {
3261 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3262 push @{$res->{allowed_nodes
}}, $node;
3266 $res->{not_allowed_nodes
} = $checked_nodes;
3270 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3271 $res->{local_disks
} = [ values %$local_disks ];;
3273 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3275 $res->{local_resources
} = $local_resources;
3282 __PACKAGE__-
>register_method({
3283 name
=> 'migrate_vm',
3284 path
=> '{vmid}/migrate',
3288 description
=> "Migrate virtual machine. Creates a new migration task.",
3290 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3293 additionalProperties
=> 0,
3295 node
=> get_standard_option
('pve-node'),
3296 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3297 target
=> get_standard_option
('pve-node', {
3298 description
=> "Target node.",
3299 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3303 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3308 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3313 enum
=> ['secure', 'insecure'],
3314 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3317 migration_network
=> {
3318 type
=> 'string', format
=> 'CIDR',
3319 description
=> "CIDR of the (sub) network that is used for migration.",
3322 "with-local-disks" => {
3324 description
=> "Enable live storage migration for local disk",
3327 targetstorage
=> get_standard_option
('pve-storage-id', {
3328 description
=> "Default target storage.",
3330 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3333 description
=> "Override I/O bandwidth limit (in KiB/s).",
3337 default => 'migrate limit from datacenter or storage config',
3343 description
=> "the task ID.",
3348 my $rpcenv = PVE
::RPCEnvironment
::get
();
3349 my $authuser = $rpcenv->get_user();
3351 my $target = extract_param
($param, 'target');
3353 my $localnode = PVE
::INotify
::nodename
();
3354 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3356 PVE
::Cluster
::check_cfs_quorum
();
3358 PVE
::Cluster
::check_node_exists
($target);
3360 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3362 my $vmid = extract_param
($param, 'vmid');
3364 raise_param_exc
({ force
=> "Only root may use this option." })
3365 if $param->{force
} && $authuser ne 'root@pam';
3367 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3368 if $param->{migration_type
} && $authuser ne 'root@pam';
3370 # allow root only until better network permissions are available
3371 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3372 if $param->{migration_network
} && $authuser ne 'root@pam';
3375 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3377 # try to detect errors early
3379 PVE
::QemuConfig-
>check_lock($conf);
3381 if (PVE
::QemuServer
::check_running
($vmid)) {
3382 die "can't migrate running VM without --online\n" if !$param->{online
};
3384 warn "VM isn't running. Doing offline migration instead\n." if $param->{online
};
3385 $param->{online
} = 0;
3388 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3389 if !$param->{online
} && $param->{targetstorage
};
3391 my $storecfg = PVE
::Storage
::config
();
3393 if( $param->{targetstorage
}) {
3394 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3396 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3399 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3404 print "Requesting HA migration for VM $vmid to node $target\n";
3406 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3407 PVE
::Tools
::run_command
($cmd);
3411 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3416 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3420 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3423 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3428 __PACKAGE__-
>register_method({
3430 path
=> '{vmid}/monitor',
3434 description
=> "Execute Qemu monitor commands.",
3436 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3437 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3440 additionalProperties
=> 0,
3442 node
=> get_standard_option
('pve-node'),
3443 vmid
=> get_standard_option
('pve-vmid'),
3446 description
=> "The monitor command.",
3450 returns
=> { type
=> 'string'},
3454 my $rpcenv = PVE
::RPCEnvironment
::get
();
3455 my $authuser = $rpcenv->get_user();
3458 my $command = shift;
3459 return $command =~ m/^\s*info(\s+|$)/
3460 || $command =~ m/^\s*help\s*$/;
3463 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3464 if !&$is_ro($param->{command
});
3466 my $vmid = $param->{vmid
};
3468 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3472 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3474 $res = "ERROR: $@" if $@;
3479 __PACKAGE__-
>register_method({
3480 name
=> 'resize_vm',
3481 path
=> '{vmid}/resize',
3485 description
=> "Extend volume size.",
3487 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3490 additionalProperties
=> 0,
3492 node
=> get_standard_option
('pve-node'),
3493 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3494 skiplock
=> get_standard_option
('skiplock'),
3497 description
=> "The disk you want to resize.",
3498 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3502 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3503 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.",
3507 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3513 returns
=> { type
=> 'null'},
3517 my $rpcenv = PVE
::RPCEnvironment
::get
();
3519 my $authuser = $rpcenv->get_user();
3521 my $node = extract_param
($param, 'node');
3523 my $vmid = extract_param
($param, 'vmid');
3525 my $digest = extract_param
($param, 'digest');
3527 my $disk = extract_param
($param, 'disk');
3529 my $sizestr = extract_param
($param, 'size');
3531 my $skiplock = extract_param
($param, 'skiplock');
3532 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3533 if $skiplock && $authuser ne 'root@pam';
3535 my $storecfg = PVE
::Storage
::config
();
3537 my $updatefn = sub {
3539 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3541 die "checksum missmatch (file change by other user?)\n"
3542 if $digest && $digest ne $conf->{digest
};
3543 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3545 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3547 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3549 my (undef, undef, undef, undef, undef, undef, $format) =
3550 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3552 die "can't resize volume: $disk if snapshot exists\n"
3553 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3555 my $volid = $drive->{file
};
3557 die "disk '$disk' has no associated volume\n" if !$volid;
3559 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3561 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3563 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3565 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3566 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3568 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3570 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3571 my ($ext, $newsize, $unit) = ($1, $2, $4);
3574 $newsize = $newsize * 1024;
3575 } elsif ($unit eq 'M') {
3576 $newsize = $newsize * 1024 * 1024;
3577 } elsif ($unit eq 'G') {
3578 $newsize = $newsize * 1024 * 1024 * 1024;
3579 } elsif ($unit eq 'T') {
3580 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3583 $newsize += $size if $ext;
3584 $newsize = int($newsize);
3586 die "shrinking disks is not supported\n" if $newsize < $size;
3588 return if $size == $newsize;
3590 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3592 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3594 $drive->{size
} = $newsize;
3595 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3597 PVE
::QemuConfig-
>write_config($vmid, $conf);
3600 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3604 __PACKAGE__-
>register_method({
3605 name
=> 'snapshot_list',
3606 path
=> '{vmid}/snapshot',
3608 description
=> "List all snapshots.",
3610 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3613 protected
=> 1, # qemu pid files are only readable by root
3615 additionalProperties
=> 0,
3617 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3618 node
=> get_standard_option
('pve-node'),
3627 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3631 description
=> "Snapshot includes RAM.",
3636 description
=> "Snapshot description.",
3640 description
=> "Snapshot creation time",
3642 renderer
=> 'timestamp',
3646 description
=> "Parent snapshot identifier.",
3652 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3657 my $vmid = $param->{vmid
};
3659 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3660 my $snaphash = $conf->{snapshots
} || {};
3664 foreach my $name (keys %$snaphash) {
3665 my $d = $snaphash->{$name};
3668 snaptime
=> $d->{snaptime
} || 0,
3669 vmstate
=> $d->{vmstate
} ?
1 : 0,
3670 description
=> $d->{description
} || '',
3672 $item->{parent
} = $d->{parent
} if $d->{parent
};
3673 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3677 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3680 digest
=> $conf->{digest
},
3681 running
=> $running,
3682 description
=> "You are here!",
3684 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3686 push @$res, $current;
3691 __PACKAGE__-
>register_method({
3693 path
=> '{vmid}/snapshot',
3697 description
=> "Snapshot a VM.",
3699 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3702 additionalProperties
=> 0,
3704 node
=> get_standard_option
('pve-node'),
3705 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3706 snapname
=> get_standard_option
('pve-snapshot-name'),
3710 description
=> "Save the vmstate",
3715 description
=> "A textual description or comment.",
3721 description
=> "the task ID.",
3726 my $rpcenv = PVE
::RPCEnvironment
::get
();
3728 my $authuser = $rpcenv->get_user();
3730 my $node = extract_param
($param, 'node');
3732 my $vmid = extract_param
($param, 'vmid');
3734 my $snapname = extract_param
($param, 'snapname');
3736 die "unable to use snapshot name 'current' (reserved name)\n"
3737 if $snapname eq 'current';
3740 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3741 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3742 $param->{description
});
3745 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3748 __PACKAGE__-
>register_method({
3749 name
=> 'snapshot_cmd_idx',
3750 path
=> '{vmid}/snapshot/{snapname}',
3757 additionalProperties
=> 0,
3759 vmid
=> get_standard_option
('pve-vmid'),
3760 node
=> get_standard_option
('pve-node'),
3761 snapname
=> get_standard_option
('pve-snapshot-name'),
3770 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3777 push @$res, { cmd
=> 'rollback' };
3778 push @$res, { cmd
=> 'config' };
3783 __PACKAGE__-
>register_method({
3784 name
=> 'update_snapshot_config',
3785 path
=> '{vmid}/snapshot/{snapname}/config',
3789 description
=> "Update snapshot metadata.",
3791 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3794 additionalProperties
=> 0,
3796 node
=> get_standard_option
('pve-node'),
3797 vmid
=> get_standard_option
('pve-vmid'),
3798 snapname
=> get_standard_option
('pve-snapshot-name'),
3802 description
=> "A textual description or comment.",
3806 returns
=> { type
=> 'null' },
3810 my $rpcenv = PVE
::RPCEnvironment
::get
();
3812 my $authuser = $rpcenv->get_user();
3814 my $vmid = extract_param
($param, 'vmid');
3816 my $snapname = extract_param
($param, 'snapname');
3818 return undef if !defined($param->{description
});
3820 my $updatefn = sub {
3822 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3824 PVE
::QemuConfig-
>check_lock($conf);
3826 my $snap = $conf->{snapshots
}->{$snapname};
3828 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3830 $snap->{description
} = $param->{description
} if defined($param->{description
});
3832 PVE
::QemuConfig-
>write_config($vmid, $conf);
3835 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3840 __PACKAGE__-
>register_method({
3841 name
=> 'get_snapshot_config',
3842 path
=> '{vmid}/snapshot/{snapname}/config',
3845 description
=> "Get snapshot configuration",
3847 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3850 additionalProperties
=> 0,
3852 node
=> get_standard_option
('pve-node'),
3853 vmid
=> get_standard_option
('pve-vmid'),
3854 snapname
=> get_standard_option
('pve-snapshot-name'),
3857 returns
=> { type
=> "object" },
3861 my $rpcenv = PVE
::RPCEnvironment
::get
();
3863 my $authuser = $rpcenv->get_user();
3865 my $vmid = extract_param
($param, 'vmid');
3867 my $snapname = extract_param
($param, 'snapname');
3869 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3871 my $snap = $conf->{snapshots
}->{$snapname};
3873 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3878 __PACKAGE__-
>register_method({
3880 path
=> '{vmid}/snapshot/{snapname}/rollback',
3884 description
=> "Rollback VM state to specified snapshot.",
3886 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3889 additionalProperties
=> 0,
3891 node
=> get_standard_option
('pve-node'),
3892 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3893 snapname
=> get_standard_option
('pve-snapshot-name'),
3898 description
=> "the task ID.",
3903 my $rpcenv = PVE
::RPCEnvironment
::get
();
3905 my $authuser = $rpcenv->get_user();
3907 my $node = extract_param
($param, 'node');
3909 my $vmid = extract_param
($param, 'vmid');
3911 my $snapname = extract_param
($param, 'snapname');
3914 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3915 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3919 # hold migration lock, this makes sure that nobody create replication snapshots
3920 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3923 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3926 __PACKAGE__-
>register_method({
3927 name
=> 'delsnapshot',
3928 path
=> '{vmid}/snapshot/{snapname}',
3932 description
=> "Delete a VM snapshot.",
3934 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3937 additionalProperties
=> 0,
3939 node
=> get_standard_option
('pve-node'),
3940 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3941 snapname
=> get_standard_option
('pve-snapshot-name'),
3945 description
=> "For removal from config file, even if removing disk snapshots fails.",
3951 description
=> "the task ID.",
3956 my $rpcenv = PVE
::RPCEnvironment
::get
();
3958 my $authuser = $rpcenv->get_user();
3960 my $node = extract_param
($param, 'node');
3962 my $vmid = extract_param
($param, 'vmid');
3964 my $snapname = extract_param
($param, 'snapname');
3967 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3968 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3971 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3974 __PACKAGE__-
>register_method({
3976 path
=> '{vmid}/template',
3980 description
=> "Create a Template.",
3982 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3983 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3986 additionalProperties
=> 0,
3988 node
=> get_standard_option
('pve-node'),
3989 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3993 description
=> "If you want to convert only 1 disk to base image.",
3994 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3999 returns
=> { type
=> 'null'},
4003 my $rpcenv = PVE
::RPCEnvironment
::get
();
4005 my $authuser = $rpcenv->get_user();
4007 my $node = extract_param
($param, 'node');
4009 my $vmid = extract_param
($param, 'vmid');
4011 my $disk = extract_param
($param, 'disk');
4013 my $updatefn = sub {
4015 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4017 PVE
::QemuConfig-
>check_lock($conf);
4019 die "unable to create template, because VM contains snapshots\n"
4020 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4022 die "you can't convert a template to a template\n"
4023 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4025 die "you can't convert a VM to template if VM is running\n"
4026 if PVE
::QemuServer
::check_running
($vmid);
4029 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4032 $conf->{template
} = 1;
4033 PVE
::QemuConfig-
>write_config($vmid, $conf);
4035 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4038 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4042 __PACKAGE__-
>register_method({
4043 name
=> 'cloudinit_generated_config_dump',
4044 path
=> '{vmid}/cloudinit/dump',
4047 description
=> "Get automatically generated cloudinit config.",
4049 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4052 additionalProperties
=> 0,
4054 node
=> get_standard_option
('pve-node'),
4055 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4057 description
=> 'Config type.',
4059 enum
=> ['user', 'network', 'meta'],
4069 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4071 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});