1 package PVE
::API2
::Qemu
;
11 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
14 use PVE
::Tools
qw(extract_param);
15 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
17 use PVE
::JSONSchema
qw(get_standard_option);
19 use PVE
::ReplicationConfig
;
20 use PVE
::GuestHelpers
;
23 use PVE
::QemuServer
::Monitor
qw(mon_cmd);
25 use PVE
::RPCEnvironment
;
26 use PVE
::AccessControl
;
30 use PVE
::API2
::Firewall
::VM
;
31 use PVE
::API2
::Qemu
::Agent
;
32 use PVE
::VZDump
::Plugin
;
33 use PVE
::DataCenterConfig
;
37 if (!$ENV{PVE_GENERATING_DOCS
}) {
38 require PVE
::HA
::Env
::PVE2
;
39 import PVE
::HA
::Env
::PVE2
;
40 require PVE
::HA
::Config
;
41 import PVE
::HA
::Config
;
45 use Data
::Dumper
; # fixme: remove
47 use base
qw(PVE::RESTHandler);
49 my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal.";
51 my $resolve_cdrom_alias = sub {
54 if (my $value = $param->{cdrom
}) {
55 $value .= ",media=cdrom" if $value !~ m/media=/;
56 $param->{ide2
} = $value;
57 delete $param->{cdrom
};
61 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
62 my $check_storage_access = sub {
63 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
65 PVE
::QemuServer
::foreach_drive
($settings, sub {
66 my ($ds, $drive) = @_;
68 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
70 my $volid = $drive->{file
};
71 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
73 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit' || (defined($volname) && $volname eq 'cloudinit'))) {
75 } elsif ($isCDROM && ($volid eq 'cdrom')) {
76 $rpcenv->check($authuser, "/", ['Sys.Console']);
77 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
78 my ($storeid, $size) = ($2 || $default_storage, $3);
79 die "no storage ID specified (and no default storage)\n" if !$storeid;
80 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
81 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
82 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
83 if !$scfg->{content
}->{images
};
85 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
89 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
90 if defined($settings->{vmstatestorage
});
93 my $check_storage_access_clone = sub {
94 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
98 PVE
::QemuServer
::foreach_drive
($conf, sub {
99 my ($ds, $drive) = @_;
101 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
103 my $volid = $drive->{file
};
105 return if !$volid || $volid eq 'none';
108 if ($volid eq 'cdrom') {
109 $rpcenv->check($authuser, "/", ['Sys.Console']);
111 # we simply allow access
112 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
113 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
114 $sharedvm = 0 if !$scfg->{shared
};
118 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
119 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
120 $sharedvm = 0 if !$scfg->{shared
};
122 $sid = $storage if $storage;
123 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
127 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
128 if defined($conf->{vmstatestorage
});
133 # Note: $pool is only needed when creating a VM, because pool permissions
134 # are automatically inherited if VM already exists inside a pool.
135 my $create_disks = sub {
136 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
143 my ($ds, $disk) = @_;
145 my $volid = $disk->{file
};
146 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
148 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
149 delete $disk->{size
};
150 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
151 } elsif (defined($volname) && $volname eq 'cloudinit') {
152 $storeid = $storeid // $default_storage;
153 die "no storage ID specified (and no default storage)\n" if !$storeid;
154 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
155 my $name = "vm-$vmid-cloudinit";
159 $fmt = $disk->{format
} // "qcow2";
162 $fmt = $disk->{format
} // "raw";
165 # Initial disk created with 4 MB and aligned to 4MB on regeneration
166 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
167 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
168 $disk->{file
} = $volid;
169 $disk->{media
} = 'cdrom';
170 push @$vollist, $volid;
171 delete $disk->{format
}; # no longer needed
172 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
173 } elsif ($volid =~ $NEW_DISK_RE) {
174 my ($storeid, $size) = ($2 || $default_storage, $3);
175 die "no storage ID specified (and no default storage)\n" if !$storeid;
176 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
177 my $fmt = $disk->{format
} || $defformat;
179 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
182 if ($ds eq 'efidisk0') {
183 ($volid, $size) = PVE
::QemuServer
::create_efidisk
($storecfg, $storeid, $vmid, $fmt, $arch);
185 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
187 push @$vollist, $volid;
188 $disk->{file
} = $volid;
189 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
190 delete $disk->{format
}; # no longer needed
191 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
194 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
196 my $volid_is_new = 1;
199 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
200 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
205 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
207 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
209 die "volume $volid does not exist\n" if !$size;
211 $disk->{size
} = $size;
214 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
218 eval { PVE
::QemuServer
::foreach_drive
($settings, $code); };
220 # free allocated images on error
222 syslog
('err', "VM $vmid creating disks failed");
223 foreach my $volid (@$vollist) {
224 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
230 # modify vm config if everything went well
231 foreach my $ds (keys %$res) {
232 $conf->{$ds} = $res->{$ds};
249 my $memoryoptions = {
255 my $hwtypeoptions = {
268 my $generaloptions = {
275 'migrate_downtime' => 1,
276 'migrate_speed' => 1,
289 my $vmpoweroptions = {
296 'vmstatestorage' => 1,
299 my $cloudinitoptions = {
309 my $check_vm_modify_config_perm = sub {
310 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
312 return 1 if $authuser eq 'root@pam';
314 foreach my $opt (@$key_list) {
315 # some checks (e.g., disk, serial port, usb) need to be done somewhere
316 # else, as there the permission can be value dependend
317 next if PVE
::QemuServer
::is_valid_drivename
($opt);
318 next if $opt eq 'cdrom';
319 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
322 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
323 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
324 } elsif ($memoryoptions->{$opt}) {
325 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
326 } elsif ($hwtypeoptions->{$opt}) {
327 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
328 } elsif ($generaloptions->{$opt}) {
329 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
330 # special case for startup since it changes host behaviour
331 if ($opt eq 'startup') {
332 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
334 } elsif ($vmpoweroptions->{$opt}) {
335 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
336 } elsif ($diskoptions->{$opt}) {
337 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
338 } elsif ($cloudinitoptions->{$opt} || ($opt =~ m/^(?:net|ipconfig)\d+$/)) {
339 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
340 } elsif ($opt eq 'vmstate') {
341 # the user needs Disk and PowerMgmt privileges to change the vmstate
342 # also needs privileges on the storage, that will be checked later
343 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt' ]);
345 # catches hostpci\d+, args, lock, etc.
346 # new options will be checked here
347 die "only root can set '$opt' config\n";
354 __PACKAGE__-
>register_method({
358 description
=> "Virtual machine index (per node).",
360 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
364 protected
=> 1, # qemu pid files are only readable by root
366 additionalProperties
=> 0,
368 node
=> get_standard_option
('pve-node'),
372 description
=> "Determine the full status of active VMs.",
380 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
382 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
387 my $rpcenv = PVE
::RPCEnvironment
::get
();
388 my $authuser = $rpcenv->get_user();
390 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
393 foreach my $vmid (keys %$vmstatus) {
394 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
396 my $data = $vmstatus->{$vmid};
405 __PACKAGE__-
>register_method({
409 description
=> "Create or restore a virtual machine.",
411 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
412 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
413 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
414 user
=> 'all', # check inside
419 additionalProperties
=> 0,
420 properties
=> PVE
::QemuServer
::json_config_properties
(
422 node
=> get_standard_option
('pve-node'),
423 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
425 description
=> "The backup file.",
429 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
431 storage
=> get_standard_option
('pve-storage-id', {
432 description
=> "Default storage.",
434 completion
=> \
&PVE
::QemuServer
::complete_storage
,
439 description
=> "Allow to overwrite existing VM.",
440 requires
=> 'archive',
445 description
=> "Assign a unique random ethernet address.",
446 requires
=> 'archive',
450 type
=> 'string', format
=> 'pve-poolid',
451 description
=> "Add the VM to the specified pool.",
454 description
=> "Override I/O bandwidth limit (in KiB/s).",
458 default => 'restore limit from datacenter or storage config',
464 description
=> "Start VM after it was created successfully.",
474 my $rpcenv = PVE
::RPCEnvironment
::get
();
475 my $authuser = $rpcenv->get_user();
477 my $node = extract_param
($param, 'node');
478 my $vmid = extract_param
($param, 'vmid');
480 my $archive = extract_param
($param, 'archive');
481 my $is_restore = !!$archive;
483 my $bwlimit = extract_param
($param, 'bwlimit');
484 my $force = extract_param
($param, 'force');
485 my $pool = extract_param
($param, 'pool');
486 my $start_after_create = extract_param
($param, 'start');
487 my $storage = extract_param
($param, 'storage');
488 my $unique = extract_param
($param, 'unique');
490 if (defined(my $ssh_keys = $param->{sshkeys
})) {
491 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
492 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
495 PVE
::Cluster
::check_cfs_quorum
();
497 my $filename = PVE
::QemuConfig-
>config_file($vmid);
498 my $storecfg = PVE
::Storage
::config
();
500 if (defined($pool)) {
501 $rpcenv->check_pool_exist($pool);
504 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
505 if defined($storage);
507 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
509 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
511 } elsif ($archive && $force && (-f
$filename) &&
512 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
513 # OK: user has VM.Backup permissions, and want to restore an existing VM
519 &$resolve_cdrom_alias($param);
521 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
523 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
525 foreach my $opt (keys %$param) {
526 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
527 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
528 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
530 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
531 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
535 PVE
::QemuServer
::add_random_macs
($param);
537 my $keystr = join(' ', keys %$param);
538 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
540 if ($archive eq '-') {
541 die "pipe requires cli environment\n"
542 if $rpcenv->{type
} ne 'cli';
544 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
545 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
549 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
551 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
552 die "$emsg $@" if $@;
554 my $restorefn = sub {
555 my $conf = PVE
::QemuConfig-
>load_config($vmid);
557 PVE
::QemuConfig-
>check_protection($conf, $emsg);
559 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
562 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
568 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
569 # Convert restored VM to template if backup was VM template
570 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
571 warn "Convert to template.\n";
572 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
576 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
579 # ensure no old replication state are exists
580 PVE
::ReplicationState
::delete_guest_states
($vmid);
582 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
584 if ($start_after_create) {
585 print "Execute autostart\n";
586 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
592 # ensure no old replication state are exists
593 PVE
::ReplicationState
::delete_guest_states
($vmid);
597 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
601 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
603 if (!$conf->{bootdisk
}) {
604 my $firstdisk = PVE
::QemuServer
::resolve_first_disk
($conf);
605 $conf->{bootdisk
} = $firstdisk if $firstdisk;
608 # auto generate uuid if user did not specify smbios1 option
609 if (!$conf->{smbios1
}) {
610 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
613 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
614 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
617 PVE
::QemuConfig-
>write_config($vmid, $conf);
623 foreach my $volid (@$vollist) {
624 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
630 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
633 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
635 if ($start_after_create) {
636 print "Execute autostart\n";
637 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
642 my ($code, $worker_name);
644 $worker_name = 'qmrestore';
646 eval { $restorefn->() };
648 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
654 $worker_name = 'qmcreate';
656 eval { $createfn->() };
659 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
660 unlink($conffile) or die "failed to remove config file: $!\n";
668 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
671 __PACKAGE__-
>register_method({
676 description
=> "Directory index",
681 additionalProperties
=> 0,
683 node
=> get_standard_option
('pve-node'),
684 vmid
=> get_standard_option
('pve-vmid'),
692 subdir
=> { type
=> 'string' },
695 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
701 { subdir
=> 'config' },
702 { subdir
=> 'pending' },
703 { subdir
=> 'status' },
704 { subdir
=> 'unlink' },
705 { subdir
=> 'vncproxy' },
706 { subdir
=> 'termproxy' },
707 { subdir
=> 'migrate' },
708 { subdir
=> 'resize' },
709 { subdir
=> 'move' },
711 { subdir
=> 'rrddata' },
712 { subdir
=> 'monitor' },
713 { subdir
=> 'agent' },
714 { subdir
=> 'snapshot' },
715 { subdir
=> 'spiceproxy' },
716 { subdir
=> 'sendkey' },
717 { subdir
=> 'firewall' },
723 __PACKAGE__-
>register_method ({
724 subclass
=> "PVE::API2::Firewall::VM",
725 path
=> '{vmid}/firewall',
728 __PACKAGE__-
>register_method ({
729 subclass
=> "PVE::API2::Qemu::Agent",
730 path
=> '{vmid}/agent',
733 __PACKAGE__-
>register_method({
735 path
=> '{vmid}/rrd',
737 protected
=> 1, # fixme: can we avoid that?
739 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
741 description
=> "Read VM RRD statistics (returns PNG)",
743 additionalProperties
=> 0,
745 node
=> get_standard_option
('pve-node'),
746 vmid
=> get_standard_option
('pve-vmid'),
748 description
=> "Specify the time frame you are interested in.",
750 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
753 description
=> "The list of datasources you want to display.",
754 type
=> 'string', format
=> 'pve-configid-list',
757 description
=> "The RRD consolidation function",
759 enum
=> [ 'AVERAGE', 'MAX' ],
767 filename
=> { type
=> 'string' },
773 return PVE
::RRD
::create_rrd_graph
(
774 "pve2-vm/$param->{vmid}", $param->{timeframe
},
775 $param->{ds
}, $param->{cf
});
779 __PACKAGE__-
>register_method({
781 path
=> '{vmid}/rrddata',
783 protected
=> 1, # fixme: can we avoid that?
785 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
787 description
=> "Read VM RRD statistics",
789 additionalProperties
=> 0,
791 node
=> get_standard_option
('pve-node'),
792 vmid
=> get_standard_option
('pve-vmid'),
794 description
=> "Specify the time frame you are interested in.",
796 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
799 description
=> "The RRD consolidation function",
801 enum
=> [ 'AVERAGE', 'MAX' ],
816 return PVE
::RRD
::create_rrd_data
(
817 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
821 __PACKAGE__-
>register_method({
823 path
=> '{vmid}/config',
826 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
828 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
831 additionalProperties
=> 0,
833 node
=> get_standard_option
('pve-node'),
834 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
836 description
=> "Get current values (instead of pending values).",
841 snapshot
=> get_standard_option
('pve-snapshot-name', {
842 description
=> "Fetch config values from given snapshot.",
845 my ($cmd, $pname, $cur, $args) = @_;
846 PVE
::QemuConfig-
>snapshot_list($args->[0]);
852 description
=> "The current VM configuration.",
854 properties
=> PVE
::QemuServer
::json_config_properties
({
857 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
864 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
865 current
=> "cannot use 'snapshot' parameter with 'current'"})
866 if ($param->{snapshot
} && $param->{current
});
869 if ($param->{snapshot
}) {
870 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
872 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
874 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
879 __PACKAGE__-
>register_method({
880 name
=> 'vm_pending',
881 path
=> '{vmid}/pending',
884 description
=> "Get virtual machine configuration, including pending changes.",
886 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
889 additionalProperties
=> 0,
891 node
=> get_standard_option
('pve-node'),
892 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
901 description
=> "Configuration option name.",
905 description
=> "Current value.",
910 description
=> "Pending value.",
915 description
=> "Indicates a pending delete request if present and not 0. " .
916 "The value 2 indicates a force-delete request.",
928 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
930 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
932 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
933 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
935 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
938 # POST/PUT {vmid}/config implementation
940 # The original API used PUT (idempotent) an we assumed that all operations
941 # are fast. But it turned out that almost any configuration change can
942 # involve hot-plug actions, or disk alloc/free. Such actions can take long
943 # time to complete and have side effects (not idempotent).
945 # The new implementation uses POST and forks a worker process. We added
946 # a new option 'background_delay'. If specified we wait up to
947 # 'background_delay' second for the worker task to complete. It returns null
948 # if the task is finished within that time, else we return the UPID.
950 my $update_vm_api = sub {
951 my ($param, $sync) = @_;
953 my $rpcenv = PVE
::RPCEnvironment
::get
();
955 my $authuser = $rpcenv->get_user();
957 my $node = extract_param
($param, 'node');
959 my $vmid = extract_param
($param, 'vmid');
961 my $digest = extract_param
($param, 'digest');
963 my $background_delay = extract_param
($param, 'background_delay');
965 if (defined(my $cipassword = $param->{cipassword
})) {
966 # Same logic as in cloud-init (but with the regex fixed...)
967 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
968 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
971 my @paramarr = (); # used for log message
972 foreach my $key (sort keys %$param) {
973 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
974 push @paramarr, "-$key", $value;
977 my $skiplock = extract_param
($param, 'skiplock');
978 raise_param_exc
({ skiplock
=> "Only root may use this option." })
979 if $skiplock && $authuser ne 'root@pam';
981 my $delete_str = extract_param
($param, 'delete');
983 my $revert_str = extract_param
($param, 'revert');
985 my $force = extract_param
($param, 'force');
987 if (defined(my $ssh_keys = $param->{sshkeys
})) {
988 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
989 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
992 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
994 my $storecfg = PVE
::Storage
::config
();
996 my $defaults = PVE
::QemuServer
::load_defaults
();
998 &$resolve_cdrom_alias($param);
1000 # now try to verify all parameters
1003 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1004 if (!PVE
::QemuServer
::option_exists
($opt)) {
1005 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1008 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1009 "-revert $opt' at the same time" })
1010 if defined($param->{$opt});
1012 $revert->{$opt} = 1;
1016 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1017 $opt = 'ide2' if $opt eq 'cdrom';
1019 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1020 "-delete $opt' at the same time" })
1021 if defined($param->{$opt});
1023 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1024 "-revert $opt' at the same time" })
1027 if (!PVE
::QemuServer
::option_exists
($opt)) {
1028 raise_param_exc
({ delete => "unknown option '$opt'" });
1034 my $repl_conf = PVE
::ReplicationConfig-
>new();
1035 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1036 my $check_replication = sub {
1038 return if !$is_replicated;
1039 my $volid = $drive->{file
};
1040 return if !$volid || !($drive->{replicate
}//1);
1041 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1043 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1044 return if $volname eq 'cloudinit';
1047 if ($volid =~ $NEW_DISK_RE) {
1049 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1051 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1053 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1054 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1055 return if $scfg->{shared
};
1056 die "cannot add non-replicatable volume to a replicated VM\n";
1059 foreach my $opt (keys %$param) {
1060 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1061 # cleanup drive path
1062 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1063 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1064 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1065 $check_replication->($drive);
1066 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
1067 } elsif ($opt =~ m/^net(\d+)$/) {
1069 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1070 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1071 } elsif ($opt eq 'vmgenid') {
1072 if ($param->{$opt} eq '1') {
1073 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1075 } elsif ($opt eq 'hookscript') {
1076 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1077 raise_param_exc
({ $opt => $@ }) if $@;
1081 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1083 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1085 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1087 my $updatefn = sub {
1089 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1091 die "checksum missmatch (file change by other user?)\n"
1092 if $digest && $digest ne $conf->{digest
};
1094 # FIXME: 'suspended' lock should probabyl be a state or "weak" lock?!
1095 if (scalar(@delete) && grep { $_ eq 'vmstate'} @delete) {
1096 if (defined($conf->{lock}) && $conf->{lock} eq 'suspended') {
1097 delete $conf->{lock}; # for check lock check, not written out
1098 push @delete, 'lock'; # this is the real deal to write it out
1100 push @delete, 'runningmachine' if $conf->{runningmachine
};
1103 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1105 foreach my $opt (keys %$revert) {
1106 if (defined($conf->{$opt})) {
1107 $param->{$opt} = $conf->{$opt};
1108 } elsif (defined($conf->{pending
}->{$opt})) {
1113 if ($param->{memory
} || defined($param->{balloon
})) {
1114 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1115 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1117 die "balloon value too large (must be smaller than assigned memory)\n"
1118 if $balloon && $balloon > $maxmem;
1121 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1125 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1127 # write updates to pending section
1129 my $modified = {}; # record what $option we modify
1131 foreach my $opt (@delete) {
1132 $modified->{$opt} = 1;
1133 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1135 # value of what we want to delete, independent if pending or not
1136 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1137 if (!defined($val)) {
1138 warn "cannot delete '$opt' - not set in current configuration!\n";
1139 $modified->{$opt} = 0;
1142 my $is_pending_val = defined($conf->{pending
}->{$opt});
1143 delete $conf->{pending
}->{$opt};
1145 if ($opt =~ m/^unused/) {
1146 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1147 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1148 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1149 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1150 delete $conf->{$opt};
1151 PVE
::QemuConfig-
>write_config($vmid, $conf);
1153 } elsif ($opt eq 'vmstate') {
1154 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1155 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1156 delete $conf->{$opt};
1157 PVE
::QemuConfig-
>write_config($vmid, $conf);
1159 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1160 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1161 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1162 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1164 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1165 PVE
::QemuConfig-
>write_config($vmid, $conf);
1166 } elsif ($opt =~ m/^serial\d+$/) {
1167 if ($val eq 'socket') {
1168 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1169 } elsif ($authuser ne 'root@pam') {
1170 die "only root can delete '$opt' config for real devices\n";
1172 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1173 PVE
::QemuConfig-
>write_config($vmid, $conf);
1174 } elsif ($opt =~ m/^usb\d+$/) {
1175 if ($val =~ m/spice/) {
1176 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1177 } elsif ($authuser ne 'root@pam') {
1178 die "only root can delete '$opt' config for real devices\n";
1180 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1181 PVE
::QemuConfig-
>write_config($vmid, $conf);
1183 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1184 PVE
::QemuConfig-
>write_config($vmid, $conf);
1188 foreach my $opt (keys %$param) { # add/change
1189 $modified->{$opt} = 1;
1190 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1191 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1193 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1195 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1196 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1197 # FIXME: cloudinit: CDROM or Disk?
1198 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1199 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1201 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1203 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1204 if defined($conf->{pending
}->{$opt});
1206 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1207 } elsif ($opt =~ m/^serial\d+/) {
1208 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1209 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1210 } elsif ($authuser ne 'root@pam') {
1211 die "only root can modify '$opt' config for real devices\n";
1213 $conf->{pending
}->{$opt} = $param->{$opt};
1214 } elsif ($opt =~ m/^usb\d+/) {
1215 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1216 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1217 } elsif ($authuser ne 'root@pam') {
1218 die "only root can modify '$opt' config for real devices\n";
1220 $conf->{pending
}->{$opt} = $param->{$opt};
1222 $conf->{pending
}->{$opt} = $param->{$opt};
1224 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1225 PVE
::QemuConfig-
>write_config($vmid, $conf);
1228 # remove pending changes when nothing changed
1229 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1230 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1231 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1233 return if !scalar(keys %{$conf->{pending
}});
1235 my $running = PVE
::QemuServer
::check_running
($vmid);
1237 # apply pending changes
1239 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1243 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1245 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running, $errors);
1247 raise_param_exc
($errors) if scalar(keys %$errors);
1256 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1258 if ($background_delay) {
1260 # Note: It would be better to do that in the Event based HTTPServer
1261 # to avoid blocking call to sleep.
1263 my $end_time = time() + $background_delay;
1265 my $task = PVE
::Tools
::upid_decode
($upid);
1268 while (time() < $end_time) {
1269 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1271 sleep(1); # this gets interrupted when child process ends
1275 my $status = PVE
::Tools
::upid_read_status
($upid);
1276 return undef if $status eq 'OK';
1285 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1288 my $vm_config_perm_list = [
1293 'VM.Config.Network',
1295 'VM.Config.Options',
1298 __PACKAGE__-
>register_method({
1299 name
=> 'update_vm_async',
1300 path
=> '{vmid}/config',
1304 description
=> "Set virtual machine options (asynchrounous API).",
1306 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1309 additionalProperties
=> 0,
1310 properties
=> PVE
::QemuServer
::json_config_properties
(
1312 node
=> get_standard_option
('pve-node'),
1313 vmid
=> get_standard_option
('pve-vmid'),
1314 skiplock
=> get_standard_option
('skiplock'),
1316 type
=> 'string', format
=> 'pve-configid-list',
1317 description
=> "A list of settings you want to delete.",
1321 type
=> 'string', format
=> 'pve-configid-list',
1322 description
=> "Revert a pending change.",
1327 description
=> $opt_force_description,
1329 requires
=> 'delete',
1333 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1337 background_delay
=> {
1339 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1350 code
=> $update_vm_api,
1353 __PACKAGE__-
>register_method({
1354 name
=> 'update_vm',
1355 path
=> '{vmid}/config',
1359 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1361 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1364 additionalProperties
=> 0,
1365 properties
=> PVE
::QemuServer
::json_config_properties
(
1367 node
=> get_standard_option
('pve-node'),
1368 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1369 skiplock
=> get_standard_option
('skiplock'),
1371 type
=> 'string', format
=> 'pve-configid-list',
1372 description
=> "A list of settings you want to delete.",
1376 type
=> 'string', format
=> 'pve-configid-list',
1377 description
=> "Revert a pending change.",
1382 description
=> $opt_force_description,
1384 requires
=> 'delete',
1388 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1394 returns
=> { type
=> 'null' },
1397 &$update_vm_api($param, 1);
1402 __PACKAGE__-
>register_method({
1403 name
=> 'destroy_vm',
1408 description
=> "Destroy the vm (also delete all used/owned volumes).",
1410 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1413 additionalProperties
=> 0,
1415 node
=> get_standard_option
('pve-node'),
1416 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1417 skiplock
=> get_standard_option
('skiplock'),
1420 description
=> "Remove vmid from backup cron jobs.",
1431 my $rpcenv = PVE
::RPCEnvironment
::get
();
1432 my $authuser = $rpcenv->get_user();
1433 my $vmid = $param->{vmid
};
1435 my $skiplock = $param->{skiplock
};
1436 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1437 if $skiplock && $authuser ne 'root@pam';
1440 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1441 my $storecfg = PVE
::Storage
::config
();
1442 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1443 die "unable to remove VM $vmid - used in HA resources\n"
1444 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1446 if (!$param->{purge
}) {
1447 # don't allow destroy if with replication jobs but no purge param
1448 my $repl_conf = PVE
::ReplicationConfig-
>new();
1449 $repl_conf->check_for_existing_jobs($vmid);
1452 # early tests (repeat after locking)
1453 die "VM $vmid is running - destroy failed\n"
1454 if PVE
::QemuServer
::check_running
($vmid);
1459 syslog
('info', "destroy VM $vmid: $upid\n");
1460 PVE
::QemuConfig-
>lock_config($vmid, sub {
1461 die "VM $vmid is running - destroy failed\n"
1462 if (PVE
::QemuServer
::check_running
($vmid));
1464 PVE
::QemuServer
::destroy_vm
($storecfg, $vmid, $skiplock, { lock => 'destroyed' });
1466 PVE
::AccessControl
::remove_vm_access
($vmid);
1467 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1468 if ($param->{purge
}) {
1469 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1470 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1473 # only now remove the zombie config, else we can have reuse race
1474 PVE
::QemuConfig-
>destroy_config($vmid);
1478 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1481 __PACKAGE__-
>register_method({
1483 path
=> '{vmid}/unlink',
1487 description
=> "Unlink/delete disk images.",
1489 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1492 additionalProperties
=> 0,
1494 node
=> get_standard_option
('pve-node'),
1495 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1497 type
=> 'string', format
=> 'pve-configid-list',
1498 description
=> "A list of disk IDs you want to delete.",
1502 description
=> $opt_force_description,
1507 returns
=> { type
=> 'null'},
1511 $param->{delete} = extract_param
($param, 'idlist');
1513 __PACKAGE__-
>update_vm($param);
1520 __PACKAGE__-
>register_method({
1522 path
=> '{vmid}/vncproxy',
1526 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1528 description
=> "Creates a TCP VNC proxy connections.",
1530 additionalProperties
=> 0,
1532 node
=> get_standard_option
('pve-node'),
1533 vmid
=> get_standard_option
('pve-vmid'),
1537 description
=> "starts websockify instead of vncproxy",
1542 additionalProperties
=> 0,
1544 user
=> { type
=> 'string' },
1545 ticket
=> { type
=> 'string' },
1546 cert
=> { type
=> 'string' },
1547 port
=> { type
=> 'integer' },
1548 upid
=> { type
=> 'string' },
1554 my $rpcenv = PVE
::RPCEnvironment
::get
();
1556 my $authuser = $rpcenv->get_user();
1558 my $vmid = $param->{vmid
};
1559 my $node = $param->{node
};
1560 my $websocket = $param->{websocket
};
1562 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1563 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1565 my $authpath = "/vms/$vmid";
1567 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1569 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1575 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1576 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1577 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1578 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1579 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1581 $family = PVE
::Tools
::get_host_address_family
($node);
1584 my $port = PVE
::Tools
::next_vnc_port
($family);
1591 syslog
('info', "starting vnc proxy $upid\n");
1597 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1599 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1600 '-timeout', $timeout, '-authpath', $authpath,
1601 '-perm', 'Sys.Console'];
1603 if ($param->{websocket
}) {
1604 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1605 push @$cmd, '-notls', '-listen', 'localhost';
1608 push @$cmd, '-c', @$remcmd, @$termcmd;
1610 PVE
::Tools
::run_command
($cmd);
1614 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1616 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1618 my $sock = IO
::Socket
::IP-
>new(
1623 GetAddrInfoFlags
=> 0,
1624 ) or die "failed to create socket: $!\n";
1625 # Inside the worker we shouldn't have any previous alarms
1626 # running anyway...:
1628 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1630 accept(my $cli, $sock) or die "connection failed: $!\n";
1633 if (PVE
::Tools
::run_command
($cmd,
1634 output
=> '>&'.fileno($cli),
1635 input
=> '<&'.fileno($cli),
1638 die "Failed to run vncproxy.\n";
1645 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1647 PVE
::Tools
::wait_for_vnc_port
($port);
1658 __PACKAGE__-
>register_method({
1659 name
=> 'termproxy',
1660 path
=> '{vmid}/termproxy',
1664 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1666 description
=> "Creates a TCP proxy connections.",
1668 additionalProperties
=> 0,
1670 node
=> get_standard_option
('pve-node'),
1671 vmid
=> get_standard_option
('pve-vmid'),
1675 enum
=> [qw(serial0 serial1 serial2 serial3)],
1676 description
=> "opens a serial terminal (defaults to display)",
1681 additionalProperties
=> 0,
1683 user
=> { type
=> 'string' },
1684 ticket
=> { type
=> 'string' },
1685 port
=> { type
=> 'integer' },
1686 upid
=> { type
=> 'string' },
1692 my $rpcenv = PVE
::RPCEnvironment
::get
();
1694 my $authuser = $rpcenv->get_user();
1696 my $vmid = $param->{vmid
};
1697 my $node = $param->{node
};
1698 my $serial = $param->{serial
};
1700 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1702 if (!defined($serial)) {
1703 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1704 $serial = $conf->{vga
};
1708 my $authpath = "/vms/$vmid";
1710 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1715 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1716 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1717 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1718 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1719 push @$remcmd, '--';
1721 $family = PVE
::Tools
::get_host_address_family
($node);
1724 my $port = PVE
::Tools
::next_vnc_port
($family);
1726 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1727 push @$termcmd, '-iface', $serial if $serial;
1732 syslog
('info', "starting qemu termproxy $upid\n");
1734 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1735 '--perm', 'VM.Console', '--'];
1736 push @$cmd, @$remcmd, @$termcmd;
1738 PVE
::Tools
::run_command
($cmd);
1741 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1743 PVE
::Tools
::wait_for_vnc_port
($port);
1753 __PACKAGE__-
>register_method({
1754 name
=> 'vncwebsocket',
1755 path
=> '{vmid}/vncwebsocket',
1758 description
=> "You also need to pass a valid ticket (vncticket).",
1759 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1761 description
=> "Opens a weksocket for VNC traffic.",
1763 additionalProperties
=> 0,
1765 node
=> get_standard_option
('pve-node'),
1766 vmid
=> get_standard_option
('pve-vmid'),
1768 description
=> "Ticket from previous call to vncproxy.",
1773 description
=> "Port number returned by previous vncproxy call.",
1783 port
=> { type
=> 'string' },
1789 my $rpcenv = PVE
::RPCEnvironment
::get
();
1791 my $authuser = $rpcenv->get_user();
1793 my $vmid = $param->{vmid
};
1794 my $node = $param->{node
};
1796 my $authpath = "/vms/$vmid";
1798 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1800 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1802 # Note: VNC ports are acessible from outside, so we do not gain any
1803 # security if we verify that $param->{port} belongs to VM $vmid. This
1804 # check is done by verifying the VNC ticket (inside VNC protocol).
1806 my $port = $param->{port
};
1808 return { port
=> $port };
1811 __PACKAGE__-
>register_method({
1812 name
=> 'spiceproxy',
1813 path
=> '{vmid}/spiceproxy',
1818 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1820 description
=> "Returns a SPICE configuration to connect to the VM.",
1822 additionalProperties
=> 0,
1824 node
=> get_standard_option
('pve-node'),
1825 vmid
=> get_standard_option
('pve-vmid'),
1826 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1829 returns
=> get_standard_option
('remote-viewer-config'),
1833 my $rpcenv = PVE
::RPCEnvironment
::get
();
1835 my $authuser = $rpcenv->get_user();
1837 my $vmid = $param->{vmid
};
1838 my $node = $param->{node
};
1839 my $proxy = $param->{proxy
};
1841 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1842 my $title = "VM $vmid";
1843 $title .= " - ". $conf->{name
} if $conf->{name
};
1845 my $port = PVE
::QemuServer
::spice_port
($vmid);
1847 my ($ticket, undef, $remote_viewer_config) =
1848 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1850 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1851 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1853 return $remote_viewer_config;
1856 __PACKAGE__-
>register_method({
1858 path
=> '{vmid}/status',
1861 description
=> "Directory index",
1866 additionalProperties
=> 0,
1868 node
=> get_standard_option
('pve-node'),
1869 vmid
=> get_standard_option
('pve-vmid'),
1877 subdir
=> { type
=> 'string' },
1880 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1886 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1889 { subdir
=> 'current' },
1890 { subdir
=> 'start' },
1891 { subdir
=> 'stop' },
1892 { subdir
=> 'reset' },
1893 { subdir
=> 'shutdown' },
1894 { subdir
=> 'suspend' },
1895 { subdir
=> 'reboot' },
1901 __PACKAGE__-
>register_method({
1902 name
=> 'vm_status',
1903 path
=> '{vmid}/status/current',
1906 protected
=> 1, # qemu pid files are only readable by root
1907 description
=> "Get virtual machine status.",
1909 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1912 additionalProperties
=> 0,
1914 node
=> get_standard_option
('pve-node'),
1915 vmid
=> get_standard_option
('pve-vmid'),
1921 %$PVE::QemuServer
::vmstatus_return_properties
,
1923 description
=> "HA manager service status.",
1927 description
=> "Qemu VGA configuration supports spice.",
1932 description
=> "Qemu GuestAgent enabled in config.",
1942 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1944 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1945 my $status = $vmstatus->{$param->{vmid
}};
1947 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1949 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1950 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1955 __PACKAGE__-
>register_method({
1957 path
=> '{vmid}/status/start',
1961 description
=> "Start virtual machine.",
1963 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1966 additionalProperties
=> 0,
1968 node
=> get_standard_option
('pve-node'),
1969 vmid
=> get_standard_option
('pve-vmid',
1970 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1971 skiplock
=> get_standard_option
('skiplock'),
1972 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1973 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1976 enum
=> ['secure', 'insecure'],
1977 description
=> "Migration traffic is encrypted using an SSH " .
1978 "tunnel by default. On secure, completely private networks " .
1979 "this can be disabled to increase performance.",
1982 migration_network
=> {
1983 type
=> 'string', format
=> 'CIDR',
1984 description
=> "CIDR of the (sub) network that is used for migration.",
1987 machine
=> get_standard_option
('pve-qemu-machine'),
1989 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
2001 my $rpcenv = PVE
::RPCEnvironment
::get
();
2002 my $authuser = $rpcenv->get_user();
2004 my $node = extract_param
($param, 'node');
2005 my $vmid = extract_param
($param, 'vmid');
2007 my $machine = extract_param
($param, 'machine');
2009 my $get_root_param = sub {
2010 my $value = extract_param
($param, $_[0]);
2011 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2012 if $value && $authuser ne 'root@pam';
2016 my $stateuri = $get_root_param->('stateuri');
2017 my $skiplock = $get_root_param->('skiplock');
2018 my $migratedfrom = $get_root_param->('migratedfrom');
2019 my $migration_type = $get_root_param->('migration_type');
2020 my $migration_network = $get_root_param->('migration_network');
2021 my $targetstorage = $get_root_param->('targetstorage');
2023 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2024 if $targetstorage && !$migratedfrom;
2026 # read spice ticket from STDIN
2028 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2029 if (defined(my $line = <STDIN
>)) {
2031 $spice_ticket = $line;
2035 PVE
::Cluster
::check_cfs_quorum
();
2037 my $storecfg = PVE
::Storage
::config
();
2039 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2043 print "Requesting HA start for VM $vmid\n";
2045 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2046 PVE
::Tools
::run_command
($cmd);
2050 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2057 syslog
('info', "start VM $vmid: $upid\n");
2059 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2060 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
2064 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2068 __PACKAGE__-
>register_method({
2070 path
=> '{vmid}/status/stop',
2074 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2075 "is akin to pulling the power plug of a running computer and may damage the VM data",
2077 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2080 additionalProperties
=> 0,
2082 node
=> get_standard_option
('pve-node'),
2083 vmid
=> get_standard_option
('pve-vmid',
2084 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2085 skiplock
=> get_standard_option
('skiplock'),
2086 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2088 description
=> "Wait maximal timeout seconds.",
2094 description
=> "Do not deactivate storage volumes.",
2107 my $rpcenv = PVE
::RPCEnvironment
::get
();
2108 my $authuser = $rpcenv->get_user();
2110 my $node = extract_param
($param, 'node');
2111 my $vmid = extract_param
($param, 'vmid');
2113 my $skiplock = extract_param
($param, 'skiplock');
2114 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2115 if $skiplock && $authuser ne 'root@pam';
2117 my $keepActive = extract_param
($param, 'keepActive');
2118 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2119 if $keepActive && $authuser ne 'root@pam';
2121 my $migratedfrom = extract_param
($param, 'migratedfrom');
2122 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2123 if $migratedfrom && $authuser ne 'root@pam';
2126 my $storecfg = PVE
::Storage
::config
();
2128 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2133 print "Requesting HA stop for VM $vmid\n";
2135 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2136 PVE
::Tools
::run_command
($cmd);
2140 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2146 syslog
('info', "stop VM $vmid: $upid\n");
2148 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2149 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2153 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2157 __PACKAGE__-
>register_method({
2159 path
=> '{vmid}/status/reset',
2163 description
=> "Reset virtual machine.",
2165 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2168 additionalProperties
=> 0,
2170 node
=> get_standard_option
('pve-node'),
2171 vmid
=> get_standard_option
('pve-vmid',
2172 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2173 skiplock
=> get_standard_option
('skiplock'),
2182 my $rpcenv = PVE
::RPCEnvironment
::get
();
2184 my $authuser = $rpcenv->get_user();
2186 my $node = extract_param
($param, 'node');
2188 my $vmid = extract_param
($param, 'vmid');
2190 my $skiplock = extract_param
($param, 'skiplock');
2191 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2192 if $skiplock && $authuser ne 'root@pam';
2194 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2199 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2204 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2207 __PACKAGE__-
>register_method({
2208 name
=> 'vm_shutdown',
2209 path
=> '{vmid}/status/shutdown',
2213 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2214 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2216 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2219 additionalProperties
=> 0,
2221 node
=> get_standard_option
('pve-node'),
2222 vmid
=> get_standard_option
('pve-vmid',
2223 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2224 skiplock
=> get_standard_option
('skiplock'),
2226 description
=> "Wait maximal timeout seconds.",
2232 description
=> "Make sure the VM stops.",
2238 description
=> "Do not deactivate storage volumes.",
2251 my $rpcenv = PVE
::RPCEnvironment
::get
();
2252 my $authuser = $rpcenv->get_user();
2254 my $node = extract_param
($param, 'node');
2255 my $vmid = extract_param
($param, 'vmid');
2257 my $skiplock = extract_param
($param, 'skiplock');
2258 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2259 if $skiplock && $authuser ne 'root@pam';
2261 my $keepActive = extract_param
($param, 'keepActive');
2262 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2263 if $keepActive && $authuser ne 'root@pam';
2265 my $storecfg = PVE
::Storage
::config
();
2269 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2270 # otherwise, we will infer a shutdown command, but run into the timeout,
2271 # then when the vm is resumed, it will instantly shutdown
2273 # checking the qmp status here to get feedback to the gui/cli/api
2274 # and the status query should not take too long
2275 my $qmpstatus = eval {
2276 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2277 mon_cmd
($vmid, "query-status");
2281 if (!$err && $qmpstatus->{status
} eq "paused") {
2282 if ($param->{forceStop
}) {
2283 warn "VM is paused - stop instead of shutdown\n";
2286 die "VM is paused - cannot shutdown\n";
2290 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2292 my $timeout = $param->{timeout
} // 60;
2296 print "Requesting HA stop for VM $vmid\n";
2298 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2299 PVE
::Tools
::run_command
($cmd);
2303 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2310 syslog
('info', "shutdown VM $vmid: $upid\n");
2312 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2313 $shutdown, $param->{forceStop
}, $keepActive);
2317 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2321 __PACKAGE__-
>register_method({
2322 name
=> 'vm_reboot',
2323 path
=> '{vmid}/status/reboot',
2327 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2329 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2332 additionalProperties
=> 0,
2334 node
=> get_standard_option
('pve-node'),
2335 vmid
=> get_standard_option
('pve-vmid',
2336 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2338 description
=> "Wait maximal timeout seconds for the shutdown.",
2351 my $rpcenv = PVE
::RPCEnvironment
::get
();
2352 my $authuser = $rpcenv->get_user();
2354 my $node = extract_param
($param, 'node');
2355 my $vmid = extract_param
($param, 'vmid');
2357 my $qmpstatus = eval {
2358 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2359 mon_cmd
($vmid, "query-status");
2363 if (!$err && $qmpstatus->{status
} eq "paused") {
2364 die "VM is paused - cannot shutdown\n";
2367 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2372 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2373 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2377 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2380 __PACKAGE__-
>register_method({
2381 name
=> 'vm_suspend',
2382 path
=> '{vmid}/status/suspend',
2386 description
=> "Suspend virtual machine.",
2388 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2389 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2390 " on the storage for the vmstate.",
2391 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2394 additionalProperties
=> 0,
2396 node
=> get_standard_option
('pve-node'),
2397 vmid
=> get_standard_option
('pve-vmid',
2398 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2399 skiplock
=> get_standard_option
('skiplock'),
2404 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2406 statestorage
=> get_standard_option
('pve-storage-id', {
2407 description
=> "The storage for the VM state",
2408 requires
=> 'todisk',
2410 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2420 my $rpcenv = PVE
::RPCEnvironment
::get
();
2421 my $authuser = $rpcenv->get_user();
2423 my $node = extract_param
($param, 'node');
2424 my $vmid = extract_param
($param, 'vmid');
2426 my $todisk = extract_param
($param, 'todisk') // 0;
2428 my $statestorage = extract_param
($param, 'statestorage');
2430 my $skiplock = extract_param
($param, 'skiplock');
2431 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2432 if $skiplock && $authuser ne 'root@pam';
2434 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2436 die "Cannot suspend HA managed VM to disk\n"
2437 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2439 # early check for storage permission, for better user feedback
2441 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2443 if (!$statestorage) {
2444 # get statestorage from config if none is given
2445 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2446 my $storecfg = PVE
::Storage
::config
();
2447 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2450 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2456 syslog
('info', "suspend VM $vmid: $upid\n");
2458 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2463 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2464 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2467 __PACKAGE__-
>register_method({
2468 name
=> 'vm_resume',
2469 path
=> '{vmid}/status/resume',
2473 description
=> "Resume virtual machine.",
2475 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2478 additionalProperties
=> 0,
2480 node
=> get_standard_option
('pve-node'),
2481 vmid
=> get_standard_option
('pve-vmid',
2482 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2483 skiplock
=> get_standard_option
('skiplock'),
2484 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2494 my $rpcenv = PVE
::RPCEnvironment
::get
();
2496 my $authuser = $rpcenv->get_user();
2498 my $node = extract_param
($param, 'node');
2500 my $vmid = extract_param
($param, 'vmid');
2502 my $skiplock = extract_param
($param, 'skiplock');
2503 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2504 if $skiplock && $authuser ne 'root@pam';
2506 my $nocheck = extract_param
($param, 'nocheck');
2508 my $to_disk_suspended;
2510 PVE
::QemuConfig-
>lock_config($vmid, sub {
2511 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2512 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2516 die "VM $vmid not running\n"
2517 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2522 syslog
('info', "resume VM $vmid: $upid\n");
2524 if (!$to_disk_suspended) {
2525 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2527 my $storecfg = PVE
::Storage
::config
();
2528 PVE
::QemuServer
::vm_start
($storecfg, $vmid, undef, $skiplock);
2534 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2537 __PACKAGE__-
>register_method({
2538 name
=> 'vm_sendkey',
2539 path
=> '{vmid}/sendkey',
2543 description
=> "Send key event to virtual machine.",
2545 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2548 additionalProperties
=> 0,
2550 node
=> get_standard_option
('pve-node'),
2551 vmid
=> get_standard_option
('pve-vmid',
2552 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2553 skiplock
=> get_standard_option
('skiplock'),
2555 description
=> "The key (qemu monitor encoding).",
2560 returns
=> { type
=> 'null'},
2564 my $rpcenv = PVE
::RPCEnvironment
::get
();
2566 my $authuser = $rpcenv->get_user();
2568 my $node = extract_param
($param, 'node');
2570 my $vmid = extract_param
($param, 'vmid');
2572 my $skiplock = extract_param
($param, 'skiplock');
2573 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2574 if $skiplock && $authuser ne 'root@pam';
2576 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2581 __PACKAGE__-
>register_method({
2582 name
=> 'vm_feature',
2583 path
=> '{vmid}/feature',
2587 description
=> "Check if feature for virtual machine is available.",
2589 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2592 additionalProperties
=> 0,
2594 node
=> get_standard_option
('pve-node'),
2595 vmid
=> get_standard_option
('pve-vmid'),
2597 description
=> "Feature to check.",
2599 enum
=> [ 'snapshot', 'clone', 'copy' ],
2601 snapname
=> get_standard_option
('pve-snapshot-name', {
2609 hasFeature
=> { type
=> 'boolean' },
2612 items
=> { type
=> 'string' },
2619 my $node = extract_param
($param, 'node');
2621 my $vmid = extract_param
($param, 'vmid');
2623 my $snapname = extract_param
($param, 'snapname');
2625 my $feature = extract_param
($param, 'feature');
2627 my $running = PVE
::QemuServer
::check_running
($vmid);
2629 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2632 my $snap = $conf->{snapshots
}->{$snapname};
2633 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2636 my $storecfg = PVE
::Storage
::config
();
2638 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2639 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2642 hasFeature
=> $hasFeature,
2643 nodes
=> [ keys %$nodelist ],
2647 __PACKAGE__-
>register_method({
2649 path
=> '{vmid}/clone',
2653 description
=> "Create a copy of virtual machine/template.",
2655 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2656 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2657 "'Datastore.AllocateSpace' on any used storage.",
2660 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2662 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2663 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2668 additionalProperties
=> 0,
2670 node
=> get_standard_option
('pve-node'),
2671 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2672 newid
=> get_standard_option
('pve-vmid', {
2673 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2674 description
=> 'VMID for the clone.' }),
2677 type
=> 'string', format
=> 'dns-name',
2678 description
=> "Set a name for the new VM.",
2683 description
=> "Description for the new VM.",
2687 type
=> 'string', format
=> 'pve-poolid',
2688 description
=> "Add the new VM to the specified pool.",
2690 snapname
=> get_standard_option
('pve-snapshot-name', {
2693 storage
=> get_standard_option
('pve-storage-id', {
2694 description
=> "Target storage for full clone.",
2698 description
=> "Target format for file storage. Only valid for full clone.",
2701 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2706 description
=> "Create a full copy of all disks. This is always done when " .
2707 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2709 target
=> get_standard_option
('pve-node', {
2710 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2714 description
=> "Override I/O bandwidth limit (in KiB/s).",
2718 default => 'clone limit from datacenter or storage config',
2728 my $rpcenv = PVE
::RPCEnvironment
::get
();
2729 my $authuser = $rpcenv->get_user();
2731 my $node = extract_param
($param, 'node');
2732 my $vmid = extract_param
($param, 'vmid');
2733 my $newid = extract_param
($param, 'newid');
2734 my $pool = extract_param
($param, 'pool');
2735 $rpcenv->check_pool_exist($pool) if defined($pool);
2737 my $snapname = extract_param
($param, 'snapname');
2738 my $storage = extract_param
($param, 'storage');
2739 my $format = extract_param
($param, 'format');
2740 my $target = extract_param
($param, 'target');
2742 my $localnode = PVE
::INotify
::nodename
();
2744 if ($target && ($target eq $localnode || $target eq 'localhost')) {
2747 PVE
::Cluster
::check_node_exists
($target);
2750 my $storecfg = PVE
::Storage
::config
();
2753 # check if storage is enabled on local node
2754 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2756 # check if storage is available on target node
2757 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2758 # clone only works if target storage is shared
2759 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2760 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2764 PVE
::Cluster
::check_cfs_quorum
();
2766 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2768 # exclusive lock if VM is running - else shared lock is enough;
2769 my $shared_lock = $running ?
0 : 1;
2772 # do all tests after lock but before forking worker - if possible
2774 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2775 PVE
::QemuConfig-
>check_lock($conf);
2777 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2778 die "unexpected state change\n" if $verify_running != $running;
2780 die "snapshot '$snapname' does not exist\n"
2781 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2783 my $full = extract_param
($param, 'full') // !PVE
::QemuConfig-
>is_template($conf);
2785 die "parameter 'storage' not allowed for linked clones\n"
2786 if defined($storage) && !$full;
2788 die "parameter 'format' not allowed for linked clones\n"
2789 if defined($format) && !$full;
2791 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2793 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2795 die "can't clone VM to node '$target' (VM uses local storage)\n"
2796 if $target && !$sharedvm;
2798 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2799 die "unable to create VM $newid: config file already exists\n"
2802 my $newconf = { lock => 'clone' };
2807 foreach my $opt (keys %$oldconf) {
2808 my $value = $oldconf->{$opt};
2810 # do not copy snapshot related info
2811 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2812 $opt eq 'vmstate' || $opt eq 'snapstate';
2814 # no need to copy unused images, because VMID(owner) changes anyways
2815 next if $opt =~ m/^unused\d+$/;
2817 # always change MAC! address
2818 if ($opt =~ m/^net(\d+)$/) {
2819 my $net = PVE
::QemuServer
::parse_net
($value);
2820 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2821 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2822 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2823 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2824 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2825 die "unable to parse drive options for '$opt'\n" if !$drive;
2826 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2827 $newconf->{$opt} = $value; # simply copy configuration
2829 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2830 die "Full clone feature is not supported for drive '$opt'\n"
2831 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2832 $fullclone->{$opt} = 1;
2834 # not full means clone instead of copy
2835 die "Linked clone feature is not supported for drive '$opt'\n"
2836 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2838 $drives->{$opt} = $drive;
2839 push @$vollist, $drive->{file
};
2842 # copy everything else
2843 $newconf->{$opt} = $value;
2847 # auto generate a new uuid
2848 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2849 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2850 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2851 # auto generate a new vmgenid only if the option was set for template
2852 if ($newconf->{vmgenid
}) {
2853 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2856 delete $newconf->{template
};
2858 if ($param->{name
}) {
2859 $newconf->{name
} = $param->{name
};
2861 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
2864 if ($param->{description
}) {
2865 $newconf->{description
} = $param->{description
};
2868 # create empty/temp config - this fails if VM already exists on other node
2869 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
2870 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2875 my $newvollist = [];
2882 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2884 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2886 my $bwlimit = extract_param
($param, 'bwlimit');
2888 my $total_jobs = scalar(keys %{$drives});
2891 foreach my $opt (keys %$drives) {
2892 my $drive = $drives->{$opt};
2893 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2895 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
2896 my $storage_list = [ $src_sid ];
2897 push @$storage_list, $storage if defined($storage);
2898 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
2900 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2901 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2902 $jobs, $skipcomplete, $oldconf->{agent
}, $clonelimit);
2904 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
2906 PVE
::QemuConfig-
>write_config($newid, $newconf);
2910 delete $newconf->{lock};
2912 # do not write pending changes
2913 if (my @changes = keys %{$newconf->{pending
}}) {
2914 my $pending = join(',', @changes);
2915 warn "found pending changes for '$pending', discarding for clone\n";
2916 delete $newconf->{pending
};
2919 PVE
::QemuConfig-
>write_config($newid, $newconf);
2922 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2923 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2924 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2926 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2927 die "Failed to move config to node '$target' - rename failed: $!\n"
2928 if !rename($conffile, $newconffile);
2931 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2936 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2937 sleep 1; # some storage like rbd need to wait before release volume - really?
2939 foreach my $volid (@$newvollist) {
2940 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2943 die "clone failed: $err";
2949 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2951 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2954 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2955 # Aquire exclusive lock lock for $newid
2956 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2961 __PACKAGE__-
>register_method({
2962 name
=> 'move_vm_disk',
2963 path
=> '{vmid}/move_disk',
2967 description
=> "Move volume to different storage.",
2969 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2971 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2972 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2976 additionalProperties
=> 0,
2978 node
=> get_standard_option
('pve-node'),
2979 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2982 description
=> "The disk you want to move.",
2983 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2985 storage
=> get_standard_option
('pve-storage-id', {
2986 description
=> "Target storage.",
2987 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2991 description
=> "Target Format.",
2992 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2997 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3003 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3008 description
=> "Override I/O bandwidth limit (in KiB/s).",
3012 default => 'move limit from datacenter or storage config',
3018 description
=> "the task ID.",
3023 my $rpcenv = PVE
::RPCEnvironment
::get
();
3024 my $authuser = $rpcenv->get_user();
3026 my $node = extract_param
($param, 'node');
3027 my $vmid = extract_param
($param, 'vmid');
3028 my $digest = extract_param
($param, 'digest');
3029 my $disk = extract_param
($param, 'disk');
3030 my $storeid = extract_param
($param, 'storage');
3031 my $format = extract_param
($param, 'format');
3033 my $storecfg = PVE
::Storage
::config
();
3035 my $updatefn = sub {
3036 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3037 PVE
::QemuConfig-
>check_lock($conf);
3039 die "VM config checksum missmatch (file change by other user?)\n"
3040 if $digest && $digest ne $conf->{digest
};
3042 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3044 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3046 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3047 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3049 my $old_volid = $drive->{file
};
3051 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3052 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3056 die "you can't move to the same storage with same format\n" if $oldstoreid eq $storeid &&
3057 (!$format || !$oldfmt || $oldfmt eq $format);
3059 # this only checks snapshots because $disk is passed!
3060 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3061 die "you can't move a disk with snapshots and delete the source\n"
3062 if $snapshotted && $param->{delete};
3064 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3066 my $running = PVE
::QemuServer
::check_running
($vmid);
3068 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3071 my $newvollist = [];
3077 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3079 warn "moving disk with snapshots, snapshots will not be moved!\n"
3082 my $bwlimit = extract_param
($param, 'bwlimit');
3083 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3085 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3086 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit);
3088 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3090 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3092 # convert moved disk to base if part of template
3093 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3094 if PVE
::QemuConfig-
>is_template($conf);
3096 PVE
::QemuConfig-
>write_config($vmid, $conf);
3098 my $do_trim = PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
};
3099 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3100 eval { mon_cmd
($vmid, "guest-fstrim") };
3104 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3105 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3111 foreach my $volid (@$newvollist) {
3112 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3115 die "storage migration failed: $err";
3118 if ($param->{delete}) {
3120 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3121 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3127 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3130 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3133 my $check_vm_disks_local = sub {
3134 my ($storecfg, $vmconf, $vmid) = @_;
3136 my $local_disks = {};
3138 # add some more information to the disks e.g. cdrom
3139 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3140 my ($volid, $attr) = @_;
3142 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3144 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3145 return if $scfg->{shared
};
3147 # The shared attr here is just a special case where the vdisk
3148 # is marked as shared manually
3149 return if $attr->{shared
};
3150 return if $attr->{cdrom
} and $volid eq "none";
3152 if (exists $local_disks->{$volid}) {
3153 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3155 $local_disks->{$volid} = $attr;
3156 # ensure volid is present in case it's needed
3157 $local_disks->{$volid}->{volid
} = $volid;
3161 return $local_disks;
3164 __PACKAGE__-
>register_method({
3165 name
=> 'migrate_vm_precondition',
3166 path
=> '{vmid}/migrate',
3170 description
=> "Get preconditions for migration.",
3172 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3175 additionalProperties
=> 0,
3177 node
=> get_standard_option
('pve-node'),
3178 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3179 target
=> get_standard_option
('pve-node', {
3180 description
=> "Target node.",
3181 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3189 running
=> { type
=> 'boolean' },
3193 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3195 not_allowed_nodes
=> {
3198 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3202 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3204 local_resources
=> {
3206 description
=> "List local resources e.g. pci, usb"
3213 my $rpcenv = PVE
::RPCEnvironment
::get
();
3215 my $authuser = $rpcenv->get_user();
3217 PVE
::Cluster
::check_cfs_quorum
();
3221 my $vmid = extract_param
($param, 'vmid');
3222 my $target = extract_param
($param, 'target');
3223 my $localnode = PVE
::INotify
::nodename
();
3227 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3228 my $storecfg = PVE
::Storage
::config
();
3231 # try to detect errors early
3232 PVE
::QemuConfig-
>check_lock($vmconf);
3234 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3236 # if vm is not running, return target nodes where local storage is available
3237 # for offline migration
3238 if (!$res->{running
}) {
3239 $res->{allowed_nodes
} = [];
3240 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3241 delete $checked_nodes->{$localnode};
3243 foreach my $node (keys %$checked_nodes) {
3244 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3245 push @{$res->{allowed_nodes
}}, $node;
3249 $res->{not_allowed_nodes
} = $checked_nodes;
3253 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3254 $res->{local_disks
} = [ values %$local_disks ];;
3256 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3258 $res->{local_resources
} = $local_resources;
3265 __PACKAGE__-
>register_method({
3266 name
=> 'migrate_vm',
3267 path
=> '{vmid}/migrate',
3271 description
=> "Migrate virtual machine. Creates a new migration task.",
3273 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3276 additionalProperties
=> 0,
3278 node
=> get_standard_option
('pve-node'),
3279 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3280 target
=> get_standard_option
('pve-node', {
3281 description
=> "Target node.",
3282 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3286 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3291 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3296 enum
=> ['secure', 'insecure'],
3297 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3300 migration_network
=> {
3301 type
=> 'string', format
=> 'CIDR',
3302 description
=> "CIDR of the (sub) network that is used for migration.",
3305 "with-local-disks" => {
3307 description
=> "Enable live storage migration for local disk",
3310 targetstorage
=> get_standard_option
('pve-storage-id', {
3311 description
=> "Default target storage.",
3313 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3316 description
=> "Override I/O bandwidth limit (in KiB/s).",
3320 default => 'migrate limit from datacenter or storage config',
3326 description
=> "the task ID.",
3331 my $rpcenv = PVE
::RPCEnvironment
::get
();
3332 my $authuser = $rpcenv->get_user();
3334 my $target = extract_param
($param, 'target');
3336 my $localnode = PVE
::INotify
::nodename
();
3337 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3339 PVE
::Cluster
::check_cfs_quorum
();
3341 PVE
::Cluster
::check_node_exists
($target);
3343 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3345 my $vmid = extract_param
($param, 'vmid');
3347 raise_param_exc
({ force
=> "Only root may use this option." })
3348 if $param->{force
} && $authuser ne 'root@pam';
3350 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3351 if $param->{migration_type
} && $authuser ne 'root@pam';
3353 # allow root only until better network permissions are available
3354 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3355 if $param->{migration_network
} && $authuser ne 'root@pam';
3358 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3360 # try to detect errors early
3362 PVE
::QemuConfig-
>check_lock($conf);
3364 if (PVE
::QemuServer
::check_running
($vmid)) {
3365 die "can't migrate running VM without --online\n" if !$param->{online
};
3367 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
3368 $param->{online
} = 0;
3371 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3372 if !$param->{online
} && $param->{targetstorage
};
3374 my $storecfg = PVE
::Storage
::config
();
3376 if( $param->{targetstorage
}) {
3377 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3379 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3382 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3387 print "Requesting HA migration for VM $vmid to node $target\n";
3389 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3390 PVE
::Tools
::run_command
($cmd);
3394 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3399 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3403 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3406 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3411 __PACKAGE__-
>register_method({
3413 path
=> '{vmid}/monitor',
3417 description
=> "Execute Qemu monitor commands.",
3419 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3420 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3423 additionalProperties
=> 0,
3425 node
=> get_standard_option
('pve-node'),
3426 vmid
=> get_standard_option
('pve-vmid'),
3429 description
=> "The monitor command.",
3433 returns
=> { type
=> 'string'},
3437 my $rpcenv = PVE
::RPCEnvironment
::get
();
3438 my $authuser = $rpcenv->get_user();
3441 my $command = shift;
3442 return $command =~ m/^\s*info(\s+|$)/
3443 || $command =~ m/^\s*help\s*$/;
3446 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3447 if !&$is_ro($param->{command
});
3449 my $vmid = $param->{vmid
};
3451 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3455 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3457 $res = "ERROR: $@" if $@;
3462 __PACKAGE__-
>register_method({
3463 name
=> 'resize_vm',
3464 path
=> '{vmid}/resize',
3468 description
=> "Extend volume size.",
3470 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3473 additionalProperties
=> 0,
3475 node
=> get_standard_option
('pve-node'),
3476 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3477 skiplock
=> get_standard_option
('skiplock'),
3480 description
=> "The disk you want to resize.",
3481 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3485 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3486 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.",
3490 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3496 returns
=> { type
=> 'null'},
3500 my $rpcenv = PVE
::RPCEnvironment
::get
();
3502 my $authuser = $rpcenv->get_user();
3504 my $node = extract_param
($param, 'node');
3506 my $vmid = extract_param
($param, 'vmid');
3508 my $digest = extract_param
($param, 'digest');
3510 my $disk = extract_param
($param, 'disk');
3512 my $sizestr = extract_param
($param, 'size');
3514 my $skiplock = extract_param
($param, 'skiplock');
3515 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3516 if $skiplock && $authuser ne 'root@pam';
3518 my $storecfg = PVE
::Storage
::config
();
3520 my $updatefn = sub {
3522 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3524 die "checksum missmatch (file change by other user?)\n"
3525 if $digest && $digest ne $conf->{digest
};
3526 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3528 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3530 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3532 my (undef, undef, undef, undef, undef, undef, $format) =
3533 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3535 die "can't resize volume: $disk if snapshot exists\n"
3536 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3538 my $volid = $drive->{file
};
3540 die "disk '$disk' has no associated volume\n" if !$volid;
3542 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3544 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3546 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3548 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3549 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3551 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3553 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3554 my ($ext, $newsize, $unit) = ($1, $2, $4);
3557 $newsize = $newsize * 1024;
3558 } elsif ($unit eq 'M') {
3559 $newsize = $newsize * 1024 * 1024;
3560 } elsif ($unit eq 'G') {
3561 $newsize = $newsize * 1024 * 1024 * 1024;
3562 } elsif ($unit eq 'T') {
3563 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3566 $newsize += $size if $ext;
3567 $newsize = int($newsize);
3569 die "shrinking disks is not supported\n" if $newsize < $size;
3571 return if $size == $newsize;
3573 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3575 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3577 $drive->{size
} = $newsize;
3578 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
3580 PVE
::QemuConfig-
>write_config($vmid, $conf);
3583 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3587 __PACKAGE__-
>register_method({
3588 name
=> 'snapshot_list',
3589 path
=> '{vmid}/snapshot',
3591 description
=> "List all snapshots.",
3593 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3596 protected
=> 1, # qemu pid files are only readable by root
3598 additionalProperties
=> 0,
3600 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3601 node
=> get_standard_option
('pve-node'),
3610 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3614 description
=> "Snapshot includes RAM.",
3619 description
=> "Snapshot description.",
3623 description
=> "Snapshot creation time",
3625 renderer
=> 'timestamp',
3629 description
=> "Parent snapshot identifier.",
3635 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3640 my $vmid = $param->{vmid
};
3642 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3643 my $snaphash = $conf->{snapshots
} || {};
3647 foreach my $name (keys %$snaphash) {
3648 my $d = $snaphash->{$name};
3651 snaptime
=> $d->{snaptime
} || 0,
3652 vmstate
=> $d->{vmstate
} ?
1 : 0,
3653 description
=> $d->{description
} || '',
3655 $item->{parent
} = $d->{parent
} if $d->{parent
};
3656 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3660 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3663 digest
=> $conf->{digest
},
3664 running
=> $running,
3665 description
=> "You are here!",
3667 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3669 push @$res, $current;
3674 __PACKAGE__-
>register_method({
3676 path
=> '{vmid}/snapshot',
3680 description
=> "Snapshot a VM.",
3682 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3685 additionalProperties
=> 0,
3687 node
=> get_standard_option
('pve-node'),
3688 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3689 snapname
=> get_standard_option
('pve-snapshot-name'),
3693 description
=> "Save the vmstate",
3698 description
=> "A textual description or comment.",
3704 description
=> "the task ID.",
3709 my $rpcenv = PVE
::RPCEnvironment
::get
();
3711 my $authuser = $rpcenv->get_user();
3713 my $node = extract_param
($param, 'node');
3715 my $vmid = extract_param
($param, 'vmid');
3717 my $snapname = extract_param
($param, 'snapname');
3719 die "unable to use snapshot name 'current' (reserved name)\n"
3720 if $snapname eq 'current';
3722 die "unable to use snapshot name 'pending' (reserved name)\n"
3723 if lc($snapname) eq 'pending';
3726 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3727 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3728 $param->{description
});
3731 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3734 __PACKAGE__-
>register_method({
3735 name
=> 'snapshot_cmd_idx',
3736 path
=> '{vmid}/snapshot/{snapname}',
3743 additionalProperties
=> 0,
3745 vmid
=> get_standard_option
('pve-vmid'),
3746 node
=> get_standard_option
('pve-node'),
3747 snapname
=> get_standard_option
('pve-snapshot-name'),
3756 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3763 push @$res, { cmd
=> 'rollback' };
3764 push @$res, { cmd
=> 'config' };
3769 __PACKAGE__-
>register_method({
3770 name
=> 'update_snapshot_config',
3771 path
=> '{vmid}/snapshot/{snapname}/config',
3775 description
=> "Update snapshot metadata.",
3777 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3780 additionalProperties
=> 0,
3782 node
=> get_standard_option
('pve-node'),
3783 vmid
=> get_standard_option
('pve-vmid'),
3784 snapname
=> get_standard_option
('pve-snapshot-name'),
3788 description
=> "A textual description or comment.",
3792 returns
=> { type
=> 'null' },
3796 my $rpcenv = PVE
::RPCEnvironment
::get
();
3798 my $authuser = $rpcenv->get_user();
3800 my $vmid = extract_param
($param, 'vmid');
3802 my $snapname = extract_param
($param, 'snapname');
3804 return undef if !defined($param->{description
});
3806 my $updatefn = sub {
3808 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3810 PVE
::QemuConfig-
>check_lock($conf);
3812 my $snap = $conf->{snapshots
}->{$snapname};
3814 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3816 $snap->{description
} = $param->{description
} if defined($param->{description
});
3818 PVE
::QemuConfig-
>write_config($vmid, $conf);
3821 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3826 __PACKAGE__-
>register_method({
3827 name
=> 'get_snapshot_config',
3828 path
=> '{vmid}/snapshot/{snapname}/config',
3831 description
=> "Get snapshot configuration",
3833 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3836 additionalProperties
=> 0,
3838 node
=> get_standard_option
('pve-node'),
3839 vmid
=> get_standard_option
('pve-vmid'),
3840 snapname
=> get_standard_option
('pve-snapshot-name'),
3843 returns
=> { type
=> "object" },
3847 my $rpcenv = PVE
::RPCEnvironment
::get
();
3849 my $authuser = $rpcenv->get_user();
3851 my $vmid = extract_param
($param, 'vmid');
3853 my $snapname = extract_param
($param, 'snapname');
3855 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3857 my $snap = $conf->{snapshots
}->{$snapname};
3859 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3864 __PACKAGE__-
>register_method({
3866 path
=> '{vmid}/snapshot/{snapname}/rollback',
3870 description
=> "Rollback VM state to specified snapshot.",
3872 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3875 additionalProperties
=> 0,
3877 node
=> get_standard_option
('pve-node'),
3878 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3879 snapname
=> get_standard_option
('pve-snapshot-name'),
3884 description
=> "the task ID.",
3889 my $rpcenv = PVE
::RPCEnvironment
::get
();
3891 my $authuser = $rpcenv->get_user();
3893 my $node = extract_param
($param, 'node');
3895 my $vmid = extract_param
($param, 'vmid');
3897 my $snapname = extract_param
($param, 'snapname');
3900 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3901 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3905 # hold migration lock, this makes sure that nobody create replication snapshots
3906 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3909 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3912 __PACKAGE__-
>register_method({
3913 name
=> 'delsnapshot',
3914 path
=> '{vmid}/snapshot/{snapname}',
3918 description
=> "Delete a VM snapshot.",
3920 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3923 additionalProperties
=> 0,
3925 node
=> get_standard_option
('pve-node'),
3926 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3927 snapname
=> get_standard_option
('pve-snapshot-name'),
3931 description
=> "For removal from config file, even if removing disk snapshots fails.",
3937 description
=> "the task ID.",
3942 my $rpcenv = PVE
::RPCEnvironment
::get
();
3944 my $authuser = $rpcenv->get_user();
3946 my $node = extract_param
($param, 'node');
3948 my $vmid = extract_param
($param, 'vmid');
3950 my $snapname = extract_param
($param, 'snapname');
3953 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3954 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3957 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3960 __PACKAGE__-
>register_method({
3962 path
=> '{vmid}/template',
3966 description
=> "Create a Template.",
3968 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3969 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3972 additionalProperties
=> 0,
3974 node
=> get_standard_option
('pve-node'),
3975 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3979 description
=> "If you want to convert only 1 disk to base image.",
3980 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3985 returns
=> { type
=> 'null'},
3989 my $rpcenv = PVE
::RPCEnvironment
::get
();
3991 my $authuser = $rpcenv->get_user();
3993 my $node = extract_param
($param, 'node');
3995 my $vmid = extract_param
($param, 'vmid');
3997 my $disk = extract_param
($param, 'disk');
3999 my $updatefn = sub {
4001 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4003 PVE
::QemuConfig-
>check_lock($conf);
4005 die "unable to create template, because VM contains snapshots\n"
4006 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4008 die "you can't convert a template to a template\n"
4009 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4011 die "you can't convert a VM to template if VM is running\n"
4012 if PVE
::QemuServer
::check_running
($vmid);
4015 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4018 $conf->{template
} = 1;
4019 PVE
::QemuConfig-
>write_config($vmid, $conf);
4021 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4024 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4028 __PACKAGE__-
>register_method({
4029 name
=> 'cloudinit_generated_config_dump',
4030 path
=> '{vmid}/cloudinit/dump',
4033 description
=> "Get automatically generated cloudinit config.",
4035 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4038 additionalProperties
=> 0,
4040 node
=> get_standard_option
('pve-node'),
4041 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4043 description
=> 'Config type.',
4045 enum
=> ['user', 'network', 'meta'],
4055 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4057 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});