1 package PVE
::API2
::Qemu
;
11 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
14 use PVE
::Tools
qw(extract_param);
15 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
17 use PVE
::JSONSchema
qw(get_standard_option);
19 use PVE
::ReplicationConfig
;
20 use PVE
::GuestHelpers
;
23 use PVE
::QemuServer
::Monitor
qw(mon_cmd);
25 use PVE
::RPCEnvironment
;
26 use PVE
::AccessControl
;
30 use PVE
::API2
::Firewall
::VM
;
31 use PVE
::API2
::Qemu
::Agent
;
32 use PVE
::VZDump
::Plugin
;
33 use PVE
::DataCenterConfig
;
37 if (!$ENV{PVE_GENERATING_DOCS
}) {
38 require PVE
::HA
::Env
::PVE2
;
39 import PVE
::HA
::Env
::PVE2
;
40 require PVE
::HA
::Config
;
41 import PVE
::HA
::Config
;
45 use Data
::Dumper
; # fixme: remove
47 use base
qw(PVE::RESTHandler);
49 my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal.";
51 my $resolve_cdrom_alias = sub {
54 if (my $value = $param->{cdrom
}) {
55 $value .= ",media=cdrom" if $value !~ m/media=/;
56 $param->{ide2
} = $value;
57 delete $param->{cdrom
};
61 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
62 my $check_storage_access = sub {
63 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
65 PVE
::QemuServer
::foreach_drive
($settings, sub {
66 my ($ds, $drive) = @_;
68 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
70 my $volid = $drive->{file
};
71 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
73 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit' || (defined($volname) && $volname eq 'cloudinit'))) {
75 } elsif ($isCDROM && ($volid eq 'cdrom')) {
76 $rpcenv->check($authuser, "/", ['Sys.Console']);
77 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
78 my ($storeid, $size) = ($2 || $default_storage, $3);
79 die "no storage ID specified (and no default storage)\n" if !$storeid;
80 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
81 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
82 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
83 if !$scfg->{content
}->{images
};
85 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
89 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
90 if defined($settings->{vmstatestorage
});
93 my $check_storage_access_clone = sub {
94 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
98 PVE
::QemuServer
::foreach_drive
($conf, sub {
99 my ($ds, $drive) = @_;
101 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
103 my $volid = $drive->{file
};
105 return if !$volid || $volid eq 'none';
108 if ($volid eq 'cdrom') {
109 $rpcenv->check($authuser, "/", ['Sys.Console']);
111 # we simply allow access
112 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
113 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
114 $sharedvm = 0 if !$scfg->{shared
};
118 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
119 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
120 $sharedvm = 0 if !$scfg->{shared
};
122 $sid = $storage if $storage;
123 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
127 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
128 if defined($conf->{vmstatestorage
});
133 # Note: $pool is only needed when creating a VM, because pool permissions
134 # are automatically inherited if VM already exists inside a pool.
135 my $create_disks = sub {
136 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
143 my ($ds, $disk) = @_;
145 my $volid = $disk->{file
};
146 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
148 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
149 delete $disk->{size
};
150 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
151 } elsif (defined($volname) && $volname eq 'cloudinit') {
152 $storeid = $storeid // $default_storage;
153 die "no storage ID specified (and no default storage)\n" if !$storeid;
154 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
155 my $name = "vm-$vmid-cloudinit";
159 $fmt = $disk->{format
} // "qcow2";
162 $fmt = $disk->{format
} // "raw";
165 # Initial disk created with 4 MB and aligned to 4MB on regeneration
166 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
167 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
168 $disk->{file
} = $volid;
169 $disk->{media
} = 'cdrom';
170 push @$vollist, $volid;
171 delete $disk->{format
}; # no longer needed
172 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
173 } elsif ($volid =~ $NEW_DISK_RE) {
174 my ($storeid, $size) = ($2 || $default_storage, $3);
175 die "no storage ID specified (and no default storage)\n" if !$storeid;
176 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
177 my $fmt = $disk->{format
} || $defformat;
179 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
182 if ($ds eq 'efidisk0') {
183 ($volid, $size) = PVE
::QemuServer
::create_efidisk
($storecfg, $storeid, $vmid, $fmt, $arch);
185 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
187 push @$vollist, $volid;
188 $disk->{file
} = $volid;
189 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
190 delete $disk->{format
}; # no longer needed
191 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
194 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
196 my $volid_is_new = 1;
199 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
200 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
205 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
207 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
209 die "volume $volid does not exist\n" if !$size;
211 $disk->{size
} = $size;
214 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
218 eval { PVE
::QemuServer
::foreach_drive
($settings, $code); };
220 # free allocated images on error
222 syslog
('err', "VM $vmid creating disks failed");
223 foreach my $volid (@$vollist) {
224 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
230 # modify vm config if everything went well
231 foreach my $ds (keys %$res) {
232 $conf->{$ds} = $res->{$ds};
249 my $memoryoptions = {
255 my $hwtypeoptions = {
268 my $generaloptions = {
275 'migrate_downtime' => 1,
276 'migrate_speed' => 1,
289 my $vmpoweroptions = {
296 'vmstatestorage' => 1,
299 my $cloudinitoptions = {
309 my $check_vm_modify_config_perm = sub {
310 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
312 return 1 if $authuser eq 'root@pam';
314 foreach my $opt (@$key_list) {
315 # some checks (e.g., disk, serial port, usb) need to be done somewhere
316 # else, as there the permission can be value dependend
317 next if PVE
::QemuServer
::is_valid_drivename
($opt);
318 next if $opt eq 'cdrom';
319 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
322 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
323 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
324 } elsif ($memoryoptions->{$opt}) {
325 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
326 } elsif ($hwtypeoptions->{$opt}) {
327 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
328 } elsif ($generaloptions->{$opt}) {
329 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
330 # special case for startup since it changes host behaviour
331 if ($opt eq 'startup') {
332 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
334 } elsif ($vmpoweroptions->{$opt}) {
335 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
336 } elsif ($diskoptions->{$opt}) {
337 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
338 } elsif ($cloudinitoptions->{$opt} || ($opt =~ m/^(?:net|ipconfig)\d+$/)) {
339 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
340 } elsif ($opt eq 'vmstate') {
341 # the user needs Disk and PowerMgmt privileges to change the vmstate
342 # also needs privileges on the storage, that will be checked later
343 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt' ]);
345 # catches hostpci\d+, args, lock, etc.
346 # new options will be checked here
347 die "only root can set '$opt' config\n";
354 __PACKAGE__-
>register_method({
358 description
=> "Virtual machine index (per node).",
360 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
364 protected
=> 1, # qemu pid files are only readable by root
366 additionalProperties
=> 0,
368 node
=> get_standard_option
('pve-node'),
372 description
=> "Determine the full status of active VMs.",
380 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
382 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
387 my $rpcenv = PVE
::RPCEnvironment
::get
();
388 my $authuser = $rpcenv->get_user();
390 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
393 foreach my $vmid (keys %$vmstatus) {
394 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
396 my $data = $vmstatus->{$vmid};
405 __PACKAGE__-
>register_method({
409 description
=> "Create or restore a virtual machine.",
411 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
412 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
413 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
414 user
=> 'all', # check inside
419 additionalProperties
=> 0,
420 properties
=> PVE
::QemuServer
::json_config_properties
(
422 node
=> get_standard_option
('pve-node'),
423 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
425 description
=> "The backup file.",
429 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
431 storage
=> get_standard_option
('pve-storage-id', {
432 description
=> "Default storage.",
434 completion
=> \
&PVE
::QemuServer
::complete_storage
,
439 description
=> "Allow to overwrite existing VM.",
440 requires
=> 'archive',
445 description
=> "Assign a unique random ethernet address.",
446 requires
=> 'archive',
450 type
=> 'string', format
=> 'pve-poolid',
451 description
=> "Add the VM to the specified pool.",
454 description
=> "Override I/O bandwidth limit (in KiB/s).",
458 default => 'restore limit from datacenter or storage config',
464 description
=> "Start VM after it was created successfully.",
474 my $rpcenv = PVE
::RPCEnvironment
::get
();
475 my $authuser = $rpcenv->get_user();
477 my $node = extract_param
($param, 'node');
478 my $vmid = extract_param
($param, 'vmid');
480 my $archive = extract_param
($param, 'archive');
481 my $is_restore = !!$archive;
483 my $bwlimit = extract_param
($param, 'bwlimit');
484 my $force = extract_param
($param, 'force');
485 my $pool = extract_param
($param, 'pool');
486 my $start_after_create = extract_param
($param, 'start');
487 my $storage = extract_param
($param, 'storage');
488 my $unique = extract_param
($param, 'unique');
490 if (defined(my $ssh_keys = $param->{sshkeys
})) {
491 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
492 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
495 PVE
::Cluster
::check_cfs_quorum
();
497 my $filename = PVE
::QemuConfig-
>config_file($vmid);
498 my $storecfg = PVE
::Storage
::config
();
500 if (defined($pool)) {
501 $rpcenv->check_pool_exist($pool);
504 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
505 if defined($storage);
507 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
509 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
511 } elsif ($archive && $force && (-f
$filename) &&
512 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
513 # OK: user has VM.Backup permissions, and want to restore an existing VM
519 &$resolve_cdrom_alias($param);
521 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
523 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
525 foreach my $opt (keys %$param) {
526 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
527 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
528 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
530 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
531 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
535 PVE
::QemuServer
::add_random_macs
($param);
537 my $keystr = join(' ', keys %$param);
538 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
540 if ($archive eq '-') {
541 die "pipe requires cli environment\n"
542 if $rpcenv->{type
} ne 'cli';
544 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
545 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
549 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
551 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
552 die "$emsg $@" if $@;
554 my $restorefn = sub {
555 my $conf = PVE
::QemuConfig-
>load_config($vmid);
557 PVE
::QemuConfig-
>check_protection($conf, $emsg);
559 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
562 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
568 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
569 # Convert restored VM to template if backup was VM template
570 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
571 warn "Convert to template.\n";
572 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
576 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
579 # ensure no old replication state are exists
580 PVE
::ReplicationState
::delete_guest_states
($vmid);
582 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
584 if ($start_after_create) {
585 print "Execute autostart\n";
586 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
592 # ensure no old replication state are exists
593 PVE
::ReplicationState
::delete_guest_states
($vmid);
601 my $arch = PVE
::QemuServer
::get_vm_arch
($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
::RRD
::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
::RRD
::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});
936 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
937 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
939 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
942 # POST/PUT {vmid}/config implementation
944 # The original API used PUT (idempotent) an we assumed that all operations
945 # are fast. But it turned out that almost any configuration change can
946 # involve hot-plug actions, or disk alloc/free. Such actions can take long
947 # time to complete and have side effects (not idempotent).
949 # The new implementation uses POST and forks a worker process. We added
950 # a new option 'background_delay'. If specified we wait up to
951 # 'background_delay' second for the worker task to complete. It returns null
952 # if the task is finished within that time, else we return the UPID.
954 my $update_vm_api = sub {
955 my ($param, $sync) = @_;
957 my $rpcenv = PVE
::RPCEnvironment
::get
();
959 my $authuser = $rpcenv->get_user();
961 my $node = extract_param
($param, 'node');
963 my $vmid = extract_param
($param, 'vmid');
965 my $digest = extract_param
($param, 'digest');
967 my $background_delay = extract_param
($param, 'background_delay');
969 if (defined(my $cipassword = $param->{cipassword
})) {
970 # Same logic as in cloud-init (but with the regex fixed...)
971 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
972 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
975 my @paramarr = (); # used for log message
976 foreach my $key (sort keys %$param) {
977 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
978 push @paramarr, "-$key", $value;
981 my $skiplock = extract_param
($param, 'skiplock');
982 raise_param_exc
({ skiplock
=> "Only root may use this option." })
983 if $skiplock && $authuser ne 'root@pam';
985 my $delete_str = extract_param
($param, 'delete');
987 my $revert_str = extract_param
($param, 'revert');
989 my $force = extract_param
($param, 'force');
991 if (defined(my $ssh_keys = $param->{sshkeys
})) {
992 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
993 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
996 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
998 my $storecfg = PVE
::Storage
::config
();
1000 my $defaults = PVE
::QemuServer
::load_defaults
();
1002 &$resolve_cdrom_alias($param);
1004 # now try to verify all parameters
1007 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1008 if (!PVE
::QemuServer
::option_exists
($opt)) {
1009 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1012 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1013 "-revert $opt' at the same time" })
1014 if defined($param->{$opt});
1016 $revert->{$opt} = 1;
1020 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1021 $opt = 'ide2' if $opt eq 'cdrom';
1023 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1024 "-delete $opt' at the same time" })
1025 if defined($param->{$opt});
1027 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1028 "-revert $opt' at the same time" })
1031 if (!PVE
::QemuServer
::option_exists
($opt)) {
1032 raise_param_exc
({ delete => "unknown option '$opt'" });
1038 my $repl_conf = PVE
::ReplicationConfig-
>new();
1039 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1040 my $check_replication = sub {
1042 return if !$is_replicated;
1043 my $volid = $drive->{file
};
1044 return if !$volid || !($drive->{replicate
}//1);
1045 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1047 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1048 return if $volname eq 'cloudinit';
1051 if ($volid =~ $NEW_DISK_RE) {
1053 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1055 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1057 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1058 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1059 return if $scfg->{shared
};
1060 die "cannot add non-replicatable volume to a replicated VM\n";
1063 foreach my $opt (keys %$param) {
1064 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1065 # cleanup drive path
1066 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1067 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1068 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1069 $check_replication->($drive);
1070 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
1071 } elsif ($opt =~ m/^net(\d+)$/) {
1073 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1074 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1075 } elsif ($opt eq 'vmgenid') {
1076 if ($param->{$opt} eq '1') {
1077 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1079 } elsif ($opt eq 'hookscript') {
1080 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1081 raise_param_exc
({ $opt => $@ }) if $@;
1085 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1087 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1089 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1091 my $updatefn = sub {
1093 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1095 die "checksum missmatch (file change by other user?)\n"
1096 if $digest && $digest ne $conf->{digest
};
1098 # FIXME: 'suspended' lock should probabyl be a state or "weak" lock?!
1099 if (scalar(@delete) && grep { $_ eq 'vmstate'} @delete) {
1100 if (defined($conf->{lock}) && $conf->{lock} eq 'suspended') {
1101 delete $conf->{lock}; # for check lock check, not written out
1102 push @delete, 'lock'; # this is the real deal to write it out
1104 push @delete, 'runningmachine' if $conf->{runningmachine
};
1107 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1109 foreach my $opt (keys %$revert) {
1110 if (defined($conf->{$opt})) {
1111 $param->{$opt} = $conf->{$opt};
1112 } elsif (defined($conf->{pending
}->{$opt})) {
1117 if ($param->{memory
} || defined($param->{balloon
})) {
1118 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1119 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1121 die "balloon value too large (must be smaller than assigned memory)\n"
1122 if $balloon && $balloon > $maxmem;
1125 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1129 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1131 # write updates to pending section
1133 my $modified = {}; # record what $option we modify
1135 foreach my $opt (@delete) {
1136 $modified->{$opt} = 1;
1137 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1139 # value of what we want to delete, independent if pending or not
1140 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1141 if (!defined($val)) {
1142 warn "cannot delete '$opt' - not set in current configuration!\n";
1143 $modified->{$opt} = 0;
1146 my $is_pending_val = defined($conf->{pending
}->{$opt});
1147 delete $conf->{pending
}->{$opt};
1149 if ($opt =~ m/^unused/) {
1150 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1151 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1152 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1153 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1154 delete $conf->{$opt};
1155 PVE
::QemuConfig-
>write_config($vmid, $conf);
1157 } elsif ($opt eq 'vmstate') {
1158 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1159 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1160 delete $conf->{$opt};
1161 PVE
::QemuConfig-
>write_config($vmid, $conf);
1163 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1164 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1165 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1166 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1168 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1169 PVE
::QemuConfig-
>write_config($vmid, $conf);
1170 } elsif ($opt =~ m/^serial\d+$/) {
1171 if ($val eq 'socket') {
1172 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1173 } elsif ($authuser ne 'root@pam') {
1174 die "only root can delete '$opt' config for real devices\n";
1176 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1177 PVE
::QemuConfig-
>write_config($vmid, $conf);
1178 } elsif ($opt =~ m/^usb\d+$/) {
1179 if ($val =~ m/spice/) {
1180 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1181 } elsif ($authuser ne 'root@pam') {
1182 die "only root can delete '$opt' config for real devices\n";
1184 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1185 PVE
::QemuConfig-
>write_config($vmid, $conf);
1187 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1188 PVE
::QemuConfig-
>write_config($vmid, $conf);
1192 foreach my $opt (keys %$param) { # add/change
1193 $modified->{$opt} = 1;
1194 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1195 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1197 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1199 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1200 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1201 # FIXME: cloudinit: CDROM or Disk?
1202 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1203 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1205 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1207 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1208 if defined($conf->{pending
}->{$opt});
1210 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1211 } elsif ($opt =~ m/^serial\d+/) {
1212 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1213 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1214 } elsif ($authuser ne 'root@pam') {
1215 die "only root can modify '$opt' config for real devices\n";
1217 $conf->{pending
}->{$opt} = $param->{$opt};
1218 } elsif ($opt =~ m/^usb\d+/) {
1219 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1220 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1221 } elsif ($authuser ne 'root@pam') {
1222 die "only root can modify '$opt' config for real devices\n";
1224 $conf->{pending
}->{$opt} = $param->{$opt};
1226 $conf->{pending
}->{$opt} = $param->{$opt};
1228 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1229 PVE
::QemuConfig-
>write_config($vmid, $conf);
1232 # remove pending changes when nothing changed
1233 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1234 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1235 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1237 return if !scalar(keys %{$conf->{pending
}});
1239 my $running = PVE
::QemuServer
::check_running
($vmid);
1241 # apply pending changes
1243 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1247 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1249 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running, $errors);
1251 raise_param_exc
($errors) if scalar(keys %$errors);
1260 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1262 if ($background_delay) {
1264 # Note: It would be better to do that in the Event based HTTPServer
1265 # to avoid blocking call to sleep.
1267 my $end_time = time() + $background_delay;
1269 my $task = PVE
::Tools
::upid_decode
($upid);
1272 while (time() < $end_time) {
1273 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1275 sleep(1); # this gets interrupted when child process ends
1279 my $status = PVE
::Tools
::upid_read_status
($upid);
1280 return undef if $status eq 'OK';
1289 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1292 my $vm_config_perm_list = [
1297 'VM.Config.Network',
1299 'VM.Config.Options',
1302 __PACKAGE__-
>register_method({
1303 name
=> 'update_vm_async',
1304 path
=> '{vmid}/config',
1308 description
=> "Set virtual machine options (asynchrounous API).",
1310 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1313 additionalProperties
=> 0,
1314 properties
=> PVE
::QemuServer
::json_config_properties
(
1316 node
=> get_standard_option
('pve-node'),
1317 vmid
=> get_standard_option
('pve-vmid'),
1318 skiplock
=> get_standard_option
('skiplock'),
1320 type
=> 'string', format
=> 'pve-configid-list',
1321 description
=> "A list of settings you want to delete.",
1325 type
=> 'string', format
=> 'pve-configid-list',
1326 description
=> "Revert a pending change.",
1331 description
=> $opt_force_description,
1333 requires
=> 'delete',
1337 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1341 background_delay
=> {
1343 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1354 code
=> $update_vm_api,
1357 __PACKAGE__-
>register_method({
1358 name
=> 'update_vm',
1359 path
=> '{vmid}/config',
1363 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1365 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1368 additionalProperties
=> 0,
1369 properties
=> PVE
::QemuServer
::json_config_properties
(
1371 node
=> get_standard_option
('pve-node'),
1372 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1373 skiplock
=> get_standard_option
('skiplock'),
1375 type
=> 'string', format
=> 'pve-configid-list',
1376 description
=> "A list of settings you want to delete.",
1380 type
=> 'string', format
=> 'pve-configid-list',
1381 description
=> "Revert a pending change.",
1386 description
=> $opt_force_description,
1388 requires
=> 'delete',
1392 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1398 returns
=> { type
=> 'null' },
1401 &$update_vm_api($param, 1);
1406 __PACKAGE__-
>register_method({
1407 name
=> 'destroy_vm',
1412 description
=> "Destroy the vm (also delete all used/owned volumes).",
1414 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1417 additionalProperties
=> 0,
1419 node
=> get_standard_option
('pve-node'),
1420 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1421 skiplock
=> get_standard_option
('skiplock'),
1424 description
=> "Remove vmid from backup cron jobs.",
1435 my $rpcenv = PVE
::RPCEnvironment
::get
();
1436 my $authuser = $rpcenv->get_user();
1437 my $vmid = $param->{vmid
};
1439 my $skiplock = $param->{skiplock
};
1440 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1441 if $skiplock && $authuser ne 'root@pam';
1444 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1445 my $storecfg = PVE
::Storage
::config
();
1446 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1447 die "unable to remove VM $vmid - used in HA resources\n"
1448 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1450 if (!$param->{purge
}) {
1451 # don't allow destroy if with replication jobs but no purge param
1452 my $repl_conf = PVE
::ReplicationConfig-
>new();
1453 $repl_conf->check_for_existing_jobs($vmid);
1456 # early tests (repeat after locking)
1457 die "VM $vmid is running - destroy failed\n"
1458 if PVE
::QemuServer
::check_running
($vmid);
1463 syslog
('info', "destroy VM $vmid: $upid\n");
1464 PVE
::QemuConfig-
>lock_config($vmid, sub {
1465 die "VM $vmid is running - destroy failed\n"
1466 if (PVE
::QemuServer
::check_running
($vmid));
1468 PVE
::QemuServer
::destroy_vm
($storecfg, $vmid, $skiplock, { lock => 'destroyed' });
1470 PVE
::AccessControl
::remove_vm_access
($vmid);
1471 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1472 if ($param->{purge
}) {
1473 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1474 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1477 # only now remove the zombie config, else we can have reuse race
1478 PVE
::QemuConfig-
>destroy_config($vmid);
1482 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1485 __PACKAGE__-
>register_method({
1487 path
=> '{vmid}/unlink',
1491 description
=> "Unlink/delete disk images.",
1493 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1496 additionalProperties
=> 0,
1498 node
=> get_standard_option
('pve-node'),
1499 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1501 type
=> 'string', format
=> 'pve-configid-list',
1502 description
=> "A list of disk IDs you want to delete.",
1506 description
=> $opt_force_description,
1511 returns
=> { type
=> 'null'},
1515 $param->{delete} = extract_param
($param, 'idlist');
1517 __PACKAGE__-
>update_vm($param);
1524 __PACKAGE__-
>register_method({
1526 path
=> '{vmid}/vncproxy',
1530 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1532 description
=> "Creates a TCP VNC proxy connections.",
1534 additionalProperties
=> 0,
1536 node
=> get_standard_option
('pve-node'),
1537 vmid
=> get_standard_option
('pve-vmid'),
1541 description
=> "starts websockify instead of vncproxy",
1546 additionalProperties
=> 0,
1548 user
=> { type
=> 'string' },
1549 ticket
=> { type
=> 'string' },
1550 cert
=> { type
=> 'string' },
1551 port
=> { type
=> 'integer' },
1552 upid
=> { type
=> 'string' },
1558 my $rpcenv = PVE
::RPCEnvironment
::get
();
1560 my $authuser = $rpcenv->get_user();
1562 my $vmid = $param->{vmid
};
1563 my $node = $param->{node
};
1564 my $websocket = $param->{websocket
};
1566 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1567 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1569 my $authpath = "/vms/$vmid";
1571 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1573 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1579 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1580 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1581 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1582 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1583 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1585 $family = PVE
::Tools
::get_host_address_family
($node);
1588 my $port = PVE
::Tools
::next_vnc_port
($family);
1595 syslog
('info', "starting vnc proxy $upid\n");
1601 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1603 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1604 '-timeout', $timeout, '-authpath', $authpath,
1605 '-perm', 'Sys.Console'];
1607 if ($param->{websocket
}) {
1608 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1609 push @$cmd, '-notls', '-listen', 'localhost';
1612 push @$cmd, '-c', @$remcmd, @$termcmd;
1614 PVE
::Tools
::run_command
($cmd);
1618 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1620 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1622 my $sock = IO
::Socket
::IP-
>new(
1627 GetAddrInfoFlags
=> 0,
1628 ) or die "failed to create socket: $!\n";
1629 # Inside the worker we shouldn't have any previous alarms
1630 # running anyway...:
1632 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1634 accept(my $cli, $sock) or die "connection failed: $!\n";
1637 if (PVE
::Tools
::run_command
($cmd,
1638 output
=> '>&'.fileno($cli),
1639 input
=> '<&'.fileno($cli),
1642 die "Failed to run vncproxy.\n";
1649 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1651 PVE
::Tools
::wait_for_vnc_port
($port);
1662 __PACKAGE__-
>register_method({
1663 name
=> 'termproxy',
1664 path
=> '{vmid}/termproxy',
1668 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1670 description
=> "Creates a TCP proxy connections.",
1672 additionalProperties
=> 0,
1674 node
=> get_standard_option
('pve-node'),
1675 vmid
=> get_standard_option
('pve-vmid'),
1679 enum
=> [qw(serial0 serial1 serial2 serial3)],
1680 description
=> "opens a serial terminal (defaults to display)",
1685 additionalProperties
=> 0,
1687 user
=> { type
=> 'string' },
1688 ticket
=> { type
=> 'string' },
1689 port
=> { type
=> 'integer' },
1690 upid
=> { type
=> 'string' },
1696 my $rpcenv = PVE
::RPCEnvironment
::get
();
1698 my $authuser = $rpcenv->get_user();
1700 my $vmid = $param->{vmid
};
1701 my $node = $param->{node
};
1702 my $serial = $param->{serial
};
1704 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1706 if (!defined($serial)) {
1707 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1708 $serial = $conf->{vga
};
1712 my $authpath = "/vms/$vmid";
1714 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1719 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1720 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1721 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1722 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1723 push @$remcmd, '--';
1725 $family = PVE
::Tools
::get_host_address_family
($node);
1728 my $port = PVE
::Tools
::next_vnc_port
($family);
1730 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1731 push @$termcmd, '-iface', $serial if $serial;
1736 syslog
('info', "starting qemu termproxy $upid\n");
1738 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1739 '--perm', 'VM.Console', '--'];
1740 push @$cmd, @$remcmd, @$termcmd;
1742 PVE
::Tools
::run_command
($cmd);
1745 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1747 PVE
::Tools
::wait_for_vnc_port
($port);
1757 __PACKAGE__-
>register_method({
1758 name
=> 'vncwebsocket',
1759 path
=> '{vmid}/vncwebsocket',
1762 description
=> "You also need to pass a valid ticket (vncticket).",
1763 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1765 description
=> "Opens a weksocket for VNC traffic.",
1767 additionalProperties
=> 0,
1769 node
=> get_standard_option
('pve-node'),
1770 vmid
=> get_standard_option
('pve-vmid'),
1772 description
=> "Ticket from previous call to vncproxy.",
1777 description
=> "Port number returned by previous vncproxy call.",
1787 port
=> { type
=> 'string' },
1793 my $rpcenv = PVE
::RPCEnvironment
::get
();
1795 my $authuser = $rpcenv->get_user();
1797 my $vmid = $param->{vmid
};
1798 my $node = $param->{node
};
1800 my $authpath = "/vms/$vmid";
1802 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1804 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1806 # Note: VNC ports are acessible from outside, so we do not gain any
1807 # security if we verify that $param->{port} belongs to VM $vmid. This
1808 # check is done by verifying the VNC ticket (inside VNC protocol).
1810 my $port = $param->{port
};
1812 return { port
=> $port };
1815 __PACKAGE__-
>register_method({
1816 name
=> 'spiceproxy',
1817 path
=> '{vmid}/spiceproxy',
1822 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1824 description
=> "Returns a SPICE configuration to connect to the VM.",
1826 additionalProperties
=> 0,
1828 node
=> get_standard_option
('pve-node'),
1829 vmid
=> get_standard_option
('pve-vmid'),
1830 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1833 returns
=> get_standard_option
('remote-viewer-config'),
1837 my $rpcenv = PVE
::RPCEnvironment
::get
();
1839 my $authuser = $rpcenv->get_user();
1841 my $vmid = $param->{vmid
};
1842 my $node = $param->{node
};
1843 my $proxy = $param->{proxy
};
1845 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1846 my $title = "VM $vmid";
1847 $title .= " - ". $conf->{name
} if $conf->{name
};
1849 my $port = PVE
::QemuServer
::spice_port
($vmid);
1851 my ($ticket, undef, $remote_viewer_config) =
1852 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1854 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1855 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1857 return $remote_viewer_config;
1860 __PACKAGE__-
>register_method({
1862 path
=> '{vmid}/status',
1865 description
=> "Directory index",
1870 additionalProperties
=> 0,
1872 node
=> get_standard_option
('pve-node'),
1873 vmid
=> get_standard_option
('pve-vmid'),
1881 subdir
=> { type
=> 'string' },
1884 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1890 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1893 { subdir
=> 'current' },
1894 { subdir
=> 'start' },
1895 { subdir
=> 'stop' },
1896 { subdir
=> 'reset' },
1897 { subdir
=> 'shutdown' },
1898 { subdir
=> 'suspend' },
1899 { subdir
=> 'reboot' },
1905 __PACKAGE__-
>register_method({
1906 name
=> 'vm_status',
1907 path
=> '{vmid}/status/current',
1910 protected
=> 1, # qemu pid files are only readable by root
1911 description
=> "Get virtual machine status.",
1913 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1916 additionalProperties
=> 0,
1918 node
=> get_standard_option
('pve-node'),
1919 vmid
=> get_standard_option
('pve-vmid'),
1925 %$PVE::QemuServer
::vmstatus_return_properties
,
1927 description
=> "HA manager service status.",
1931 description
=> "Qemu VGA configuration supports spice.",
1936 description
=> "Qemu GuestAgent enabled in config.",
1946 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1948 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1949 my $status = $vmstatus->{$param->{vmid
}};
1951 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1953 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1954 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1959 __PACKAGE__-
>register_method({
1961 path
=> '{vmid}/status/start',
1965 description
=> "Start virtual machine.",
1967 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1970 additionalProperties
=> 0,
1972 node
=> get_standard_option
('pve-node'),
1973 vmid
=> get_standard_option
('pve-vmid',
1974 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1975 skiplock
=> get_standard_option
('skiplock'),
1976 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1977 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1980 enum
=> ['secure', 'insecure'],
1981 description
=> "Migration traffic is encrypted using an SSH " .
1982 "tunnel by default. On secure, completely private networks " .
1983 "this can be disabled to increase performance.",
1986 migration_network
=> {
1987 type
=> 'string', format
=> 'CIDR',
1988 description
=> "CIDR of the (sub) network that is used for migration.",
1991 machine
=> get_standard_option
('pve-qemu-machine'),
1993 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
2005 my $rpcenv = PVE
::RPCEnvironment
::get
();
2006 my $authuser = $rpcenv->get_user();
2008 my $node = extract_param
($param, 'node');
2009 my $vmid = extract_param
($param, 'vmid');
2011 my $machine = extract_param
($param, 'machine');
2013 my $get_root_param = sub {
2014 my $value = extract_param
($param, $_[0]);
2015 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2016 if $value && $authuser ne 'root@pam';
2020 my $stateuri = $get_root_param->('stateuri');
2021 my $skiplock = $get_root_param->('skiplock');
2022 my $migratedfrom = $get_root_param->('migratedfrom');
2023 my $migration_type = $get_root_param->('migration_type');
2024 my $migration_network = $get_root_param->('migration_network');
2025 my $targetstorage = $get_root_param->('targetstorage');
2027 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2028 if $targetstorage && !$migratedfrom;
2030 # read spice ticket from STDIN
2032 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2033 if (defined(my $line = <STDIN
>)) {
2035 $spice_ticket = $line;
2039 PVE
::Cluster
::check_cfs_quorum
();
2041 my $storecfg = PVE
::Storage
::config
();
2043 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2047 print "Requesting HA start for VM $vmid\n";
2049 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2050 PVE
::Tools
::run_command
($cmd);
2054 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2061 syslog
('info', "start VM $vmid: $upid\n");
2063 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2064 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
2068 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2072 __PACKAGE__-
>register_method({
2074 path
=> '{vmid}/status/stop',
2078 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2079 "is akin to pulling the power plug of a running computer and may damage the VM data",
2081 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2084 additionalProperties
=> 0,
2086 node
=> get_standard_option
('pve-node'),
2087 vmid
=> get_standard_option
('pve-vmid',
2088 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2089 skiplock
=> get_standard_option
('skiplock'),
2090 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2092 description
=> "Wait maximal timeout seconds.",
2098 description
=> "Do not deactivate storage volumes.",
2111 my $rpcenv = PVE
::RPCEnvironment
::get
();
2112 my $authuser = $rpcenv->get_user();
2114 my $node = extract_param
($param, 'node');
2115 my $vmid = extract_param
($param, 'vmid');
2117 my $skiplock = extract_param
($param, 'skiplock');
2118 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2119 if $skiplock && $authuser ne 'root@pam';
2121 my $keepActive = extract_param
($param, 'keepActive');
2122 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2123 if $keepActive && $authuser ne 'root@pam';
2125 my $migratedfrom = extract_param
($param, 'migratedfrom');
2126 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2127 if $migratedfrom && $authuser ne 'root@pam';
2130 my $storecfg = PVE
::Storage
::config
();
2132 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2137 print "Requesting HA stop for VM $vmid\n";
2139 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2140 PVE
::Tools
::run_command
($cmd);
2144 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2150 syslog
('info', "stop VM $vmid: $upid\n");
2152 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2153 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2157 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2161 __PACKAGE__-
>register_method({
2163 path
=> '{vmid}/status/reset',
2167 description
=> "Reset virtual machine.",
2169 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2172 additionalProperties
=> 0,
2174 node
=> get_standard_option
('pve-node'),
2175 vmid
=> get_standard_option
('pve-vmid',
2176 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2177 skiplock
=> get_standard_option
('skiplock'),
2186 my $rpcenv = PVE
::RPCEnvironment
::get
();
2188 my $authuser = $rpcenv->get_user();
2190 my $node = extract_param
($param, 'node');
2192 my $vmid = extract_param
($param, 'vmid');
2194 my $skiplock = extract_param
($param, 'skiplock');
2195 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2196 if $skiplock && $authuser ne 'root@pam';
2198 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2203 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2208 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2211 __PACKAGE__-
>register_method({
2212 name
=> 'vm_shutdown',
2213 path
=> '{vmid}/status/shutdown',
2217 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2218 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2220 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2223 additionalProperties
=> 0,
2225 node
=> get_standard_option
('pve-node'),
2226 vmid
=> get_standard_option
('pve-vmid',
2227 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2228 skiplock
=> get_standard_option
('skiplock'),
2230 description
=> "Wait maximal timeout seconds.",
2236 description
=> "Make sure the VM stops.",
2242 description
=> "Do not deactivate storage volumes.",
2255 my $rpcenv = PVE
::RPCEnvironment
::get
();
2256 my $authuser = $rpcenv->get_user();
2258 my $node = extract_param
($param, 'node');
2259 my $vmid = extract_param
($param, 'vmid');
2261 my $skiplock = extract_param
($param, 'skiplock');
2262 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2263 if $skiplock && $authuser ne 'root@pam';
2265 my $keepActive = extract_param
($param, 'keepActive');
2266 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2267 if $keepActive && $authuser ne 'root@pam';
2269 my $storecfg = PVE
::Storage
::config
();
2273 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2274 # otherwise, we will infer a shutdown command, but run into the timeout,
2275 # then when the vm is resumed, it will instantly shutdown
2277 # checking the qmp status here to get feedback to the gui/cli/api
2278 # and the status query should not take too long
2279 my $qmpstatus = eval {
2280 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2281 mon_cmd
($vmid, "query-status");
2285 if (!$err && $qmpstatus->{status
} eq "paused") {
2286 if ($param->{forceStop
}) {
2287 warn "VM is paused - stop instead of shutdown\n";
2290 die "VM is paused - cannot shutdown\n";
2294 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2296 my $timeout = $param->{timeout
} // 60;
2300 print "Requesting HA stop for VM $vmid\n";
2302 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2303 PVE
::Tools
::run_command
($cmd);
2307 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2314 syslog
('info', "shutdown VM $vmid: $upid\n");
2316 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2317 $shutdown, $param->{forceStop
}, $keepActive);
2321 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2325 __PACKAGE__-
>register_method({
2326 name
=> 'vm_reboot',
2327 path
=> '{vmid}/status/reboot',
2331 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2333 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2336 additionalProperties
=> 0,
2338 node
=> get_standard_option
('pve-node'),
2339 vmid
=> get_standard_option
('pve-vmid',
2340 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2342 description
=> "Wait maximal timeout seconds for the shutdown.",
2355 my $rpcenv = PVE
::RPCEnvironment
::get
();
2356 my $authuser = $rpcenv->get_user();
2358 my $node = extract_param
($param, 'node');
2359 my $vmid = extract_param
($param, 'vmid');
2361 my $qmpstatus = eval {
2362 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2363 mon_cmd
($vmid, "query-status");
2367 if (!$err && $qmpstatus->{status
} eq "paused") {
2368 die "VM is paused - cannot shutdown\n";
2371 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2376 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2377 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2381 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2384 __PACKAGE__-
>register_method({
2385 name
=> 'vm_suspend',
2386 path
=> '{vmid}/status/suspend',
2390 description
=> "Suspend virtual machine.",
2392 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2393 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2394 " on the storage for the vmstate.",
2395 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2398 additionalProperties
=> 0,
2400 node
=> get_standard_option
('pve-node'),
2401 vmid
=> get_standard_option
('pve-vmid',
2402 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2403 skiplock
=> get_standard_option
('skiplock'),
2408 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2410 statestorage
=> get_standard_option
('pve-storage-id', {
2411 description
=> "The storage for the VM state",
2412 requires
=> 'todisk',
2414 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2424 my $rpcenv = PVE
::RPCEnvironment
::get
();
2425 my $authuser = $rpcenv->get_user();
2427 my $node = extract_param
($param, 'node');
2428 my $vmid = extract_param
($param, 'vmid');
2430 my $todisk = extract_param
($param, 'todisk') // 0;
2432 my $statestorage = extract_param
($param, 'statestorage');
2434 my $skiplock = extract_param
($param, 'skiplock');
2435 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2436 if $skiplock && $authuser ne 'root@pam';
2438 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2440 die "Cannot suspend HA managed VM to disk\n"
2441 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2443 # early check for storage permission, for better user feedback
2445 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2447 if (!$statestorage) {
2448 # get statestorage from config if none is given
2449 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2450 my $storecfg = PVE
::Storage
::config
();
2451 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2454 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2460 syslog
('info', "suspend VM $vmid: $upid\n");
2462 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2467 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2468 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2471 __PACKAGE__-
>register_method({
2472 name
=> 'vm_resume',
2473 path
=> '{vmid}/status/resume',
2477 description
=> "Resume virtual machine.",
2479 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2482 additionalProperties
=> 0,
2484 node
=> get_standard_option
('pve-node'),
2485 vmid
=> get_standard_option
('pve-vmid',
2486 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2487 skiplock
=> get_standard_option
('skiplock'),
2488 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2498 my $rpcenv = PVE
::RPCEnvironment
::get
();
2500 my $authuser = $rpcenv->get_user();
2502 my $node = extract_param
($param, 'node');
2504 my $vmid = extract_param
($param, 'vmid');
2506 my $skiplock = extract_param
($param, 'skiplock');
2507 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2508 if $skiplock && $authuser ne 'root@pam';
2510 my $nocheck = extract_param
($param, 'nocheck');
2512 my $to_disk_suspended;
2514 PVE
::QemuConfig-
>lock_config($vmid, sub {
2515 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2516 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2520 die "VM $vmid not running\n"
2521 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2526 syslog
('info', "resume VM $vmid: $upid\n");
2528 if (!$to_disk_suspended) {
2529 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2531 my $storecfg = PVE
::Storage
::config
();
2532 PVE
::QemuServer
::vm_start
($storecfg, $vmid, undef, $skiplock);
2538 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2541 __PACKAGE__-
>register_method({
2542 name
=> 'vm_sendkey',
2543 path
=> '{vmid}/sendkey',
2547 description
=> "Send key event to virtual machine.",
2549 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2552 additionalProperties
=> 0,
2554 node
=> get_standard_option
('pve-node'),
2555 vmid
=> get_standard_option
('pve-vmid',
2556 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2557 skiplock
=> get_standard_option
('skiplock'),
2559 description
=> "The key (qemu monitor encoding).",
2564 returns
=> { type
=> 'null'},
2568 my $rpcenv = PVE
::RPCEnvironment
::get
();
2570 my $authuser = $rpcenv->get_user();
2572 my $node = extract_param
($param, 'node');
2574 my $vmid = extract_param
($param, 'vmid');
2576 my $skiplock = extract_param
($param, 'skiplock');
2577 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2578 if $skiplock && $authuser ne 'root@pam';
2580 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2585 __PACKAGE__-
>register_method({
2586 name
=> 'vm_feature',
2587 path
=> '{vmid}/feature',
2591 description
=> "Check if feature for virtual machine is available.",
2593 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2596 additionalProperties
=> 0,
2598 node
=> get_standard_option
('pve-node'),
2599 vmid
=> get_standard_option
('pve-vmid'),
2601 description
=> "Feature to check.",
2603 enum
=> [ 'snapshot', 'clone', 'copy' ],
2605 snapname
=> get_standard_option
('pve-snapshot-name', {
2613 hasFeature
=> { type
=> 'boolean' },
2616 items
=> { type
=> 'string' },
2623 my $node = extract_param
($param, 'node');
2625 my $vmid = extract_param
($param, 'vmid');
2627 my $snapname = extract_param
($param, 'snapname');
2629 my $feature = extract_param
($param, 'feature');
2631 my $running = PVE
::QemuServer
::check_running
($vmid);
2633 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2636 my $snap = $conf->{snapshots
}->{$snapname};
2637 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2640 my $storecfg = PVE
::Storage
::config
();
2642 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2643 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2646 hasFeature
=> $hasFeature,
2647 nodes
=> [ keys %$nodelist ],
2651 __PACKAGE__-
>register_method({
2653 path
=> '{vmid}/clone',
2657 description
=> "Create a copy of virtual machine/template.",
2659 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2660 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2661 "'Datastore.AllocateSpace' on any used storage.",
2664 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2666 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2667 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2672 additionalProperties
=> 0,
2674 node
=> get_standard_option
('pve-node'),
2675 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2676 newid
=> get_standard_option
('pve-vmid', {
2677 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2678 description
=> 'VMID for the clone.' }),
2681 type
=> 'string', format
=> 'dns-name',
2682 description
=> "Set a name for the new VM.",
2687 description
=> "Description for the new VM.",
2691 type
=> 'string', format
=> 'pve-poolid',
2692 description
=> "Add the new VM to the specified pool.",
2694 snapname
=> get_standard_option
('pve-snapshot-name', {
2697 storage
=> get_standard_option
('pve-storage-id', {
2698 description
=> "Target storage for full clone.",
2702 description
=> "Target format for file storage. Only valid for full clone.",
2705 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2710 description
=> "Create a full copy of all disks. This is always done when " .
2711 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2713 target
=> get_standard_option
('pve-node', {
2714 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2718 description
=> "Override I/O bandwidth limit (in KiB/s).",
2722 default => 'clone limit from datacenter or storage config',
2732 my $rpcenv = PVE
::RPCEnvironment
::get
();
2734 my $authuser = $rpcenv->get_user();
2736 my $node = extract_param
($param, 'node');
2738 my $vmid = extract_param
($param, 'vmid');
2740 my $newid = extract_param
($param, 'newid');
2742 my $pool = extract_param
($param, 'pool');
2744 if (defined($pool)) {
2745 $rpcenv->check_pool_exist($pool);
2748 my $snapname = extract_param
($param, 'snapname');
2750 my $storage = extract_param
($param, 'storage');
2752 my $format = extract_param
($param, 'format');
2754 my $target = extract_param
($param, 'target');
2756 my $localnode = PVE
::INotify
::nodename
();
2758 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2760 PVE
::Cluster
::check_node_exists
($target) if $target;
2762 my $storecfg = PVE
::Storage
::config
();
2765 # check if storage is enabled on local node
2766 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2768 # check if storage is available on target node
2769 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2770 # clone only works if target storage is shared
2771 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2772 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2776 PVE
::Cluster
::check_cfs_quorum
();
2778 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2780 # exclusive lock if VM is running - else shared lock is enough;
2781 my $shared_lock = $running ?
0 : 1;
2785 # do all tests after lock
2786 # we also try to do all tests before we fork the worker
2788 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2790 PVE
::QemuConfig-
>check_lock($conf);
2792 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2794 die "unexpected state change\n" if $verify_running != $running;
2796 die "snapshot '$snapname' does not exist\n"
2797 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2799 my $full = extract_param
($param, 'full');
2800 if (!defined($full)) {
2801 $full = !PVE
::QemuConfig-
>is_template($conf);
2804 die "parameter 'storage' not allowed for linked clones\n"
2805 if defined($storage) && !$full;
2807 die "parameter 'format' not allowed for linked clones\n"
2808 if defined($format) && !$full;
2810 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2812 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2814 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2816 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2818 die "unable to create VM $newid: config file already exists\n"
2821 my $newconf = { lock => 'clone' };
2826 foreach my $opt (keys %$oldconf) {
2827 my $value = $oldconf->{$opt};
2829 # do not copy snapshot related info
2830 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2831 $opt eq 'vmstate' || $opt eq 'snapstate';
2833 # no need to copy unused images, because VMID(owner) changes anyways
2834 next if $opt =~ m/^unused\d+$/;
2836 # always change MAC! address
2837 if ($opt =~ m/^net(\d+)$/) {
2838 my $net = PVE
::QemuServer
::parse_net
($value);
2839 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2840 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2841 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2842 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2843 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2844 die "unable to parse drive options for '$opt'\n" if !$drive;
2845 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2846 $newconf->{$opt} = $value; # simply copy configuration
2848 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2849 die "Full clone feature is not supported for drive '$opt'\n"
2850 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2851 $fullclone->{$opt} = 1;
2853 # not full means clone instead of copy
2854 die "Linked clone feature is not supported for drive '$opt'\n"
2855 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2857 $drives->{$opt} = $drive;
2858 push @$vollist, $drive->{file
};
2861 # copy everything else
2862 $newconf->{$opt} = $value;
2866 # auto generate a new uuid
2867 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2868 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2869 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2871 # auto generate a new vmgenid if the option was set
2872 if ($newconf->{vmgenid
}) {
2873 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2876 delete $newconf->{template
};
2878 if ($param->{name
}) {
2879 $newconf->{name
} = $param->{name
};
2881 if ($oldconf->{name
}) {
2882 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2884 $newconf->{name
} = "Copy-of-VM-$vmid";
2888 if ($param->{description
}) {
2889 $newconf->{description
} = $param->{description
};
2892 # create empty/temp config - this fails if VM already exists on other node
2893 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2898 my $newvollist = [];
2905 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2907 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2909 my $bwlimit = extract_param
($param, 'bwlimit');
2911 my $total_jobs = scalar(keys %{$drives});
2914 foreach my $opt (keys %$drives) {
2915 my $drive = $drives->{$opt};
2916 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2918 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
2919 my $storage_list = [ $src_sid ];
2920 push @$storage_list, $storage if defined($storage);
2921 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
2923 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2924 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2925 $jobs, $skipcomplete, $oldconf->{agent
}, $clonelimit);
2927 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
2929 PVE
::QemuConfig-
>write_config($newid, $newconf);
2933 delete $newconf->{lock};
2935 # do not write pending changes
2936 if (my @changes = keys %{$newconf->{pending
}}) {
2937 my $pending = join(',', @changes);
2938 warn "found pending changes for '$pending', discarding for clone\n";
2939 delete $newconf->{pending
};
2942 PVE
::QemuConfig-
>write_config($newid, $newconf);
2945 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2946 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2947 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2949 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2950 die "Failed to move config to node '$target' - rename failed: $!\n"
2951 if !rename($conffile, $newconffile);
2954 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2959 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2961 sleep 1; # some storage like rbd need to wait before release volume - really?
2963 foreach my $volid (@$newvollist) {
2964 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2967 die "clone failed: $err";
2973 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2975 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2978 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2979 # Aquire exclusive lock lock for $newid
2980 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2985 __PACKAGE__-
>register_method({
2986 name
=> 'move_vm_disk',
2987 path
=> '{vmid}/move_disk',
2991 description
=> "Move volume to different storage.",
2993 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2995 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2996 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
3000 additionalProperties
=> 0,
3002 node
=> get_standard_option
('pve-node'),
3003 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3006 description
=> "The disk you want to move.",
3007 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
3009 storage
=> get_standard_option
('pve-storage-id', {
3010 description
=> "Target storage.",
3011 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3015 description
=> "Target Format.",
3016 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3021 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3027 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3032 description
=> "Override I/O bandwidth limit (in KiB/s).",
3036 default => 'move limit from datacenter or storage config',
3042 description
=> "the task ID.",
3047 my $rpcenv = PVE
::RPCEnvironment
::get
();
3049 my $authuser = $rpcenv->get_user();
3051 my $node = extract_param
($param, 'node');
3053 my $vmid = extract_param
($param, 'vmid');
3055 my $digest = extract_param
($param, 'digest');
3057 my $disk = extract_param
($param, 'disk');
3059 my $storeid = extract_param
($param, 'storage');
3061 my $format = extract_param
($param, 'format');
3063 my $storecfg = PVE
::Storage
::config
();
3065 my $updatefn = sub {
3067 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3069 PVE
::QemuConfig-
>check_lock($conf);
3071 die "checksum missmatch (file change by other user?)\n"
3072 if $digest && $digest ne $conf->{digest
};
3074 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3076 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3078 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
3080 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3083 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3084 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3088 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
3089 (!$format || !$oldfmt || $oldfmt eq $format);
3091 # this only checks snapshots because $disk is passed!
3092 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3093 die "you can't move a disk with snapshots and delete the source\n"
3094 if $snapshotted && $param->{delete};
3096 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3098 my $running = PVE
::QemuServer
::check_running
($vmid);
3100 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3104 my $newvollist = [];
3110 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3112 warn "moving disk with snapshots, snapshots will not be moved!\n"
3115 my $bwlimit = extract_param
($param, 'bwlimit');
3116 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3118 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3119 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit);
3121 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3123 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3125 # convert moved disk to base if part of template
3126 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3127 if PVE
::QemuConfig-
>is_template($conf);
3129 PVE
::QemuConfig-
>write_config($vmid, $conf);
3131 if ($running && PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
} && PVE
::QemuServer
::qga_check_running
($vmid)) {
3132 eval { mon_cmd
($vmid, "guest-fstrim"); };
3136 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3137 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3144 foreach my $volid (@$newvollist) {
3145 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3148 die "storage migration failed: $err";
3151 if ($param->{delete}) {
3153 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3154 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3160 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3163 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3166 my $check_vm_disks_local = sub {
3167 my ($storecfg, $vmconf, $vmid) = @_;
3169 my $local_disks = {};
3171 # add some more information to the disks e.g. cdrom
3172 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3173 my ($volid, $attr) = @_;
3175 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3177 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3178 return if $scfg->{shared
};
3180 # The shared attr here is just a special case where the vdisk
3181 # is marked as shared manually
3182 return if $attr->{shared
};
3183 return if $attr->{cdrom
} and $volid eq "none";
3185 if (exists $local_disks->{$volid}) {
3186 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3188 $local_disks->{$volid} = $attr;
3189 # ensure volid is present in case it's needed
3190 $local_disks->{$volid}->{volid
} = $volid;
3194 return $local_disks;
3197 __PACKAGE__-
>register_method({
3198 name
=> 'migrate_vm_precondition',
3199 path
=> '{vmid}/migrate',
3203 description
=> "Get preconditions for migration.",
3205 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3208 additionalProperties
=> 0,
3210 node
=> get_standard_option
('pve-node'),
3211 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3212 target
=> get_standard_option
('pve-node', {
3213 description
=> "Target node.",
3214 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3222 running
=> { type
=> 'boolean' },
3226 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3228 not_allowed_nodes
=> {
3231 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3235 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3237 local_resources
=> {
3239 description
=> "List local resources e.g. pci, usb"
3246 my $rpcenv = PVE
::RPCEnvironment
::get
();
3248 my $authuser = $rpcenv->get_user();
3250 PVE
::Cluster
::check_cfs_quorum
();
3254 my $vmid = extract_param
($param, 'vmid');
3255 my $target = extract_param
($param, 'target');
3256 my $localnode = PVE
::INotify
::nodename
();
3260 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3261 my $storecfg = PVE
::Storage
::config
();
3264 # try to detect errors early
3265 PVE
::QemuConfig-
>check_lock($vmconf);
3267 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3269 # if vm is not running, return target nodes where local storage is available
3270 # for offline migration
3271 if (!$res->{running
}) {
3272 $res->{allowed_nodes
} = [];
3273 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3274 delete $checked_nodes->{$localnode};
3276 foreach my $node (keys %$checked_nodes) {
3277 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3278 push @{$res->{allowed_nodes
}}, $node;
3282 $res->{not_allowed_nodes
} = $checked_nodes;
3286 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3287 $res->{local_disks
} = [ values %$local_disks ];;
3289 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3291 $res->{local_resources
} = $local_resources;
3298 __PACKAGE__-
>register_method({
3299 name
=> 'migrate_vm',
3300 path
=> '{vmid}/migrate',
3304 description
=> "Migrate virtual machine. Creates a new migration task.",
3306 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3309 additionalProperties
=> 0,
3311 node
=> get_standard_option
('pve-node'),
3312 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3313 target
=> get_standard_option
('pve-node', {
3314 description
=> "Target node.",
3315 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3319 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3324 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3329 enum
=> ['secure', 'insecure'],
3330 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3333 migration_network
=> {
3334 type
=> 'string', format
=> 'CIDR',
3335 description
=> "CIDR of the (sub) network that is used for migration.",
3338 "with-local-disks" => {
3340 description
=> "Enable live storage migration for local disk",
3343 targetstorage
=> get_standard_option
('pve-storage-id', {
3344 description
=> "Default target storage.",
3346 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3349 description
=> "Override I/O bandwidth limit (in KiB/s).",
3353 default => 'migrate limit from datacenter or storage config',
3359 description
=> "the task ID.",
3364 my $rpcenv = PVE
::RPCEnvironment
::get
();
3365 my $authuser = $rpcenv->get_user();
3367 my $target = extract_param
($param, 'target');
3369 my $localnode = PVE
::INotify
::nodename
();
3370 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3372 PVE
::Cluster
::check_cfs_quorum
();
3374 PVE
::Cluster
::check_node_exists
($target);
3376 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3378 my $vmid = extract_param
($param, 'vmid');
3380 raise_param_exc
({ force
=> "Only root may use this option." })
3381 if $param->{force
} && $authuser ne 'root@pam';
3383 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3384 if $param->{migration_type
} && $authuser ne 'root@pam';
3386 # allow root only until better network permissions are available
3387 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3388 if $param->{migration_network
} && $authuser ne 'root@pam';
3391 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3393 # try to detect errors early
3395 PVE
::QemuConfig-
>check_lock($conf);
3397 if (PVE
::QemuServer
::check_running
($vmid)) {
3398 die "can't migrate running VM without --online\n" if !$param->{online
};
3400 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
3401 $param->{online
} = 0;
3404 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3405 if !$param->{online
} && $param->{targetstorage
};
3407 my $storecfg = PVE
::Storage
::config
();
3409 if( $param->{targetstorage
}) {
3410 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3412 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3415 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3420 print "Requesting HA migration for VM $vmid to node $target\n";
3422 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3423 PVE
::Tools
::run_command
($cmd);
3427 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3432 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3436 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3439 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3444 __PACKAGE__-
>register_method({
3446 path
=> '{vmid}/monitor',
3450 description
=> "Execute Qemu monitor commands.",
3452 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3453 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3456 additionalProperties
=> 0,
3458 node
=> get_standard_option
('pve-node'),
3459 vmid
=> get_standard_option
('pve-vmid'),
3462 description
=> "The monitor command.",
3466 returns
=> { type
=> 'string'},
3470 my $rpcenv = PVE
::RPCEnvironment
::get
();
3471 my $authuser = $rpcenv->get_user();
3474 my $command = shift;
3475 return $command =~ m/^\s*info(\s+|$)/
3476 || $command =~ m/^\s*help\s*$/;
3479 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3480 if !&$is_ro($param->{command
});
3482 my $vmid = $param->{vmid
};
3484 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3488 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3490 $res = "ERROR: $@" if $@;
3495 __PACKAGE__-
>register_method({
3496 name
=> 'resize_vm',
3497 path
=> '{vmid}/resize',
3501 description
=> "Extend volume size.",
3503 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3506 additionalProperties
=> 0,
3508 node
=> get_standard_option
('pve-node'),
3509 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3510 skiplock
=> get_standard_option
('skiplock'),
3513 description
=> "The disk you want to resize.",
3514 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3518 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3519 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.",
3523 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3529 returns
=> { type
=> 'null'},
3533 my $rpcenv = PVE
::RPCEnvironment
::get
();
3535 my $authuser = $rpcenv->get_user();
3537 my $node = extract_param
($param, 'node');
3539 my $vmid = extract_param
($param, 'vmid');
3541 my $digest = extract_param
($param, 'digest');
3543 my $disk = extract_param
($param, 'disk');
3545 my $sizestr = extract_param
($param, 'size');
3547 my $skiplock = extract_param
($param, 'skiplock');
3548 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3549 if $skiplock && $authuser ne 'root@pam';
3551 my $storecfg = PVE
::Storage
::config
();
3553 my $updatefn = sub {
3555 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3557 die "checksum missmatch (file change by other user?)\n"
3558 if $digest && $digest ne $conf->{digest
};
3559 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3561 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3563 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3565 my (undef, undef, undef, undef, undef, undef, $format) =
3566 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3568 die "can't resize volume: $disk if snapshot exists\n"
3569 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3571 my $volid = $drive->{file
};
3573 die "disk '$disk' has no associated volume\n" if !$volid;
3575 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3577 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3579 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3581 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3582 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3584 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3586 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3587 my ($ext, $newsize, $unit) = ($1, $2, $4);
3590 $newsize = $newsize * 1024;
3591 } elsif ($unit eq 'M') {
3592 $newsize = $newsize * 1024 * 1024;
3593 } elsif ($unit eq 'G') {
3594 $newsize = $newsize * 1024 * 1024 * 1024;
3595 } elsif ($unit eq 'T') {
3596 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3599 $newsize += $size if $ext;
3600 $newsize = int($newsize);
3602 die "shrinking disks is not supported\n" if $newsize < $size;
3604 return if $size == $newsize;
3606 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3608 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3610 $drive->{size
} = $newsize;
3611 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
3613 PVE
::QemuConfig-
>write_config($vmid, $conf);
3616 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3620 __PACKAGE__-
>register_method({
3621 name
=> 'snapshot_list',
3622 path
=> '{vmid}/snapshot',
3624 description
=> "List all snapshots.",
3626 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3629 protected
=> 1, # qemu pid files are only readable by root
3631 additionalProperties
=> 0,
3633 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3634 node
=> get_standard_option
('pve-node'),
3643 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3647 description
=> "Snapshot includes RAM.",
3652 description
=> "Snapshot description.",
3656 description
=> "Snapshot creation time",
3658 renderer
=> 'timestamp',
3662 description
=> "Parent snapshot identifier.",
3668 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3673 my $vmid = $param->{vmid
};
3675 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3676 my $snaphash = $conf->{snapshots
} || {};
3680 foreach my $name (keys %$snaphash) {
3681 my $d = $snaphash->{$name};
3684 snaptime
=> $d->{snaptime
} || 0,
3685 vmstate
=> $d->{vmstate
} ?
1 : 0,
3686 description
=> $d->{description
} || '',
3688 $item->{parent
} = $d->{parent
} if $d->{parent
};
3689 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3693 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3696 digest
=> $conf->{digest
},
3697 running
=> $running,
3698 description
=> "You are here!",
3700 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3702 push @$res, $current;
3707 __PACKAGE__-
>register_method({
3709 path
=> '{vmid}/snapshot',
3713 description
=> "Snapshot a VM.",
3715 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3718 additionalProperties
=> 0,
3720 node
=> get_standard_option
('pve-node'),
3721 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3722 snapname
=> get_standard_option
('pve-snapshot-name'),
3726 description
=> "Save the vmstate",
3731 description
=> "A textual description or comment.",
3737 description
=> "the task ID.",
3742 my $rpcenv = PVE
::RPCEnvironment
::get
();
3744 my $authuser = $rpcenv->get_user();
3746 my $node = extract_param
($param, 'node');
3748 my $vmid = extract_param
($param, 'vmid');
3750 my $snapname = extract_param
($param, 'snapname');
3752 die "unable to use snapshot name 'current' (reserved name)\n"
3753 if $snapname eq 'current';
3755 die "unable to use snapshot name 'pending' (reserved name)\n"
3756 if lc($snapname) eq 'pending';
3759 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3760 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3761 $param->{description
});
3764 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3767 __PACKAGE__-
>register_method({
3768 name
=> 'snapshot_cmd_idx',
3769 path
=> '{vmid}/snapshot/{snapname}',
3776 additionalProperties
=> 0,
3778 vmid
=> get_standard_option
('pve-vmid'),
3779 node
=> get_standard_option
('pve-node'),
3780 snapname
=> get_standard_option
('pve-snapshot-name'),
3789 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3796 push @$res, { cmd
=> 'rollback' };
3797 push @$res, { cmd
=> 'config' };
3802 __PACKAGE__-
>register_method({
3803 name
=> 'update_snapshot_config',
3804 path
=> '{vmid}/snapshot/{snapname}/config',
3808 description
=> "Update snapshot metadata.",
3810 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3813 additionalProperties
=> 0,
3815 node
=> get_standard_option
('pve-node'),
3816 vmid
=> get_standard_option
('pve-vmid'),
3817 snapname
=> get_standard_option
('pve-snapshot-name'),
3821 description
=> "A textual description or comment.",
3825 returns
=> { type
=> 'null' },
3829 my $rpcenv = PVE
::RPCEnvironment
::get
();
3831 my $authuser = $rpcenv->get_user();
3833 my $vmid = extract_param
($param, 'vmid');
3835 my $snapname = extract_param
($param, 'snapname');
3837 return undef if !defined($param->{description
});
3839 my $updatefn = sub {
3841 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3843 PVE
::QemuConfig-
>check_lock($conf);
3845 my $snap = $conf->{snapshots
}->{$snapname};
3847 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3849 $snap->{description
} = $param->{description
} if defined($param->{description
});
3851 PVE
::QemuConfig-
>write_config($vmid, $conf);
3854 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3859 __PACKAGE__-
>register_method({
3860 name
=> 'get_snapshot_config',
3861 path
=> '{vmid}/snapshot/{snapname}/config',
3864 description
=> "Get snapshot configuration",
3866 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3869 additionalProperties
=> 0,
3871 node
=> get_standard_option
('pve-node'),
3872 vmid
=> get_standard_option
('pve-vmid'),
3873 snapname
=> get_standard_option
('pve-snapshot-name'),
3876 returns
=> { type
=> "object" },
3880 my $rpcenv = PVE
::RPCEnvironment
::get
();
3882 my $authuser = $rpcenv->get_user();
3884 my $vmid = extract_param
($param, 'vmid');
3886 my $snapname = extract_param
($param, 'snapname');
3888 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3890 my $snap = $conf->{snapshots
}->{$snapname};
3892 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3897 __PACKAGE__-
>register_method({
3899 path
=> '{vmid}/snapshot/{snapname}/rollback',
3903 description
=> "Rollback VM state to specified snapshot.",
3905 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3908 additionalProperties
=> 0,
3910 node
=> get_standard_option
('pve-node'),
3911 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3912 snapname
=> get_standard_option
('pve-snapshot-name'),
3917 description
=> "the task ID.",
3922 my $rpcenv = PVE
::RPCEnvironment
::get
();
3924 my $authuser = $rpcenv->get_user();
3926 my $node = extract_param
($param, 'node');
3928 my $vmid = extract_param
($param, 'vmid');
3930 my $snapname = extract_param
($param, 'snapname');
3933 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3934 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3938 # hold migration lock, this makes sure that nobody create replication snapshots
3939 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3942 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3945 __PACKAGE__-
>register_method({
3946 name
=> 'delsnapshot',
3947 path
=> '{vmid}/snapshot/{snapname}',
3951 description
=> "Delete a VM snapshot.",
3953 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3956 additionalProperties
=> 0,
3958 node
=> get_standard_option
('pve-node'),
3959 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3960 snapname
=> get_standard_option
('pve-snapshot-name'),
3964 description
=> "For removal from config file, even if removing disk snapshots fails.",
3970 description
=> "the task ID.",
3975 my $rpcenv = PVE
::RPCEnvironment
::get
();
3977 my $authuser = $rpcenv->get_user();
3979 my $node = extract_param
($param, 'node');
3981 my $vmid = extract_param
($param, 'vmid');
3983 my $snapname = extract_param
($param, 'snapname');
3986 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3987 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3990 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3993 __PACKAGE__-
>register_method({
3995 path
=> '{vmid}/template',
3999 description
=> "Create a Template.",
4001 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4002 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4005 additionalProperties
=> 0,
4007 node
=> get_standard_option
('pve-node'),
4008 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4012 description
=> "If you want to convert only 1 disk to base image.",
4013 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
4018 returns
=> { type
=> 'null'},
4022 my $rpcenv = PVE
::RPCEnvironment
::get
();
4024 my $authuser = $rpcenv->get_user();
4026 my $node = extract_param
($param, 'node');
4028 my $vmid = extract_param
($param, 'vmid');
4030 my $disk = extract_param
($param, 'disk');
4032 my $updatefn = sub {
4034 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4036 PVE
::QemuConfig-
>check_lock($conf);
4038 die "unable to create template, because VM contains snapshots\n"
4039 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4041 die "you can't convert a template to a template\n"
4042 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4044 die "you can't convert a VM to template if VM is running\n"
4045 if PVE
::QemuServer
::check_running
($vmid);
4048 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4051 $conf->{template
} = 1;
4052 PVE
::QemuConfig-
>write_config($vmid, $conf);
4054 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4057 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4061 __PACKAGE__-
>register_method({
4062 name
=> 'cloudinit_generated_config_dump',
4063 path
=> '{vmid}/cloudinit/dump',
4066 description
=> "Get automatically generated cloudinit config.",
4068 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4071 additionalProperties
=> 0,
4073 node
=> get_standard_option
('pve-node'),
4074 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4076 description
=> 'Config type.',
4078 enum
=> ['user', 'network', 'meta'],
4088 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4090 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});