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 the virtual machine configuration with pending configuration " .
827 "changes applied. Set the 'current' parameter to get the current configuration instead.",
829 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
832 additionalProperties
=> 0,
834 node
=> get_standard_option
('pve-node'),
835 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
837 description
=> "Get current values (instead of pending values).",
842 snapshot
=> get_standard_option
('pve-snapshot-name', {
843 description
=> "Fetch config values from given snapshot.",
846 my ($cmd, $pname, $cur, $args) = @_;
847 PVE
::QemuConfig-
>snapshot_list($args->[0]);
853 description
=> "The VM configuration.",
855 properties
=> PVE
::QemuServer
::json_config_properties
({
858 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
865 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
866 current
=> "cannot use 'snapshot' parameter with 'current'"})
867 if ($param->{snapshot
} && $param->{current
});
870 if ($param->{snapshot
}) {
871 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
873 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
875 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
880 __PACKAGE__-
>register_method({
881 name
=> 'vm_pending',
882 path
=> '{vmid}/pending',
885 description
=> "Get the virtual machine configuration with both current and pending values.",
887 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
890 additionalProperties
=> 0,
892 node
=> get_standard_option
('pve-node'),
893 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
902 description
=> "Configuration option name.",
906 description
=> "Current value.",
911 description
=> "Pending value.",
916 description
=> "Indicates a pending delete request if present and not 0. " .
917 "The value 2 indicates a force-delete request.",
929 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
931 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
933 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
934 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
936 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
939 # POST/PUT {vmid}/config implementation
941 # The original API used PUT (idempotent) an we assumed that all operations
942 # are fast. But it turned out that almost any configuration change can
943 # involve hot-plug actions, or disk alloc/free. Such actions can take long
944 # time to complete and have side effects (not idempotent).
946 # The new implementation uses POST and forks a worker process. We added
947 # a new option 'background_delay'. If specified we wait up to
948 # 'background_delay' second for the worker task to complete. It returns null
949 # if the task is finished within that time, else we return the UPID.
951 my $update_vm_api = sub {
952 my ($param, $sync) = @_;
954 my $rpcenv = PVE
::RPCEnvironment
::get
();
956 my $authuser = $rpcenv->get_user();
958 my $node = extract_param
($param, 'node');
960 my $vmid = extract_param
($param, 'vmid');
962 my $digest = extract_param
($param, 'digest');
964 my $background_delay = extract_param
($param, 'background_delay');
966 if (defined(my $cipassword = $param->{cipassword
})) {
967 # Same logic as in cloud-init (but with the regex fixed...)
968 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
969 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
972 my @paramarr = (); # used for log message
973 foreach my $key (sort keys %$param) {
974 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
975 push @paramarr, "-$key", $value;
978 my $skiplock = extract_param
($param, 'skiplock');
979 raise_param_exc
({ skiplock
=> "Only root may use this option." })
980 if $skiplock && $authuser ne 'root@pam';
982 my $delete_str = extract_param
($param, 'delete');
984 my $revert_str = extract_param
($param, 'revert');
986 my $force = extract_param
($param, 'force');
988 if (defined(my $ssh_keys = $param->{sshkeys
})) {
989 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
990 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
993 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
995 my $storecfg = PVE
::Storage
::config
();
997 my $defaults = PVE
::QemuServer
::load_defaults
();
999 &$resolve_cdrom_alias($param);
1001 # now try to verify all parameters
1004 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1005 if (!PVE
::QemuServer
::option_exists
($opt)) {
1006 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1009 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1010 "-revert $opt' at the same time" })
1011 if defined($param->{$opt});
1013 $revert->{$opt} = 1;
1017 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1018 $opt = 'ide2' if $opt eq 'cdrom';
1020 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1021 "-delete $opt' at the same time" })
1022 if defined($param->{$opt});
1024 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1025 "-revert $opt' at the same time" })
1028 if (!PVE
::QemuServer
::option_exists
($opt)) {
1029 raise_param_exc
({ delete => "unknown option '$opt'" });
1035 my $repl_conf = PVE
::ReplicationConfig-
>new();
1036 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1037 my $check_replication = sub {
1039 return if !$is_replicated;
1040 my $volid = $drive->{file
};
1041 return if !$volid || !($drive->{replicate
}//1);
1042 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1044 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1045 return if $volname eq 'cloudinit';
1048 if ($volid =~ $NEW_DISK_RE) {
1050 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1052 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1054 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1055 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1056 return if $scfg->{shared
};
1057 die "cannot add non-replicatable volume to a replicated VM\n";
1060 foreach my $opt (keys %$param) {
1061 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1062 # cleanup drive path
1063 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1064 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1065 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1066 $check_replication->($drive);
1067 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
1068 } elsif ($opt =~ m/^net(\d+)$/) {
1070 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1071 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1072 } elsif ($opt eq 'vmgenid') {
1073 if ($param->{$opt} eq '1') {
1074 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1076 } elsif ($opt eq 'hookscript') {
1077 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1078 raise_param_exc
({ $opt => $@ }) if $@;
1082 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1084 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1086 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1088 my $updatefn = sub {
1090 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1092 die "checksum missmatch (file change by other user?)\n"
1093 if $digest && $digest ne $conf->{digest
};
1095 # FIXME: 'suspended' lock should probabyl be a state or "weak" lock?!
1096 if (scalar(@delete) && grep { $_ eq 'vmstate'} @delete) {
1097 if (defined($conf->{lock}) && $conf->{lock} eq 'suspended') {
1098 delete $conf->{lock}; # for check lock check, not written out
1099 push @delete, 'lock'; # this is the real deal to write it out
1101 push @delete, 'runningmachine' if $conf->{runningmachine
};
1104 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1106 foreach my $opt (keys %$revert) {
1107 if (defined($conf->{$opt})) {
1108 $param->{$opt} = $conf->{$opt};
1109 } elsif (defined($conf->{pending
}->{$opt})) {
1114 if ($param->{memory
} || defined($param->{balloon
})) {
1115 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1116 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1118 die "balloon value too large (must be smaller than assigned memory)\n"
1119 if $balloon && $balloon > $maxmem;
1122 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1126 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1128 # write updates to pending section
1130 my $modified = {}; # record what $option we modify
1132 foreach my $opt (@delete) {
1133 $modified->{$opt} = 1;
1134 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1136 # value of what we want to delete, independent if pending or not
1137 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1138 if (!defined($val)) {
1139 warn "cannot delete '$opt' - not set in current configuration!\n";
1140 $modified->{$opt} = 0;
1143 my $is_pending_val = defined($conf->{pending
}->{$opt});
1144 delete $conf->{pending
}->{$opt};
1146 if ($opt =~ m/^unused/) {
1147 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1148 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1149 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1150 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1151 delete $conf->{$opt};
1152 PVE
::QemuConfig-
>write_config($vmid, $conf);
1154 } elsif ($opt eq 'vmstate') {
1155 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1156 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1157 delete $conf->{$opt};
1158 PVE
::QemuConfig-
>write_config($vmid, $conf);
1160 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1161 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1162 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1163 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1165 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1166 PVE
::QemuConfig-
>write_config($vmid, $conf);
1167 } elsif ($opt =~ m/^serial\d+$/) {
1168 if ($val eq 'socket') {
1169 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1170 } elsif ($authuser ne 'root@pam') {
1171 die "only root can delete '$opt' config for real devices\n";
1173 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1174 PVE
::QemuConfig-
>write_config($vmid, $conf);
1175 } elsif ($opt =~ m/^usb\d+$/) {
1176 if ($val =~ m/spice/) {
1177 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1178 } elsif ($authuser ne 'root@pam') {
1179 die "only root can delete '$opt' config for real devices\n";
1181 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1182 PVE
::QemuConfig-
>write_config($vmid, $conf);
1184 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1185 PVE
::QemuConfig-
>write_config($vmid, $conf);
1189 foreach my $opt (keys %$param) { # add/change
1190 $modified->{$opt} = 1;
1191 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1192 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1194 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1196 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1197 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1198 # FIXME: cloudinit: CDROM or Disk?
1199 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1200 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1202 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1204 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1205 if defined($conf->{pending
}->{$opt});
1207 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1208 } elsif ($opt =~ m/^serial\d+/) {
1209 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1210 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1211 } elsif ($authuser ne 'root@pam') {
1212 die "only root can modify '$opt' config for real devices\n";
1214 $conf->{pending
}->{$opt} = $param->{$opt};
1215 } elsif ($opt =~ m/^usb\d+/) {
1216 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1217 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1218 } elsif ($authuser ne 'root@pam') {
1219 die "only root can modify '$opt' config for real devices\n";
1221 $conf->{pending
}->{$opt} = $param->{$opt};
1223 $conf->{pending
}->{$opt} = $param->{$opt};
1225 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1226 PVE
::QemuConfig-
>write_config($vmid, $conf);
1229 # remove pending changes when nothing changed
1230 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1231 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1232 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1234 return if !scalar(keys %{$conf->{pending
}});
1236 my $running = PVE
::QemuServer
::check_running
($vmid);
1238 # apply pending changes
1240 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1244 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1246 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running, $errors);
1248 raise_param_exc
($errors) if scalar(keys %$errors);
1257 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1259 if ($background_delay) {
1261 # Note: It would be better to do that in the Event based HTTPServer
1262 # to avoid blocking call to sleep.
1264 my $end_time = time() + $background_delay;
1266 my $task = PVE
::Tools
::upid_decode
($upid);
1269 while (time() < $end_time) {
1270 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1272 sleep(1); # this gets interrupted when child process ends
1276 my $status = PVE
::Tools
::upid_read_status
($upid);
1277 return undef if $status eq 'OK';
1286 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1289 my $vm_config_perm_list = [
1294 'VM.Config.Network',
1296 'VM.Config.Options',
1299 __PACKAGE__-
>register_method({
1300 name
=> 'update_vm_async',
1301 path
=> '{vmid}/config',
1305 description
=> "Set virtual machine options (asynchrounous API).",
1307 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1310 additionalProperties
=> 0,
1311 properties
=> PVE
::QemuServer
::json_config_properties
(
1313 node
=> get_standard_option
('pve-node'),
1314 vmid
=> get_standard_option
('pve-vmid'),
1315 skiplock
=> get_standard_option
('skiplock'),
1317 type
=> 'string', format
=> 'pve-configid-list',
1318 description
=> "A list of settings you want to delete.",
1322 type
=> 'string', format
=> 'pve-configid-list',
1323 description
=> "Revert a pending change.",
1328 description
=> $opt_force_description,
1330 requires
=> 'delete',
1334 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1338 background_delay
=> {
1340 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1351 code
=> $update_vm_api,
1354 __PACKAGE__-
>register_method({
1355 name
=> 'update_vm',
1356 path
=> '{vmid}/config',
1360 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1362 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1365 additionalProperties
=> 0,
1366 properties
=> PVE
::QemuServer
::json_config_properties
(
1368 node
=> get_standard_option
('pve-node'),
1369 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1370 skiplock
=> get_standard_option
('skiplock'),
1372 type
=> 'string', format
=> 'pve-configid-list',
1373 description
=> "A list of settings you want to delete.",
1377 type
=> 'string', format
=> 'pve-configid-list',
1378 description
=> "Revert a pending change.",
1383 description
=> $opt_force_description,
1385 requires
=> 'delete',
1389 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1395 returns
=> { type
=> 'null' },
1398 &$update_vm_api($param, 1);
1403 __PACKAGE__-
>register_method({
1404 name
=> 'destroy_vm',
1409 description
=> "Destroy the vm (also delete all used/owned volumes).",
1411 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1414 additionalProperties
=> 0,
1416 node
=> get_standard_option
('pve-node'),
1417 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1418 skiplock
=> get_standard_option
('skiplock'),
1421 description
=> "Remove vmid from backup cron jobs.",
1432 my $rpcenv = PVE
::RPCEnvironment
::get
();
1433 my $authuser = $rpcenv->get_user();
1434 my $vmid = $param->{vmid
};
1436 my $skiplock = $param->{skiplock
};
1437 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1438 if $skiplock && $authuser ne 'root@pam';
1441 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1442 my $storecfg = PVE
::Storage
::config
();
1443 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1444 die "unable to remove VM $vmid - used in HA resources\n"
1445 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1447 if (!$param->{purge
}) {
1448 # don't allow destroy if with replication jobs but no purge param
1449 my $repl_conf = PVE
::ReplicationConfig-
>new();
1450 $repl_conf->check_for_existing_jobs($vmid);
1453 # early tests (repeat after locking)
1454 die "VM $vmid is running - destroy failed\n"
1455 if PVE
::QemuServer
::check_running
($vmid);
1460 syslog
('info', "destroy VM $vmid: $upid\n");
1461 PVE
::QemuConfig-
>lock_config($vmid, sub {
1462 die "VM $vmid is running - destroy failed\n"
1463 if (PVE
::QemuServer
::check_running
($vmid));
1465 PVE
::QemuServer
::destroy_vm
($storecfg, $vmid, $skiplock, { lock => 'destroyed' });
1467 PVE
::AccessControl
::remove_vm_access
($vmid);
1468 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1469 if ($param->{purge
}) {
1470 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1471 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1474 # only now remove the zombie config, else we can have reuse race
1475 PVE
::QemuConfig-
>destroy_config($vmid);
1479 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1482 __PACKAGE__-
>register_method({
1484 path
=> '{vmid}/unlink',
1488 description
=> "Unlink/delete disk images.",
1490 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1493 additionalProperties
=> 0,
1495 node
=> get_standard_option
('pve-node'),
1496 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1498 type
=> 'string', format
=> 'pve-configid-list',
1499 description
=> "A list of disk IDs you want to delete.",
1503 description
=> $opt_force_description,
1508 returns
=> { type
=> 'null'},
1512 $param->{delete} = extract_param
($param, 'idlist');
1514 __PACKAGE__-
>update_vm($param);
1521 __PACKAGE__-
>register_method({
1523 path
=> '{vmid}/vncproxy',
1527 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1529 description
=> "Creates a TCP VNC proxy connections.",
1531 additionalProperties
=> 0,
1533 node
=> get_standard_option
('pve-node'),
1534 vmid
=> get_standard_option
('pve-vmid'),
1538 description
=> "starts websockify instead of vncproxy",
1543 additionalProperties
=> 0,
1545 user
=> { type
=> 'string' },
1546 ticket
=> { type
=> 'string' },
1547 cert
=> { type
=> 'string' },
1548 port
=> { type
=> 'integer' },
1549 upid
=> { type
=> 'string' },
1555 my $rpcenv = PVE
::RPCEnvironment
::get
();
1557 my $authuser = $rpcenv->get_user();
1559 my $vmid = $param->{vmid
};
1560 my $node = $param->{node
};
1561 my $websocket = $param->{websocket
};
1563 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1564 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1566 my $authpath = "/vms/$vmid";
1568 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1570 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1576 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1577 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1578 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1579 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1580 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1582 $family = PVE
::Tools
::get_host_address_family
($node);
1585 my $port = PVE
::Tools
::next_vnc_port
($family);
1592 syslog
('info', "starting vnc proxy $upid\n");
1598 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1600 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1601 '-timeout', $timeout, '-authpath', $authpath,
1602 '-perm', 'Sys.Console'];
1604 if ($param->{websocket
}) {
1605 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1606 push @$cmd, '-notls', '-listen', 'localhost';
1609 push @$cmd, '-c', @$remcmd, @$termcmd;
1611 PVE
::Tools
::run_command
($cmd);
1615 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1617 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1619 my $sock = IO
::Socket
::IP-
>new(
1624 GetAddrInfoFlags
=> 0,
1625 ) or die "failed to create socket: $!\n";
1626 # Inside the worker we shouldn't have any previous alarms
1627 # running anyway...:
1629 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1631 accept(my $cli, $sock) or die "connection failed: $!\n";
1634 if (PVE
::Tools
::run_command
($cmd,
1635 output
=> '>&'.fileno($cli),
1636 input
=> '<&'.fileno($cli),
1639 die "Failed to run vncproxy.\n";
1646 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1648 PVE
::Tools
::wait_for_vnc_port
($port);
1659 __PACKAGE__-
>register_method({
1660 name
=> 'termproxy',
1661 path
=> '{vmid}/termproxy',
1665 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1667 description
=> "Creates a TCP proxy connections.",
1669 additionalProperties
=> 0,
1671 node
=> get_standard_option
('pve-node'),
1672 vmid
=> get_standard_option
('pve-vmid'),
1676 enum
=> [qw(serial0 serial1 serial2 serial3)],
1677 description
=> "opens a serial terminal (defaults to display)",
1682 additionalProperties
=> 0,
1684 user
=> { type
=> 'string' },
1685 ticket
=> { type
=> 'string' },
1686 port
=> { type
=> 'integer' },
1687 upid
=> { type
=> 'string' },
1693 my $rpcenv = PVE
::RPCEnvironment
::get
();
1695 my $authuser = $rpcenv->get_user();
1697 my $vmid = $param->{vmid
};
1698 my $node = $param->{node
};
1699 my $serial = $param->{serial
};
1701 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1703 if (!defined($serial)) {
1704 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1705 $serial = $conf->{vga
};
1709 my $authpath = "/vms/$vmid";
1711 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1716 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1717 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1718 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1719 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1720 push @$remcmd, '--';
1722 $family = PVE
::Tools
::get_host_address_family
($node);
1725 my $port = PVE
::Tools
::next_vnc_port
($family);
1727 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1728 push @$termcmd, '-iface', $serial if $serial;
1733 syslog
('info', "starting qemu termproxy $upid\n");
1735 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1736 '--perm', 'VM.Console', '--'];
1737 push @$cmd, @$remcmd, @$termcmd;
1739 PVE
::Tools
::run_command
($cmd);
1742 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1744 PVE
::Tools
::wait_for_vnc_port
($port);
1754 __PACKAGE__-
>register_method({
1755 name
=> 'vncwebsocket',
1756 path
=> '{vmid}/vncwebsocket',
1759 description
=> "You also need to pass a valid ticket (vncticket).",
1760 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1762 description
=> "Opens a weksocket for VNC traffic.",
1764 additionalProperties
=> 0,
1766 node
=> get_standard_option
('pve-node'),
1767 vmid
=> get_standard_option
('pve-vmid'),
1769 description
=> "Ticket from previous call to vncproxy.",
1774 description
=> "Port number returned by previous vncproxy call.",
1784 port
=> { type
=> 'string' },
1790 my $rpcenv = PVE
::RPCEnvironment
::get
();
1792 my $authuser = $rpcenv->get_user();
1794 my $vmid = $param->{vmid
};
1795 my $node = $param->{node
};
1797 my $authpath = "/vms/$vmid";
1799 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1801 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1803 # Note: VNC ports are acessible from outside, so we do not gain any
1804 # security if we verify that $param->{port} belongs to VM $vmid. This
1805 # check is done by verifying the VNC ticket (inside VNC protocol).
1807 my $port = $param->{port
};
1809 return { port
=> $port };
1812 __PACKAGE__-
>register_method({
1813 name
=> 'spiceproxy',
1814 path
=> '{vmid}/spiceproxy',
1819 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1821 description
=> "Returns a SPICE configuration to connect to the VM.",
1823 additionalProperties
=> 0,
1825 node
=> get_standard_option
('pve-node'),
1826 vmid
=> get_standard_option
('pve-vmid'),
1827 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1830 returns
=> get_standard_option
('remote-viewer-config'),
1834 my $rpcenv = PVE
::RPCEnvironment
::get
();
1836 my $authuser = $rpcenv->get_user();
1838 my $vmid = $param->{vmid
};
1839 my $node = $param->{node
};
1840 my $proxy = $param->{proxy
};
1842 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1843 my $title = "VM $vmid";
1844 $title .= " - ". $conf->{name
} if $conf->{name
};
1846 my $port = PVE
::QemuServer
::spice_port
($vmid);
1848 my ($ticket, undef, $remote_viewer_config) =
1849 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1851 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1852 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1854 return $remote_viewer_config;
1857 __PACKAGE__-
>register_method({
1859 path
=> '{vmid}/status',
1862 description
=> "Directory index",
1867 additionalProperties
=> 0,
1869 node
=> get_standard_option
('pve-node'),
1870 vmid
=> get_standard_option
('pve-vmid'),
1878 subdir
=> { type
=> 'string' },
1881 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1887 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1890 { subdir
=> 'current' },
1891 { subdir
=> 'start' },
1892 { subdir
=> 'stop' },
1893 { subdir
=> 'reset' },
1894 { subdir
=> 'shutdown' },
1895 { subdir
=> 'suspend' },
1896 { subdir
=> 'reboot' },
1902 __PACKAGE__-
>register_method({
1903 name
=> 'vm_status',
1904 path
=> '{vmid}/status/current',
1907 protected
=> 1, # qemu pid files are only readable by root
1908 description
=> "Get virtual machine status.",
1910 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1913 additionalProperties
=> 0,
1915 node
=> get_standard_option
('pve-node'),
1916 vmid
=> get_standard_option
('pve-vmid'),
1922 %$PVE::QemuServer
::vmstatus_return_properties
,
1924 description
=> "HA manager service status.",
1928 description
=> "Qemu VGA configuration supports spice.",
1933 description
=> "Qemu GuestAgent enabled in config.",
1943 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1945 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1946 my $status = $vmstatus->{$param->{vmid
}};
1948 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1950 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1951 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1956 __PACKAGE__-
>register_method({
1958 path
=> '{vmid}/status/start',
1962 description
=> "Start virtual machine.",
1964 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1967 additionalProperties
=> 0,
1969 node
=> get_standard_option
('pve-node'),
1970 vmid
=> get_standard_option
('pve-vmid',
1971 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1972 skiplock
=> get_standard_option
('skiplock'),
1973 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1974 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1977 enum
=> ['secure', 'insecure'],
1978 description
=> "Migration traffic is encrypted using an SSH " .
1979 "tunnel by default. On secure, completely private networks " .
1980 "this can be disabled to increase performance.",
1983 migration_network
=> {
1984 type
=> 'string', format
=> 'CIDR',
1985 description
=> "CIDR of the (sub) network that is used for migration.",
1988 machine
=> get_standard_option
('pve-qemu-machine'),
1990 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1995 description
=> "Wait maximal timeout seconds.",
1998 default => 'max(30, vm memory in GiB)',
2009 my $rpcenv = PVE
::RPCEnvironment
::get
();
2010 my $authuser = $rpcenv->get_user();
2012 my $node = extract_param
($param, 'node');
2013 my $vmid = extract_param
($param, 'vmid');
2014 my $timeout = extract_param
($param, 'timeout');
2016 my $machine = extract_param
($param, 'machine');
2018 my $get_root_param = sub {
2019 my $value = extract_param
($param, $_[0]);
2020 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2021 if $value && $authuser ne 'root@pam';
2025 my $stateuri = $get_root_param->('stateuri');
2026 my $skiplock = $get_root_param->('skiplock');
2027 my $migratedfrom = $get_root_param->('migratedfrom');
2028 my $migration_type = $get_root_param->('migration_type');
2029 my $migration_network = $get_root_param->('migration_network');
2030 my $targetstorage = $get_root_param->('targetstorage');
2032 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2033 if $targetstorage && !$migratedfrom;
2035 # read spice ticket from STDIN
2037 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2038 if (defined(my $line = <STDIN
>)) {
2040 $spice_ticket = $line;
2044 PVE
::Cluster
::check_cfs_quorum
();
2046 my $storecfg = PVE
::Storage
::config
();
2048 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2052 print "Requesting HA start for VM $vmid\n";
2054 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2055 PVE
::Tools
::run_command
($cmd);
2059 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2066 syslog
('info', "start VM $vmid: $upid\n");
2068 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef, $machine,
2069 $spice_ticket, $migration_network, $migration_type, $targetstorage, $timeout);
2073 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2077 __PACKAGE__-
>register_method({
2079 path
=> '{vmid}/status/stop',
2083 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2084 "is akin to pulling the power plug of a running computer and may damage the VM data",
2086 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2089 additionalProperties
=> 0,
2091 node
=> get_standard_option
('pve-node'),
2092 vmid
=> get_standard_option
('pve-vmid',
2093 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2094 skiplock
=> get_standard_option
('skiplock'),
2095 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2097 description
=> "Wait maximal timeout seconds.",
2103 description
=> "Do not deactivate storage volumes.",
2116 my $rpcenv = PVE
::RPCEnvironment
::get
();
2117 my $authuser = $rpcenv->get_user();
2119 my $node = extract_param
($param, 'node');
2120 my $vmid = extract_param
($param, 'vmid');
2122 my $skiplock = extract_param
($param, 'skiplock');
2123 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2124 if $skiplock && $authuser ne 'root@pam';
2126 my $keepActive = extract_param
($param, 'keepActive');
2127 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2128 if $keepActive && $authuser ne 'root@pam';
2130 my $migratedfrom = extract_param
($param, 'migratedfrom');
2131 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2132 if $migratedfrom && $authuser ne 'root@pam';
2135 my $storecfg = PVE
::Storage
::config
();
2137 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2142 print "Requesting HA stop for VM $vmid\n";
2144 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2145 PVE
::Tools
::run_command
($cmd);
2149 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2155 syslog
('info', "stop VM $vmid: $upid\n");
2157 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2158 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2162 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2166 __PACKAGE__-
>register_method({
2168 path
=> '{vmid}/status/reset',
2172 description
=> "Reset virtual machine.",
2174 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2177 additionalProperties
=> 0,
2179 node
=> get_standard_option
('pve-node'),
2180 vmid
=> get_standard_option
('pve-vmid',
2181 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2182 skiplock
=> get_standard_option
('skiplock'),
2191 my $rpcenv = PVE
::RPCEnvironment
::get
();
2193 my $authuser = $rpcenv->get_user();
2195 my $node = extract_param
($param, 'node');
2197 my $vmid = extract_param
($param, 'vmid');
2199 my $skiplock = extract_param
($param, 'skiplock');
2200 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2201 if $skiplock && $authuser ne 'root@pam';
2203 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2208 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2213 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2216 __PACKAGE__-
>register_method({
2217 name
=> 'vm_shutdown',
2218 path
=> '{vmid}/status/shutdown',
2222 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2223 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2225 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2228 additionalProperties
=> 0,
2230 node
=> get_standard_option
('pve-node'),
2231 vmid
=> get_standard_option
('pve-vmid',
2232 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2233 skiplock
=> get_standard_option
('skiplock'),
2235 description
=> "Wait maximal timeout seconds.",
2241 description
=> "Make sure the VM stops.",
2247 description
=> "Do not deactivate storage volumes.",
2260 my $rpcenv = PVE
::RPCEnvironment
::get
();
2261 my $authuser = $rpcenv->get_user();
2263 my $node = extract_param
($param, 'node');
2264 my $vmid = extract_param
($param, 'vmid');
2266 my $skiplock = extract_param
($param, 'skiplock');
2267 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2268 if $skiplock && $authuser ne 'root@pam';
2270 my $keepActive = extract_param
($param, 'keepActive');
2271 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2272 if $keepActive && $authuser ne 'root@pam';
2274 my $storecfg = PVE
::Storage
::config
();
2278 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2279 # otherwise, we will infer a shutdown command, but run into the timeout,
2280 # then when the vm is resumed, it will instantly shutdown
2282 # checking the qmp status here to get feedback to the gui/cli/api
2283 # and the status query should not take too long
2284 my $qmpstatus = eval {
2285 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2286 mon_cmd
($vmid, "query-status");
2290 if (!$err && $qmpstatus->{status
} eq "paused") {
2291 if ($param->{forceStop
}) {
2292 warn "VM is paused - stop instead of shutdown\n";
2295 die "VM is paused - cannot shutdown\n";
2299 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2301 my $timeout = $param->{timeout
} // 60;
2305 print "Requesting HA stop for VM $vmid\n";
2307 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2308 PVE
::Tools
::run_command
($cmd);
2312 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2319 syslog
('info', "shutdown VM $vmid: $upid\n");
2321 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2322 $shutdown, $param->{forceStop
}, $keepActive);
2326 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2330 __PACKAGE__-
>register_method({
2331 name
=> 'vm_reboot',
2332 path
=> '{vmid}/status/reboot',
2336 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2338 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2341 additionalProperties
=> 0,
2343 node
=> get_standard_option
('pve-node'),
2344 vmid
=> get_standard_option
('pve-vmid',
2345 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2347 description
=> "Wait maximal timeout seconds for the shutdown.",
2360 my $rpcenv = PVE
::RPCEnvironment
::get
();
2361 my $authuser = $rpcenv->get_user();
2363 my $node = extract_param
($param, 'node');
2364 my $vmid = extract_param
($param, 'vmid');
2366 my $qmpstatus = eval {
2367 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2368 mon_cmd
($vmid, "query-status");
2372 if (!$err && $qmpstatus->{status
} eq "paused") {
2373 die "VM is paused - cannot shutdown\n";
2376 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2381 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2382 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2386 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2389 __PACKAGE__-
>register_method({
2390 name
=> 'vm_suspend',
2391 path
=> '{vmid}/status/suspend',
2395 description
=> "Suspend virtual machine.",
2397 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2398 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2399 " on the storage for the vmstate.",
2400 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2403 additionalProperties
=> 0,
2405 node
=> get_standard_option
('pve-node'),
2406 vmid
=> get_standard_option
('pve-vmid',
2407 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2408 skiplock
=> get_standard_option
('skiplock'),
2413 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2415 statestorage
=> get_standard_option
('pve-storage-id', {
2416 description
=> "The storage for the VM state",
2417 requires
=> 'todisk',
2419 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2429 my $rpcenv = PVE
::RPCEnvironment
::get
();
2430 my $authuser = $rpcenv->get_user();
2432 my $node = extract_param
($param, 'node');
2433 my $vmid = extract_param
($param, 'vmid');
2435 my $todisk = extract_param
($param, 'todisk') // 0;
2437 my $statestorage = extract_param
($param, 'statestorage');
2439 my $skiplock = extract_param
($param, 'skiplock');
2440 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2441 if $skiplock && $authuser ne 'root@pam';
2443 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2445 die "Cannot suspend HA managed VM to disk\n"
2446 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2448 # early check for storage permission, for better user feedback
2450 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2452 if (!$statestorage) {
2453 # get statestorage from config if none is given
2454 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2455 my $storecfg = PVE
::Storage
::config
();
2456 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2459 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2465 syslog
('info', "suspend VM $vmid: $upid\n");
2467 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2472 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2473 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2476 __PACKAGE__-
>register_method({
2477 name
=> 'vm_resume',
2478 path
=> '{vmid}/status/resume',
2482 description
=> "Resume virtual machine.",
2484 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2487 additionalProperties
=> 0,
2489 node
=> get_standard_option
('pve-node'),
2490 vmid
=> get_standard_option
('pve-vmid',
2491 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2492 skiplock
=> get_standard_option
('skiplock'),
2493 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2503 my $rpcenv = PVE
::RPCEnvironment
::get
();
2505 my $authuser = $rpcenv->get_user();
2507 my $node = extract_param
($param, 'node');
2509 my $vmid = extract_param
($param, 'vmid');
2511 my $skiplock = extract_param
($param, 'skiplock');
2512 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2513 if $skiplock && $authuser ne 'root@pam';
2515 my $nocheck = extract_param
($param, 'nocheck');
2517 my $to_disk_suspended;
2519 PVE
::QemuConfig-
>lock_config($vmid, sub {
2520 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2521 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2525 die "VM $vmid not running\n"
2526 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2531 syslog
('info', "resume VM $vmid: $upid\n");
2533 if (!$to_disk_suspended) {
2534 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2536 my $storecfg = PVE
::Storage
::config
();
2537 PVE
::QemuServer
::vm_start
($storecfg, $vmid, undef, $skiplock);
2543 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2546 __PACKAGE__-
>register_method({
2547 name
=> 'vm_sendkey',
2548 path
=> '{vmid}/sendkey',
2552 description
=> "Send key event to virtual machine.",
2554 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2557 additionalProperties
=> 0,
2559 node
=> get_standard_option
('pve-node'),
2560 vmid
=> get_standard_option
('pve-vmid',
2561 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2562 skiplock
=> get_standard_option
('skiplock'),
2564 description
=> "The key (qemu monitor encoding).",
2569 returns
=> { type
=> 'null'},
2573 my $rpcenv = PVE
::RPCEnvironment
::get
();
2575 my $authuser = $rpcenv->get_user();
2577 my $node = extract_param
($param, 'node');
2579 my $vmid = extract_param
($param, 'vmid');
2581 my $skiplock = extract_param
($param, 'skiplock');
2582 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2583 if $skiplock && $authuser ne 'root@pam';
2585 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2590 __PACKAGE__-
>register_method({
2591 name
=> 'vm_feature',
2592 path
=> '{vmid}/feature',
2596 description
=> "Check if feature for virtual machine is available.",
2598 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2601 additionalProperties
=> 0,
2603 node
=> get_standard_option
('pve-node'),
2604 vmid
=> get_standard_option
('pve-vmid'),
2606 description
=> "Feature to check.",
2608 enum
=> [ 'snapshot', 'clone', 'copy' ],
2610 snapname
=> get_standard_option
('pve-snapshot-name', {
2618 hasFeature
=> { type
=> 'boolean' },
2621 items
=> { type
=> 'string' },
2628 my $node = extract_param
($param, 'node');
2630 my $vmid = extract_param
($param, 'vmid');
2632 my $snapname = extract_param
($param, 'snapname');
2634 my $feature = extract_param
($param, 'feature');
2636 my $running = PVE
::QemuServer
::check_running
($vmid);
2638 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2641 my $snap = $conf->{snapshots
}->{$snapname};
2642 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2645 my $storecfg = PVE
::Storage
::config
();
2647 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2648 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2651 hasFeature
=> $hasFeature,
2652 nodes
=> [ keys %$nodelist ],
2656 __PACKAGE__-
>register_method({
2658 path
=> '{vmid}/clone',
2662 description
=> "Create a copy of virtual machine/template.",
2664 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2665 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2666 "'Datastore.AllocateSpace' on any used storage.",
2669 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2671 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2672 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2677 additionalProperties
=> 0,
2679 node
=> get_standard_option
('pve-node'),
2680 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2681 newid
=> get_standard_option
('pve-vmid', {
2682 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2683 description
=> 'VMID for the clone.' }),
2686 type
=> 'string', format
=> 'dns-name',
2687 description
=> "Set a name for the new VM.",
2692 description
=> "Description for the new VM.",
2696 type
=> 'string', format
=> 'pve-poolid',
2697 description
=> "Add the new VM to the specified pool.",
2699 snapname
=> get_standard_option
('pve-snapshot-name', {
2702 storage
=> get_standard_option
('pve-storage-id', {
2703 description
=> "Target storage for full clone.",
2707 description
=> "Target format for file storage. Only valid for full clone.",
2710 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2715 description
=> "Create a full copy of all disks. This is always done when " .
2716 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2718 target
=> get_standard_option
('pve-node', {
2719 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2723 description
=> "Override I/O bandwidth limit (in KiB/s).",
2727 default => 'clone limit from datacenter or storage config',
2737 my $rpcenv = PVE
::RPCEnvironment
::get
();
2738 my $authuser = $rpcenv->get_user();
2740 my $node = extract_param
($param, 'node');
2741 my $vmid = extract_param
($param, 'vmid');
2742 my $newid = extract_param
($param, 'newid');
2743 my $pool = extract_param
($param, 'pool');
2744 $rpcenv->check_pool_exist($pool) if defined($pool);
2746 my $snapname = extract_param
($param, 'snapname');
2747 my $storage = extract_param
($param, 'storage');
2748 my $format = extract_param
($param, 'format');
2749 my $target = extract_param
($param, 'target');
2751 my $localnode = PVE
::INotify
::nodename
();
2753 if ($target && ($target eq $localnode || $target eq 'localhost')) {
2757 PVE
::Cluster
::check_node_exists
($target) if $target;
2759 my $storecfg = PVE
::Storage
::config
();
2762 # check if storage is enabled on local node
2763 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2765 # check if storage is available on target node
2766 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2767 # clone only works if target storage is shared
2768 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2769 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2773 PVE
::Cluster
::check_cfs_quorum
();
2775 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2777 # exclusive lock if VM is running - else shared lock is enough;
2778 my $shared_lock = $running ?
0 : 1;
2781 # do all tests after lock but before forking worker - if possible
2783 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2784 PVE
::QemuConfig-
>check_lock($conf);
2786 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2787 die "unexpected state change\n" if $verify_running != $running;
2789 die "snapshot '$snapname' does not exist\n"
2790 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2792 my $full = extract_param
($param, 'full') // !PVE
::QemuConfig-
>is_template($conf);
2794 die "parameter 'storage' not allowed for linked clones\n"
2795 if defined($storage) && !$full;
2797 die "parameter 'format' not allowed for linked clones\n"
2798 if defined($format) && !$full;
2800 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2802 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2804 die "can't clone VM to node '$target' (VM uses local storage)\n"
2805 if $target && !$sharedvm;
2807 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2808 die "unable to create VM $newid: config file already exists\n"
2811 my $newconf = { lock => 'clone' };
2816 foreach my $opt (keys %$oldconf) {
2817 my $value = $oldconf->{$opt};
2819 # do not copy snapshot related info
2820 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2821 $opt eq 'vmstate' || $opt eq 'snapstate';
2823 # no need to copy unused images, because VMID(owner) changes anyways
2824 next if $opt =~ m/^unused\d+$/;
2826 # always change MAC! address
2827 if ($opt =~ m/^net(\d+)$/) {
2828 my $net = PVE
::QemuServer
::parse_net
($value);
2829 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2830 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2831 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2832 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2833 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2834 die "unable to parse drive options for '$opt'\n" if !$drive;
2835 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2836 $newconf->{$opt} = $value; # simply copy configuration
2838 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2839 die "Full clone feature is not supported for drive '$opt'\n"
2840 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2841 $fullclone->{$opt} = 1;
2843 # not full means clone instead of copy
2844 die "Linked clone feature is not supported for drive '$opt'\n"
2845 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2847 $drives->{$opt} = $drive;
2848 push @$vollist, $drive->{file
};
2851 # copy everything else
2852 $newconf->{$opt} = $value;
2856 # auto generate a new uuid
2857 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2858 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2859 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2860 # auto generate a new vmgenid only if the option was set for template
2861 if ($newconf->{vmgenid
}) {
2862 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2865 delete $newconf->{template
};
2867 if ($param->{name
}) {
2868 $newconf->{name
} = $param->{name
};
2870 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
2873 if ($param->{description
}) {
2874 $newconf->{description
} = $param->{description
};
2877 # create empty/temp config - this fails if VM already exists on other node
2878 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
2879 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2884 my $newvollist = [];
2891 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2893 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2895 my $bwlimit = extract_param
($param, 'bwlimit');
2897 my $total_jobs = scalar(keys %{$drives});
2900 foreach my $opt (keys %$drives) {
2901 my $drive = $drives->{$opt};
2902 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2904 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
2905 my $storage_list = [ $src_sid ];
2906 push @$storage_list, $storage if defined($storage);
2907 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
2909 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2910 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2911 $jobs, $skipcomplete, $oldconf->{agent
}, $clonelimit);
2913 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
2915 PVE
::QemuConfig-
>write_config($newid, $newconf);
2919 delete $newconf->{lock};
2921 # do not write pending changes
2922 if (my @changes = keys %{$newconf->{pending
}}) {
2923 my $pending = join(',', @changes);
2924 warn "found pending changes for '$pending', discarding for clone\n";
2925 delete $newconf->{pending
};
2928 PVE
::QemuConfig-
>write_config($newid, $newconf);
2931 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2932 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2933 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2935 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2936 die "Failed to move config to node '$target' - rename failed: $!\n"
2937 if !rename($conffile, $newconffile);
2940 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2943 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2944 sleep 1; # some storage like rbd need to wait before release volume - really?
2946 foreach my $volid (@$newvollist) {
2947 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2951 PVE
::Firewall
::remove_vmfw_conf
($newid);
2953 unlink $conffile; # avoid races -> last thing before die
2955 die "clone failed: $err";
2961 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2963 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2966 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2967 # Aquire exclusive lock lock for $newid
2968 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2973 __PACKAGE__-
>register_method({
2974 name
=> 'move_vm_disk',
2975 path
=> '{vmid}/move_disk',
2979 description
=> "Move volume to different storage.",
2981 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2983 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2984 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2988 additionalProperties
=> 0,
2990 node
=> get_standard_option
('pve-node'),
2991 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2994 description
=> "The disk you want to move.",
2995 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2997 storage
=> get_standard_option
('pve-storage-id', {
2998 description
=> "Target storage.",
2999 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3003 description
=> "Target Format.",
3004 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3009 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3015 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3020 description
=> "Override I/O bandwidth limit (in KiB/s).",
3024 default => 'move limit from datacenter or storage config',
3030 description
=> "the task ID.",
3035 my $rpcenv = PVE
::RPCEnvironment
::get
();
3036 my $authuser = $rpcenv->get_user();
3038 my $node = extract_param
($param, 'node');
3039 my $vmid = extract_param
($param, 'vmid');
3040 my $digest = extract_param
($param, 'digest');
3041 my $disk = extract_param
($param, 'disk');
3042 my $storeid = extract_param
($param, 'storage');
3043 my $format = extract_param
($param, 'format');
3045 my $storecfg = PVE
::Storage
::config
();
3047 my $updatefn = sub {
3048 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3049 PVE
::QemuConfig-
>check_lock($conf);
3051 die "VM config checksum missmatch (file change by other user?)\n"
3052 if $digest && $digest ne $conf->{digest
};
3054 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3056 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3058 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3059 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3061 my $old_volid = $drive->{file
};
3063 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3064 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3068 die "you can't move to the same storage with same format\n" if $oldstoreid eq $storeid &&
3069 (!$format || !$oldfmt || $oldfmt eq $format);
3071 # this only checks snapshots because $disk is passed!
3072 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3073 die "you can't move a disk with snapshots and delete the source\n"
3074 if $snapshotted && $param->{delete};
3076 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3078 my $running = PVE
::QemuServer
::check_running
($vmid);
3080 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3083 my $newvollist = [];
3089 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3091 warn "moving disk with snapshots, snapshots will not be moved!\n"
3094 my $bwlimit = extract_param
($param, 'bwlimit');
3095 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3097 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3098 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit);
3100 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3102 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3104 # convert moved disk to base if part of template
3105 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3106 if PVE
::QemuConfig-
>is_template($conf);
3108 PVE
::QemuConfig-
>write_config($vmid, $conf);
3110 my $do_trim = PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
};
3111 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3112 eval { mon_cmd
($vmid, "guest-fstrim") };
3116 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3117 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3123 foreach my $volid (@$newvollist) {
3124 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3127 die "storage migration failed: $err";
3130 if ($param->{delete}) {
3132 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3133 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3139 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3142 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3145 my $check_vm_disks_local = sub {
3146 my ($storecfg, $vmconf, $vmid) = @_;
3148 my $local_disks = {};
3150 # add some more information to the disks e.g. cdrom
3151 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3152 my ($volid, $attr) = @_;
3154 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3156 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3157 return if $scfg->{shared
};
3159 # The shared attr here is just a special case where the vdisk
3160 # is marked as shared manually
3161 return if $attr->{shared
};
3162 return if $attr->{cdrom
} and $volid eq "none";
3164 if (exists $local_disks->{$volid}) {
3165 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3167 $local_disks->{$volid} = $attr;
3168 # ensure volid is present in case it's needed
3169 $local_disks->{$volid}->{volid
} = $volid;
3173 return $local_disks;
3176 __PACKAGE__-
>register_method({
3177 name
=> 'migrate_vm_precondition',
3178 path
=> '{vmid}/migrate',
3182 description
=> "Get preconditions for migration.",
3184 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3187 additionalProperties
=> 0,
3189 node
=> get_standard_option
('pve-node'),
3190 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3191 target
=> get_standard_option
('pve-node', {
3192 description
=> "Target node.",
3193 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3201 running
=> { type
=> 'boolean' },
3205 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3207 not_allowed_nodes
=> {
3210 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3214 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3216 local_resources
=> {
3218 description
=> "List local resources e.g. pci, usb"
3225 my $rpcenv = PVE
::RPCEnvironment
::get
();
3227 my $authuser = $rpcenv->get_user();
3229 PVE
::Cluster
::check_cfs_quorum
();
3233 my $vmid = extract_param
($param, 'vmid');
3234 my $target = extract_param
($param, 'target');
3235 my $localnode = PVE
::INotify
::nodename
();
3239 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3240 my $storecfg = PVE
::Storage
::config
();
3243 # try to detect errors early
3244 PVE
::QemuConfig-
>check_lock($vmconf);
3246 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3248 # if vm is not running, return target nodes where local storage is available
3249 # for offline migration
3250 if (!$res->{running
}) {
3251 $res->{allowed_nodes
} = [];
3252 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3253 delete $checked_nodes->{$localnode};
3255 foreach my $node (keys %$checked_nodes) {
3256 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3257 push @{$res->{allowed_nodes
}}, $node;
3261 $res->{not_allowed_nodes
} = $checked_nodes;
3265 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3266 $res->{local_disks
} = [ values %$local_disks ];;
3268 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3270 $res->{local_resources
} = $local_resources;
3277 __PACKAGE__-
>register_method({
3278 name
=> 'migrate_vm',
3279 path
=> '{vmid}/migrate',
3283 description
=> "Migrate virtual machine. Creates a new migration task.",
3285 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3288 additionalProperties
=> 0,
3290 node
=> get_standard_option
('pve-node'),
3291 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3292 target
=> get_standard_option
('pve-node', {
3293 description
=> "Target node.",
3294 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3298 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3303 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3308 enum
=> ['secure', 'insecure'],
3309 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3312 migration_network
=> {
3313 type
=> 'string', format
=> 'CIDR',
3314 description
=> "CIDR of the (sub) network that is used for migration.",
3317 "with-local-disks" => {
3319 description
=> "Enable live storage migration for local disk",
3322 targetstorage
=> get_standard_option
('pve-storage-id', {
3323 description
=> "Default target storage.",
3325 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3328 description
=> "Override I/O bandwidth limit (in KiB/s).",
3332 default => 'migrate limit from datacenter or storage config',
3338 description
=> "the task ID.",
3343 my $rpcenv = PVE
::RPCEnvironment
::get
();
3344 my $authuser = $rpcenv->get_user();
3346 my $target = extract_param
($param, 'target');
3348 my $localnode = PVE
::INotify
::nodename
();
3349 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3351 PVE
::Cluster
::check_cfs_quorum
();
3353 PVE
::Cluster
::check_node_exists
($target);
3355 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3357 my $vmid = extract_param
($param, 'vmid');
3359 raise_param_exc
({ force
=> "Only root may use this option." })
3360 if $param->{force
} && $authuser ne 'root@pam';
3362 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3363 if $param->{migration_type
} && $authuser ne 'root@pam';
3365 # allow root only until better network permissions are available
3366 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3367 if $param->{migration_network
} && $authuser ne 'root@pam';
3370 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3372 # try to detect errors early
3374 PVE
::QemuConfig-
>check_lock($conf);
3376 if (PVE
::QemuServer
::check_running
($vmid)) {
3377 die "can't migrate running VM without --online\n" if !$param->{online
};
3379 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
3380 $param->{online
} = 0;
3383 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3384 if !$param->{online
} && $param->{targetstorage
};
3386 my $storecfg = PVE
::Storage
::config
();
3388 if( $param->{targetstorage
}) {
3389 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3391 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3394 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3399 print "Requesting HA migration for VM $vmid to node $target\n";
3401 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3402 PVE
::Tools
::run_command
($cmd);
3406 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3411 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3415 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3418 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3423 __PACKAGE__-
>register_method({
3425 path
=> '{vmid}/monitor',
3429 description
=> "Execute Qemu monitor commands.",
3431 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3432 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3435 additionalProperties
=> 0,
3437 node
=> get_standard_option
('pve-node'),
3438 vmid
=> get_standard_option
('pve-vmid'),
3441 description
=> "The monitor command.",
3445 returns
=> { type
=> 'string'},
3449 my $rpcenv = PVE
::RPCEnvironment
::get
();
3450 my $authuser = $rpcenv->get_user();
3453 my $command = shift;
3454 return $command =~ m/^\s*info(\s+|$)/
3455 || $command =~ m/^\s*help\s*$/;
3458 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3459 if !&$is_ro($param->{command
});
3461 my $vmid = $param->{vmid
};
3463 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3467 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3469 $res = "ERROR: $@" if $@;
3474 __PACKAGE__-
>register_method({
3475 name
=> 'resize_vm',
3476 path
=> '{vmid}/resize',
3480 description
=> "Extend volume size.",
3482 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3485 additionalProperties
=> 0,
3487 node
=> get_standard_option
('pve-node'),
3488 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3489 skiplock
=> get_standard_option
('skiplock'),
3492 description
=> "The disk you want to resize.",
3493 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3497 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3498 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.",
3502 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3508 returns
=> { type
=> 'null'},
3512 my $rpcenv = PVE
::RPCEnvironment
::get
();
3514 my $authuser = $rpcenv->get_user();
3516 my $node = extract_param
($param, 'node');
3518 my $vmid = extract_param
($param, 'vmid');
3520 my $digest = extract_param
($param, 'digest');
3522 my $disk = extract_param
($param, 'disk');
3524 my $sizestr = extract_param
($param, 'size');
3526 my $skiplock = extract_param
($param, 'skiplock');
3527 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3528 if $skiplock && $authuser ne 'root@pam';
3530 my $storecfg = PVE
::Storage
::config
();
3532 my $updatefn = sub {
3534 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3536 die "checksum missmatch (file change by other user?)\n"
3537 if $digest && $digest ne $conf->{digest
};
3538 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3540 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3542 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3544 my (undef, undef, undef, undef, undef, undef, $format) =
3545 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3547 die "can't resize volume: $disk if snapshot exists\n"
3548 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3550 my $volid = $drive->{file
};
3552 die "disk '$disk' has no associated volume\n" if !$volid;
3554 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3556 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3558 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3560 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3561 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3563 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3565 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3566 my ($ext, $newsize, $unit) = ($1, $2, $4);
3569 $newsize = $newsize * 1024;
3570 } elsif ($unit eq 'M') {
3571 $newsize = $newsize * 1024 * 1024;
3572 } elsif ($unit eq 'G') {
3573 $newsize = $newsize * 1024 * 1024 * 1024;
3574 } elsif ($unit eq 'T') {
3575 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3578 $newsize += $size if $ext;
3579 $newsize = int($newsize);
3581 die "shrinking disks is not supported\n" if $newsize < $size;
3583 return if $size == $newsize;
3585 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3587 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3589 my $effective_size = eval { PVE
::Storage
::volume_size_info
($storecfg, $volid, 3); };
3590 $drive->{size
} = $effective_size // $newsize;
3591 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
3593 PVE
::QemuConfig-
>write_config($vmid, $conf);
3596 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3600 __PACKAGE__-
>register_method({
3601 name
=> 'snapshot_list',
3602 path
=> '{vmid}/snapshot',
3604 description
=> "List all snapshots.",
3606 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3609 protected
=> 1, # qemu pid files are only readable by root
3611 additionalProperties
=> 0,
3613 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3614 node
=> get_standard_option
('pve-node'),
3623 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3627 description
=> "Snapshot includes RAM.",
3632 description
=> "Snapshot description.",
3636 description
=> "Snapshot creation time",
3638 renderer
=> 'timestamp',
3642 description
=> "Parent snapshot identifier.",
3648 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3653 my $vmid = $param->{vmid
};
3655 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3656 my $snaphash = $conf->{snapshots
} || {};
3660 foreach my $name (keys %$snaphash) {
3661 my $d = $snaphash->{$name};
3664 snaptime
=> $d->{snaptime
} || 0,
3665 vmstate
=> $d->{vmstate
} ?
1 : 0,
3666 description
=> $d->{description
} || '',
3668 $item->{parent
} = $d->{parent
} if $d->{parent
};
3669 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3673 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3676 digest
=> $conf->{digest
},
3677 running
=> $running,
3678 description
=> "You are here!",
3680 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3682 push @$res, $current;
3687 __PACKAGE__-
>register_method({
3689 path
=> '{vmid}/snapshot',
3693 description
=> "Snapshot a VM.",
3695 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3698 additionalProperties
=> 0,
3700 node
=> get_standard_option
('pve-node'),
3701 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3702 snapname
=> get_standard_option
('pve-snapshot-name'),
3706 description
=> "Save the vmstate",
3711 description
=> "A textual description or comment.",
3717 description
=> "the task ID.",
3722 my $rpcenv = PVE
::RPCEnvironment
::get
();
3724 my $authuser = $rpcenv->get_user();
3726 my $node = extract_param
($param, 'node');
3728 my $vmid = extract_param
($param, 'vmid');
3730 my $snapname = extract_param
($param, 'snapname');
3732 die "unable to use snapshot name 'current' (reserved name)\n"
3733 if $snapname eq 'current';
3735 die "unable to use snapshot name 'pending' (reserved name)\n"
3736 if lc($snapname) eq 'pending';
3739 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3740 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3741 $param->{description
});
3744 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3747 __PACKAGE__-
>register_method({
3748 name
=> 'snapshot_cmd_idx',
3749 path
=> '{vmid}/snapshot/{snapname}',
3756 additionalProperties
=> 0,
3758 vmid
=> get_standard_option
('pve-vmid'),
3759 node
=> get_standard_option
('pve-node'),
3760 snapname
=> get_standard_option
('pve-snapshot-name'),
3769 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3776 push @$res, { cmd
=> 'rollback' };
3777 push @$res, { cmd
=> 'config' };
3782 __PACKAGE__-
>register_method({
3783 name
=> 'update_snapshot_config',
3784 path
=> '{vmid}/snapshot/{snapname}/config',
3788 description
=> "Update snapshot metadata.",
3790 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3793 additionalProperties
=> 0,
3795 node
=> get_standard_option
('pve-node'),
3796 vmid
=> get_standard_option
('pve-vmid'),
3797 snapname
=> get_standard_option
('pve-snapshot-name'),
3801 description
=> "A textual description or comment.",
3805 returns
=> { type
=> 'null' },
3809 my $rpcenv = PVE
::RPCEnvironment
::get
();
3811 my $authuser = $rpcenv->get_user();
3813 my $vmid = extract_param
($param, 'vmid');
3815 my $snapname = extract_param
($param, 'snapname');
3817 return undef if !defined($param->{description
});
3819 my $updatefn = sub {
3821 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3823 PVE
::QemuConfig-
>check_lock($conf);
3825 my $snap = $conf->{snapshots
}->{$snapname};
3827 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3829 $snap->{description
} = $param->{description
} if defined($param->{description
});
3831 PVE
::QemuConfig-
>write_config($vmid, $conf);
3834 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3839 __PACKAGE__-
>register_method({
3840 name
=> 'get_snapshot_config',
3841 path
=> '{vmid}/snapshot/{snapname}/config',
3844 description
=> "Get snapshot configuration",
3846 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
3849 additionalProperties
=> 0,
3851 node
=> get_standard_option
('pve-node'),
3852 vmid
=> get_standard_option
('pve-vmid'),
3853 snapname
=> get_standard_option
('pve-snapshot-name'),
3856 returns
=> { type
=> "object" },
3860 my $rpcenv = PVE
::RPCEnvironment
::get
();
3862 my $authuser = $rpcenv->get_user();
3864 my $vmid = extract_param
($param, 'vmid');
3866 my $snapname = extract_param
($param, 'snapname');
3868 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3870 my $snap = $conf->{snapshots
}->{$snapname};
3872 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3877 __PACKAGE__-
>register_method({
3879 path
=> '{vmid}/snapshot/{snapname}/rollback',
3883 description
=> "Rollback VM state to specified snapshot.",
3885 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3888 additionalProperties
=> 0,
3890 node
=> get_standard_option
('pve-node'),
3891 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3892 snapname
=> get_standard_option
('pve-snapshot-name'),
3897 description
=> "the task ID.",
3902 my $rpcenv = PVE
::RPCEnvironment
::get
();
3904 my $authuser = $rpcenv->get_user();
3906 my $node = extract_param
($param, 'node');
3908 my $vmid = extract_param
($param, 'vmid');
3910 my $snapname = extract_param
($param, 'snapname');
3913 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3914 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3918 # hold migration lock, this makes sure that nobody create replication snapshots
3919 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3922 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3925 __PACKAGE__-
>register_method({
3926 name
=> 'delsnapshot',
3927 path
=> '{vmid}/snapshot/{snapname}',
3931 description
=> "Delete a VM snapshot.",
3933 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3936 additionalProperties
=> 0,
3938 node
=> get_standard_option
('pve-node'),
3939 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3940 snapname
=> get_standard_option
('pve-snapshot-name'),
3944 description
=> "For removal from config file, even if removing disk snapshots fails.",
3950 description
=> "the task ID.",
3955 my $rpcenv = PVE
::RPCEnvironment
::get
();
3957 my $authuser = $rpcenv->get_user();
3959 my $node = extract_param
($param, 'node');
3961 my $vmid = extract_param
($param, 'vmid');
3963 my $snapname = extract_param
($param, 'snapname');
3966 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3967 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3970 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3973 __PACKAGE__-
>register_method({
3975 path
=> '{vmid}/template',
3979 description
=> "Create a Template.",
3981 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3982 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3985 additionalProperties
=> 0,
3987 node
=> get_standard_option
('pve-node'),
3988 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3992 description
=> "If you want to convert only 1 disk to base image.",
3993 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3998 returns
=> { type
=> 'null'},
4002 my $rpcenv = PVE
::RPCEnvironment
::get
();
4004 my $authuser = $rpcenv->get_user();
4006 my $node = extract_param
($param, 'node');
4008 my $vmid = extract_param
($param, 'vmid');
4010 my $disk = extract_param
($param, 'disk');
4012 my $updatefn = sub {
4014 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4016 PVE
::QemuConfig-
>check_lock($conf);
4018 die "unable to create template, because VM contains snapshots\n"
4019 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4021 die "you can't convert a template to a template\n"
4022 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4024 die "you can't convert a VM to template if VM is running\n"
4025 if PVE
::QemuServer
::check_running
($vmid);
4028 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4031 $conf->{template
} = 1;
4032 PVE
::QemuConfig-
>write_config($vmid, $conf);
4034 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4037 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4041 __PACKAGE__-
>register_method({
4042 name
=> 'cloudinit_generated_config_dump',
4043 path
=> '{vmid}/cloudinit/dump',
4046 description
=> "Get automatically generated cloudinit config.",
4048 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4051 additionalProperties
=> 0,
4053 node
=> get_standard_option
('pve-node'),
4054 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4056 description
=> 'Config type.',
4058 enum
=> ['user', 'network', 'meta'],
4068 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4070 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});