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.)",
1994 description
=> "Wait maximal timeout seconds.",
2007 my $rpcenv = PVE
::RPCEnvironment
::get
();
2008 my $authuser = $rpcenv->get_user();
2010 my $node = extract_param
($param, 'node');
2011 my $vmid = extract_param
($param, 'vmid');
2012 my $timeout = extract_param
($param, 'timeout');
2014 my $machine = extract_param
($param, 'machine');
2016 my $get_root_param = sub {
2017 my $value = extract_param
($param, $_[0]);
2018 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2019 if $value && $authuser ne 'root@pam';
2023 my $stateuri = $get_root_param->('stateuri');
2024 my $skiplock = $get_root_param->('skiplock');
2025 my $migratedfrom = $get_root_param->('migratedfrom');
2026 my $migration_type = $get_root_param->('migration_type');
2027 my $migration_network = $get_root_param->('migration_network');
2028 my $targetstorage = $get_root_param->('targetstorage');
2030 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2031 if $targetstorage && !$migratedfrom;
2033 # read spice ticket from STDIN
2035 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2036 if (defined(my $line = <STDIN
>)) {
2038 $spice_ticket = $line;
2042 PVE
::Cluster
::check_cfs_quorum
();
2044 my $storecfg = PVE
::Storage
::config
();
2046 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2050 print "Requesting HA start for VM $vmid\n";
2052 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2053 PVE
::Tools
::run_command
($cmd);
2057 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2064 syslog
('info', "start VM $vmid: $upid\n");
2066 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef, $machine,
2067 $spice_ticket, $migration_network, $migration_type, $targetstorage, $timeout);
2071 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2075 __PACKAGE__-
>register_method({
2077 path
=> '{vmid}/status/stop',
2081 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2082 "is akin to pulling the power plug of a running computer and may damage the VM data",
2084 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2087 additionalProperties
=> 0,
2089 node
=> get_standard_option
('pve-node'),
2090 vmid
=> get_standard_option
('pve-vmid',
2091 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2092 skiplock
=> get_standard_option
('skiplock'),
2093 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2095 description
=> "Wait maximal timeout seconds.",
2101 description
=> "Do not deactivate storage volumes.",
2114 my $rpcenv = PVE
::RPCEnvironment
::get
();
2115 my $authuser = $rpcenv->get_user();
2117 my $node = extract_param
($param, 'node');
2118 my $vmid = extract_param
($param, 'vmid');
2120 my $skiplock = extract_param
($param, 'skiplock');
2121 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2122 if $skiplock && $authuser ne 'root@pam';
2124 my $keepActive = extract_param
($param, 'keepActive');
2125 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2126 if $keepActive && $authuser ne 'root@pam';
2128 my $migratedfrom = extract_param
($param, 'migratedfrom');
2129 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2130 if $migratedfrom && $authuser ne 'root@pam';
2133 my $storecfg = PVE
::Storage
::config
();
2135 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2140 print "Requesting HA stop for VM $vmid\n";
2142 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2143 PVE
::Tools
::run_command
($cmd);
2147 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2153 syslog
('info', "stop VM $vmid: $upid\n");
2155 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2156 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2160 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2164 __PACKAGE__-
>register_method({
2166 path
=> '{vmid}/status/reset',
2170 description
=> "Reset virtual machine.",
2172 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2175 additionalProperties
=> 0,
2177 node
=> get_standard_option
('pve-node'),
2178 vmid
=> get_standard_option
('pve-vmid',
2179 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2180 skiplock
=> get_standard_option
('skiplock'),
2189 my $rpcenv = PVE
::RPCEnvironment
::get
();
2191 my $authuser = $rpcenv->get_user();
2193 my $node = extract_param
($param, 'node');
2195 my $vmid = extract_param
($param, 'vmid');
2197 my $skiplock = extract_param
($param, 'skiplock');
2198 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2199 if $skiplock && $authuser ne 'root@pam';
2201 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2206 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2211 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2214 __PACKAGE__-
>register_method({
2215 name
=> 'vm_shutdown',
2216 path
=> '{vmid}/status/shutdown',
2220 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2221 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2223 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2226 additionalProperties
=> 0,
2228 node
=> get_standard_option
('pve-node'),
2229 vmid
=> get_standard_option
('pve-vmid',
2230 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2231 skiplock
=> get_standard_option
('skiplock'),
2233 description
=> "Wait maximal timeout seconds.",
2239 description
=> "Make sure the VM stops.",
2245 description
=> "Do not deactivate storage volumes.",
2258 my $rpcenv = PVE
::RPCEnvironment
::get
();
2259 my $authuser = $rpcenv->get_user();
2261 my $node = extract_param
($param, 'node');
2262 my $vmid = extract_param
($param, 'vmid');
2264 my $skiplock = extract_param
($param, 'skiplock');
2265 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2266 if $skiplock && $authuser ne 'root@pam';
2268 my $keepActive = extract_param
($param, 'keepActive');
2269 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2270 if $keepActive && $authuser ne 'root@pam';
2272 my $storecfg = PVE
::Storage
::config
();
2276 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2277 # otherwise, we will infer a shutdown command, but run into the timeout,
2278 # then when the vm is resumed, it will instantly shutdown
2280 # checking the qmp status here to get feedback to the gui/cli/api
2281 # and the status query should not take too long
2282 my $qmpstatus = eval {
2283 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2284 mon_cmd
($vmid, "query-status");
2288 if (!$err && $qmpstatus->{status
} eq "paused") {
2289 if ($param->{forceStop
}) {
2290 warn "VM is paused - stop instead of shutdown\n";
2293 die "VM is paused - cannot shutdown\n";
2297 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2299 my $timeout = $param->{timeout
} // 60;
2303 print "Requesting HA stop for VM $vmid\n";
2305 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2306 PVE
::Tools
::run_command
($cmd);
2310 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2317 syslog
('info', "shutdown VM $vmid: $upid\n");
2319 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2320 $shutdown, $param->{forceStop
}, $keepActive);
2324 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2328 __PACKAGE__-
>register_method({
2329 name
=> 'vm_reboot',
2330 path
=> '{vmid}/status/reboot',
2334 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2336 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2339 additionalProperties
=> 0,
2341 node
=> get_standard_option
('pve-node'),
2342 vmid
=> get_standard_option
('pve-vmid',
2343 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2345 description
=> "Wait maximal timeout seconds for the shutdown.",
2358 my $rpcenv = PVE
::RPCEnvironment
::get
();
2359 my $authuser = $rpcenv->get_user();
2361 my $node = extract_param
($param, 'node');
2362 my $vmid = extract_param
($param, 'vmid');
2364 my $qmpstatus = eval {
2365 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2366 mon_cmd
($vmid, "query-status");
2370 if (!$err && $qmpstatus->{status
} eq "paused") {
2371 die "VM is paused - cannot shutdown\n";
2374 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2379 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2380 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2384 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2387 __PACKAGE__-
>register_method({
2388 name
=> 'vm_suspend',
2389 path
=> '{vmid}/status/suspend',
2393 description
=> "Suspend virtual machine.",
2395 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2396 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2397 " on the storage for the vmstate.",
2398 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2401 additionalProperties
=> 0,
2403 node
=> get_standard_option
('pve-node'),
2404 vmid
=> get_standard_option
('pve-vmid',
2405 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2406 skiplock
=> get_standard_option
('skiplock'),
2411 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2413 statestorage
=> get_standard_option
('pve-storage-id', {
2414 description
=> "The storage for the VM state",
2415 requires
=> 'todisk',
2417 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2427 my $rpcenv = PVE
::RPCEnvironment
::get
();
2428 my $authuser = $rpcenv->get_user();
2430 my $node = extract_param
($param, 'node');
2431 my $vmid = extract_param
($param, 'vmid');
2433 my $todisk = extract_param
($param, 'todisk') // 0;
2435 my $statestorage = extract_param
($param, 'statestorage');
2437 my $skiplock = extract_param
($param, 'skiplock');
2438 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2439 if $skiplock && $authuser ne 'root@pam';
2441 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2443 die "Cannot suspend HA managed VM to disk\n"
2444 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2446 # early check for storage permission, for better user feedback
2448 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2450 if (!$statestorage) {
2451 # get statestorage from config if none is given
2452 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2453 my $storecfg = PVE
::Storage
::config
();
2454 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2457 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2463 syslog
('info', "suspend VM $vmid: $upid\n");
2465 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2470 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2471 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2474 __PACKAGE__-
>register_method({
2475 name
=> 'vm_resume',
2476 path
=> '{vmid}/status/resume',
2480 description
=> "Resume virtual machine.",
2482 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2485 additionalProperties
=> 0,
2487 node
=> get_standard_option
('pve-node'),
2488 vmid
=> get_standard_option
('pve-vmid',
2489 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2490 skiplock
=> get_standard_option
('skiplock'),
2491 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2501 my $rpcenv = PVE
::RPCEnvironment
::get
();
2503 my $authuser = $rpcenv->get_user();
2505 my $node = extract_param
($param, 'node');
2507 my $vmid = extract_param
($param, 'vmid');
2509 my $skiplock = extract_param
($param, 'skiplock');
2510 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2511 if $skiplock && $authuser ne 'root@pam';
2513 my $nocheck = extract_param
($param, 'nocheck');
2515 my $to_disk_suspended;
2517 PVE
::QemuConfig-
>lock_config($vmid, sub {
2518 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2519 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2523 die "VM $vmid not running\n"
2524 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2529 syslog
('info', "resume VM $vmid: $upid\n");
2531 if (!$to_disk_suspended) {
2532 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2534 my $storecfg = PVE
::Storage
::config
();
2535 PVE
::QemuServer
::vm_start
($storecfg, $vmid, undef, $skiplock);
2541 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2544 __PACKAGE__-
>register_method({
2545 name
=> 'vm_sendkey',
2546 path
=> '{vmid}/sendkey',
2550 description
=> "Send key event to virtual machine.",
2552 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2555 additionalProperties
=> 0,
2557 node
=> get_standard_option
('pve-node'),
2558 vmid
=> get_standard_option
('pve-vmid',
2559 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2560 skiplock
=> get_standard_option
('skiplock'),
2562 description
=> "The key (qemu monitor encoding).",
2567 returns
=> { type
=> 'null'},
2571 my $rpcenv = PVE
::RPCEnvironment
::get
();
2573 my $authuser = $rpcenv->get_user();
2575 my $node = extract_param
($param, 'node');
2577 my $vmid = extract_param
($param, 'vmid');
2579 my $skiplock = extract_param
($param, 'skiplock');
2580 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2581 if $skiplock && $authuser ne 'root@pam';
2583 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2588 __PACKAGE__-
>register_method({
2589 name
=> 'vm_feature',
2590 path
=> '{vmid}/feature',
2594 description
=> "Check if feature for virtual machine is available.",
2596 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2599 additionalProperties
=> 0,
2601 node
=> get_standard_option
('pve-node'),
2602 vmid
=> get_standard_option
('pve-vmid'),
2604 description
=> "Feature to check.",
2606 enum
=> [ 'snapshot', 'clone', 'copy' ],
2608 snapname
=> get_standard_option
('pve-snapshot-name', {
2616 hasFeature
=> { type
=> 'boolean' },
2619 items
=> { type
=> 'string' },
2626 my $node = extract_param
($param, 'node');
2628 my $vmid = extract_param
($param, 'vmid');
2630 my $snapname = extract_param
($param, 'snapname');
2632 my $feature = extract_param
($param, 'feature');
2634 my $running = PVE
::QemuServer
::check_running
($vmid);
2636 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2639 my $snap = $conf->{snapshots
}->{$snapname};
2640 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2643 my $storecfg = PVE
::Storage
::config
();
2645 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2646 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2649 hasFeature
=> $hasFeature,
2650 nodes
=> [ keys %$nodelist ],
2654 __PACKAGE__-
>register_method({
2656 path
=> '{vmid}/clone',
2660 description
=> "Create a copy of virtual machine/template.",
2662 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2663 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2664 "'Datastore.AllocateSpace' on any used storage.",
2667 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2669 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2670 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2675 additionalProperties
=> 0,
2677 node
=> get_standard_option
('pve-node'),
2678 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2679 newid
=> get_standard_option
('pve-vmid', {
2680 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2681 description
=> 'VMID for the clone.' }),
2684 type
=> 'string', format
=> 'dns-name',
2685 description
=> "Set a name for the new VM.",
2690 description
=> "Description for the new VM.",
2694 type
=> 'string', format
=> 'pve-poolid',
2695 description
=> "Add the new VM to the specified pool.",
2697 snapname
=> get_standard_option
('pve-snapshot-name', {
2700 storage
=> get_standard_option
('pve-storage-id', {
2701 description
=> "Target storage for full clone.",
2705 description
=> "Target format for file storage. Only valid for full clone.",
2708 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2713 description
=> "Create a full copy of all disks. This is always done when " .
2714 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2716 target
=> get_standard_option
('pve-node', {
2717 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2721 description
=> "Override I/O bandwidth limit (in KiB/s).",
2725 default => 'clone limit from datacenter or storage config',
2735 my $rpcenv = PVE
::RPCEnvironment
::get
();
2736 my $authuser = $rpcenv->get_user();
2738 my $node = extract_param
($param, 'node');
2739 my $vmid = extract_param
($param, 'vmid');
2740 my $newid = extract_param
($param, 'newid');
2741 my $pool = extract_param
($param, 'pool');
2742 $rpcenv->check_pool_exist($pool) if defined($pool);
2744 my $snapname = extract_param
($param, 'snapname');
2745 my $storage = extract_param
($param, 'storage');
2746 my $format = extract_param
($param, 'format');
2747 my $target = extract_param
($param, 'target');
2749 my $localnode = PVE
::INotify
::nodename
();
2751 if ($target && ($target eq $localnode || $target eq 'localhost')) {
2754 PVE
::Cluster
::check_node_exists
($target);
2757 my $storecfg = PVE
::Storage
::config
();
2760 # check if storage is enabled on local node
2761 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2763 # check if storage is available on target node
2764 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2765 # clone only works if target storage is shared
2766 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2767 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2771 PVE
::Cluster
::check_cfs_quorum
();
2773 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2775 # exclusive lock if VM is running - else shared lock is enough;
2776 my $shared_lock = $running ?
0 : 1;
2779 # do all tests after lock but before forking worker - if possible
2781 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2782 PVE
::QemuConfig-
>check_lock($conf);
2784 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2785 die "unexpected state change\n" if $verify_running != $running;
2787 die "snapshot '$snapname' does not exist\n"
2788 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2790 my $full = extract_param
($param, 'full') // !PVE
::QemuConfig-
>is_template($conf);
2792 die "parameter 'storage' not allowed for linked clones\n"
2793 if defined($storage) && !$full;
2795 die "parameter 'format' not allowed for linked clones\n"
2796 if defined($format) && !$full;
2798 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2800 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2802 die "can't clone VM to node '$target' (VM uses local storage)\n"
2803 if $target && !$sharedvm;
2805 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2806 die "unable to create VM $newid: config file already exists\n"
2809 my $newconf = { lock => 'clone' };
2814 foreach my $opt (keys %$oldconf) {
2815 my $value = $oldconf->{$opt};
2817 # do not copy snapshot related info
2818 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2819 $opt eq 'vmstate' || $opt eq 'snapstate';
2821 # no need to copy unused images, because VMID(owner) changes anyways
2822 next if $opt =~ m/^unused\d+$/;
2824 # always change MAC! address
2825 if ($opt =~ m/^net(\d+)$/) {
2826 my $net = PVE
::QemuServer
::parse_net
($value);
2827 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2828 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2829 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2830 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2831 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2832 die "unable to parse drive options for '$opt'\n" if !$drive;
2833 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2834 $newconf->{$opt} = $value; # simply copy configuration
2836 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2837 die "Full clone feature is not supported for drive '$opt'\n"
2838 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2839 $fullclone->{$opt} = 1;
2841 # not full means clone instead of copy
2842 die "Linked clone feature is not supported for drive '$opt'\n"
2843 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2845 $drives->{$opt} = $drive;
2846 push @$vollist, $drive->{file
};
2849 # copy everything else
2850 $newconf->{$opt} = $value;
2854 # auto generate a new uuid
2855 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2856 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2857 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2858 # auto generate a new vmgenid only if the option was set for template
2859 if ($newconf->{vmgenid
}) {
2860 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2863 delete $newconf->{template
};
2865 if ($param->{name
}) {
2866 $newconf->{name
} = $param->{name
};
2868 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
2871 if ($param->{description
}) {
2872 $newconf->{description
} = $param->{description
};
2875 # create empty/temp config - this fails if VM already exists on other node
2876 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
2877 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2882 my $newvollist = [];
2889 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2891 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2893 my $bwlimit = extract_param
($param, 'bwlimit');
2895 my $total_jobs = scalar(keys %{$drives});
2898 foreach my $opt (keys %$drives) {
2899 my $drive = $drives->{$opt};
2900 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2902 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
2903 my $storage_list = [ $src_sid ];
2904 push @$storage_list, $storage if defined($storage);
2905 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
2907 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2908 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2909 $jobs, $skipcomplete, $oldconf->{agent
}, $clonelimit);
2911 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
2913 PVE
::QemuConfig-
>write_config($newid, $newconf);
2917 delete $newconf->{lock};
2919 # do not write pending changes
2920 if (my @changes = keys %{$newconf->{pending
}}) {
2921 my $pending = join(',', @changes);
2922 warn "found pending changes for '$pending', discarding for clone\n";
2923 delete $newconf->{pending
};
2926 PVE
::QemuConfig-
>write_config($newid, $newconf);
2929 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2930 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2931 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2933 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2934 die "Failed to move config to node '$target' - rename failed: $!\n"
2935 if !rename($conffile, $newconffile);
2938 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2941 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2942 sleep 1; # some storage like rbd need to wait before release volume - really?
2944 foreach my $volid (@$newvollist) {
2945 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2949 PVE
::Firewall
::remove_vmfw_conf
($newid);
2951 unlink $conffile; # avoid races -> last thing before die
2953 die "clone failed: $err";
2959 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2961 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2964 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2965 # Aquire exclusive lock lock for $newid
2966 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2971 __PACKAGE__-
>register_method({
2972 name
=> 'move_vm_disk',
2973 path
=> '{vmid}/move_disk',
2977 description
=> "Move volume to different storage.",
2979 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2981 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2982 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2986 additionalProperties
=> 0,
2988 node
=> get_standard_option
('pve-node'),
2989 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2992 description
=> "The disk you want to move.",
2993 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2995 storage
=> get_standard_option
('pve-storage-id', {
2996 description
=> "Target storage.",
2997 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3001 description
=> "Target Format.",
3002 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3007 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3013 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3018 description
=> "Override I/O bandwidth limit (in KiB/s).",
3022 default => 'move limit from datacenter or storage config',
3028 description
=> "the task ID.",
3033 my $rpcenv = PVE
::RPCEnvironment
::get
();
3034 my $authuser = $rpcenv->get_user();
3036 my $node = extract_param
($param, 'node');
3037 my $vmid = extract_param
($param, 'vmid');
3038 my $digest = extract_param
($param, 'digest');
3039 my $disk = extract_param
($param, 'disk');
3040 my $storeid = extract_param
($param, 'storage');
3041 my $format = extract_param
($param, 'format');
3043 my $storecfg = PVE
::Storage
::config
();
3045 my $updatefn = sub {
3046 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3047 PVE
::QemuConfig-
>check_lock($conf);
3049 die "VM config checksum missmatch (file change by other user?)\n"
3050 if $digest && $digest ne $conf->{digest
};
3052 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3054 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3056 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3057 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3059 my $old_volid = $drive->{file
};
3061 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3062 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3066 die "you can't move to the same storage with same format\n" if $oldstoreid eq $storeid &&
3067 (!$format || !$oldfmt || $oldfmt eq $format);
3069 # this only checks snapshots because $disk is passed!
3070 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3071 die "you can't move a disk with snapshots and delete the source\n"
3072 if $snapshotted && $param->{delete};
3074 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3076 my $running = PVE
::QemuServer
::check_running
($vmid);
3078 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3081 my $newvollist = [];
3087 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3089 warn "moving disk with snapshots, snapshots will not be moved!\n"
3092 my $bwlimit = extract_param
($param, 'bwlimit');
3093 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3095 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3096 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit);
3098 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3100 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3102 # convert moved disk to base if part of template
3103 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3104 if PVE
::QemuConfig-
>is_template($conf);
3106 PVE
::QemuConfig-
>write_config($vmid, $conf);
3108 my $do_trim = PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
};
3109 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3110 eval { mon_cmd
($vmid, "guest-fstrim") };
3114 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3115 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3121 foreach my $volid (@$newvollist) {
3122 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3125 die "storage migration failed: $err";
3128 if ($param->{delete}) {
3130 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3131 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3137 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3140 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3143 my $check_vm_disks_local = sub {
3144 my ($storecfg, $vmconf, $vmid) = @_;
3146 my $local_disks = {};
3148 # add some more information to the disks e.g. cdrom
3149 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3150 my ($volid, $attr) = @_;
3152 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3154 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3155 return if $scfg->{shared
};
3157 # The shared attr here is just a special case where the vdisk
3158 # is marked as shared manually
3159 return if $attr->{shared
};
3160 return if $attr->{cdrom
} and $volid eq "none";
3162 if (exists $local_disks->{$volid}) {
3163 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3165 $local_disks->{$volid} = $attr;
3166 # ensure volid is present in case it's needed
3167 $local_disks->{$volid}->{volid
} = $volid;
3171 return $local_disks;
3174 __PACKAGE__-
>register_method({
3175 name
=> 'migrate_vm_precondition',
3176 path
=> '{vmid}/migrate',
3180 description
=> "Get preconditions for migration.",
3182 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3185 additionalProperties
=> 0,
3187 node
=> get_standard_option
('pve-node'),
3188 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3189 target
=> get_standard_option
('pve-node', {
3190 description
=> "Target node.",
3191 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3199 running
=> { type
=> 'boolean' },
3203 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3205 not_allowed_nodes
=> {
3208 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3212 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3214 local_resources
=> {
3216 description
=> "List local resources e.g. pci, usb"
3223 my $rpcenv = PVE
::RPCEnvironment
::get
();
3225 my $authuser = $rpcenv->get_user();
3227 PVE
::Cluster
::check_cfs_quorum
();
3231 my $vmid = extract_param
($param, 'vmid');
3232 my $target = extract_param
($param, 'target');
3233 my $localnode = PVE
::INotify
::nodename
();
3237 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3238 my $storecfg = PVE
::Storage
::config
();
3241 # try to detect errors early
3242 PVE
::QemuConfig-
>check_lock($vmconf);
3244 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3246 # if vm is not running, return target nodes where local storage is available
3247 # for offline migration
3248 if (!$res->{running
}) {
3249 $res->{allowed_nodes
} = [];
3250 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3251 delete $checked_nodes->{$localnode};
3253 foreach my $node (keys %$checked_nodes) {
3254 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3255 push @{$res->{allowed_nodes
}}, $node;
3259 $res->{not_allowed_nodes
} = $checked_nodes;
3263 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3264 $res->{local_disks
} = [ values %$local_disks ];;
3266 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3268 $res->{local_resources
} = $local_resources;
3275 __PACKAGE__-
>register_method({
3276 name
=> 'migrate_vm',
3277 path
=> '{vmid}/migrate',
3281 description
=> "Migrate virtual machine. Creates a new migration task.",
3283 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3286 additionalProperties
=> 0,
3288 node
=> get_standard_option
('pve-node'),
3289 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3290 target
=> get_standard_option
('pve-node', {
3291 description
=> "Target node.",
3292 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3296 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3301 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3306 enum
=> ['secure', 'insecure'],
3307 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3310 migration_network
=> {
3311 type
=> 'string', format
=> 'CIDR',
3312 description
=> "CIDR of the (sub) network that is used for migration.",
3315 "with-local-disks" => {
3317 description
=> "Enable live storage migration for local disk",
3320 targetstorage
=> get_standard_option
('pve-storage-id', {
3321 description
=> "Default target storage.",
3323 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3326 description
=> "Override I/O bandwidth limit (in KiB/s).",
3330 default => 'migrate limit from datacenter or storage config',
3336 description
=> "the task ID.",
3341 my $rpcenv = PVE
::RPCEnvironment
::get
();
3342 my $authuser = $rpcenv->get_user();
3344 my $target = extract_param
($param, 'target');
3346 my $localnode = PVE
::INotify
::nodename
();
3347 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3349 PVE
::Cluster
::check_cfs_quorum
();
3351 PVE
::Cluster
::check_node_exists
($target);
3353 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3355 my $vmid = extract_param
($param, 'vmid');
3357 raise_param_exc
({ force
=> "Only root may use this option." })
3358 if $param->{force
} && $authuser ne 'root@pam';
3360 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3361 if $param->{migration_type
} && $authuser ne 'root@pam';
3363 # allow root only until better network permissions are available
3364 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3365 if $param->{migration_network
} && $authuser ne 'root@pam';
3368 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3370 # try to detect errors early
3372 PVE
::QemuConfig-
>check_lock($conf);
3374 if (PVE
::QemuServer
::check_running
($vmid)) {
3375 die "can't migrate running VM without --online\n" if !$param->{online
};
3377 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
3378 $param->{online
} = 0;
3381 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3382 if !$param->{online
} && $param->{targetstorage
};
3384 my $storecfg = PVE
::Storage
::config
();
3386 if( $param->{targetstorage
}) {
3387 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3389 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3392 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3397 print "Requesting HA migration for VM $vmid to node $target\n";
3399 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3400 PVE
::Tools
::run_command
($cmd);
3404 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3409 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3413 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3416 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3421 __PACKAGE__-
>register_method({
3423 path
=> '{vmid}/monitor',
3427 description
=> "Execute Qemu monitor commands.",
3429 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3430 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3433 additionalProperties
=> 0,
3435 node
=> get_standard_option
('pve-node'),
3436 vmid
=> get_standard_option
('pve-vmid'),
3439 description
=> "The monitor command.",
3443 returns
=> { type
=> 'string'},
3447 my $rpcenv = PVE
::RPCEnvironment
::get
();
3448 my $authuser = $rpcenv->get_user();
3451 my $command = shift;
3452 return $command =~ m/^\s*info(\s+|$)/
3453 || $command =~ m/^\s*help\s*$/;
3456 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3457 if !&$is_ro($param->{command
});
3459 my $vmid = $param->{vmid
};
3461 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3465 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3467 $res = "ERROR: $@" if $@;
3472 __PACKAGE__-
>register_method({
3473 name
=> 'resize_vm',
3474 path
=> '{vmid}/resize',
3478 description
=> "Extend volume size.",
3480 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3483 additionalProperties
=> 0,
3485 node
=> get_standard_option
('pve-node'),
3486 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3487 skiplock
=> get_standard_option
('skiplock'),
3490 description
=> "The disk you want to resize.",
3491 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3495 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3496 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.",
3500 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3506 returns
=> { type
=> 'null'},
3510 my $rpcenv = PVE
::RPCEnvironment
::get
();
3512 my $authuser = $rpcenv->get_user();
3514 my $node = extract_param
($param, 'node');
3516 my $vmid = extract_param
($param, 'vmid');
3518 my $digest = extract_param
($param, 'digest');
3520 my $disk = extract_param
($param, 'disk');
3522 my $sizestr = extract_param
($param, 'size');
3524 my $skiplock = extract_param
($param, 'skiplock');
3525 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3526 if $skiplock && $authuser ne 'root@pam';
3528 my $storecfg = PVE
::Storage
::config
();
3530 my $updatefn = sub {
3532 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3534 die "checksum missmatch (file change by other user?)\n"
3535 if $digest && $digest ne $conf->{digest
};
3536 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3538 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3540 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3542 my (undef, undef, undef, undef, undef, undef, $format) =
3543 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3545 die "can't resize volume: $disk if snapshot exists\n"
3546 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3548 my $volid = $drive->{file
};
3550 die "disk '$disk' has no associated volume\n" if !$volid;
3552 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3554 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3556 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3558 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3559 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3561 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3563 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3564 my ($ext, $newsize, $unit) = ($1, $2, $4);
3567 $newsize = $newsize * 1024;
3568 } elsif ($unit eq 'M') {
3569 $newsize = $newsize * 1024 * 1024;
3570 } elsif ($unit eq 'G') {
3571 $newsize = $newsize * 1024 * 1024 * 1024;
3572 } elsif ($unit eq 'T') {
3573 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3576 $newsize += $size if $ext;
3577 $newsize = int($newsize);
3579 die "shrinking disks is not supported\n" if $newsize < $size;
3581 return if $size == $newsize;
3583 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3585 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3587 $drive->{size
} = $newsize;
3588 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
3590 PVE
::QemuConfig-
>write_config($vmid, $conf);
3593 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3597 __PACKAGE__-
>register_method({
3598 name
=> 'snapshot_list',
3599 path
=> '{vmid}/snapshot',
3601 description
=> "List all snapshots.",
3603 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3606 protected
=> 1, # qemu pid files are only readable by root
3608 additionalProperties
=> 0,
3610 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3611 node
=> get_standard_option
('pve-node'),
3620 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3624 description
=> "Snapshot includes RAM.",
3629 description
=> "Snapshot description.",
3633 description
=> "Snapshot creation time",
3635 renderer
=> 'timestamp',
3639 description
=> "Parent snapshot identifier.",
3645 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3650 my $vmid = $param->{vmid
};
3652 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3653 my $snaphash = $conf->{snapshots
} || {};
3657 foreach my $name (keys %$snaphash) {
3658 my $d = $snaphash->{$name};
3661 snaptime
=> $d->{snaptime
} || 0,
3662 vmstate
=> $d->{vmstate
} ?
1 : 0,
3663 description
=> $d->{description
} || '',
3665 $item->{parent
} = $d->{parent
} if $d->{parent
};
3666 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3670 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3673 digest
=> $conf->{digest
},
3674 running
=> $running,
3675 description
=> "You are here!",
3677 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3679 push @$res, $current;
3684 __PACKAGE__-
>register_method({
3686 path
=> '{vmid}/snapshot',
3690 description
=> "Snapshot a VM.",
3692 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3695 additionalProperties
=> 0,
3697 node
=> get_standard_option
('pve-node'),
3698 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3699 snapname
=> get_standard_option
('pve-snapshot-name'),
3703 description
=> "Save the vmstate",
3708 description
=> "A textual description or comment.",
3714 description
=> "the task ID.",
3719 my $rpcenv = PVE
::RPCEnvironment
::get
();
3721 my $authuser = $rpcenv->get_user();
3723 my $node = extract_param
($param, 'node');
3725 my $vmid = extract_param
($param, 'vmid');
3727 my $snapname = extract_param
($param, 'snapname');
3729 die "unable to use snapshot name 'current' (reserved name)\n"
3730 if $snapname eq 'current';
3732 die "unable to use snapshot name 'pending' (reserved name)\n"
3733 if lc($snapname) eq 'pending';
3736 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3737 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3738 $param->{description
});
3741 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3744 __PACKAGE__-
>register_method({
3745 name
=> 'snapshot_cmd_idx',
3746 path
=> '{vmid}/snapshot/{snapname}',
3753 additionalProperties
=> 0,
3755 vmid
=> get_standard_option
('pve-vmid'),
3756 node
=> get_standard_option
('pve-node'),
3757 snapname
=> get_standard_option
('pve-snapshot-name'),
3766 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3773 push @$res, { cmd
=> 'rollback' };
3774 push @$res, { cmd
=> 'config' };
3779 __PACKAGE__-
>register_method({
3780 name
=> 'update_snapshot_config',
3781 path
=> '{vmid}/snapshot/{snapname}/config',
3785 description
=> "Update snapshot metadata.",
3787 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3790 additionalProperties
=> 0,
3792 node
=> get_standard_option
('pve-node'),
3793 vmid
=> get_standard_option
('pve-vmid'),
3794 snapname
=> get_standard_option
('pve-snapshot-name'),
3798 description
=> "A textual description or comment.",
3802 returns
=> { type
=> 'null' },
3806 my $rpcenv = PVE
::RPCEnvironment
::get
();
3808 my $authuser = $rpcenv->get_user();
3810 my $vmid = extract_param
($param, 'vmid');
3812 my $snapname = extract_param
($param, 'snapname');
3814 return undef if !defined($param->{description
});
3816 my $updatefn = sub {
3818 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3820 PVE
::QemuConfig-
>check_lock($conf);
3822 my $snap = $conf->{snapshots
}->{$snapname};
3824 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3826 $snap->{description
} = $param->{description
} if defined($param->{description
});
3828 PVE
::QemuConfig-
>write_config($vmid, $conf);
3831 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3836 __PACKAGE__-
>register_method({
3837 name
=> 'get_snapshot_config',
3838 path
=> '{vmid}/snapshot/{snapname}/config',
3841 description
=> "Get snapshot configuration",
3843 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3846 additionalProperties
=> 0,
3848 node
=> get_standard_option
('pve-node'),
3849 vmid
=> get_standard_option
('pve-vmid'),
3850 snapname
=> get_standard_option
('pve-snapshot-name'),
3853 returns
=> { type
=> "object" },
3857 my $rpcenv = PVE
::RPCEnvironment
::get
();
3859 my $authuser = $rpcenv->get_user();
3861 my $vmid = extract_param
($param, 'vmid');
3863 my $snapname = extract_param
($param, 'snapname');
3865 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3867 my $snap = $conf->{snapshots
}->{$snapname};
3869 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3874 __PACKAGE__-
>register_method({
3876 path
=> '{vmid}/snapshot/{snapname}/rollback',
3880 description
=> "Rollback VM state to specified snapshot.",
3882 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3885 additionalProperties
=> 0,
3887 node
=> get_standard_option
('pve-node'),
3888 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3889 snapname
=> get_standard_option
('pve-snapshot-name'),
3894 description
=> "the task ID.",
3899 my $rpcenv = PVE
::RPCEnvironment
::get
();
3901 my $authuser = $rpcenv->get_user();
3903 my $node = extract_param
($param, 'node');
3905 my $vmid = extract_param
($param, 'vmid');
3907 my $snapname = extract_param
($param, 'snapname');
3910 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3911 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3915 # hold migration lock, this makes sure that nobody create replication snapshots
3916 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3919 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3922 __PACKAGE__-
>register_method({
3923 name
=> 'delsnapshot',
3924 path
=> '{vmid}/snapshot/{snapname}',
3928 description
=> "Delete a VM snapshot.",
3930 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3933 additionalProperties
=> 0,
3935 node
=> get_standard_option
('pve-node'),
3936 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3937 snapname
=> get_standard_option
('pve-snapshot-name'),
3941 description
=> "For removal from config file, even if removing disk snapshots fails.",
3947 description
=> "the task ID.",
3952 my $rpcenv = PVE
::RPCEnvironment
::get
();
3954 my $authuser = $rpcenv->get_user();
3956 my $node = extract_param
($param, 'node');
3958 my $vmid = extract_param
($param, 'vmid');
3960 my $snapname = extract_param
($param, 'snapname');
3963 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3964 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3967 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3970 __PACKAGE__-
>register_method({
3972 path
=> '{vmid}/template',
3976 description
=> "Create a Template.",
3978 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3979 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3982 additionalProperties
=> 0,
3984 node
=> get_standard_option
('pve-node'),
3985 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3989 description
=> "If you want to convert only 1 disk to base image.",
3990 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3995 returns
=> { type
=> 'null'},
3999 my $rpcenv = PVE
::RPCEnvironment
::get
();
4001 my $authuser = $rpcenv->get_user();
4003 my $node = extract_param
($param, 'node');
4005 my $vmid = extract_param
($param, 'vmid');
4007 my $disk = extract_param
($param, 'disk');
4009 my $updatefn = sub {
4011 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4013 PVE
::QemuConfig-
>check_lock($conf);
4015 die "unable to create template, because VM contains snapshots\n"
4016 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4018 die "you can't convert a template to a template\n"
4019 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4021 die "you can't convert a VM to template if VM is running\n"
4022 if PVE
::QemuServer
::check_running
($vmid);
4025 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4028 $conf->{template
} = 1;
4029 PVE
::QemuConfig-
>write_config($vmid, $conf);
4031 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4034 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4038 __PACKAGE__-
>register_method({
4039 name
=> 'cloudinit_generated_config_dump',
4040 path
=> '{vmid}/cloudinit/dump',
4043 description
=> "Get automatically generated cloudinit config.",
4045 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4048 additionalProperties
=> 0,
4050 node
=> get_standard_option
('pve-node'),
4051 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4053 description
=> 'Config type.',
4055 enum
=> ['user', 'network', 'meta'],
4065 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4067 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});