1 package PVE
::API2
::Qemu
;
11 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
13 use PVE
::Tools
qw(extract_param);
14 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
16 use PVE
::JSONSchema
qw(get_standard_option);
18 use PVE
::ReplicationConfig
;
19 use PVE
::GuestHelpers
;
23 use PVE
::RPCEnvironment
;
24 use PVE
::AccessControl
;
28 use PVE
::API2
::Firewall
::VM
;
29 use PVE
::API2
::Qemu
::Agent
;
30 use PVE
::VZDump
::Plugin
;
31 use PVE
::DataCenterConfig
;
35 if (!$ENV{PVE_GENERATING_DOCS
}) {
36 require PVE
::HA
::Env
::PVE2
;
37 import PVE
::HA
::Env
::PVE2
;
38 require PVE
::HA
::Config
;
39 import PVE
::HA
::Config
;
43 use Data
::Dumper
; # fixme: remove
45 use base
qw(PVE::RESTHandler);
47 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.";
49 my $resolve_cdrom_alias = sub {
52 if (my $value = $param->{cdrom
}) {
53 $value .= ",media=cdrom" if $value !~ m/media=/;
54 $param->{ide2
} = $value;
55 delete $param->{cdrom
};
59 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
60 my $check_storage_access = sub {
61 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
63 PVE
::QemuServer
::foreach_drive
($settings, sub {
64 my ($ds, $drive) = @_;
66 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
68 my $volid = $drive->{file
};
69 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
71 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit' || $volname eq 'cloudinit')) {
73 } elsif ($isCDROM && ($volid eq 'cdrom')) {
74 $rpcenv->check($authuser, "/", ['Sys.Console']);
75 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
76 my ($storeid, $size) = ($2 || $default_storage, $3);
77 die "no storage ID specified (and no default storage)\n" if !$storeid;
78 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
79 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
80 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
81 if !$scfg->{content
}->{images
};
83 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
87 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
88 if defined($settings->{vmstatestorage
});
91 my $check_storage_access_clone = sub {
92 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
96 PVE
::QemuServer
::foreach_drive
($conf, sub {
97 my ($ds, $drive) = @_;
99 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
101 my $volid = $drive->{file
};
103 return if !$volid || $volid eq 'none';
106 if ($volid eq 'cdrom') {
107 $rpcenv->check($authuser, "/", ['Sys.Console']);
109 # we simply allow access
110 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
111 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
112 $sharedvm = 0 if !$scfg->{shared
};
116 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
117 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
118 $sharedvm = 0 if !$scfg->{shared
};
120 $sid = $storage if $storage;
121 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
125 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
126 if defined($conf->{vmstatestorage
});
131 # Note: $pool is only needed when creating a VM, because pool permissions
132 # are automatically inherited if VM already exists inside a pool.
133 my $create_disks = sub {
134 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
141 my ($ds, $disk) = @_;
143 my $volid = $disk->{file
};
144 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
146 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
147 delete $disk->{size
};
148 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
149 } elsif ($volname eq 'cloudinit') {
150 $storeid = $storeid // $default_storage;
151 die "no storage ID specified (and no default storage)\n" if !$storeid;
152 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
153 my $name = "vm-$vmid-cloudinit";
157 $fmt = $disk->{format
} // "qcow2";
160 $fmt = $disk->{format
} // "raw";
163 # Initial disk created with 4 MB and aligned to 4MB on regeneration
164 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
165 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
166 $disk->{file
} = $volid;
167 $disk->{media
} = 'cdrom';
168 push @$vollist, $volid;
169 delete $disk->{format
}; # no longer needed
170 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
171 } elsif ($volid =~ $NEW_DISK_RE) {
172 my ($storeid, $size) = ($2 || $default_storage, $3);
173 die "no storage ID specified (and no default storage)\n" if !$storeid;
174 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
175 my $fmt = $disk->{format
} || $defformat;
177 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
180 if ($ds eq 'efidisk0') {
181 ($volid, $size) = PVE
::QemuServer
::create_efidisk
($storecfg, $storeid, $vmid, $fmt, $arch);
183 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
185 push @$vollist, $volid;
186 $disk->{file
} = $volid;
187 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
188 delete $disk->{format
}; # no longer needed
189 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
192 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
194 my $volid_is_new = 1;
197 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
198 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
203 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
205 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
207 die "volume $volid does not exists\n" if !$size;
209 $disk->{size
} = $size;
212 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
216 eval { PVE
::QemuServer
::foreach_drive
($settings, $code); };
218 # free allocated images on error
220 syslog
('err', "VM $vmid creating disks failed");
221 foreach my $volid (@$vollist) {
222 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
228 # modify vm config if everything went well
229 foreach my $ds (keys %$res) {
230 $conf->{$ds} = $res->{$ds};
247 my $memoryoptions = {
253 my $hwtypeoptions = {
266 my $generaloptions = {
273 'migrate_downtime' => 1,
274 'migrate_speed' => 1,
286 my $vmpoweroptions = {
293 'vmstatestorage' => 1,
296 my $cloudinitoptions = {
306 my $check_vm_modify_config_perm = sub {
307 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
309 return 1 if $authuser eq 'root@pam';
311 foreach my $opt (@$key_list) {
312 # some checks (e.g., disk, serial port, usb) need to be done somewhere
313 # else, as there the permission can be value dependend
314 next if PVE
::QemuServer
::is_valid_drivename
($opt);
315 next if $opt eq 'cdrom';
316 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
319 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
320 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
321 } elsif ($memoryoptions->{$opt}) {
322 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
323 } elsif ($hwtypeoptions->{$opt}) {
324 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
325 } elsif ($generaloptions->{$opt}) {
326 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
327 # special case for startup since it changes host behaviour
328 if ($opt eq 'startup') {
329 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
331 } elsif ($vmpoweroptions->{$opt}) {
332 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
333 } elsif ($diskoptions->{$opt}) {
334 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
335 } elsif ($cloudinitoptions->{$opt} || ($opt =~ m/^(?:net|ipconfig)\d+$/)) {
336 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
338 # catches hostpci\d+, args, lock, etc.
339 # new options will be checked here
340 die "only root can set '$opt' config\n";
347 __PACKAGE__-
>register_method({
351 description
=> "Virtual machine index (per node).",
353 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
357 protected
=> 1, # qemu pid files are only readable by root
359 additionalProperties
=> 0,
361 node
=> get_standard_option
('pve-node'),
365 description
=> "Determine the full status of active VMs.",
373 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
375 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
380 my $rpcenv = PVE
::RPCEnvironment
::get
();
381 my $authuser = $rpcenv->get_user();
383 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
386 foreach my $vmid (keys %$vmstatus) {
387 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
389 my $data = $vmstatus->{$vmid};
398 __PACKAGE__-
>register_method({
402 description
=> "Create or restore a virtual machine.",
404 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
405 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
406 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
407 user
=> 'all', # check inside
412 additionalProperties
=> 0,
413 properties
=> PVE
::QemuServer
::json_config_properties
(
415 node
=> get_standard_option
('pve-node'),
416 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
418 description
=> "The backup file.",
422 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
424 storage
=> get_standard_option
('pve-storage-id', {
425 description
=> "Default storage.",
427 completion
=> \
&PVE
::QemuServer
::complete_storage
,
432 description
=> "Allow to overwrite existing VM.",
433 requires
=> 'archive',
438 description
=> "Assign a unique random ethernet address.",
439 requires
=> 'archive',
443 type
=> 'string', format
=> 'pve-poolid',
444 description
=> "Add the VM to the specified pool.",
447 description
=> "Override I/O bandwidth limit (in KiB/s).",
451 default => 'restore limit from datacenter or storage config',
457 description
=> "Start VM after it was created successfully.",
467 my $rpcenv = PVE
::RPCEnvironment
::get
();
468 my $authuser = $rpcenv->get_user();
470 my $node = extract_param
($param, 'node');
471 my $vmid = extract_param
($param, 'vmid');
473 my $archive = extract_param
($param, 'archive');
474 my $is_restore = !!$archive;
476 my $bwlimit = extract_param
($param, 'bwlimit');
477 my $force = extract_param
($param, 'force');
478 my $pool = extract_param
($param, 'pool');
479 my $start_after_create = extract_param
($param, 'start');
480 my $storage = extract_param
($param, 'storage');
481 my $unique = extract_param
($param, 'unique');
483 if (defined(my $ssh_keys = $param->{sshkeys
})) {
484 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
485 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
488 PVE
::Cluster
::check_cfs_quorum
();
490 my $filename = PVE
::QemuConfig-
>config_file($vmid);
491 my $storecfg = PVE
::Storage
::config
();
493 if (defined($pool)) {
494 $rpcenv->check_pool_exist($pool);
497 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
498 if defined($storage);
500 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
502 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
504 } elsif ($archive && $force && (-f
$filename) &&
505 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
506 # OK: user has VM.Backup permissions, and want to restore an existing VM
512 &$resolve_cdrom_alias($param);
514 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
516 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
518 foreach my $opt (keys %$param) {
519 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
520 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
521 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
523 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
524 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
528 PVE
::QemuServer
::add_random_macs
($param);
530 my $keystr = join(' ', keys %$param);
531 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
533 if ($archive eq '-') {
534 die "pipe requires cli environment\n"
535 if $rpcenv->{type
} ne 'cli';
537 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
538 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
542 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
544 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
545 die "$emsg $@" if $@;
547 my $restorefn = sub {
548 my $conf = PVE
::QemuConfig-
>load_config($vmid);
550 PVE
::QemuConfig-
>check_protection($conf, $emsg);
552 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
555 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
561 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
562 # Convert restored VM to template if backup was VM template
563 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
564 warn "Convert to template.\n";
565 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
569 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
571 if ($start_after_create) {
572 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
577 # ensure no old replication state are exists
578 PVE
::ReplicationState
::delete_guest_states
($vmid);
580 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
584 # ensure no old replication state are exists
585 PVE
::ReplicationState
::delete_guest_states
($vmid);
593 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
597 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
599 if (!$conf->{bootdisk
}) {
600 my $firstdisk = PVE
::QemuServer
::resolve_first_disk
($conf);
601 $conf->{bootdisk
} = $firstdisk if $firstdisk;
604 # auto generate uuid if user did not specify smbios1 option
605 if (!$conf->{smbios1
}) {
606 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
609 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
610 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
613 PVE
::QemuConfig-
>write_config($vmid, $conf);
619 foreach my $volid (@$vollist) {
620 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
626 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
629 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
631 if ($start_after_create) {
632 print "Execute autostart\n";
633 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
638 my ($code, $worker_name);
640 $worker_name = 'qmrestore';
642 eval { $restorefn->() };
644 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
650 $worker_name = 'qmcreate';
652 eval { $createfn->() };
655 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
656 unlink($conffile) or die "failed to remove config file: $!\n";
664 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
667 __PACKAGE__-
>register_method({
672 description
=> "Directory index",
677 additionalProperties
=> 0,
679 node
=> get_standard_option
('pve-node'),
680 vmid
=> get_standard_option
('pve-vmid'),
688 subdir
=> { type
=> 'string' },
691 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
697 { subdir
=> 'config' },
698 { subdir
=> 'pending' },
699 { subdir
=> 'status' },
700 { subdir
=> 'unlink' },
701 { subdir
=> 'vncproxy' },
702 { subdir
=> 'termproxy' },
703 { subdir
=> 'migrate' },
704 { subdir
=> 'resize' },
705 { subdir
=> 'move' },
707 { subdir
=> 'rrddata' },
708 { subdir
=> 'monitor' },
709 { subdir
=> 'agent' },
710 { subdir
=> 'snapshot' },
711 { subdir
=> 'spiceproxy' },
712 { subdir
=> 'sendkey' },
713 { subdir
=> 'firewall' },
719 __PACKAGE__-
>register_method ({
720 subclass
=> "PVE::API2::Firewall::VM",
721 path
=> '{vmid}/firewall',
724 __PACKAGE__-
>register_method ({
725 subclass
=> "PVE::API2::Qemu::Agent",
726 path
=> '{vmid}/agent',
729 __PACKAGE__-
>register_method({
731 path
=> '{vmid}/rrd',
733 protected
=> 1, # fixme: can we avoid that?
735 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
737 description
=> "Read VM RRD statistics (returns PNG)",
739 additionalProperties
=> 0,
741 node
=> get_standard_option
('pve-node'),
742 vmid
=> get_standard_option
('pve-vmid'),
744 description
=> "Specify the time frame you are interested in.",
746 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
749 description
=> "The list of datasources you want to display.",
750 type
=> 'string', format
=> 'pve-configid-list',
753 description
=> "The RRD consolidation function",
755 enum
=> [ 'AVERAGE', 'MAX' ],
763 filename
=> { type
=> 'string' },
769 return PVE
::Cluster
::create_rrd_graph
(
770 "pve2-vm/$param->{vmid}", $param->{timeframe
},
771 $param->{ds
}, $param->{cf
});
775 __PACKAGE__-
>register_method({
777 path
=> '{vmid}/rrddata',
779 protected
=> 1, # fixme: can we avoid that?
781 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
783 description
=> "Read VM RRD statistics",
785 additionalProperties
=> 0,
787 node
=> get_standard_option
('pve-node'),
788 vmid
=> get_standard_option
('pve-vmid'),
790 description
=> "Specify the time frame you are interested in.",
792 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
795 description
=> "The RRD consolidation function",
797 enum
=> [ 'AVERAGE', 'MAX' ],
812 return PVE
::Cluster
::create_rrd_data
(
813 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
817 __PACKAGE__-
>register_method({
819 path
=> '{vmid}/config',
822 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
824 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
827 additionalProperties
=> 0,
829 node
=> get_standard_option
('pve-node'),
830 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
832 description
=> "Get current values (instead of pending values).",
837 snapshot
=> get_standard_option
('pve-snapshot-name', {
838 description
=> "Fetch config values from given snapshot.",
841 my ($cmd, $pname, $cur, $args) = @_;
842 PVE
::QemuConfig-
>snapshot_list($args->[0]);
848 description
=> "The current VM configuration.",
850 properties
=> PVE
::QemuServer
::json_config_properties
({
853 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
860 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
861 current
=> "cannot use 'snapshot' parameter with 'current'"})
862 if ($param->{snapshot
} && $param->{current
});
865 if ($param->{snapshot
}) {
866 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
868 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
870 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
875 __PACKAGE__-
>register_method({
876 name
=> 'vm_pending',
877 path
=> '{vmid}/pending',
880 description
=> "Get virtual machine configuration, including pending changes.",
882 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
885 additionalProperties
=> 0,
887 node
=> get_standard_option
('pve-node'),
888 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
897 description
=> "Configuration option name.",
901 description
=> "Current value.",
906 description
=> "Pending value.",
911 description
=> "Indicates a pending delete request if present and not 0. " .
912 "The value 2 indicates a force-delete request.",
924 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
926 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
928 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
929 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
931 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
934 # POST/PUT {vmid}/config implementation
936 # The original API used PUT (idempotent) an we assumed that all operations
937 # are fast. But it turned out that almost any configuration change can
938 # involve hot-plug actions, or disk alloc/free. Such actions can take long
939 # time to complete and have side effects (not idempotent).
941 # The new implementation uses POST and forks a worker process. We added
942 # a new option 'background_delay'. If specified we wait up to
943 # 'background_delay' second for the worker task to complete. It returns null
944 # if the task is finished within that time, else we return the UPID.
946 my $update_vm_api = sub {
947 my ($param, $sync) = @_;
949 my $rpcenv = PVE
::RPCEnvironment
::get
();
951 my $authuser = $rpcenv->get_user();
953 my $node = extract_param
($param, 'node');
955 my $vmid = extract_param
($param, 'vmid');
957 my $digest = extract_param
($param, 'digest');
959 my $background_delay = extract_param
($param, 'background_delay');
961 if (defined(my $cipassword = $param->{cipassword
})) {
962 # Same logic as in cloud-init (but with the regex fixed...)
963 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
964 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
967 my @paramarr = (); # used for log message
968 foreach my $key (sort keys %$param) {
969 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
970 push @paramarr, "-$key", $value;
973 my $skiplock = extract_param
($param, 'skiplock');
974 raise_param_exc
({ skiplock
=> "Only root may use this option." })
975 if $skiplock && $authuser ne 'root@pam';
977 my $delete_str = extract_param
($param, 'delete');
979 my $revert_str = extract_param
($param, 'revert');
981 my $force = extract_param
($param, 'force');
983 if (defined(my $ssh_keys = $param->{sshkeys
})) {
984 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
985 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
988 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
990 my $storecfg = PVE
::Storage
::config
();
992 my $defaults = PVE
::QemuServer
::load_defaults
();
994 &$resolve_cdrom_alias($param);
996 # now try to verify all parameters
999 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1000 if (!PVE
::QemuServer
::option_exists
($opt)) {
1001 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1004 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1005 "-revert $opt' at the same time" })
1006 if defined($param->{$opt});
1008 $revert->{$opt} = 1;
1012 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1013 $opt = 'ide2' if $opt eq 'cdrom';
1015 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1016 "-delete $opt' at the same time" })
1017 if defined($param->{$opt});
1019 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1020 "-revert $opt' at the same time" })
1023 if (!PVE
::QemuServer
::option_exists
($opt)) {
1024 raise_param_exc
({ delete => "unknown option '$opt'" });
1030 my $repl_conf = PVE
::ReplicationConfig-
>new();
1031 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1032 my $check_replication = sub {
1034 return if !$is_replicated;
1035 my $volid = $drive->{file
};
1036 return if !$volid || !($drive->{replicate
}//1);
1037 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1039 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1040 return if $volname eq 'cloudinit';
1043 if ($volid =~ $NEW_DISK_RE) {
1045 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1047 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1049 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1050 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1051 return if $scfg->{shared
};
1052 die "cannot add non-replicatable volume to a replicated VM\n";
1055 foreach my $opt (keys %$param) {
1056 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1057 # cleanup drive path
1058 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1059 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1060 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1061 $check_replication->($drive);
1062 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1063 } elsif ($opt =~ m/^net(\d+)$/) {
1065 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1066 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1067 } elsif ($opt eq 'vmgenid') {
1068 if ($param->{$opt} eq '1') {
1069 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1071 } elsif ($opt eq 'hookscript') {
1072 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1073 raise_param_exc
({ $opt => $@ }) if $@;
1077 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1079 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1081 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1083 my $updatefn = sub {
1085 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1087 die "checksum missmatch (file change by other user?)\n"
1088 if $digest && $digest ne $conf->{digest
};
1090 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1092 foreach my $opt (keys %$revert) {
1093 if (defined($conf->{$opt})) {
1094 $param->{$opt} = $conf->{$opt};
1095 } elsif (defined($conf->{pending
}->{$opt})) {
1100 if ($param->{memory
} || defined($param->{balloon
})) {
1101 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1102 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1104 die "balloon value too large (must be smaller than assigned memory)\n"
1105 if $balloon && $balloon > $maxmem;
1108 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1112 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1114 # write updates to pending section
1116 my $modified = {}; # record what $option we modify
1118 foreach my $opt (@delete) {
1119 $modified->{$opt} = 1;
1120 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1122 # value of what we want to delete, independent if pending or not
1123 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1124 if (!defined($val)) {
1125 warn "cannot delete '$opt' - not set in current configuration!\n";
1126 $modified->{$opt} = 0;
1129 my $is_pending_val = defined($conf->{pending
}->{$opt});
1130 delete $conf->{pending
}->{$opt};
1132 if ($opt =~ m/^unused/) {
1133 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1134 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1135 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1136 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1137 delete $conf->{$opt};
1138 PVE
::QemuConfig-
>write_config($vmid, $conf);
1140 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1141 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1142 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1143 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1145 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1146 PVE
::QemuConfig-
>write_config($vmid, $conf);
1147 } elsif ($opt =~ m/^serial\d+$/) {
1148 if ($val eq 'socket') {
1149 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1150 } elsif ($authuser ne 'root@pam') {
1151 die "only root can delete '$opt' config for real devices\n";
1153 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1154 PVE
::QemuConfig-
>write_config($vmid, $conf);
1155 } elsif ($opt =~ m/^usb\d+$/) {
1156 if ($val =~ m/spice/) {
1157 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1158 } elsif ($authuser ne 'root@pam') {
1159 die "only root can delete '$opt' config for real devices\n";
1161 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1162 PVE
::QemuConfig-
>write_config($vmid, $conf);
1164 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1165 PVE
::QemuConfig-
>write_config($vmid, $conf);
1169 foreach my $opt (keys %$param) { # add/change
1170 $modified->{$opt} = 1;
1171 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1172 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1174 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
1176 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1177 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1178 # FIXME: cloudinit: CDROM or Disk?
1179 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1180 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1182 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1184 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1185 if defined($conf->{pending
}->{$opt});
1187 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1188 } elsif ($opt =~ m/^serial\d+/) {
1189 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1190 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1191 } elsif ($authuser ne 'root@pam') {
1192 die "only root can modify '$opt' config for real devices\n";
1194 $conf->{pending
}->{$opt} = $param->{$opt};
1195 } elsif ($opt =~ m/^usb\d+/) {
1196 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1197 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1198 } elsif ($authuser ne 'root@pam') {
1199 die "only root can modify '$opt' config for real devices\n";
1201 $conf->{pending
}->{$opt} = $param->{$opt};
1203 $conf->{pending
}->{$opt} = $param->{$opt};
1205 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1206 PVE
::QemuConfig-
>write_config($vmid, $conf);
1209 # remove pending changes when nothing changed
1210 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1211 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1212 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1214 return if !scalar(keys %{$conf->{pending
}});
1216 my $running = PVE
::QemuServer
::check_running
($vmid);
1218 # apply pending changes
1220 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1224 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1225 raise_param_exc
($errors) if scalar(keys %$errors);
1227 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1237 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1239 if ($background_delay) {
1241 # Note: It would be better to do that in the Event based HTTPServer
1242 # to avoid blocking call to sleep.
1244 my $end_time = time() + $background_delay;
1246 my $task = PVE
::Tools
::upid_decode
($upid);
1249 while (time() < $end_time) {
1250 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1252 sleep(1); # this gets interrupted when child process ends
1256 my $status = PVE
::Tools
::upid_read_status
($upid);
1257 return undef if $status eq 'OK';
1266 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1269 my $vm_config_perm_list = [
1274 'VM.Config.Network',
1276 'VM.Config.Options',
1279 __PACKAGE__-
>register_method({
1280 name
=> 'update_vm_async',
1281 path
=> '{vmid}/config',
1285 description
=> "Set virtual machine options (asynchrounous API).",
1287 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1290 additionalProperties
=> 0,
1291 properties
=> PVE
::QemuServer
::json_config_properties
(
1293 node
=> get_standard_option
('pve-node'),
1294 vmid
=> get_standard_option
('pve-vmid'),
1295 skiplock
=> get_standard_option
('skiplock'),
1297 type
=> 'string', format
=> 'pve-configid-list',
1298 description
=> "A list of settings you want to delete.",
1302 type
=> 'string', format
=> 'pve-configid-list',
1303 description
=> "Revert a pending change.",
1308 description
=> $opt_force_description,
1310 requires
=> 'delete',
1314 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1318 background_delay
=> {
1320 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1331 code
=> $update_vm_api,
1334 __PACKAGE__-
>register_method({
1335 name
=> 'update_vm',
1336 path
=> '{vmid}/config',
1340 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1342 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1345 additionalProperties
=> 0,
1346 properties
=> PVE
::QemuServer
::json_config_properties
(
1348 node
=> get_standard_option
('pve-node'),
1349 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1350 skiplock
=> get_standard_option
('skiplock'),
1352 type
=> 'string', format
=> 'pve-configid-list',
1353 description
=> "A list of settings you want to delete.",
1357 type
=> 'string', format
=> 'pve-configid-list',
1358 description
=> "Revert a pending change.",
1363 description
=> $opt_force_description,
1365 requires
=> 'delete',
1369 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1375 returns
=> { type
=> 'null' },
1378 &$update_vm_api($param, 1);
1383 __PACKAGE__-
>register_method({
1384 name
=> 'destroy_vm',
1389 description
=> "Destroy the vm (also delete all used/owned volumes).",
1391 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1394 additionalProperties
=> 0,
1396 node
=> get_standard_option
('pve-node'),
1397 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1398 skiplock
=> get_standard_option
('skiplock'),
1401 description
=> "Remove vmid from backup cron jobs.",
1412 my $rpcenv = PVE
::RPCEnvironment
::get
();
1413 my $authuser = $rpcenv->get_user();
1414 my $vmid = $param->{vmid
};
1416 my $skiplock = $param->{skiplock
};
1417 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1418 if $skiplock && $authuser ne 'root@pam';
1421 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1422 my $storecfg = PVE
::Storage
::config
();
1423 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1424 die "unable to remove VM $vmid - used in HA resources\n"
1425 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1427 if (!$param->{purge
}) {
1428 # don't allow destroy if with replication jobs but no purge param
1429 my $repl_conf = PVE
::ReplicationConfig-
>new();
1430 $repl_conf->check_for_existing_jobs($vmid);
1433 # early tests (repeat after locking)
1434 die "VM $vmid is running - destroy failed\n"
1435 if PVE
::QemuServer
::check_running
($vmid);
1440 syslog
('info', "destroy VM $vmid: $upid\n");
1441 PVE
::QemuConfig-
>lock_config($vmid, sub {
1442 die "VM $vmid is running - destroy failed\n"
1443 if (PVE
::QemuServer
::check_running
($vmid));
1445 PVE
::QemuServer
::destroy_vm
($storecfg, $vmid, $skiplock, { lock => 'destroyed' });
1447 PVE
::AccessControl
::remove_vm_access
($vmid);
1448 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1449 if ($param->{purge
}) {
1450 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1451 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1454 # only now remove the zombie config, else we can have reuse race
1455 PVE
::QemuConfig-
>destroy_config($vmid);
1459 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1462 __PACKAGE__-
>register_method({
1464 path
=> '{vmid}/unlink',
1468 description
=> "Unlink/delete disk images.",
1470 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1473 additionalProperties
=> 0,
1475 node
=> get_standard_option
('pve-node'),
1476 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1478 type
=> 'string', format
=> 'pve-configid-list',
1479 description
=> "A list of disk IDs you want to delete.",
1483 description
=> $opt_force_description,
1488 returns
=> { type
=> 'null'},
1492 $param->{delete} = extract_param
($param, 'idlist');
1494 __PACKAGE__-
>update_vm($param);
1501 __PACKAGE__-
>register_method({
1503 path
=> '{vmid}/vncproxy',
1507 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1509 description
=> "Creates a TCP VNC proxy connections.",
1511 additionalProperties
=> 0,
1513 node
=> get_standard_option
('pve-node'),
1514 vmid
=> get_standard_option
('pve-vmid'),
1518 description
=> "starts websockify instead of vncproxy",
1523 additionalProperties
=> 0,
1525 user
=> { type
=> 'string' },
1526 ticket
=> { type
=> 'string' },
1527 cert
=> { type
=> 'string' },
1528 port
=> { type
=> 'integer' },
1529 upid
=> { type
=> 'string' },
1535 my $rpcenv = PVE
::RPCEnvironment
::get
();
1537 my $authuser = $rpcenv->get_user();
1539 my $vmid = $param->{vmid
};
1540 my $node = $param->{node
};
1541 my $websocket = $param->{websocket
};
1543 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1544 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1546 my $authpath = "/vms/$vmid";
1548 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1550 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1556 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1557 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1558 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1559 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1560 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1562 $family = PVE
::Tools
::get_host_address_family
($node);
1565 my $port = PVE
::Tools
::next_vnc_port
($family);
1572 syslog
('info', "starting vnc proxy $upid\n");
1578 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1580 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1581 '-timeout', $timeout, '-authpath', $authpath,
1582 '-perm', 'Sys.Console'];
1584 if ($param->{websocket
}) {
1585 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1586 push @$cmd, '-notls', '-listen', 'localhost';
1589 push @$cmd, '-c', @$remcmd, @$termcmd;
1591 PVE
::Tools
::run_command
($cmd);
1595 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1597 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1599 my $sock = IO
::Socket
::IP-
>new(
1604 GetAddrInfoFlags
=> 0,
1605 ) or die "failed to create socket: $!\n";
1606 # Inside the worker we shouldn't have any previous alarms
1607 # running anyway...:
1609 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1611 accept(my $cli, $sock) or die "connection failed: $!\n";
1614 if (PVE
::Tools
::run_command
($cmd,
1615 output
=> '>&'.fileno($cli),
1616 input
=> '<&'.fileno($cli),
1619 die "Failed to run vncproxy.\n";
1626 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1628 PVE
::Tools
::wait_for_vnc_port
($port);
1639 __PACKAGE__-
>register_method({
1640 name
=> 'termproxy',
1641 path
=> '{vmid}/termproxy',
1645 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1647 description
=> "Creates a TCP proxy connections.",
1649 additionalProperties
=> 0,
1651 node
=> get_standard_option
('pve-node'),
1652 vmid
=> get_standard_option
('pve-vmid'),
1656 enum
=> [qw(serial0 serial1 serial2 serial3)],
1657 description
=> "opens a serial terminal (defaults to display)",
1662 additionalProperties
=> 0,
1664 user
=> { type
=> 'string' },
1665 ticket
=> { type
=> 'string' },
1666 port
=> { type
=> 'integer' },
1667 upid
=> { type
=> 'string' },
1673 my $rpcenv = PVE
::RPCEnvironment
::get
();
1675 my $authuser = $rpcenv->get_user();
1677 my $vmid = $param->{vmid
};
1678 my $node = $param->{node
};
1679 my $serial = $param->{serial
};
1681 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1683 if (!defined($serial)) {
1684 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1685 $serial = $conf->{vga
};
1689 my $authpath = "/vms/$vmid";
1691 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1696 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1697 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1698 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1699 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1700 push @$remcmd, '--';
1702 $family = PVE
::Tools
::get_host_address_family
($node);
1705 my $port = PVE
::Tools
::next_vnc_port
($family);
1707 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1708 push @$termcmd, '-iface', $serial if $serial;
1713 syslog
('info', "starting qemu termproxy $upid\n");
1715 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1716 '--perm', 'VM.Console', '--'];
1717 push @$cmd, @$remcmd, @$termcmd;
1719 PVE
::Tools
::run_command
($cmd);
1722 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1724 PVE
::Tools
::wait_for_vnc_port
($port);
1734 __PACKAGE__-
>register_method({
1735 name
=> 'vncwebsocket',
1736 path
=> '{vmid}/vncwebsocket',
1739 description
=> "You also need to pass a valid ticket (vncticket).",
1740 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1742 description
=> "Opens a weksocket for VNC traffic.",
1744 additionalProperties
=> 0,
1746 node
=> get_standard_option
('pve-node'),
1747 vmid
=> get_standard_option
('pve-vmid'),
1749 description
=> "Ticket from previous call to vncproxy.",
1754 description
=> "Port number returned by previous vncproxy call.",
1764 port
=> { type
=> 'string' },
1770 my $rpcenv = PVE
::RPCEnvironment
::get
();
1772 my $authuser = $rpcenv->get_user();
1774 my $vmid = $param->{vmid
};
1775 my $node = $param->{node
};
1777 my $authpath = "/vms/$vmid";
1779 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1781 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1783 # Note: VNC ports are acessible from outside, so we do not gain any
1784 # security if we verify that $param->{port} belongs to VM $vmid. This
1785 # check is done by verifying the VNC ticket (inside VNC protocol).
1787 my $port = $param->{port
};
1789 return { port
=> $port };
1792 __PACKAGE__-
>register_method({
1793 name
=> 'spiceproxy',
1794 path
=> '{vmid}/spiceproxy',
1799 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1801 description
=> "Returns a SPICE configuration to connect to the VM.",
1803 additionalProperties
=> 0,
1805 node
=> get_standard_option
('pve-node'),
1806 vmid
=> get_standard_option
('pve-vmid'),
1807 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1810 returns
=> get_standard_option
('remote-viewer-config'),
1814 my $rpcenv = PVE
::RPCEnvironment
::get
();
1816 my $authuser = $rpcenv->get_user();
1818 my $vmid = $param->{vmid
};
1819 my $node = $param->{node
};
1820 my $proxy = $param->{proxy
};
1822 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1823 my $title = "VM $vmid";
1824 $title .= " - ". $conf->{name
} if $conf->{name
};
1826 my $port = PVE
::QemuServer
::spice_port
($vmid);
1828 my ($ticket, undef, $remote_viewer_config) =
1829 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1831 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1832 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1834 return $remote_viewer_config;
1837 __PACKAGE__-
>register_method({
1839 path
=> '{vmid}/status',
1842 description
=> "Directory index",
1847 additionalProperties
=> 0,
1849 node
=> get_standard_option
('pve-node'),
1850 vmid
=> get_standard_option
('pve-vmid'),
1858 subdir
=> { type
=> 'string' },
1861 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1867 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1870 { subdir
=> 'current' },
1871 { subdir
=> 'start' },
1872 { subdir
=> 'stop' },
1873 { subdir
=> 'reset' },
1874 { subdir
=> 'shutdown' },
1875 { subdir
=> 'suspend' },
1876 { subdir
=> 'reboot' },
1882 __PACKAGE__-
>register_method({
1883 name
=> 'vm_status',
1884 path
=> '{vmid}/status/current',
1887 protected
=> 1, # qemu pid files are only readable by root
1888 description
=> "Get virtual machine status.",
1890 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1893 additionalProperties
=> 0,
1895 node
=> get_standard_option
('pve-node'),
1896 vmid
=> get_standard_option
('pve-vmid'),
1902 %$PVE::QemuServer
::vmstatus_return_properties
,
1904 description
=> "HA manager service status.",
1908 description
=> "Qemu VGA configuration supports spice.",
1913 description
=> "Qemu GuestAgent enabled in config.",
1923 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1925 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1926 my $status = $vmstatus->{$param->{vmid
}};
1928 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1930 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1931 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1936 __PACKAGE__-
>register_method({
1938 path
=> '{vmid}/status/start',
1942 description
=> "Start virtual machine.",
1944 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1947 additionalProperties
=> 0,
1949 node
=> get_standard_option
('pve-node'),
1950 vmid
=> get_standard_option
('pve-vmid',
1951 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1952 skiplock
=> get_standard_option
('skiplock'),
1953 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1954 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1957 enum
=> ['secure', 'insecure'],
1958 description
=> "Migration traffic is encrypted using an SSH " .
1959 "tunnel by default. On secure, completely private networks " .
1960 "this can be disabled to increase performance.",
1963 migration_network
=> {
1964 type
=> 'string', format
=> 'CIDR',
1965 description
=> "CIDR of the (sub) network that is used for migration.",
1968 machine
=> get_standard_option
('pve-qm-machine'),
1970 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1982 my $rpcenv = PVE
::RPCEnvironment
::get
();
1983 my $authuser = $rpcenv->get_user();
1985 my $node = extract_param
($param, 'node');
1986 my $vmid = extract_param
($param, 'vmid');
1988 my $machine = extract_param
($param, 'machine');
1990 my $get_root_param = sub {
1991 my $value = extract_param
($param, $_[0]);
1992 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
1993 if $value && $authuser ne 'root@pam';
1997 my $stateuri = $get_root_param->('stateuri');
1998 my $skiplock = $get_root_param->('skiplock');
1999 my $migratedfrom = $get_root_param->('migratedfrom');
2000 my $migration_type = $get_root_param->('migration_type');
2001 my $migration_network = $get_root_param->('migration_network');
2002 my $targetstorage = $get_root_param->('targetstorage');
2004 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2005 if $targetstorage && !$migratedfrom;
2007 # read spice ticket from STDIN
2009 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2010 if (defined(my $line = <STDIN
>)) {
2012 $spice_ticket = $line;
2016 PVE
::Cluster
::check_cfs_quorum
();
2018 my $storecfg = PVE
::Storage
::config
();
2020 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2024 print "Requesting HA start for VM $vmid\n";
2026 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2027 PVE
::Tools
::run_command
($cmd);
2031 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2038 syslog
('info', "start VM $vmid: $upid\n");
2040 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2041 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
2045 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2049 __PACKAGE__-
>register_method({
2051 path
=> '{vmid}/status/stop',
2055 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2056 "is akin to pulling the power plug of a running computer and may damage the VM data",
2058 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2061 additionalProperties
=> 0,
2063 node
=> get_standard_option
('pve-node'),
2064 vmid
=> get_standard_option
('pve-vmid',
2065 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2066 skiplock
=> get_standard_option
('skiplock'),
2067 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2069 description
=> "Wait maximal timeout seconds.",
2075 description
=> "Do not deactivate storage volumes.",
2088 my $rpcenv = PVE
::RPCEnvironment
::get
();
2089 my $authuser = $rpcenv->get_user();
2091 my $node = extract_param
($param, 'node');
2092 my $vmid = extract_param
($param, 'vmid');
2094 my $skiplock = extract_param
($param, 'skiplock');
2095 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2096 if $skiplock && $authuser ne 'root@pam';
2098 my $keepActive = extract_param
($param, 'keepActive');
2099 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2100 if $keepActive && $authuser ne 'root@pam';
2102 my $migratedfrom = extract_param
($param, 'migratedfrom');
2103 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2104 if $migratedfrom && $authuser ne 'root@pam';
2107 my $storecfg = PVE
::Storage
::config
();
2109 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2114 print "Requesting HA stop for VM $vmid\n";
2116 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2117 PVE
::Tools
::run_command
($cmd);
2121 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2127 syslog
('info', "stop VM $vmid: $upid\n");
2129 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2130 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2134 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2138 __PACKAGE__-
>register_method({
2140 path
=> '{vmid}/status/reset',
2144 description
=> "Reset virtual machine.",
2146 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2149 additionalProperties
=> 0,
2151 node
=> get_standard_option
('pve-node'),
2152 vmid
=> get_standard_option
('pve-vmid',
2153 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2154 skiplock
=> get_standard_option
('skiplock'),
2163 my $rpcenv = PVE
::RPCEnvironment
::get
();
2165 my $authuser = $rpcenv->get_user();
2167 my $node = extract_param
($param, 'node');
2169 my $vmid = extract_param
($param, 'vmid');
2171 my $skiplock = extract_param
($param, 'skiplock');
2172 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2173 if $skiplock && $authuser ne 'root@pam';
2175 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2180 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2185 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2188 __PACKAGE__-
>register_method({
2189 name
=> 'vm_shutdown',
2190 path
=> '{vmid}/status/shutdown',
2194 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2195 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2197 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2200 additionalProperties
=> 0,
2202 node
=> get_standard_option
('pve-node'),
2203 vmid
=> get_standard_option
('pve-vmid',
2204 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2205 skiplock
=> get_standard_option
('skiplock'),
2207 description
=> "Wait maximal timeout seconds.",
2213 description
=> "Make sure the VM stops.",
2219 description
=> "Do not deactivate storage volumes.",
2232 my $rpcenv = PVE
::RPCEnvironment
::get
();
2233 my $authuser = $rpcenv->get_user();
2235 my $node = extract_param
($param, 'node');
2236 my $vmid = extract_param
($param, 'vmid');
2238 my $skiplock = extract_param
($param, 'skiplock');
2239 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2240 if $skiplock && $authuser ne 'root@pam';
2242 my $keepActive = extract_param
($param, 'keepActive');
2243 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2244 if $keepActive && $authuser ne 'root@pam';
2246 my $storecfg = PVE
::Storage
::config
();
2250 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2251 # otherwise, we will infer a shutdown command, but run into the timeout,
2252 # then when the vm is resumed, it will instantly shutdown
2254 # checking the qmp status here to get feedback to the gui/cli/api
2255 # and the status query should not take too long
2256 my $qmpstatus = eval {
2257 PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2261 if (!$err && $qmpstatus->{status
} eq "paused") {
2262 if ($param->{forceStop
}) {
2263 warn "VM is paused - stop instead of shutdown\n";
2266 die "VM is paused - cannot shutdown\n";
2270 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2272 my $timeout = $param->{timeout
} // 60;
2276 print "Requesting HA stop for VM $vmid\n";
2278 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2279 PVE
::Tools
::run_command
($cmd);
2283 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2290 syslog
('info', "shutdown VM $vmid: $upid\n");
2292 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2293 $shutdown, $param->{forceStop
}, $keepActive);
2297 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2301 __PACKAGE__-
>register_method({
2302 name
=> 'vm_reboot',
2303 path
=> '{vmid}/status/reboot',
2307 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2309 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2312 additionalProperties
=> 0,
2314 node
=> get_standard_option
('pve-node'),
2315 vmid
=> get_standard_option
('pve-vmid',
2316 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2318 description
=> "Wait maximal timeout seconds for the shutdown.",
2331 my $rpcenv = PVE
::RPCEnvironment
::get
();
2332 my $authuser = $rpcenv->get_user();
2334 my $node = extract_param
($param, 'node');
2335 my $vmid = extract_param
($param, 'vmid');
2337 my $qmpstatus = eval {
2338 PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2342 if (!$err && $qmpstatus->{status
} eq "paused") {
2343 die "VM is paused - cannot shutdown\n";
2346 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2351 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2352 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2356 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2359 __PACKAGE__-
>register_method({
2360 name
=> 'vm_suspend',
2361 path
=> '{vmid}/status/suspend',
2365 description
=> "Suspend virtual machine.",
2367 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2370 additionalProperties
=> 0,
2372 node
=> get_standard_option
('pve-node'),
2373 vmid
=> get_standard_option
('pve-vmid',
2374 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2375 skiplock
=> get_standard_option
('skiplock'),
2380 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2382 statestorage
=> get_standard_option
('pve-storage-id', {
2383 description
=> "The storage for the VM state",
2384 requires
=> 'todisk',
2386 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2396 my $rpcenv = PVE
::RPCEnvironment
::get
();
2397 my $authuser = $rpcenv->get_user();
2399 my $node = extract_param
($param, 'node');
2400 my $vmid = extract_param
($param, 'vmid');
2402 my $todisk = extract_param
($param, 'todisk') // 0;
2404 my $statestorage = extract_param
($param, 'statestorage');
2406 my $skiplock = extract_param
($param, 'skiplock');
2407 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2408 if $skiplock && $authuser ne 'root@pam';
2410 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2412 die "Cannot suspend HA managed VM to disk\n"
2413 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2418 syslog
('info', "suspend VM $vmid: $upid\n");
2420 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2425 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2426 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2429 __PACKAGE__-
>register_method({
2430 name
=> 'vm_resume',
2431 path
=> '{vmid}/status/resume',
2435 description
=> "Resume virtual machine.",
2437 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2440 additionalProperties
=> 0,
2442 node
=> get_standard_option
('pve-node'),
2443 vmid
=> get_standard_option
('pve-vmid',
2444 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2445 skiplock
=> get_standard_option
('skiplock'),
2446 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2456 my $rpcenv = PVE
::RPCEnvironment
::get
();
2458 my $authuser = $rpcenv->get_user();
2460 my $node = extract_param
($param, 'node');
2462 my $vmid = extract_param
($param, 'vmid');
2464 my $skiplock = extract_param
($param, 'skiplock');
2465 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2466 if $skiplock && $authuser ne 'root@pam';
2468 my $nocheck = extract_param
($param, 'nocheck');
2470 my $to_disk_suspended;
2472 PVE
::QemuConfig-
>lock_config($vmid, sub {
2473 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2474 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2478 die "VM $vmid not running\n"
2479 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2484 syslog
('info', "resume VM $vmid: $upid\n");
2486 if (!$to_disk_suspended) {
2487 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2489 my $storecfg = PVE
::Storage
::config
();
2490 PVE
::QemuServer
::vm_start
($storecfg, $vmid, undef, $skiplock);
2496 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2499 __PACKAGE__-
>register_method({
2500 name
=> 'vm_sendkey',
2501 path
=> '{vmid}/sendkey',
2505 description
=> "Send key event to virtual machine.",
2507 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2510 additionalProperties
=> 0,
2512 node
=> get_standard_option
('pve-node'),
2513 vmid
=> get_standard_option
('pve-vmid',
2514 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2515 skiplock
=> get_standard_option
('skiplock'),
2517 description
=> "The key (qemu monitor encoding).",
2522 returns
=> { type
=> 'null'},
2526 my $rpcenv = PVE
::RPCEnvironment
::get
();
2528 my $authuser = $rpcenv->get_user();
2530 my $node = extract_param
($param, 'node');
2532 my $vmid = extract_param
($param, 'vmid');
2534 my $skiplock = extract_param
($param, 'skiplock');
2535 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2536 if $skiplock && $authuser ne 'root@pam';
2538 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2543 __PACKAGE__-
>register_method({
2544 name
=> 'vm_feature',
2545 path
=> '{vmid}/feature',
2549 description
=> "Check if feature for virtual machine is available.",
2551 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2554 additionalProperties
=> 0,
2556 node
=> get_standard_option
('pve-node'),
2557 vmid
=> get_standard_option
('pve-vmid'),
2559 description
=> "Feature to check.",
2561 enum
=> [ 'snapshot', 'clone', 'copy' ],
2563 snapname
=> get_standard_option
('pve-snapshot-name', {
2571 hasFeature
=> { type
=> 'boolean' },
2574 items
=> { type
=> 'string' },
2581 my $node = extract_param
($param, 'node');
2583 my $vmid = extract_param
($param, 'vmid');
2585 my $snapname = extract_param
($param, 'snapname');
2587 my $feature = extract_param
($param, 'feature');
2589 my $running = PVE
::QemuServer
::check_running
($vmid);
2591 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2594 my $snap = $conf->{snapshots
}->{$snapname};
2595 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2598 my $storecfg = PVE
::Storage
::config
();
2600 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2601 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2604 hasFeature
=> $hasFeature,
2605 nodes
=> [ keys %$nodelist ],
2609 __PACKAGE__-
>register_method({
2611 path
=> '{vmid}/clone',
2615 description
=> "Create a copy of virtual machine/template.",
2617 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2618 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2619 "'Datastore.AllocateSpace' on any used storage.",
2622 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2624 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2625 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2630 additionalProperties
=> 0,
2632 node
=> get_standard_option
('pve-node'),
2633 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2634 newid
=> get_standard_option
('pve-vmid', {
2635 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2636 description
=> 'VMID for the clone.' }),
2639 type
=> 'string', format
=> 'dns-name',
2640 description
=> "Set a name for the new VM.",
2645 description
=> "Description for the new VM.",
2649 type
=> 'string', format
=> 'pve-poolid',
2650 description
=> "Add the new VM to the specified pool.",
2652 snapname
=> get_standard_option
('pve-snapshot-name', {
2655 storage
=> get_standard_option
('pve-storage-id', {
2656 description
=> "Target storage for full clone.",
2660 description
=> "Target format for file storage. Only valid for full clone.",
2663 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2668 description
=> "Create a full copy of all disks. This is always done when " .
2669 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2671 target
=> get_standard_option
('pve-node', {
2672 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2676 description
=> "Override I/O bandwidth limit (in KiB/s).",
2680 default => 'clone limit from datacenter or storage config',
2690 my $rpcenv = PVE
::RPCEnvironment
::get
();
2692 my $authuser = $rpcenv->get_user();
2694 my $node = extract_param
($param, 'node');
2696 my $vmid = extract_param
($param, 'vmid');
2698 my $newid = extract_param
($param, 'newid');
2700 my $pool = extract_param
($param, 'pool');
2702 if (defined($pool)) {
2703 $rpcenv->check_pool_exist($pool);
2706 my $snapname = extract_param
($param, 'snapname');
2708 my $storage = extract_param
($param, 'storage');
2710 my $format = extract_param
($param, 'format');
2712 my $target = extract_param
($param, 'target');
2714 my $localnode = PVE
::INotify
::nodename
();
2716 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2718 PVE
::Cluster
::check_node_exists
($target) if $target;
2720 my $storecfg = PVE
::Storage
::config
();
2723 # check if storage is enabled on local node
2724 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2726 # check if storage is available on target node
2727 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2728 # clone only works if target storage is shared
2729 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2730 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2734 PVE
::Cluster
::check_cfs_quorum
();
2736 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2738 # exclusive lock if VM is running - else shared lock is enough;
2739 my $shared_lock = $running ?
0 : 1;
2743 # do all tests after lock
2744 # we also try to do all tests before we fork the worker
2746 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2748 PVE
::QemuConfig-
>check_lock($conf);
2750 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2752 die "unexpected state change\n" if $verify_running != $running;
2754 die "snapshot '$snapname' does not exist\n"
2755 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2757 my $full = extract_param
($param, 'full');
2758 if (!defined($full)) {
2759 $full = !PVE
::QemuConfig-
>is_template($conf);
2762 die "parameter 'storage' not allowed for linked clones\n"
2763 if defined($storage) && !$full;
2765 die "parameter 'format' not allowed for linked clones\n"
2766 if defined($format) && !$full;
2768 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2770 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2772 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2774 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2776 die "unable to create VM $newid: config file already exists\n"
2779 my $newconf = { lock => 'clone' };
2784 foreach my $opt (keys %$oldconf) {
2785 my $value = $oldconf->{$opt};
2787 # do not copy snapshot related info
2788 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2789 $opt eq 'vmstate' || $opt eq 'snapstate';
2791 # no need to copy unused images, because VMID(owner) changes anyways
2792 next if $opt =~ m/^unused\d+$/;
2794 # always change MAC! address
2795 if ($opt =~ m/^net(\d+)$/) {
2796 my $net = PVE
::QemuServer
::parse_net
($value);
2797 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2798 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2799 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2800 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2801 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2802 die "unable to parse drive options for '$opt'\n" if !$drive;
2803 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2804 $newconf->{$opt} = $value; # simply copy configuration
2807 die "Full clone feature is not supported for drive '$opt'\n"
2808 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2809 $fullclone->{$opt} = 1;
2811 # not full means clone instead of copy
2812 die "Linked clone feature is not supported for drive '$opt'\n"
2813 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2815 $drives->{$opt} = $drive;
2816 push @$vollist, $drive->{file
};
2819 # copy everything else
2820 $newconf->{$opt} = $value;
2824 # auto generate a new uuid
2825 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2826 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2827 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2829 # auto generate a new vmgenid if the option was set
2830 if ($newconf->{vmgenid
}) {
2831 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2834 delete $newconf->{template
};
2836 if ($param->{name
}) {
2837 $newconf->{name
} = $param->{name
};
2839 if ($oldconf->{name
}) {
2840 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2842 $newconf->{name
} = "Copy-of-VM-$vmid";
2846 if ($param->{description
}) {
2847 $newconf->{description
} = $param->{description
};
2850 # create empty/temp config - this fails if VM already exists on other node
2851 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2856 my $newvollist = [];
2863 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2865 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2867 my $bwlimit = extract_param
($param, 'bwlimit');
2869 my $total_jobs = scalar(keys %{$drives});
2872 foreach my $opt (keys %$drives) {
2873 my $drive = $drives->{$opt};
2874 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2876 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
2877 my $storage_list = [ $src_sid ];
2878 push @$storage_list, $storage if defined($storage);
2879 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
2881 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2882 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2883 $jobs, $skipcomplete, $oldconf->{agent
}, $clonelimit);
2885 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2887 PVE
::QemuConfig-
>write_config($newid, $newconf);
2891 delete $newconf->{lock};
2893 # do not write pending changes
2894 if (my @changes = keys %{$newconf->{pending
}}) {
2895 my $pending = join(',', @changes);
2896 warn "found pending changes for '$pending', discarding for clone\n";
2897 delete $newconf->{pending
};
2900 PVE
::QemuConfig-
>write_config($newid, $newconf);
2903 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2904 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2905 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2907 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2908 die "Failed to move config to node '$target' - rename failed: $!\n"
2909 if !rename($conffile, $newconffile);
2912 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2917 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2919 sleep 1; # some storage like rbd need to wait before release volume - really?
2921 foreach my $volid (@$newvollist) {
2922 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2925 die "clone failed: $err";
2931 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2933 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2936 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2937 # Aquire exclusive lock lock for $newid
2938 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2943 __PACKAGE__-
>register_method({
2944 name
=> 'move_vm_disk',
2945 path
=> '{vmid}/move_disk',
2949 description
=> "Move volume to different storage.",
2951 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2953 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2954 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2958 additionalProperties
=> 0,
2960 node
=> get_standard_option
('pve-node'),
2961 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2964 description
=> "The disk you want to move.",
2965 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2967 storage
=> get_standard_option
('pve-storage-id', {
2968 description
=> "Target storage.",
2969 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2973 description
=> "Target Format.",
2974 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2979 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2985 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2990 description
=> "Override I/O bandwidth limit (in KiB/s).",
2994 default => 'move limit from datacenter or storage config',
3000 description
=> "the task ID.",
3005 my $rpcenv = PVE
::RPCEnvironment
::get
();
3007 my $authuser = $rpcenv->get_user();
3009 my $node = extract_param
($param, 'node');
3011 my $vmid = extract_param
($param, 'vmid');
3013 my $digest = extract_param
($param, 'digest');
3015 my $disk = extract_param
($param, 'disk');
3017 my $storeid = extract_param
($param, 'storage');
3019 my $format = extract_param
($param, 'format');
3021 my $storecfg = PVE
::Storage
::config
();
3023 my $updatefn = sub {
3025 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3027 PVE
::QemuConfig-
>check_lock($conf);
3029 die "checksum missmatch (file change by other user?)\n"
3030 if $digest && $digest ne $conf->{digest
};
3032 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3034 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3036 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
3038 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3041 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3042 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3046 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
3047 (!$format || !$oldfmt || $oldfmt eq $format);
3049 # this only checks snapshots because $disk is passed!
3050 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3051 die "you can't move a disk with snapshots and delete the source\n"
3052 if $snapshotted && $param->{delete};
3054 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3056 my $running = PVE
::QemuServer
::check_running
($vmid);
3058 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3062 my $newvollist = [];
3068 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3070 warn "moving disk with snapshots, snapshots will not be moved!\n"
3073 my $bwlimit = extract_param
($param, 'bwlimit');
3074 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3076 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3077 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit);
3079 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
3081 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3083 # convert moved disk to base if part of template
3084 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3085 if PVE
::QemuConfig-
>is_template($conf);
3087 PVE
::QemuConfig-
>write_config($vmid, $conf);
3089 if ($running && PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
} && PVE
::QemuServer
::qga_check_running
($vmid)) {
3090 eval { PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-fstrim"); };
3094 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3095 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3102 foreach my $volid (@$newvollist) {
3103 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3106 die "storage migration failed: $err";
3109 if ($param->{delete}) {
3111 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3112 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3118 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3121 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3124 my $check_vm_disks_local = sub {
3125 my ($storecfg, $vmconf, $vmid) = @_;
3127 my $local_disks = {};
3129 # add some more information to the disks e.g. cdrom
3130 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3131 my ($volid, $attr) = @_;
3133 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3135 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3136 return if $scfg->{shared
};
3138 # The shared attr here is just a special case where the vdisk
3139 # is marked as shared manually
3140 return if $attr->{shared
};
3141 return if $attr->{cdrom
} and $volid eq "none";
3143 if (exists $local_disks->{$volid}) {
3144 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3146 $local_disks->{$volid} = $attr;
3147 # ensure volid is present in case it's needed
3148 $local_disks->{$volid}->{volid
} = $volid;
3152 return $local_disks;
3155 __PACKAGE__-
>register_method({
3156 name
=> 'migrate_vm_precondition',
3157 path
=> '{vmid}/migrate',
3161 description
=> "Get preconditions for migration.",
3163 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3166 additionalProperties
=> 0,
3168 node
=> get_standard_option
('pve-node'),
3169 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3170 target
=> get_standard_option
('pve-node', {
3171 description
=> "Target node.",
3172 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3180 running
=> { type
=> 'boolean' },
3184 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3186 not_allowed_nodes
=> {
3189 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3193 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3195 local_resources
=> {
3197 description
=> "List local resources e.g. pci, usb"
3204 my $rpcenv = PVE
::RPCEnvironment
::get
();
3206 my $authuser = $rpcenv->get_user();
3208 PVE
::Cluster
::check_cfs_quorum
();
3212 my $vmid = extract_param
($param, 'vmid');
3213 my $target = extract_param
($param, 'target');
3214 my $localnode = PVE
::INotify
::nodename
();
3218 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3219 my $storecfg = PVE
::Storage
::config
();
3222 # try to detect errors early
3223 PVE
::QemuConfig-
>check_lock($vmconf);
3225 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3227 # if vm is not running, return target nodes where local storage is available
3228 # for offline migration
3229 if (!$res->{running
}) {
3230 $res->{allowed_nodes
} = [];
3231 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3232 delete $checked_nodes->{$localnode};
3234 foreach my $node (keys %$checked_nodes) {
3235 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3236 push @{$res->{allowed_nodes
}}, $node;
3240 $res->{not_allowed_nodes
} = $checked_nodes;
3244 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3245 $res->{local_disks
} = [ values %$local_disks ];;
3247 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3249 $res->{local_resources
} = $local_resources;
3256 __PACKAGE__-
>register_method({
3257 name
=> 'migrate_vm',
3258 path
=> '{vmid}/migrate',
3262 description
=> "Migrate virtual machine. Creates a new migration task.",
3264 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3267 additionalProperties
=> 0,
3269 node
=> get_standard_option
('pve-node'),
3270 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3271 target
=> get_standard_option
('pve-node', {
3272 description
=> "Target node.",
3273 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3277 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3282 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3287 enum
=> ['secure', 'insecure'],
3288 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3291 migration_network
=> {
3292 type
=> 'string', format
=> 'CIDR',
3293 description
=> "CIDR of the (sub) network that is used for migration.",
3296 "with-local-disks" => {
3298 description
=> "Enable live storage migration for local disk",
3301 targetstorage
=> get_standard_option
('pve-storage-id', {
3302 description
=> "Default target storage.",
3304 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3307 description
=> "Override I/O bandwidth limit (in KiB/s).",
3311 default => 'migrate limit from datacenter or storage config',
3317 description
=> "the task ID.",
3322 my $rpcenv = PVE
::RPCEnvironment
::get
();
3323 my $authuser = $rpcenv->get_user();
3325 my $target = extract_param
($param, 'target');
3327 my $localnode = PVE
::INotify
::nodename
();
3328 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3330 PVE
::Cluster
::check_cfs_quorum
();
3332 PVE
::Cluster
::check_node_exists
($target);
3334 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3336 my $vmid = extract_param
($param, 'vmid');
3338 raise_param_exc
({ force
=> "Only root may use this option." })
3339 if $param->{force
} && $authuser ne 'root@pam';
3341 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3342 if $param->{migration_type
} && $authuser ne 'root@pam';
3344 # allow root only until better network permissions are available
3345 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3346 if $param->{migration_network
} && $authuser ne 'root@pam';
3349 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3351 # try to detect errors early
3353 PVE
::QemuConfig-
>check_lock($conf);
3355 if (PVE
::QemuServer
::check_running
($vmid)) {
3356 die "can't migrate running VM without --online\n" if !$param->{online
};
3358 warn "VM isn't running. Doing offline migration instead\n." if $param->{online
};
3359 $param->{online
} = 0;
3362 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3363 if !$param->{online
} && $param->{targetstorage
};
3365 my $storecfg = PVE
::Storage
::config
();
3367 if( $param->{targetstorage
}) {
3368 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3370 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3373 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3378 print "Requesting HA migration for VM $vmid to node $target\n";
3380 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3381 PVE
::Tools
::run_command
($cmd);
3385 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3390 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3394 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3397 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3402 __PACKAGE__-
>register_method({
3404 path
=> '{vmid}/monitor',
3408 description
=> "Execute Qemu monitor commands.",
3410 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3411 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3414 additionalProperties
=> 0,
3416 node
=> get_standard_option
('pve-node'),
3417 vmid
=> get_standard_option
('pve-vmid'),
3420 description
=> "The monitor command.",
3424 returns
=> { type
=> 'string'},
3428 my $rpcenv = PVE
::RPCEnvironment
::get
();
3429 my $authuser = $rpcenv->get_user();
3432 my $command = shift;
3433 return $command =~ m/^\s*info(\s+|$)/
3434 || $command =~ m/^\s*help\s*$/;
3437 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3438 if !&$is_ro($param->{command
});
3440 my $vmid = $param->{vmid
};
3442 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3446 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3448 $res = "ERROR: $@" if $@;
3453 __PACKAGE__-
>register_method({
3454 name
=> 'resize_vm',
3455 path
=> '{vmid}/resize',
3459 description
=> "Extend volume size.",
3461 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3464 additionalProperties
=> 0,
3466 node
=> get_standard_option
('pve-node'),
3467 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3468 skiplock
=> get_standard_option
('skiplock'),
3471 description
=> "The disk you want to resize.",
3472 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3476 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3477 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.",
3481 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3487 returns
=> { type
=> 'null'},
3491 my $rpcenv = PVE
::RPCEnvironment
::get
();
3493 my $authuser = $rpcenv->get_user();
3495 my $node = extract_param
($param, 'node');
3497 my $vmid = extract_param
($param, 'vmid');
3499 my $digest = extract_param
($param, 'digest');
3501 my $disk = extract_param
($param, 'disk');
3503 my $sizestr = extract_param
($param, 'size');
3505 my $skiplock = extract_param
($param, 'skiplock');
3506 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3507 if $skiplock && $authuser ne 'root@pam';
3509 my $storecfg = PVE
::Storage
::config
();
3511 my $updatefn = sub {
3513 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3515 die "checksum missmatch (file change by other user?)\n"
3516 if $digest && $digest ne $conf->{digest
};
3517 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3519 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3521 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3523 my (undef, undef, undef, undef, undef, undef, $format) =
3524 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3526 die "can't resize volume: $disk if snapshot exists\n"
3527 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3529 my $volid = $drive->{file
};
3531 die "disk '$disk' has no associated volume\n" if !$volid;
3533 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3535 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3537 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3539 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3540 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3542 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3544 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3545 my ($ext, $newsize, $unit) = ($1, $2, $4);
3548 $newsize = $newsize * 1024;
3549 } elsif ($unit eq 'M') {
3550 $newsize = $newsize * 1024 * 1024;
3551 } elsif ($unit eq 'G') {
3552 $newsize = $newsize * 1024 * 1024 * 1024;
3553 } elsif ($unit eq 'T') {
3554 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3557 $newsize += $size if $ext;
3558 $newsize = int($newsize);
3560 die "shrinking disks is not supported\n" if $newsize < $size;
3562 return if $size == $newsize;
3564 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3566 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3568 $drive->{size
} = $newsize;
3569 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3571 PVE
::QemuConfig-
>write_config($vmid, $conf);
3574 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3578 __PACKAGE__-
>register_method({
3579 name
=> 'snapshot_list',
3580 path
=> '{vmid}/snapshot',
3582 description
=> "List all snapshots.",
3584 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3587 protected
=> 1, # qemu pid files are only readable by root
3589 additionalProperties
=> 0,
3591 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3592 node
=> get_standard_option
('pve-node'),
3601 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3605 description
=> "Snapshot includes RAM.",
3610 description
=> "Snapshot description.",
3614 description
=> "Snapshot creation time",
3616 renderer
=> 'timestamp',
3620 description
=> "Parent snapshot identifier.",
3626 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3631 my $vmid = $param->{vmid
};
3633 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3634 my $snaphash = $conf->{snapshots
} || {};
3638 foreach my $name (keys %$snaphash) {
3639 my $d = $snaphash->{$name};
3642 snaptime
=> $d->{snaptime
} || 0,
3643 vmstate
=> $d->{vmstate
} ?
1 : 0,
3644 description
=> $d->{description
} || '',
3646 $item->{parent
} = $d->{parent
} if $d->{parent
};
3647 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3651 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3654 digest
=> $conf->{digest
},
3655 running
=> $running,
3656 description
=> "You are here!",
3658 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3660 push @$res, $current;
3665 __PACKAGE__-
>register_method({
3667 path
=> '{vmid}/snapshot',
3671 description
=> "Snapshot a VM.",
3673 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3676 additionalProperties
=> 0,
3678 node
=> get_standard_option
('pve-node'),
3679 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3680 snapname
=> get_standard_option
('pve-snapshot-name'),
3684 description
=> "Save the vmstate",
3689 description
=> "A textual description or comment.",
3695 description
=> "the task ID.",
3700 my $rpcenv = PVE
::RPCEnvironment
::get
();
3702 my $authuser = $rpcenv->get_user();
3704 my $node = extract_param
($param, 'node');
3706 my $vmid = extract_param
($param, 'vmid');
3708 my $snapname = extract_param
($param, 'snapname');
3710 die "unable to use snapshot name 'current' (reserved name)\n"
3711 if $snapname eq 'current';
3714 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3715 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3716 $param->{description
});
3719 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3722 __PACKAGE__-
>register_method({
3723 name
=> 'snapshot_cmd_idx',
3724 path
=> '{vmid}/snapshot/{snapname}',
3731 additionalProperties
=> 0,
3733 vmid
=> get_standard_option
('pve-vmid'),
3734 node
=> get_standard_option
('pve-node'),
3735 snapname
=> get_standard_option
('pve-snapshot-name'),
3744 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3751 push @$res, { cmd
=> 'rollback' };
3752 push @$res, { cmd
=> 'config' };
3757 __PACKAGE__-
>register_method({
3758 name
=> 'update_snapshot_config',
3759 path
=> '{vmid}/snapshot/{snapname}/config',
3763 description
=> "Update snapshot metadata.",
3765 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3768 additionalProperties
=> 0,
3770 node
=> get_standard_option
('pve-node'),
3771 vmid
=> get_standard_option
('pve-vmid'),
3772 snapname
=> get_standard_option
('pve-snapshot-name'),
3776 description
=> "A textual description or comment.",
3780 returns
=> { type
=> 'null' },
3784 my $rpcenv = PVE
::RPCEnvironment
::get
();
3786 my $authuser = $rpcenv->get_user();
3788 my $vmid = extract_param
($param, 'vmid');
3790 my $snapname = extract_param
($param, 'snapname');
3792 return undef if !defined($param->{description
});
3794 my $updatefn = sub {
3796 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3798 PVE
::QemuConfig-
>check_lock($conf);
3800 my $snap = $conf->{snapshots
}->{$snapname};
3802 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3804 $snap->{description
} = $param->{description
} if defined($param->{description
});
3806 PVE
::QemuConfig-
>write_config($vmid, $conf);
3809 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3814 __PACKAGE__-
>register_method({
3815 name
=> 'get_snapshot_config',
3816 path
=> '{vmid}/snapshot/{snapname}/config',
3819 description
=> "Get snapshot configuration",
3821 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3824 additionalProperties
=> 0,
3826 node
=> get_standard_option
('pve-node'),
3827 vmid
=> get_standard_option
('pve-vmid'),
3828 snapname
=> get_standard_option
('pve-snapshot-name'),
3831 returns
=> { type
=> "object" },
3835 my $rpcenv = PVE
::RPCEnvironment
::get
();
3837 my $authuser = $rpcenv->get_user();
3839 my $vmid = extract_param
($param, 'vmid');
3841 my $snapname = extract_param
($param, 'snapname');
3843 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3845 my $snap = $conf->{snapshots
}->{$snapname};
3847 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3852 __PACKAGE__-
>register_method({
3854 path
=> '{vmid}/snapshot/{snapname}/rollback',
3858 description
=> "Rollback VM state to specified snapshot.",
3860 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3863 additionalProperties
=> 0,
3865 node
=> get_standard_option
('pve-node'),
3866 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3867 snapname
=> get_standard_option
('pve-snapshot-name'),
3872 description
=> "the task ID.",
3877 my $rpcenv = PVE
::RPCEnvironment
::get
();
3879 my $authuser = $rpcenv->get_user();
3881 my $node = extract_param
($param, 'node');
3883 my $vmid = extract_param
($param, 'vmid');
3885 my $snapname = extract_param
($param, 'snapname');
3888 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3889 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3893 # hold migration lock, this makes sure that nobody create replication snapshots
3894 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3897 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3900 __PACKAGE__-
>register_method({
3901 name
=> 'delsnapshot',
3902 path
=> '{vmid}/snapshot/{snapname}',
3906 description
=> "Delete a VM snapshot.",
3908 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3911 additionalProperties
=> 0,
3913 node
=> get_standard_option
('pve-node'),
3914 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3915 snapname
=> get_standard_option
('pve-snapshot-name'),
3919 description
=> "For removal from config file, even if removing disk snapshots fails.",
3925 description
=> "the task ID.",
3930 my $rpcenv = PVE
::RPCEnvironment
::get
();
3932 my $authuser = $rpcenv->get_user();
3934 my $node = extract_param
($param, 'node');
3936 my $vmid = extract_param
($param, 'vmid');
3938 my $snapname = extract_param
($param, 'snapname');
3941 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3942 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3945 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3948 __PACKAGE__-
>register_method({
3950 path
=> '{vmid}/template',
3954 description
=> "Create a Template.",
3956 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3957 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3960 additionalProperties
=> 0,
3962 node
=> get_standard_option
('pve-node'),
3963 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3967 description
=> "If you want to convert only 1 disk to base image.",
3968 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3973 returns
=> { type
=> 'null'},
3977 my $rpcenv = PVE
::RPCEnvironment
::get
();
3979 my $authuser = $rpcenv->get_user();
3981 my $node = extract_param
($param, 'node');
3983 my $vmid = extract_param
($param, 'vmid');
3985 my $disk = extract_param
($param, 'disk');
3987 my $updatefn = sub {
3989 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3991 PVE
::QemuConfig-
>check_lock($conf);
3993 die "unable to create template, because VM contains snapshots\n"
3994 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3996 die "you can't convert a template to a template\n"
3997 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3999 die "you can't convert a VM to template if VM is running\n"
4000 if PVE
::QemuServer
::check_running
($vmid);
4003 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4006 $conf->{template
} = 1;
4007 PVE
::QemuConfig-
>write_config($vmid, $conf);
4009 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4012 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4016 __PACKAGE__-
>register_method({
4017 name
=> 'cloudinit_generated_config_dump',
4018 path
=> '{vmid}/cloudinit/dump',
4021 description
=> "Get automatically generated cloudinit config.",
4023 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4026 additionalProperties
=> 0,
4028 node
=> get_standard_option
('pve-node'),
4029 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4031 description
=> 'Config type.',
4033 enum
=> ['user', 'network', 'meta'],
4043 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4045 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});