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;
578 if ($start_after_create) {
579 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
584 # ensure no old replication state are exists
585 PVE
::ReplicationState
::delete_guest_states
($vmid);
587 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
591 # ensure no old replication state are exists
592 PVE
::ReplicationState
::delete_guest_states
($vmid);
600 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
604 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
606 if (!$conf->{bootdisk
}) {
607 my $firstdisk = PVE
::QemuServer
::resolve_first_disk
($conf);
608 $conf->{bootdisk
} = $firstdisk if $firstdisk;
611 # auto generate uuid if user did not specify smbios1 option
612 if (!$conf->{smbios1
}) {
613 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
616 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
617 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
620 PVE
::QemuConfig-
>write_config($vmid, $conf);
626 foreach my $volid (@$vollist) {
627 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
633 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
636 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
638 if ($start_after_create) {
639 print "Execute autostart\n";
640 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
645 my ($code, $worker_name);
647 $worker_name = 'qmrestore';
649 eval { $restorefn->() };
651 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
657 $worker_name = 'qmcreate';
659 eval { $createfn->() };
662 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
663 unlink($conffile) or die "failed to remove config file: $!\n";
671 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
674 __PACKAGE__-
>register_method({
679 description
=> "Directory index",
684 additionalProperties
=> 0,
686 node
=> get_standard_option
('pve-node'),
687 vmid
=> get_standard_option
('pve-vmid'),
695 subdir
=> { type
=> 'string' },
698 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
704 { subdir
=> 'config' },
705 { subdir
=> 'pending' },
706 { subdir
=> 'status' },
707 { subdir
=> 'unlink' },
708 { subdir
=> 'vncproxy' },
709 { subdir
=> 'termproxy' },
710 { subdir
=> 'migrate' },
711 { subdir
=> 'resize' },
712 { subdir
=> 'move' },
714 { subdir
=> 'rrddata' },
715 { subdir
=> 'monitor' },
716 { subdir
=> 'agent' },
717 { subdir
=> 'snapshot' },
718 { subdir
=> 'spiceproxy' },
719 { subdir
=> 'sendkey' },
720 { subdir
=> 'firewall' },
726 __PACKAGE__-
>register_method ({
727 subclass
=> "PVE::API2::Firewall::VM",
728 path
=> '{vmid}/firewall',
731 __PACKAGE__-
>register_method ({
732 subclass
=> "PVE::API2::Qemu::Agent",
733 path
=> '{vmid}/agent',
736 __PACKAGE__-
>register_method({
738 path
=> '{vmid}/rrd',
740 protected
=> 1, # fixme: can we avoid that?
742 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
744 description
=> "Read VM RRD statistics (returns PNG)",
746 additionalProperties
=> 0,
748 node
=> get_standard_option
('pve-node'),
749 vmid
=> get_standard_option
('pve-vmid'),
751 description
=> "Specify the time frame you are interested in.",
753 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
756 description
=> "The list of datasources you want to display.",
757 type
=> 'string', format
=> 'pve-configid-list',
760 description
=> "The RRD consolidation function",
762 enum
=> [ 'AVERAGE', 'MAX' ],
770 filename
=> { type
=> 'string' },
776 return PVE
::RRD
::create_rrd_graph
(
777 "pve2-vm/$param->{vmid}", $param->{timeframe
},
778 $param->{ds
}, $param->{cf
});
782 __PACKAGE__-
>register_method({
784 path
=> '{vmid}/rrddata',
786 protected
=> 1, # fixme: can we avoid that?
788 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
790 description
=> "Read VM RRD statistics",
792 additionalProperties
=> 0,
794 node
=> get_standard_option
('pve-node'),
795 vmid
=> get_standard_option
('pve-vmid'),
797 description
=> "Specify the time frame you are interested in.",
799 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
802 description
=> "The RRD consolidation function",
804 enum
=> [ 'AVERAGE', 'MAX' ],
819 return PVE
::RRD
::create_rrd_data
(
820 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
824 __PACKAGE__-
>register_method({
826 path
=> '{vmid}/config',
829 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
831 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
834 additionalProperties
=> 0,
836 node
=> get_standard_option
('pve-node'),
837 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
839 description
=> "Get current values (instead of pending values).",
844 snapshot
=> get_standard_option
('pve-snapshot-name', {
845 description
=> "Fetch config values from given snapshot.",
848 my ($cmd, $pname, $cur, $args) = @_;
849 PVE
::QemuConfig-
>snapshot_list($args->[0]);
855 description
=> "The current VM configuration.",
857 properties
=> PVE
::QemuServer
::json_config_properties
({
860 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
867 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
868 current
=> "cannot use 'snapshot' parameter with 'current'"})
869 if ($param->{snapshot
} && $param->{current
});
872 if ($param->{snapshot
}) {
873 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
875 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
877 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
882 __PACKAGE__-
>register_method({
883 name
=> 'vm_pending',
884 path
=> '{vmid}/pending',
887 description
=> "Get virtual machine configuration, including pending changes.",
889 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
892 additionalProperties
=> 0,
894 node
=> get_standard_option
('pve-node'),
895 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
904 description
=> "Configuration option name.",
908 description
=> "Current value.",
913 description
=> "Pending value.",
918 description
=> "Indicates a pending delete request if present and not 0. " .
919 "The value 2 indicates a force-delete request.",
931 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
933 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
935 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
936 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
938 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
941 # POST/PUT {vmid}/config implementation
943 # The original API used PUT (idempotent) an we assumed that all operations
944 # are fast. But it turned out that almost any configuration change can
945 # involve hot-plug actions, or disk alloc/free. Such actions can take long
946 # time to complete and have side effects (not idempotent).
948 # The new implementation uses POST and forks a worker process. We added
949 # a new option 'background_delay'. If specified we wait up to
950 # 'background_delay' second for the worker task to complete. It returns null
951 # if the task is finished within that time, else we return the UPID.
953 my $update_vm_api = sub {
954 my ($param, $sync) = @_;
956 my $rpcenv = PVE
::RPCEnvironment
::get
();
958 my $authuser = $rpcenv->get_user();
960 my $node = extract_param
($param, 'node');
962 my $vmid = extract_param
($param, 'vmid');
964 my $digest = extract_param
($param, 'digest');
966 my $background_delay = extract_param
($param, 'background_delay');
968 if (defined(my $cipassword = $param->{cipassword
})) {
969 # Same logic as in cloud-init (but with the regex fixed...)
970 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
971 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
974 my @paramarr = (); # used for log message
975 foreach my $key (sort keys %$param) {
976 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
977 push @paramarr, "-$key", $value;
980 my $skiplock = extract_param
($param, 'skiplock');
981 raise_param_exc
({ skiplock
=> "Only root may use this option." })
982 if $skiplock && $authuser ne 'root@pam';
984 my $delete_str = extract_param
($param, 'delete');
986 my $revert_str = extract_param
($param, 'revert');
988 my $force = extract_param
($param, 'force');
990 if (defined(my $ssh_keys = $param->{sshkeys
})) {
991 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
992 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
995 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
997 my $storecfg = PVE
::Storage
::config
();
999 my $defaults = PVE
::QemuServer
::load_defaults
();
1001 &$resolve_cdrom_alias($param);
1003 # now try to verify all parameters
1006 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1007 if (!PVE
::QemuServer
::option_exists
($opt)) {
1008 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1011 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1012 "-revert $opt' at the same time" })
1013 if defined($param->{$opt});
1015 $revert->{$opt} = 1;
1019 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1020 $opt = 'ide2' if $opt eq 'cdrom';
1022 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1023 "-delete $opt' at the same time" })
1024 if defined($param->{$opt});
1026 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1027 "-revert $opt' at the same time" })
1030 if (!PVE
::QemuServer
::option_exists
($opt)) {
1031 raise_param_exc
({ delete => "unknown option '$opt'" });
1037 my $repl_conf = PVE
::ReplicationConfig-
>new();
1038 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1039 my $check_replication = sub {
1041 return if !$is_replicated;
1042 my $volid = $drive->{file
};
1043 return if !$volid || !($drive->{replicate
}//1);
1044 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1046 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1047 return if $volname eq 'cloudinit';
1050 if ($volid =~ $NEW_DISK_RE) {
1052 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1054 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1056 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1057 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1058 return if $scfg->{shared
};
1059 die "cannot add non-replicatable volume to a replicated VM\n";
1062 foreach my $opt (keys %$param) {
1063 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1064 # cleanup drive path
1065 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1066 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1067 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1068 $check_replication->($drive);
1069 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
1070 } elsif ($opt =~ m/^net(\d+)$/) {
1072 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1073 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1074 } elsif ($opt eq 'vmgenid') {
1075 if ($param->{$opt} eq '1') {
1076 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1078 } elsif ($opt eq 'hookscript') {
1079 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1080 raise_param_exc
({ $opt => $@ }) if $@;
1084 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1086 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1088 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1090 my $updatefn = sub {
1092 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1094 die "checksum missmatch (file change by other user?)\n"
1095 if $digest && $digest ne $conf->{digest
};
1097 # FIXME: 'suspended' lock should probabyl be a state or "weak" lock?!
1098 if (scalar(@delete) && grep { $_ eq 'vmstate'} @delete) {
1099 if (defined($conf->{lock}) && $conf->{lock} eq 'suspended') {
1100 delete $conf->{lock}; # for check lock check, not written out
1101 push @delete, 'lock'; # this is the real deal to write it out
1103 push @delete, 'runningmachine' if $conf->{runningmachine
};
1106 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1108 foreach my $opt (keys %$revert) {
1109 if (defined($conf->{$opt})) {
1110 $param->{$opt} = $conf->{$opt};
1111 } elsif (defined($conf->{pending
}->{$opt})) {
1116 if ($param->{memory
} || defined($param->{balloon
})) {
1117 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1118 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1120 die "balloon value too large (must be smaller than assigned memory)\n"
1121 if $balloon && $balloon > $maxmem;
1124 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1128 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1130 # write updates to pending section
1132 my $modified = {}; # record what $option we modify
1134 foreach my $opt (@delete) {
1135 $modified->{$opt} = 1;
1136 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1138 # value of what we want to delete, independent if pending or not
1139 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1140 if (!defined($val)) {
1141 warn "cannot delete '$opt' - not set in current configuration!\n";
1142 $modified->{$opt} = 0;
1145 my $is_pending_val = defined($conf->{pending
}->{$opt});
1146 delete $conf->{pending
}->{$opt};
1148 if ($opt =~ m/^unused/) {
1149 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1150 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1151 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1152 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1153 delete $conf->{$opt};
1154 PVE
::QemuConfig-
>write_config($vmid, $conf);
1156 } elsif ($opt eq 'vmstate') {
1157 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1158 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1159 delete $conf->{$opt};
1160 PVE
::QemuConfig-
>write_config($vmid, $conf);
1162 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1163 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1164 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1165 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1167 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1168 PVE
::QemuConfig-
>write_config($vmid, $conf);
1169 } elsif ($opt =~ m/^serial\d+$/) {
1170 if ($val eq 'socket') {
1171 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1172 } elsif ($authuser ne 'root@pam') {
1173 die "only root can delete '$opt' config for real devices\n";
1175 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1176 PVE
::QemuConfig-
>write_config($vmid, $conf);
1177 } elsif ($opt =~ m/^usb\d+$/) {
1178 if ($val =~ m/spice/) {
1179 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1180 } elsif ($authuser ne 'root@pam') {
1181 die "only root can delete '$opt' config for real devices\n";
1183 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1184 PVE
::QemuConfig-
>write_config($vmid, $conf);
1186 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1187 PVE
::QemuConfig-
>write_config($vmid, $conf);
1191 foreach my $opt (keys %$param) { # add/change
1192 $modified->{$opt} = 1;
1193 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1194 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1196 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1198 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1199 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1200 # FIXME: cloudinit: CDROM or Disk?
1201 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1202 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1204 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1206 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1207 if defined($conf->{pending
}->{$opt});
1209 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1210 } elsif ($opt =~ m/^serial\d+/) {
1211 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1212 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1213 } elsif ($authuser ne 'root@pam') {
1214 die "only root can modify '$opt' config for real devices\n";
1216 $conf->{pending
}->{$opt} = $param->{$opt};
1217 } elsif ($opt =~ m/^usb\d+/) {
1218 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1219 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1220 } elsif ($authuser ne 'root@pam') {
1221 die "only root can modify '$opt' config for real devices\n";
1223 $conf->{pending
}->{$opt} = $param->{$opt};
1225 $conf->{pending
}->{$opt} = $param->{$opt};
1227 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1228 PVE
::QemuConfig-
>write_config($vmid, $conf);
1231 # remove pending changes when nothing changed
1232 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1233 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1234 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1236 return if !scalar(keys %{$conf->{pending
}});
1238 my $running = PVE
::QemuServer
::check_running
($vmid);
1240 # apply pending changes
1242 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1246 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1247 raise_param_exc
($errors) if scalar(keys %$errors);
1249 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1259 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1261 if ($background_delay) {
1263 # Note: It would be better to do that in the Event based HTTPServer
1264 # to avoid blocking call to sleep.
1266 my $end_time = time() + $background_delay;
1268 my $task = PVE
::Tools
::upid_decode
($upid);
1271 while (time() < $end_time) {
1272 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1274 sleep(1); # this gets interrupted when child process ends
1278 my $status = PVE
::Tools
::upid_read_status
($upid);
1279 return undef if $status eq 'OK';
1288 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1291 my $vm_config_perm_list = [
1296 'VM.Config.Network',
1298 'VM.Config.Options',
1301 __PACKAGE__-
>register_method({
1302 name
=> 'update_vm_async',
1303 path
=> '{vmid}/config',
1307 description
=> "Set virtual machine options (asynchrounous API).",
1309 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1312 additionalProperties
=> 0,
1313 properties
=> PVE
::QemuServer
::json_config_properties
(
1315 node
=> get_standard_option
('pve-node'),
1316 vmid
=> get_standard_option
('pve-vmid'),
1317 skiplock
=> get_standard_option
('skiplock'),
1319 type
=> 'string', format
=> 'pve-configid-list',
1320 description
=> "A list of settings you want to delete.",
1324 type
=> 'string', format
=> 'pve-configid-list',
1325 description
=> "Revert a pending change.",
1330 description
=> $opt_force_description,
1332 requires
=> 'delete',
1336 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1340 background_delay
=> {
1342 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1353 code
=> $update_vm_api,
1356 __PACKAGE__-
>register_method({
1357 name
=> 'update_vm',
1358 path
=> '{vmid}/config',
1362 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1364 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1367 additionalProperties
=> 0,
1368 properties
=> PVE
::QemuServer
::json_config_properties
(
1370 node
=> get_standard_option
('pve-node'),
1371 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1372 skiplock
=> get_standard_option
('skiplock'),
1374 type
=> 'string', format
=> 'pve-configid-list',
1375 description
=> "A list of settings you want to delete.",
1379 type
=> 'string', format
=> 'pve-configid-list',
1380 description
=> "Revert a pending change.",
1385 description
=> $opt_force_description,
1387 requires
=> 'delete',
1391 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1397 returns
=> { type
=> 'null' },
1400 &$update_vm_api($param, 1);
1405 __PACKAGE__-
>register_method({
1406 name
=> 'destroy_vm',
1411 description
=> "Destroy the vm (also delete all used/owned volumes).",
1413 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1416 additionalProperties
=> 0,
1418 node
=> get_standard_option
('pve-node'),
1419 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1420 skiplock
=> get_standard_option
('skiplock'),
1423 description
=> "Remove vmid from backup cron jobs.",
1434 my $rpcenv = PVE
::RPCEnvironment
::get
();
1435 my $authuser = $rpcenv->get_user();
1436 my $vmid = $param->{vmid
};
1438 my $skiplock = $param->{skiplock
};
1439 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1440 if $skiplock && $authuser ne 'root@pam';
1443 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1444 my $storecfg = PVE
::Storage
::config
();
1445 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1446 die "unable to remove VM $vmid - used in HA resources\n"
1447 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1449 if (!$param->{purge
}) {
1450 # don't allow destroy if with replication jobs but no purge param
1451 my $repl_conf = PVE
::ReplicationConfig-
>new();
1452 $repl_conf->check_for_existing_jobs($vmid);
1455 # early tests (repeat after locking)
1456 die "VM $vmid is running - destroy failed\n"
1457 if PVE
::QemuServer
::check_running
($vmid);
1462 syslog
('info', "destroy VM $vmid: $upid\n");
1463 PVE
::QemuConfig-
>lock_config($vmid, sub {
1464 die "VM $vmid is running - destroy failed\n"
1465 if (PVE
::QemuServer
::check_running
($vmid));
1467 PVE
::QemuServer
::destroy_vm
($storecfg, $vmid, $skiplock, { lock => 'destroyed' });
1469 PVE
::AccessControl
::remove_vm_access
($vmid);
1470 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1471 if ($param->{purge
}) {
1472 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1473 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1476 # only now remove the zombie config, else we can have reuse race
1477 PVE
::QemuConfig-
>destroy_config($vmid);
1481 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1484 __PACKAGE__-
>register_method({
1486 path
=> '{vmid}/unlink',
1490 description
=> "Unlink/delete disk images.",
1492 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1495 additionalProperties
=> 0,
1497 node
=> get_standard_option
('pve-node'),
1498 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1500 type
=> 'string', format
=> 'pve-configid-list',
1501 description
=> "A list of disk IDs you want to delete.",
1505 description
=> $opt_force_description,
1510 returns
=> { type
=> 'null'},
1514 $param->{delete} = extract_param
($param, 'idlist');
1516 __PACKAGE__-
>update_vm($param);
1523 __PACKAGE__-
>register_method({
1525 path
=> '{vmid}/vncproxy',
1529 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1531 description
=> "Creates a TCP VNC proxy connections.",
1533 additionalProperties
=> 0,
1535 node
=> get_standard_option
('pve-node'),
1536 vmid
=> get_standard_option
('pve-vmid'),
1540 description
=> "starts websockify instead of vncproxy",
1545 additionalProperties
=> 0,
1547 user
=> { type
=> 'string' },
1548 ticket
=> { type
=> 'string' },
1549 cert
=> { type
=> 'string' },
1550 port
=> { type
=> 'integer' },
1551 upid
=> { type
=> 'string' },
1557 my $rpcenv = PVE
::RPCEnvironment
::get
();
1559 my $authuser = $rpcenv->get_user();
1561 my $vmid = $param->{vmid
};
1562 my $node = $param->{node
};
1563 my $websocket = $param->{websocket
};
1565 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1566 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1568 my $authpath = "/vms/$vmid";
1570 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1572 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1578 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1579 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1580 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1581 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1582 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1584 $family = PVE
::Tools
::get_host_address_family
($node);
1587 my $port = PVE
::Tools
::next_vnc_port
($family);
1594 syslog
('info', "starting vnc proxy $upid\n");
1600 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1602 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1603 '-timeout', $timeout, '-authpath', $authpath,
1604 '-perm', 'Sys.Console'];
1606 if ($param->{websocket
}) {
1607 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1608 push @$cmd, '-notls', '-listen', 'localhost';
1611 push @$cmd, '-c', @$remcmd, @$termcmd;
1613 PVE
::Tools
::run_command
($cmd);
1617 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1619 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1621 my $sock = IO
::Socket
::IP-
>new(
1626 GetAddrInfoFlags
=> 0,
1627 ) or die "failed to create socket: $!\n";
1628 # Inside the worker we shouldn't have any previous alarms
1629 # running anyway...:
1631 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1633 accept(my $cli, $sock) or die "connection failed: $!\n";
1636 if (PVE
::Tools
::run_command
($cmd,
1637 output
=> '>&'.fileno($cli),
1638 input
=> '<&'.fileno($cli),
1641 die "Failed to run vncproxy.\n";
1648 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1650 PVE
::Tools
::wait_for_vnc_port
($port);
1661 __PACKAGE__-
>register_method({
1662 name
=> 'termproxy',
1663 path
=> '{vmid}/termproxy',
1667 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1669 description
=> "Creates a TCP proxy connections.",
1671 additionalProperties
=> 0,
1673 node
=> get_standard_option
('pve-node'),
1674 vmid
=> get_standard_option
('pve-vmid'),
1678 enum
=> [qw(serial0 serial1 serial2 serial3)],
1679 description
=> "opens a serial terminal (defaults to display)",
1684 additionalProperties
=> 0,
1686 user
=> { type
=> 'string' },
1687 ticket
=> { type
=> 'string' },
1688 port
=> { type
=> 'integer' },
1689 upid
=> { type
=> 'string' },
1695 my $rpcenv = PVE
::RPCEnvironment
::get
();
1697 my $authuser = $rpcenv->get_user();
1699 my $vmid = $param->{vmid
};
1700 my $node = $param->{node
};
1701 my $serial = $param->{serial
};
1703 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1705 if (!defined($serial)) {
1706 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1707 $serial = $conf->{vga
};
1711 my $authpath = "/vms/$vmid";
1713 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1718 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1719 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1720 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1721 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1722 push @$remcmd, '--';
1724 $family = PVE
::Tools
::get_host_address_family
($node);
1727 my $port = PVE
::Tools
::next_vnc_port
($family);
1729 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1730 push @$termcmd, '-iface', $serial if $serial;
1735 syslog
('info', "starting qemu termproxy $upid\n");
1737 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1738 '--perm', 'VM.Console', '--'];
1739 push @$cmd, @$remcmd, @$termcmd;
1741 PVE
::Tools
::run_command
($cmd);
1744 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1746 PVE
::Tools
::wait_for_vnc_port
($port);
1756 __PACKAGE__-
>register_method({
1757 name
=> 'vncwebsocket',
1758 path
=> '{vmid}/vncwebsocket',
1761 description
=> "You also need to pass a valid ticket (vncticket).",
1762 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1764 description
=> "Opens a weksocket for VNC traffic.",
1766 additionalProperties
=> 0,
1768 node
=> get_standard_option
('pve-node'),
1769 vmid
=> get_standard_option
('pve-vmid'),
1771 description
=> "Ticket from previous call to vncproxy.",
1776 description
=> "Port number returned by previous vncproxy call.",
1786 port
=> { type
=> 'string' },
1792 my $rpcenv = PVE
::RPCEnvironment
::get
();
1794 my $authuser = $rpcenv->get_user();
1796 my $vmid = $param->{vmid
};
1797 my $node = $param->{node
};
1799 my $authpath = "/vms/$vmid";
1801 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1803 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1805 # Note: VNC ports are acessible from outside, so we do not gain any
1806 # security if we verify that $param->{port} belongs to VM $vmid. This
1807 # check is done by verifying the VNC ticket (inside VNC protocol).
1809 my $port = $param->{port
};
1811 return { port
=> $port };
1814 __PACKAGE__-
>register_method({
1815 name
=> 'spiceproxy',
1816 path
=> '{vmid}/spiceproxy',
1821 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1823 description
=> "Returns a SPICE configuration to connect to the VM.",
1825 additionalProperties
=> 0,
1827 node
=> get_standard_option
('pve-node'),
1828 vmid
=> get_standard_option
('pve-vmid'),
1829 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1832 returns
=> get_standard_option
('remote-viewer-config'),
1836 my $rpcenv = PVE
::RPCEnvironment
::get
();
1838 my $authuser = $rpcenv->get_user();
1840 my $vmid = $param->{vmid
};
1841 my $node = $param->{node
};
1842 my $proxy = $param->{proxy
};
1844 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1845 my $title = "VM $vmid";
1846 $title .= " - ". $conf->{name
} if $conf->{name
};
1848 my $port = PVE
::QemuServer
::spice_port
($vmid);
1850 my ($ticket, undef, $remote_viewer_config) =
1851 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1853 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1854 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1856 return $remote_viewer_config;
1859 __PACKAGE__-
>register_method({
1861 path
=> '{vmid}/status',
1864 description
=> "Directory index",
1869 additionalProperties
=> 0,
1871 node
=> get_standard_option
('pve-node'),
1872 vmid
=> get_standard_option
('pve-vmid'),
1880 subdir
=> { type
=> 'string' },
1883 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1889 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1892 { subdir
=> 'current' },
1893 { subdir
=> 'start' },
1894 { subdir
=> 'stop' },
1895 { subdir
=> 'reset' },
1896 { subdir
=> 'shutdown' },
1897 { subdir
=> 'suspend' },
1898 { subdir
=> 'reboot' },
1904 __PACKAGE__-
>register_method({
1905 name
=> 'vm_status',
1906 path
=> '{vmid}/status/current',
1909 protected
=> 1, # qemu pid files are only readable by root
1910 description
=> "Get virtual machine status.",
1912 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1915 additionalProperties
=> 0,
1917 node
=> get_standard_option
('pve-node'),
1918 vmid
=> get_standard_option
('pve-vmid'),
1924 %$PVE::QemuServer
::vmstatus_return_properties
,
1926 description
=> "HA manager service status.",
1930 description
=> "Qemu VGA configuration supports spice.",
1935 description
=> "Qemu GuestAgent enabled in config.",
1945 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1947 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1948 my $status = $vmstatus->{$param->{vmid
}};
1950 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1952 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1953 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1958 __PACKAGE__-
>register_method({
1960 path
=> '{vmid}/status/start',
1964 description
=> "Start virtual machine.",
1966 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1969 additionalProperties
=> 0,
1971 node
=> get_standard_option
('pve-node'),
1972 vmid
=> get_standard_option
('pve-vmid',
1973 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1974 skiplock
=> get_standard_option
('skiplock'),
1975 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1976 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1979 enum
=> ['secure', 'insecure'],
1980 description
=> "Migration traffic is encrypted using an SSH " .
1981 "tunnel by default. On secure, completely private networks " .
1982 "this can be disabled to increase performance.",
1985 migration_network
=> {
1986 type
=> 'string', format
=> 'CIDR',
1987 description
=> "CIDR of the (sub) network that is used for migration.",
1990 machine
=> get_standard_option
('pve-qemu-machine'),
1992 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
2004 my $rpcenv = PVE
::RPCEnvironment
::get
();
2005 my $authuser = $rpcenv->get_user();
2007 my $node = extract_param
($param, 'node');
2008 my $vmid = extract_param
($param, 'vmid');
2010 my $machine = extract_param
($param, 'machine');
2012 my $get_root_param = sub {
2013 my $value = extract_param
($param, $_[0]);
2014 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2015 if $value && $authuser ne 'root@pam';
2019 my $stateuri = $get_root_param->('stateuri');
2020 my $skiplock = $get_root_param->('skiplock');
2021 my $migratedfrom = $get_root_param->('migratedfrom');
2022 my $migration_type = $get_root_param->('migration_type');
2023 my $migration_network = $get_root_param->('migration_network');
2024 my $targetstorage = $get_root_param->('targetstorage');
2026 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2027 if $targetstorage && !$migratedfrom;
2029 # read spice ticket from STDIN
2031 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2032 if (defined(my $line = <STDIN
>)) {
2034 $spice_ticket = $line;
2038 PVE
::Cluster
::check_cfs_quorum
();
2040 my $storecfg = PVE
::Storage
::config
();
2042 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2046 print "Requesting HA start for VM $vmid\n";
2048 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2049 PVE
::Tools
::run_command
($cmd);
2053 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2060 syslog
('info', "start VM $vmid: $upid\n");
2062 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2063 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
2067 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2071 __PACKAGE__-
>register_method({
2073 path
=> '{vmid}/status/stop',
2077 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2078 "is akin to pulling the power plug of a running computer and may damage the VM data",
2080 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2083 additionalProperties
=> 0,
2085 node
=> get_standard_option
('pve-node'),
2086 vmid
=> get_standard_option
('pve-vmid',
2087 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2088 skiplock
=> get_standard_option
('skiplock'),
2089 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2091 description
=> "Wait maximal timeout seconds.",
2097 description
=> "Do not deactivate storage volumes.",
2110 my $rpcenv = PVE
::RPCEnvironment
::get
();
2111 my $authuser = $rpcenv->get_user();
2113 my $node = extract_param
($param, 'node');
2114 my $vmid = extract_param
($param, 'vmid');
2116 my $skiplock = extract_param
($param, 'skiplock');
2117 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2118 if $skiplock && $authuser ne 'root@pam';
2120 my $keepActive = extract_param
($param, 'keepActive');
2121 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2122 if $keepActive && $authuser ne 'root@pam';
2124 my $migratedfrom = extract_param
($param, 'migratedfrom');
2125 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2126 if $migratedfrom && $authuser ne 'root@pam';
2129 my $storecfg = PVE
::Storage
::config
();
2131 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2136 print "Requesting HA stop for VM $vmid\n";
2138 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2139 PVE
::Tools
::run_command
($cmd);
2143 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2149 syslog
('info', "stop VM $vmid: $upid\n");
2151 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2152 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2156 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2160 __PACKAGE__-
>register_method({
2162 path
=> '{vmid}/status/reset',
2166 description
=> "Reset virtual machine.",
2168 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2171 additionalProperties
=> 0,
2173 node
=> get_standard_option
('pve-node'),
2174 vmid
=> get_standard_option
('pve-vmid',
2175 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2176 skiplock
=> get_standard_option
('skiplock'),
2185 my $rpcenv = PVE
::RPCEnvironment
::get
();
2187 my $authuser = $rpcenv->get_user();
2189 my $node = extract_param
($param, 'node');
2191 my $vmid = extract_param
($param, 'vmid');
2193 my $skiplock = extract_param
($param, 'skiplock');
2194 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2195 if $skiplock && $authuser ne 'root@pam';
2197 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2202 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2207 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2210 __PACKAGE__-
>register_method({
2211 name
=> 'vm_shutdown',
2212 path
=> '{vmid}/status/shutdown',
2216 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2217 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2219 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2222 additionalProperties
=> 0,
2224 node
=> get_standard_option
('pve-node'),
2225 vmid
=> get_standard_option
('pve-vmid',
2226 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2227 skiplock
=> get_standard_option
('skiplock'),
2229 description
=> "Wait maximal timeout seconds.",
2235 description
=> "Make sure the VM stops.",
2241 description
=> "Do not deactivate storage volumes.",
2254 my $rpcenv = PVE
::RPCEnvironment
::get
();
2255 my $authuser = $rpcenv->get_user();
2257 my $node = extract_param
($param, 'node');
2258 my $vmid = extract_param
($param, 'vmid');
2260 my $skiplock = extract_param
($param, 'skiplock');
2261 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2262 if $skiplock && $authuser ne 'root@pam';
2264 my $keepActive = extract_param
($param, 'keepActive');
2265 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2266 if $keepActive && $authuser ne 'root@pam';
2268 my $storecfg = PVE
::Storage
::config
();
2272 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2273 # otherwise, we will infer a shutdown command, but run into the timeout,
2274 # then when the vm is resumed, it will instantly shutdown
2276 # checking the qmp status here to get feedback to the gui/cli/api
2277 # and the status query should not take too long
2278 my $qmpstatus = eval {
2279 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2280 mon_cmd
($vmid, "query-status");
2284 if (!$err && $qmpstatus->{status
} eq "paused") {
2285 if ($param->{forceStop
}) {
2286 warn "VM is paused - stop instead of shutdown\n";
2289 die "VM is paused - cannot shutdown\n";
2293 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2295 my $timeout = $param->{timeout
} // 60;
2299 print "Requesting HA stop for VM $vmid\n";
2301 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2302 PVE
::Tools
::run_command
($cmd);
2306 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2313 syslog
('info', "shutdown VM $vmid: $upid\n");
2315 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2316 $shutdown, $param->{forceStop
}, $keepActive);
2320 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2324 __PACKAGE__-
>register_method({
2325 name
=> 'vm_reboot',
2326 path
=> '{vmid}/status/reboot',
2330 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2332 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2335 additionalProperties
=> 0,
2337 node
=> get_standard_option
('pve-node'),
2338 vmid
=> get_standard_option
('pve-vmid',
2339 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2341 description
=> "Wait maximal timeout seconds for the shutdown.",
2354 my $rpcenv = PVE
::RPCEnvironment
::get
();
2355 my $authuser = $rpcenv->get_user();
2357 my $node = extract_param
($param, 'node');
2358 my $vmid = extract_param
($param, 'vmid');
2360 my $qmpstatus = eval {
2361 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2362 mon_cmd
($vmid, "query-status");
2366 if (!$err && $qmpstatus->{status
} eq "paused") {
2367 die "VM is paused - cannot shutdown\n";
2370 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2375 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2376 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2380 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2383 __PACKAGE__-
>register_method({
2384 name
=> 'vm_suspend',
2385 path
=> '{vmid}/status/suspend',
2389 description
=> "Suspend virtual machine.",
2391 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2392 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2393 " on the storage for the vmstate.",
2394 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2397 additionalProperties
=> 0,
2399 node
=> get_standard_option
('pve-node'),
2400 vmid
=> get_standard_option
('pve-vmid',
2401 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2402 skiplock
=> get_standard_option
('skiplock'),
2407 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2409 statestorage
=> get_standard_option
('pve-storage-id', {
2410 description
=> "The storage for the VM state",
2411 requires
=> 'todisk',
2413 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2423 my $rpcenv = PVE
::RPCEnvironment
::get
();
2424 my $authuser = $rpcenv->get_user();
2426 my $node = extract_param
($param, 'node');
2427 my $vmid = extract_param
($param, 'vmid');
2429 my $todisk = extract_param
($param, 'todisk') // 0;
2431 my $statestorage = extract_param
($param, 'statestorage');
2433 my $skiplock = extract_param
($param, 'skiplock');
2434 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2435 if $skiplock && $authuser ne 'root@pam';
2437 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2439 die "Cannot suspend HA managed VM to disk\n"
2440 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2442 # early check for storage permission, for better user feedback
2444 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2446 if (!$statestorage) {
2447 # get statestorage from config if none is given
2448 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2449 my $storecfg = PVE
::Storage
::config
();
2450 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2453 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2459 syslog
('info', "suspend VM $vmid: $upid\n");
2461 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2466 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2467 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2470 __PACKAGE__-
>register_method({
2471 name
=> 'vm_resume',
2472 path
=> '{vmid}/status/resume',
2476 description
=> "Resume virtual machine.",
2478 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2481 additionalProperties
=> 0,
2483 node
=> get_standard_option
('pve-node'),
2484 vmid
=> get_standard_option
('pve-vmid',
2485 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2486 skiplock
=> get_standard_option
('skiplock'),
2487 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2497 my $rpcenv = PVE
::RPCEnvironment
::get
();
2499 my $authuser = $rpcenv->get_user();
2501 my $node = extract_param
($param, 'node');
2503 my $vmid = extract_param
($param, 'vmid');
2505 my $skiplock = extract_param
($param, 'skiplock');
2506 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2507 if $skiplock && $authuser ne 'root@pam';
2509 my $nocheck = extract_param
($param, 'nocheck');
2511 my $to_disk_suspended;
2513 PVE
::QemuConfig-
>lock_config($vmid, sub {
2514 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2515 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2519 die "VM $vmid not running\n"
2520 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2525 syslog
('info', "resume VM $vmid: $upid\n");
2527 if (!$to_disk_suspended) {
2528 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2530 my $storecfg = PVE
::Storage
::config
();
2531 PVE
::QemuServer
::vm_start
($storecfg, $vmid, undef, $skiplock);
2537 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2540 __PACKAGE__-
>register_method({
2541 name
=> 'vm_sendkey',
2542 path
=> '{vmid}/sendkey',
2546 description
=> "Send key event to virtual machine.",
2548 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2551 additionalProperties
=> 0,
2553 node
=> get_standard_option
('pve-node'),
2554 vmid
=> get_standard_option
('pve-vmid',
2555 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2556 skiplock
=> get_standard_option
('skiplock'),
2558 description
=> "The key (qemu monitor encoding).",
2563 returns
=> { type
=> 'null'},
2567 my $rpcenv = PVE
::RPCEnvironment
::get
();
2569 my $authuser = $rpcenv->get_user();
2571 my $node = extract_param
($param, 'node');
2573 my $vmid = extract_param
($param, 'vmid');
2575 my $skiplock = extract_param
($param, 'skiplock');
2576 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2577 if $skiplock && $authuser ne 'root@pam';
2579 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2584 __PACKAGE__-
>register_method({
2585 name
=> 'vm_feature',
2586 path
=> '{vmid}/feature',
2590 description
=> "Check if feature for virtual machine is available.",
2592 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2595 additionalProperties
=> 0,
2597 node
=> get_standard_option
('pve-node'),
2598 vmid
=> get_standard_option
('pve-vmid'),
2600 description
=> "Feature to check.",
2602 enum
=> [ 'snapshot', 'clone', 'copy' ],
2604 snapname
=> get_standard_option
('pve-snapshot-name', {
2612 hasFeature
=> { type
=> 'boolean' },
2615 items
=> { type
=> 'string' },
2622 my $node = extract_param
($param, 'node');
2624 my $vmid = extract_param
($param, 'vmid');
2626 my $snapname = extract_param
($param, 'snapname');
2628 my $feature = extract_param
($param, 'feature');
2630 my $running = PVE
::QemuServer
::check_running
($vmid);
2632 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2635 my $snap = $conf->{snapshots
}->{$snapname};
2636 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2639 my $storecfg = PVE
::Storage
::config
();
2641 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2642 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2645 hasFeature
=> $hasFeature,
2646 nodes
=> [ keys %$nodelist ],
2650 __PACKAGE__-
>register_method({
2652 path
=> '{vmid}/clone',
2656 description
=> "Create a copy of virtual machine/template.",
2658 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2659 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2660 "'Datastore.AllocateSpace' on any used storage.",
2663 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2665 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2666 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2671 additionalProperties
=> 0,
2673 node
=> get_standard_option
('pve-node'),
2674 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2675 newid
=> get_standard_option
('pve-vmid', {
2676 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2677 description
=> 'VMID for the clone.' }),
2680 type
=> 'string', format
=> 'dns-name',
2681 description
=> "Set a name for the new VM.",
2686 description
=> "Description for the new VM.",
2690 type
=> 'string', format
=> 'pve-poolid',
2691 description
=> "Add the new VM to the specified pool.",
2693 snapname
=> get_standard_option
('pve-snapshot-name', {
2696 storage
=> get_standard_option
('pve-storage-id', {
2697 description
=> "Target storage for full clone.",
2701 description
=> "Target format for file storage. Only valid for full clone.",
2704 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2709 description
=> "Create a full copy of all disks. This is always done when " .
2710 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2712 target
=> get_standard_option
('pve-node', {
2713 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2717 description
=> "Override I/O bandwidth limit (in KiB/s).",
2721 default => 'clone limit from datacenter or storage config',
2731 my $rpcenv = PVE
::RPCEnvironment
::get
();
2733 my $authuser = $rpcenv->get_user();
2735 my $node = extract_param
($param, 'node');
2737 my $vmid = extract_param
($param, 'vmid');
2739 my $newid = extract_param
($param, 'newid');
2741 my $pool = extract_param
($param, 'pool');
2743 if (defined($pool)) {
2744 $rpcenv->check_pool_exist($pool);
2747 my $snapname = extract_param
($param, 'snapname');
2749 my $storage = extract_param
($param, 'storage');
2751 my $format = extract_param
($param, 'format');
2753 my $target = extract_param
($param, 'target');
2755 my $localnode = PVE
::INotify
::nodename
();
2757 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2759 PVE
::Cluster
::check_node_exists
($target) if $target;
2761 my $storecfg = PVE
::Storage
::config
();
2764 # check if storage is enabled on local node
2765 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2767 # check if storage is available on target node
2768 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2769 # clone only works if target storage is shared
2770 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2771 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2775 PVE
::Cluster
::check_cfs_quorum
();
2777 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2779 # exclusive lock if VM is running - else shared lock is enough;
2780 my $shared_lock = $running ?
0 : 1;
2784 # do all tests after lock
2785 # we also try to do all tests before we fork the worker
2787 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2789 PVE
::QemuConfig-
>check_lock($conf);
2791 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2793 die "unexpected state change\n" if $verify_running != $running;
2795 die "snapshot '$snapname' does not exist\n"
2796 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2798 my $full = extract_param
($param, 'full');
2799 if (!defined($full)) {
2800 $full = !PVE
::QemuConfig-
>is_template($conf);
2803 die "parameter 'storage' not allowed for linked clones\n"
2804 if defined($storage) && !$full;
2806 die "parameter 'format' not allowed for linked clones\n"
2807 if defined($format) && !$full;
2809 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2811 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2813 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2815 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2817 die "unable to create VM $newid: config file already exists\n"
2820 my $newconf = { lock => 'clone' };
2825 foreach my $opt (keys %$oldconf) {
2826 my $value = $oldconf->{$opt};
2828 # do not copy snapshot related info
2829 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2830 $opt eq 'vmstate' || $opt eq 'snapstate';
2832 # no need to copy unused images, because VMID(owner) changes anyways
2833 next if $opt =~ m/^unused\d+$/;
2835 # always change MAC! address
2836 if ($opt =~ m/^net(\d+)$/) {
2837 my $net = PVE
::QemuServer
::parse_net
($value);
2838 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2839 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2840 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2841 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2842 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2843 die "unable to parse drive options for '$opt'\n" if !$drive;
2844 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2845 $newconf->{$opt} = $value; # simply copy configuration
2847 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2848 die "Full clone feature is not supported for drive '$opt'\n"
2849 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2850 $fullclone->{$opt} = 1;
2852 # not full means clone instead of copy
2853 die "Linked clone feature is not supported for drive '$opt'\n"
2854 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2856 $drives->{$opt} = $drive;
2857 push @$vollist, $drive->{file
};
2860 # copy everything else
2861 $newconf->{$opt} = $value;
2865 # auto generate a new uuid
2866 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2867 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2868 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2870 # auto generate a new vmgenid if the option was set
2871 if ($newconf->{vmgenid
}) {
2872 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2875 delete $newconf->{template
};
2877 if ($param->{name
}) {
2878 $newconf->{name
} = $param->{name
};
2880 if ($oldconf->{name
}) {
2881 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2883 $newconf->{name
} = "Copy-of-VM-$vmid";
2887 if ($param->{description
}) {
2888 $newconf->{description
} = $param->{description
};
2891 # create empty/temp config - this fails if VM already exists on other node
2892 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2897 my $newvollist = [];
2904 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2906 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2908 my $bwlimit = extract_param
($param, 'bwlimit');
2910 my $total_jobs = scalar(keys %{$drives});
2913 foreach my $opt (keys %$drives) {
2914 my $drive = $drives->{$opt};
2915 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2917 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
2918 my $storage_list = [ $src_sid ];
2919 push @$storage_list, $storage if defined($storage);
2920 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
2922 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2923 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2924 $jobs, $skipcomplete, $oldconf->{agent
}, $clonelimit);
2926 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
2928 PVE
::QemuConfig-
>write_config($newid, $newconf);
2932 delete $newconf->{lock};
2934 # do not write pending changes
2935 if (my @changes = keys %{$newconf->{pending
}}) {
2936 my $pending = join(',', @changes);
2937 warn "found pending changes for '$pending', discarding for clone\n";
2938 delete $newconf->{pending
};
2941 PVE
::QemuConfig-
>write_config($newid, $newconf);
2944 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2945 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2946 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2948 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2949 die "Failed to move config to node '$target' - rename failed: $!\n"
2950 if !rename($conffile, $newconffile);
2953 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2958 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2960 sleep 1; # some storage like rbd need to wait before release volume - really?
2962 foreach my $volid (@$newvollist) {
2963 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2966 die "clone failed: $err";
2972 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2974 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2977 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2978 # Aquire exclusive lock lock for $newid
2979 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2984 __PACKAGE__-
>register_method({
2985 name
=> 'move_vm_disk',
2986 path
=> '{vmid}/move_disk',
2990 description
=> "Move volume to different storage.",
2992 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2994 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2995 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2999 additionalProperties
=> 0,
3001 node
=> get_standard_option
('pve-node'),
3002 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3005 description
=> "The disk you want to move.",
3006 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
3008 storage
=> get_standard_option
('pve-storage-id', {
3009 description
=> "Target storage.",
3010 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3014 description
=> "Target Format.",
3015 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3020 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3026 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3031 description
=> "Override I/O bandwidth limit (in KiB/s).",
3035 default => 'move limit from datacenter or storage config',
3041 description
=> "the task ID.",
3046 my $rpcenv = PVE
::RPCEnvironment
::get
();
3048 my $authuser = $rpcenv->get_user();
3050 my $node = extract_param
($param, 'node');
3052 my $vmid = extract_param
($param, 'vmid');
3054 my $digest = extract_param
($param, 'digest');
3056 my $disk = extract_param
($param, 'disk');
3058 my $storeid = extract_param
($param, 'storage');
3060 my $format = extract_param
($param, 'format');
3062 my $storecfg = PVE
::Storage
::config
();
3064 my $updatefn = sub {
3066 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3068 PVE
::QemuConfig-
>check_lock($conf);
3070 die "checksum missmatch (file change by other user?)\n"
3071 if $digest && $digest ne $conf->{digest
};
3073 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3075 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3077 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
3079 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3082 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3083 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3087 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
3088 (!$format || !$oldfmt || $oldfmt eq $format);
3090 # this only checks snapshots because $disk is passed!
3091 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3092 die "you can't move a disk with snapshots and delete the source\n"
3093 if $snapshotted && $param->{delete};
3095 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3097 my $running = PVE
::QemuServer
::check_running
($vmid);
3099 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3103 my $newvollist = [];
3109 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3111 warn "moving disk with snapshots, snapshots will not be moved!\n"
3114 my $bwlimit = extract_param
($param, 'bwlimit');
3115 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3117 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3118 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit);
3120 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3122 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3124 # convert moved disk to base if part of template
3125 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3126 if PVE
::QemuConfig-
>is_template($conf);
3128 PVE
::QemuConfig-
>write_config($vmid, $conf);
3130 if ($running && PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
} && PVE
::QemuServer
::qga_check_running
($vmid)) {
3131 eval { mon_cmd
($vmid, "guest-fstrim"); };
3135 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3136 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3143 foreach my $volid (@$newvollist) {
3144 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3147 die "storage migration failed: $err";
3150 if ($param->{delete}) {
3152 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3153 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3159 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3162 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3165 my $check_vm_disks_local = sub {
3166 my ($storecfg, $vmconf, $vmid) = @_;
3168 my $local_disks = {};
3170 # add some more information to the disks e.g. cdrom
3171 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3172 my ($volid, $attr) = @_;
3174 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3176 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3177 return if $scfg->{shared
};
3179 # The shared attr here is just a special case where the vdisk
3180 # is marked as shared manually
3181 return if $attr->{shared
};
3182 return if $attr->{cdrom
} and $volid eq "none";
3184 if (exists $local_disks->{$volid}) {
3185 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3187 $local_disks->{$volid} = $attr;
3188 # ensure volid is present in case it's needed
3189 $local_disks->{$volid}->{volid
} = $volid;
3193 return $local_disks;
3196 __PACKAGE__-
>register_method({
3197 name
=> 'migrate_vm_precondition',
3198 path
=> '{vmid}/migrate',
3202 description
=> "Get preconditions for migration.",
3204 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3207 additionalProperties
=> 0,
3209 node
=> get_standard_option
('pve-node'),
3210 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3211 target
=> get_standard_option
('pve-node', {
3212 description
=> "Target node.",
3213 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3221 running
=> { type
=> 'boolean' },
3225 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3227 not_allowed_nodes
=> {
3230 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3234 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3236 local_resources
=> {
3238 description
=> "List local resources e.g. pci, usb"
3245 my $rpcenv = PVE
::RPCEnvironment
::get
();
3247 my $authuser = $rpcenv->get_user();
3249 PVE
::Cluster
::check_cfs_quorum
();
3253 my $vmid = extract_param
($param, 'vmid');
3254 my $target = extract_param
($param, 'target');
3255 my $localnode = PVE
::INotify
::nodename
();
3259 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3260 my $storecfg = PVE
::Storage
::config
();
3263 # try to detect errors early
3264 PVE
::QemuConfig-
>check_lock($vmconf);
3266 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3268 # if vm is not running, return target nodes where local storage is available
3269 # for offline migration
3270 if (!$res->{running
}) {
3271 $res->{allowed_nodes
} = [];
3272 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3273 delete $checked_nodes->{$localnode};
3275 foreach my $node (keys %$checked_nodes) {
3276 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3277 push @{$res->{allowed_nodes
}}, $node;
3281 $res->{not_allowed_nodes
} = $checked_nodes;
3285 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3286 $res->{local_disks
} = [ values %$local_disks ];;
3288 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3290 $res->{local_resources
} = $local_resources;
3297 __PACKAGE__-
>register_method({
3298 name
=> 'migrate_vm',
3299 path
=> '{vmid}/migrate',
3303 description
=> "Migrate virtual machine. Creates a new migration task.",
3305 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3308 additionalProperties
=> 0,
3310 node
=> get_standard_option
('pve-node'),
3311 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3312 target
=> get_standard_option
('pve-node', {
3313 description
=> "Target node.",
3314 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3318 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3323 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3328 enum
=> ['secure', 'insecure'],
3329 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3332 migration_network
=> {
3333 type
=> 'string', format
=> 'CIDR',
3334 description
=> "CIDR of the (sub) network that is used for migration.",
3337 "with-local-disks" => {
3339 description
=> "Enable live storage migration for local disk",
3342 targetstorage
=> get_standard_option
('pve-storage-id', {
3343 description
=> "Default target storage.",
3345 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3348 description
=> "Override I/O bandwidth limit (in KiB/s).",
3352 default => 'migrate limit from datacenter or storage config',
3358 description
=> "the task ID.",
3363 my $rpcenv = PVE
::RPCEnvironment
::get
();
3364 my $authuser = $rpcenv->get_user();
3366 my $target = extract_param
($param, 'target');
3368 my $localnode = PVE
::INotify
::nodename
();
3369 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3371 PVE
::Cluster
::check_cfs_quorum
();
3373 PVE
::Cluster
::check_node_exists
($target);
3375 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3377 my $vmid = extract_param
($param, 'vmid');
3379 raise_param_exc
({ force
=> "Only root may use this option." })
3380 if $param->{force
} && $authuser ne 'root@pam';
3382 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3383 if $param->{migration_type
} && $authuser ne 'root@pam';
3385 # allow root only until better network permissions are available
3386 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3387 if $param->{migration_network
} && $authuser ne 'root@pam';
3390 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3392 # try to detect errors early
3394 PVE
::QemuConfig-
>check_lock($conf);
3396 if (PVE
::QemuServer
::check_running
($vmid)) {
3397 die "can't migrate running VM without --online\n" if !$param->{online
};
3399 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
3400 $param->{online
} = 0;
3403 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3404 if !$param->{online
} && $param->{targetstorage
};
3406 my $storecfg = PVE
::Storage
::config
();
3408 if( $param->{targetstorage
}) {
3409 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3411 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3414 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3419 print "Requesting HA migration for VM $vmid to node $target\n";
3421 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3422 PVE
::Tools
::run_command
($cmd);
3426 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3431 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3435 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3438 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3443 __PACKAGE__-
>register_method({
3445 path
=> '{vmid}/monitor',
3449 description
=> "Execute Qemu monitor commands.",
3451 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3452 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3455 additionalProperties
=> 0,
3457 node
=> get_standard_option
('pve-node'),
3458 vmid
=> get_standard_option
('pve-vmid'),
3461 description
=> "The monitor command.",
3465 returns
=> { type
=> 'string'},
3469 my $rpcenv = PVE
::RPCEnvironment
::get
();
3470 my $authuser = $rpcenv->get_user();
3473 my $command = shift;
3474 return $command =~ m/^\s*info(\s+|$)/
3475 || $command =~ m/^\s*help\s*$/;
3478 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3479 if !&$is_ro($param->{command
});
3481 my $vmid = $param->{vmid
};
3483 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3487 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3489 $res = "ERROR: $@" if $@;
3494 __PACKAGE__-
>register_method({
3495 name
=> 'resize_vm',
3496 path
=> '{vmid}/resize',
3500 description
=> "Extend volume size.",
3502 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3505 additionalProperties
=> 0,
3507 node
=> get_standard_option
('pve-node'),
3508 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3509 skiplock
=> get_standard_option
('skiplock'),
3512 description
=> "The disk you want to resize.",
3513 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3517 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3518 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.",
3522 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3528 returns
=> { type
=> 'null'},
3532 my $rpcenv = PVE
::RPCEnvironment
::get
();
3534 my $authuser = $rpcenv->get_user();
3536 my $node = extract_param
($param, 'node');
3538 my $vmid = extract_param
($param, 'vmid');
3540 my $digest = extract_param
($param, 'digest');
3542 my $disk = extract_param
($param, 'disk');
3544 my $sizestr = extract_param
($param, 'size');
3546 my $skiplock = extract_param
($param, 'skiplock');
3547 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3548 if $skiplock && $authuser ne 'root@pam';
3550 my $storecfg = PVE
::Storage
::config
();
3552 my $updatefn = sub {
3554 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3556 die "checksum missmatch (file change by other user?)\n"
3557 if $digest && $digest ne $conf->{digest
};
3558 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3560 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3562 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3564 my (undef, undef, undef, undef, undef, undef, $format) =
3565 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3567 die "can't resize volume: $disk if snapshot exists\n"
3568 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3570 my $volid = $drive->{file
};
3572 die "disk '$disk' has no associated volume\n" if !$volid;
3574 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3576 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3578 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3580 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3581 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3583 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3585 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3586 my ($ext, $newsize, $unit) = ($1, $2, $4);
3589 $newsize = $newsize * 1024;
3590 } elsif ($unit eq 'M') {
3591 $newsize = $newsize * 1024 * 1024;
3592 } elsif ($unit eq 'G') {
3593 $newsize = $newsize * 1024 * 1024 * 1024;
3594 } elsif ($unit eq 'T') {
3595 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3598 $newsize += $size if $ext;
3599 $newsize = int($newsize);
3601 die "shrinking disks is not supported\n" if $newsize < $size;
3603 return if $size == $newsize;
3605 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3607 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3609 $drive->{size
} = $newsize;
3610 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
3612 PVE
::QemuConfig-
>write_config($vmid, $conf);
3615 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3619 __PACKAGE__-
>register_method({
3620 name
=> 'snapshot_list',
3621 path
=> '{vmid}/snapshot',
3623 description
=> "List all snapshots.",
3625 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3628 protected
=> 1, # qemu pid files are only readable by root
3630 additionalProperties
=> 0,
3632 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3633 node
=> get_standard_option
('pve-node'),
3642 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3646 description
=> "Snapshot includes RAM.",
3651 description
=> "Snapshot description.",
3655 description
=> "Snapshot creation time",
3657 renderer
=> 'timestamp',
3661 description
=> "Parent snapshot identifier.",
3667 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3672 my $vmid = $param->{vmid
};
3674 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3675 my $snaphash = $conf->{snapshots
} || {};
3679 foreach my $name (keys %$snaphash) {
3680 my $d = $snaphash->{$name};
3683 snaptime
=> $d->{snaptime
} || 0,
3684 vmstate
=> $d->{vmstate
} ?
1 : 0,
3685 description
=> $d->{description
} || '',
3687 $item->{parent
} = $d->{parent
} if $d->{parent
};
3688 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3692 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3695 digest
=> $conf->{digest
},
3696 running
=> $running,
3697 description
=> "You are here!",
3699 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3701 push @$res, $current;
3706 __PACKAGE__-
>register_method({
3708 path
=> '{vmid}/snapshot',
3712 description
=> "Snapshot a VM.",
3714 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3717 additionalProperties
=> 0,
3719 node
=> get_standard_option
('pve-node'),
3720 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3721 snapname
=> get_standard_option
('pve-snapshot-name'),
3725 description
=> "Save the vmstate",
3730 description
=> "A textual description or comment.",
3736 description
=> "the task ID.",
3741 my $rpcenv = PVE
::RPCEnvironment
::get
();
3743 my $authuser = $rpcenv->get_user();
3745 my $node = extract_param
($param, 'node');
3747 my $vmid = extract_param
($param, 'vmid');
3749 my $snapname = extract_param
($param, 'snapname');
3751 die "unable to use snapshot name 'current' (reserved name)\n"
3752 if $snapname eq 'current';
3754 die "unable to use snapshot name 'pending' (reserved name)\n"
3755 if lc($snapname) eq 'pending';
3758 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3759 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3760 $param->{description
});
3763 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3766 __PACKAGE__-
>register_method({
3767 name
=> 'snapshot_cmd_idx',
3768 path
=> '{vmid}/snapshot/{snapname}',
3775 additionalProperties
=> 0,
3777 vmid
=> get_standard_option
('pve-vmid'),
3778 node
=> get_standard_option
('pve-node'),
3779 snapname
=> get_standard_option
('pve-snapshot-name'),
3788 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3795 push @$res, { cmd
=> 'rollback' };
3796 push @$res, { cmd
=> 'config' };
3801 __PACKAGE__-
>register_method({
3802 name
=> 'update_snapshot_config',
3803 path
=> '{vmid}/snapshot/{snapname}/config',
3807 description
=> "Update snapshot metadata.",
3809 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3812 additionalProperties
=> 0,
3814 node
=> get_standard_option
('pve-node'),
3815 vmid
=> get_standard_option
('pve-vmid'),
3816 snapname
=> get_standard_option
('pve-snapshot-name'),
3820 description
=> "A textual description or comment.",
3824 returns
=> { type
=> 'null' },
3828 my $rpcenv = PVE
::RPCEnvironment
::get
();
3830 my $authuser = $rpcenv->get_user();
3832 my $vmid = extract_param
($param, 'vmid');
3834 my $snapname = extract_param
($param, 'snapname');
3836 return undef if !defined($param->{description
});
3838 my $updatefn = sub {
3840 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3842 PVE
::QemuConfig-
>check_lock($conf);
3844 my $snap = $conf->{snapshots
}->{$snapname};
3846 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3848 $snap->{description
} = $param->{description
} if defined($param->{description
});
3850 PVE
::QemuConfig-
>write_config($vmid, $conf);
3853 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3858 __PACKAGE__-
>register_method({
3859 name
=> 'get_snapshot_config',
3860 path
=> '{vmid}/snapshot/{snapname}/config',
3863 description
=> "Get snapshot configuration",
3865 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3868 additionalProperties
=> 0,
3870 node
=> get_standard_option
('pve-node'),
3871 vmid
=> get_standard_option
('pve-vmid'),
3872 snapname
=> get_standard_option
('pve-snapshot-name'),
3875 returns
=> { type
=> "object" },
3879 my $rpcenv = PVE
::RPCEnvironment
::get
();
3881 my $authuser = $rpcenv->get_user();
3883 my $vmid = extract_param
($param, 'vmid');
3885 my $snapname = extract_param
($param, 'snapname');
3887 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3889 my $snap = $conf->{snapshots
}->{$snapname};
3891 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3896 __PACKAGE__-
>register_method({
3898 path
=> '{vmid}/snapshot/{snapname}/rollback',
3902 description
=> "Rollback VM state to specified snapshot.",
3904 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3907 additionalProperties
=> 0,
3909 node
=> get_standard_option
('pve-node'),
3910 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3911 snapname
=> get_standard_option
('pve-snapshot-name'),
3916 description
=> "the task ID.",
3921 my $rpcenv = PVE
::RPCEnvironment
::get
();
3923 my $authuser = $rpcenv->get_user();
3925 my $node = extract_param
($param, 'node');
3927 my $vmid = extract_param
($param, 'vmid');
3929 my $snapname = extract_param
($param, 'snapname');
3932 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3933 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3937 # hold migration lock, this makes sure that nobody create replication snapshots
3938 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3941 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3944 __PACKAGE__-
>register_method({
3945 name
=> 'delsnapshot',
3946 path
=> '{vmid}/snapshot/{snapname}',
3950 description
=> "Delete a VM snapshot.",
3952 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3955 additionalProperties
=> 0,
3957 node
=> get_standard_option
('pve-node'),
3958 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3959 snapname
=> get_standard_option
('pve-snapshot-name'),
3963 description
=> "For removal from config file, even if removing disk snapshots fails.",
3969 description
=> "the task ID.",
3974 my $rpcenv = PVE
::RPCEnvironment
::get
();
3976 my $authuser = $rpcenv->get_user();
3978 my $node = extract_param
($param, 'node');
3980 my $vmid = extract_param
($param, 'vmid');
3982 my $snapname = extract_param
($param, 'snapname');
3985 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3986 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3989 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3992 __PACKAGE__-
>register_method({
3994 path
=> '{vmid}/template',
3998 description
=> "Create a Template.",
4000 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4001 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4004 additionalProperties
=> 0,
4006 node
=> get_standard_option
('pve-node'),
4007 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4011 description
=> "If you want to convert only 1 disk to base image.",
4012 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
4017 returns
=> { type
=> 'null'},
4021 my $rpcenv = PVE
::RPCEnvironment
::get
();
4023 my $authuser = $rpcenv->get_user();
4025 my $node = extract_param
($param, 'node');
4027 my $vmid = extract_param
($param, 'vmid');
4029 my $disk = extract_param
($param, 'disk');
4031 my $updatefn = sub {
4033 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4035 PVE
::QemuConfig-
>check_lock($conf);
4037 die "unable to create template, because VM contains snapshots\n"
4038 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4040 die "you can't convert a template to a template\n"
4041 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4043 die "you can't convert a VM to template if VM is running\n"
4044 if PVE
::QemuServer
::check_running
($vmid);
4047 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4050 $conf->{template
} = 1;
4051 PVE
::QemuConfig-
>write_config($vmid, $conf);
4053 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4056 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4060 __PACKAGE__-
>register_method({
4061 name
=> 'cloudinit_generated_config_dump',
4062 path
=> '{vmid}/cloudinit/dump',
4065 description
=> "Get automatically generated cloudinit config.",
4067 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4070 additionalProperties
=> 0,
4072 node
=> get_standard_option
('pve-node'),
4073 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4075 description
=> 'Config type.',
4077 enum
=> ['user', 'network', 'meta'],
4087 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4089 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});